lgtm
This commit is contained in:
parent
084117cea8
commit
a8de0a614d
36 changed files with 1329 additions and 522 deletions
|
|
@ -24,12 +24,17 @@ if (!APP_URL) {
|
|||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!CACHE_DIR) {
|
||||
const CACHE_ENABLED = parseOptionalBoolEnv(
|
||||
"SCREENSHOT_CACHE_ENABLED",
|
||||
!isDevelopmentAppUrl(APP_URL),
|
||||
);
|
||||
|
||||
if (CACHE_ENABLED && !CACHE_DIR) {
|
||||
console.error("Error: CACHE_DIR environment variable is required");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const cache = new ScreenshotCache(CACHE_DIR);
|
||||
const cache = CACHE_ENABLED ? new ScreenshotCache(CACHE_DIR as string) : null;
|
||||
const app = express();
|
||||
app.set("trust proxy", true);
|
||||
|
||||
|
|
@ -55,6 +60,28 @@ function parseRequiredPositiveIntEnv(name: string): number {
|
|||
return value;
|
||||
}
|
||||
|
||||
function parseOptionalBoolEnv(name: string, defaultValue: boolean): boolean {
|
||||
const raw = process.env[name];
|
||||
if (raw == null || raw === "") return defaultValue;
|
||||
if (raw === "1" || raw.toLowerCase() === "true") return true;
|
||||
if (raw === "0" || raw.toLowerCase() === "false") return false;
|
||||
throw new Error(`${name} must be true or false`);
|
||||
}
|
||||
|
||||
function isDevelopmentAppUrl(rawUrl: string): boolean {
|
||||
try {
|
||||
const url = new URL(rawUrl);
|
||||
return (
|
||||
url.hostname === "localhost" ||
|
||||
url.hostname === "127.0.0.1" ||
|
||||
url.hostname === "frontend" ||
|
||||
url.port === "3001"
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function grantScreenshotSlot(): ReleaseScreenshotSlot {
|
||||
activeScreenshots += 1;
|
||||
let released = false;
|
||||
|
|
@ -159,24 +186,26 @@ app.get("/screenshot", async (req, res) => {
|
|||
const { pagePath, qs } = buildScreenshotRequest(
|
||||
req.query as Record<string, unknown>,
|
||||
);
|
||||
if (pagePath !== "/") qs.set("path", pagePath);
|
||||
|
||||
// Include auth status in cache key so authenticated screenshots
|
||||
// (with hexagons outside free zone) are cached separately
|
||||
const authHeader = req.headers.authorization;
|
||||
if (authHeader) qs.set("_auth", "1");
|
||||
const cacheKey = cache.buildKey(qs);
|
||||
qs.delete("_auth");
|
||||
qs.delete("path");
|
||||
|
||||
// Check cache first
|
||||
const cached = cache.get(cacheKey);
|
||||
if (cached) {
|
||||
res.setHeader("Content-Type", "image/jpeg");
|
||||
res.setHeader("Cache-Control", "public, max-age=86400");
|
||||
res.setHeader("X-Cache", "HIT");
|
||||
cached.pipe(res);
|
||||
return;
|
||||
let cacheKey: string | null = null;
|
||||
if (cache) {
|
||||
const cacheParams = new URLSearchParams(qs);
|
||||
if (pagePath !== "/") cacheParams.set("path", pagePath);
|
||||
// Include auth status in cache key so authenticated screenshots
|
||||
// (with hexagons outside free zone) are cached separately
|
||||
if (authHeader) cacheParams.set("_auth", "1");
|
||||
cacheKey = cache.buildKey(cacheParams);
|
||||
|
||||
// Check cache first
|
||||
const cached = cache.get(cacheKey);
|
||||
if (cached) {
|
||||
res.setHeader("Content-Type", "image/jpeg");
|
||||
res.setHeader("Cache-Control", "public, max-age=86400");
|
||||
res.setHeader("X-Cache", "HIT");
|
||||
cached.pipe(res);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!allowScreenshotRequest(req)) {
|
||||
|
|
@ -199,11 +228,11 @@ app.get("/screenshot", async (req, res) => {
|
|||
const jpeg = await takeScreenshot(url, authHeader);
|
||||
|
||||
// Cache it
|
||||
cache.set(cacheKey, jpeg);
|
||||
if (cache && cacheKey) cache.set(cacheKey, jpeg);
|
||||
|
||||
res.setHeader("Content-Type", "image/jpeg");
|
||||
res.setHeader("Cache-Control", "public, max-age=86400");
|
||||
res.setHeader("X-Cache", "MISS");
|
||||
res.setHeader("Cache-Control", cache ? "public, max-age=86400" : "no-store");
|
||||
res.setHeader("X-Cache", cache ? "MISS" : "BYPASS");
|
||||
res.send(jpeg);
|
||||
} catch (err) {
|
||||
if (err instanceof ValidationError) {
|
||||
|
|
@ -220,7 +249,8 @@ app.get("/screenshot", async (req, res) => {
|
|||
const server = app.listen(PORT, () => {
|
||||
console.log(`Screenshot service listening on port ${PORT}`);
|
||||
console.log(` APP_URL: ${APP_URL}`);
|
||||
console.log(` CACHE_DIR: ${CACHE_DIR}`);
|
||||
console.log(` CACHE_ENABLED: ${CACHE_ENABLED}`);
|
||||
if (cache) console.log(` CACHE_DIR: ${CACHE_DIR}`);
|
||||
console.log(` SCREENSHOT_CONCURRENCY: ${SCREENSHOT_CONCURRENCY}`);
|
||||
console.log(
|
||||
` SCREENSHOT_RATE_LIMIT: ${RATE_LIMIT_MAX}/${RATE_LIMIT_WINDOW_MS}ms`,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue