@farm.js/preview-gateway 0.1.0-beta.92 → 0.1.0-beta.94

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -11,7 +11,6 @@ const DEFAULT_MAX_RESPONSE_BODY_BYTES = 5242880;
11
11
  const DEFAULT_POLL_REQUEST_LIMIT = 50;
12
12
  const HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
13
13
  "connection",
14
- "content-encoding",
15
14
  "content-length",
16
15
  "keep-alive",
17
16
  "proxy-authenticate",
@@ -21,6 +20,16 @@ const HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
21
20
  "transfer-encoding",
22
21
  "upgrade"
23
22
  ]);
23
+ function getHopByHopHeaderNames(headers) {
24
+ const names = new Set(HOP_BY_HOP_HEADERS);
25
+ const connection = headers instanceof Headers ? headers.get("connection") : Object.entries(headers).find(([name]) => name.toLowerCase() === "connection")?.[1];
26
+ const values = Array.isArray(connection) ? connection : connection ? [connection] : [];
27
+ for (const value of values) for (const name of value.split(",")) {
28
+ const normalized = name.trim().toLowerCase();
29
+ if (normalized) names.add(normalized);
30
+ }
31
+ return names;
32
+ }
24
33
  function createPreviewGatewayHandler(options = {}) {
25
34
  const store = options.store || createPreviewGatewayStoreFromEnv();
26
35
  const config = {
@@ -362,9 +371,10 @@ async function proxyPublicRequest(request, url, store, config, route) {
362
371
  if (response === "stale") return text(`No active Farm preview is running for "${route.name}".`, 404);
363
372
  if (!response) return text("The local Farm preview did not respond before the gateway timed out.", 504);
364
373
  const headers = new Headers();
374
+ const responseHopByHopHeaders = getHopByHopHeaderNames(response.headers || {});
365
375
  for (const [key, value] of Object.entries(response.headers || {})) {
366
376
  const normalized = key.toLowerCase();
367
- if (HOP_BY_HOP_HEADERS.has(normalized)) continue;
377
+ if (responseHopByHopHeaders.has(normalized) || normalized === "content-encoding") continue;
368
378
  for (const single of Array.isArray(value) ? value : [value]) headers.append(key, single);
369
379
  }
370
380
  headers.set("cache-control", "no-store");
@@ -411,9 +421,10 @@ function isPreviewClientOnline(session, config) {
411
421
  async function serializePreviewRequest(request, url, routePath, maxBodyBytes) {
412
422
  const method = request.method.toUpperCase();
413
423
  const headers = {};
424
+ const requestHopByHopHeaders = getHopByHopHeaderNames(request.headers);
414
425
  request.headers.forEach((value, key) => {
415
426
  const normalized = key.toLowerCase();
416
- if (!HOP_BY_HOP_HEADERS.has(normalized) && normalized !== "forwarded" && !normalized.startsWith("x-forwarded-")) headers[key] = value;
427
+ if (!requestHopByHopHeaders.has(normalized) && normalized !== "forwarded" && !normalized.startsWith("x-forwarded-")) headers[key] = value;
417
428
  });
418
429
  headers["x-forwarded-host"] = request.headers.get("host") || url.host;
419
430
  headers["x-forwarded-proto"] = url.protocol.replace(":", "");
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["delay"],"sources":["../src/index.ts"],"sourcesContent":["import type { IncomingHttpHeaders, IncomingMessage, ServerResponse } from \"node:http\";\nimport { setTimeout as delay } from \"node:timers/promises\";\n\nexport interface PreviewGatewayOptions {\n domain?: string;\n baseUrl?: string;\n store?: PreviewGatewayStore;\n sessionTtlMs?: number;\n clientHeartbeatTimeoutMs?: number;\n requestTimeoutMs?: number;\n pollTimeoutMs?: number;\n pollIntervalMs?: number;\n maxBodyBytes?: number;\n maxResponseBodyBytes?: number;\n}\n\nexport interface PreviewGatewaySession {\n id: string;\n name: string;\n hostname: string;\n publicUrl: string;\n token: string;\n localUrl?: string;\n createdAt: number;\n expiresAt: number;\n lastHeartbeatAt?: number;\n}\n\nexport interface PreviewGatewayRequest {\n id: string;\n method: string;\n path: string;\n headers: Record<string, string>;\n body?: string | null;\n encoding?: \"base64\";\n createdAt: number;\n cancelled?: true;\n}\n\nexport interface PreviewGatewayResponse {\n status: number;\n // A header may carry multiple values (notably Set-Cookie: a login response\n // commonly sets a session cookie and a CSRF cookie). Those must survive as\n // an array through the store and back onto the public response.\n headers?: Record<string, string | string[]>;\n body?: string | null;\n encoding?: \"base64\";\n}\n\nexport interface PreviewGatewayStore {\n createSession(session: PreviewGatewaySession, ttlMs: number): Promise<void>;\n getSessionByName(name: string): Promise<PreviewGatewaySession | undefined>;\n getSessionById(id: string): Promise<PreviewGatewaySession | undefined>;\n touchSession(session: PreviewGatewaySession, ttlMs: number): Promise<void>;\n enqueueRequest(sessionId: string, request: PreviewGatewayRequest, ttlMs: number): Promise<void>;\n takeRequests(sessionId: string, limit: number): Promise<PreviewGatewayRequest[]>;\n saveResponse(\n sessionId: string,\n requestId: string,\n response: PreviewGatewayResponse,\n ttlMs: number,\n ): Promise<void>;\n getResponse(sessionId: string, requestId: string): Promise<PreviewGatewayResponse | undefined>;\n deleteResponse(sessionId: string, requestId: string): Promise<void>;\n deleteSession(session: PreviewGatewaySession): Promise<void>;\n}\n\ninterface PreviewGatewayRuntimeConfig {\n domain: string;\n baseUrl?: string;\n sessionTtlMs: number;\n clientHeartbeatTimeoutMs: number;\n requestTimeoutMs: number;\n pollTimeoutMs: number;\n pollIntervalMs: number;\n maxBodyBytes: number;\n maxResponseBodyBytes: number;\n}\n\nconst DEFAULT_DOMAIN = \"preview.farming-labs.dev\";\nconst DEFAULT_SESSION_TTL_MS = 1000 * 60 * 30;\nconst DEFAULT_CLIENT_HEARTBEAT_TIMEOUT_MS = 1000 * 20;\nconst DEFAULT_REQUEST_TIMEOUT_MS = 25000;\nconst DEFAULT_POLL_TIMEOUT_MS = 15000;\nconst DEFAULT_POLL_INTERVAL_MS = 100;\nconst DEFAULT_MAX_BODY_BYTES = 1024 * 1024 * 5;\nconst DEFAULT_MAX_RESPONSE_BODY_BYTES = 1024 * 1024 * 5;\nconst DEFAULT_POLL_REQUEST_LIMIT = 50;\nconst HOP_BY_HOP_HEADERS = new Set([\n \"connection\",\n \"content-encoding\",\n \"content-length\",\n \"keep-alive\",\n \"proxy-authenticate\",\n \"proxy-authorization\",\n \"te\",\n \"trailer\",\n \"transfer-encoding\",\n \"upgrade\",\n]);\n\nexport function createPreviewGatewayHandler(options: PreviewGatewayOptions = {}) {\n const store = options.store || createPreviewGatewayStoreFromEnv();\n const config = {\n domain: normalizeDomain(options.domain || process.env.FARM_PREVIEW_DOMAIN || DEFAULT_DOMAIN),\n baseUrl: options.baseUrl || process.env.FARM_PREVIEW_GATEWAY_URL,\n sessionTtlMs: options.sessionTtlMs ?? DEFAULT_SESSION_TTL_MS,\n clientHeartbeatTimeoutMs:\n options.clientHeartbeatTimeoutMs ?? DEFAULT_CLIENT_HEARTBEAT_TIMEOUT_MS,\n requestTimeoutMs: options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,\n pollTimeoutMs: options.pollTimeoutMs ?? DEFAULT_POLL_TIMEOUT_MS,\n pollIntervalMs: options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,\n maxBodyBytes: options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES,\n maxResponseBodyBytes: options.maxResponseBodyBytes ?? DEFAULT_MAX_RESPONSE_BODY_BYTES,\n };\n\n return async function handlePreviewGatewayRequest(request: Request): Promise<Response> {\n try {\n const url = new URL(request.url);\n\n if (request.method === \"GET\" && url.pathname === \"/api/health\") {\n return json({\n ok: true,\n domain: config.domain,\n store: store.constructor.name || \"PreviewGatewayStore\",\n });\n }\n\n if (request.method === \"POST\" && url.pathname === \"/api/sessions\") {\n return await createSession(request, store, config);\n }\n\n const sessionRoute = matchSessionRoute(url.pathname);\n if (sessionRoute) {\n return await handleSessionRoute(request, url, store, config, sessionRoute);\n }\n\n const publicRoute = resolvePublicPreviewRoute(request, url, config.domain);\n if (publicRoute) {\n return await proxyPublicRequest(request, url, store, config, publicRoute);\n }\n\n return json(\n {\n ok: true,\n message: \"Farm Preview Gateway\",\n createSession: \"/api/sessions\",\n health: \"/api/health\",\n },\n 200,\n );\n } catch (error) {\n if (error instanceof GatewayHttpError) {\n return text(error.message, error.status);\n }\n return text(\n error instanceof Error ? error.message : \"Unexpected preview gateway error.\",\n 500,\n );\n }\n };\n}\n\nexport function createNodePreviewGatewayHandler(options: PreviewGatewayOptions = {}) {\n const handler = createPreviewGatewayHandler(options);\n\n return async function nodePreviewGatewayHandler(req: IncomingMessage, res: ServerResponse) {\n const controller = new AbortController();\n const abort = () => controller.abort();\n const abortOnClose = () => {\n if (!res.writableEnded) abort();\n };\n req.once(\"aborted\", abort);\n res.once(\"close\", abortOnClose);\n try {\n const response = await handler(nodeRequestToWebRequest(req, controller.signal));\n if (controller.signal.aborted || res.destroyed) {\n await response.body?.cancel();\n return;\n }\n res.statusCode = response.status;\n // Headers.forEach folds repeated headers into one comma-joined value, which\n // corrupts multiple Set-Cookie. Emit those separately as an array so each\n // cookie becomes its own header line.\n const setCookies = response.headers.getSetCookie?.() ?? [];\n response.headers.forEach((value, key) => {\n if (key.toLowerCase() === \"set-cookie\") return;\n res.setHeader(key, value);\n });\n if (setCookies.length > 0) {\n res.setHeader(\"set-cookie\", setCookies);\n }\n res.end(Buffer.from(await response.arrayBuffer()));\n } finally {\n req.off(\"aborted\", abort);\n res.off(\"close\", abortOnClose);\n }\n };\n}\n\nexport function createPreviewGatewayStoreFromEnv(): PreviewGatewayStore {\n const redisUrl = process.env.UPSTASH_REDIS_REST_URL || process.env.KV_REST_API_URL;\n const redisToken = process.env.UPSTASH_REDIS_REST_TOKEN || process.env.KV_REST_API_TOKEN;\n\n if (redisUrl && redisToken) {\n return new RedisRestPreviewGatewayStore({\n url: redisUrl,\n token: redisToken,\n });\n }\n\n return new MemoryPreviewGatewayStore();\n}\n\nexport class MemoryPreviewGatewayStore implements PreviewGatewayStore {\n private sessions = new Map<string, PreviewGatewaySession>();\n private names = new Map<string, string>();\n private queues = new Map<string, PreviewGatewayRequest[]>();\n private responses = new Map<string, PreviewGatewayResponse>();\n\n async createSession(session: PreviewGatewaySession): Promise<void> {\n this.sessions.set(session.id, session);\n this.names.set(session.name, session.id);\n this.queues.set(session.id, []);\n }\n\n async getSessionByName(name: string): Promise<PreviewGatewaySession | undefined> {\n const id = this.names.get(name);\n return id ? this.getSessionById(id) : undefined;\n }\n\n async getSessionById(id: string): Promise<PreviewGatewaySession | undefined> {\n const session = this.sessions.get(id);\n if (!session) return undefined;\n if (session.expiresAt <= Date.now()) {\n await this.deleteSession(session);\n return undefined;\n }\n return session;\n }\n\n async touchSession(session: PreviewGatewaySession, ttlMs: number): Promise<void> {\n // A concurrent delete (the preview was stopped) must win: a poll handler\n // that outlives the DELETE must never resurrect a session that is no\n // longer stored, or its name would look \"online\" and block a restart.\n if (!this.sessions.has(session.id)) return;\n session.expiresAt = Date.now() + ttlMs;\n this.sessions.set(session.id, session);\n const nameOwner = this.names.get(session.name);\n if (!nameOwner || nameOwner === session.id) {\n this.names.set(session.name, session.id);\n }\n }\n\n async enqueueRequest(sessionId: string, request: PreviewGatewayRequest): Promise<void> {\n const queue = this.queues.get(sessionId) || [];\n queue.push(request);\n this.queues.set(sessionId, queue);\n }\n\n async takeRequests(sessionId: string, limit: number): Promise<PreviewGatewayRequest[]> {\n const queue = this.queues.get(sessionId) || [];\n return queue.splice(0, limit);\n }\n\n async saveResponse(\n sessionId: string,\n requestId: string,\n response: PreviewGatewayResponse,\n ): Promise<void> {\n this.responses.set(responseKey(sessionId, requestId), response);\n }\n\n async getResponse(\n sessionId: string,\n requestId: string,\n ): Promise<PreviewGatewayResponse | undefined> {\n return this.responses.get(responseKey(sessionId, requestId));\n }\n\n async deleteResponse(sessionId: string, requestId: string): Promise<void> {\n this.responses.delete(responseKey(sessionId, requestId));\n }\n\n async deleteSession(session: PreviewGatewaySession): Promise<void> {\n this.sessions.delete(session.id);\n // The name may have been taken over by a newer session; only remove the\n // mapping if it still points at the session being deleted.\n if (this.names.get(session.name) === session.id) {\n this.names.delete(session.name);\n }\n this.queues.delete(session.id);\n }\n}\n\nexport class RedisRestPreviewGatewayStore implements PreviewGatewayStore {\n constructor(private options: { url: string; token: string; prefix?: string }) {}\n\n async createSession(session: PreviewGatewaySession, ttlMs: number): Promise<void> {\n await this.command(\"SET\", this.sessionKey(session.id), JSON.stringify(session), \"PX\", ttlMs);\n await this.command(\"SET\", this.nameKey(session.name), session.id, \"PX\", ttlMs);\n await this.command(\"DEL\", this.queueKey(session.id));\n }\n\n async getSessionByName(name: string): Promise<PreviewGatewaySession | undefined> {\n const id = await this.command<string | null>(\"GET\", this.nameKey(name));\n return id ? this.getSessionById(id) : undefined;\n }\n\n async getSessionById(id: string): Promise<PreviewGatewaySession | undefined> {\n const value = await this.command<string | null>(\"GET\", this.sessionKey(id));\n return value ? JSON.parse(value) : undefined;\n }\n\n async touchSession(session: PreviewGatewaySession, ttlMs: number): Promise<void> {\n const nextSession = {\n ...session,\n expiresAt: Date.now() + ttlMs,\n };\n // XX only refreshes a session key that still exists. A poll handler that\n // outlives a DELETE must not recreate the session (nor its name mapping),\n // which would otherwise block reclaiming the name on restart.\n const result = await this.command<string | null>(\n \"SET\",\n this.sessionKey(session.id),\n JSON.stringify(nextSession),\n \"PX\",\n ttlMs,\n \"XX\",\n );\n if (result === null) return;\n const nameOwner = await this.command<string | null>(\"GET\", this.nameKey(session.name));\n if (!nameOwner || nameOwner === session.id) {\n await this.command(\"SET\", this.nameKey(session.name), session.id, \"PX\", ttlMs);\n }\n await this.command(\"EXPIRE\", this.queueKey(session.id), Math.ceil(ttlMs / 1000));\n }\n\n async enqueueRequest(\n sessionId: string,\n request: PreviewGatewayRequest,\n ttlMs: number,\n ): Promise<void> {\n await this.command(\"RPUSH\", this.queueKey(sessionId), JSON.stringify(request));\n await this.command(\"EXPIRE\", this.queueKey(sessionId), Math.ceil(ttlMs / 1000));\n }\n\n async takeRequests(sessionId: string, limit: number): Promise<PreviewGatewayRequest[]> {\n const values = await this.command<string[] | string | null>(\n \"LPOP\",\n this.queueKey(sessionId),\n limit,\n );\n if (!values) return [];\n const list = Array.isArray(values) ? values : [values];\n return list.map((value) => JSON.parse(value));\n }\n\n async saveResponse(\n sessionId: string,\n requestId: string,\n response: PreviewGatewayResponse,\n ttlMs: number,\n ): Promise<void> {\n await this.command(\n \"SET\",\n this.responseKey(sessionId, requestId),\n JSON.stringify(response),\n \"PX\",\n ttlMs,\n );\n }\n\n async getResponse(\n sessionId: string,\n requestId: string,\n ): Promise<PreviewGatewayResponse | undefined> {\n const value = await this.command<string | null>(\"GET\", this.responseKey(sessionId, requestId));\n return value ? JSON.parse(value) : undefined;\n }\n\n async deleteResponse(sessionId: string, requestId: string): Promise<void> {\n await this.command(\"DEL\", this.responseKey(sessionId, requestId));\n }\n\n async deleteSession(session: PreviewGatewaySession): Promise<void> {\n const keys = [this.sessionKey(session.id), this.queueKey(session.id)];\n // The name may have been taken over by a newer session; only remove the\n // mapping if it still points at the session being deleted.\n const nameOwner = await this.command<string | null>(\"GET\", this.nameKey(session.name));\n if (nameOwner === session.id) {\n keys.push(this.nameKey(session.name));\n }\n await this.command(\"DEL\", ...keys);\n }\n\n private async command<T = unknown>(...args: Array<string | number>) {\n const response = await fetch(this.options.url, {\n method: \"POST\",\n headers: {\n authorization: `Bearer ${this.options.token}`,\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify(args),\n });\n\n if (!response.ok) {\n throw new Error(\n `Preview gateway Redis command failed (${response.status}): ${await response.text()}`,\n );\n }\n\n const data = (await response.json()) as { result?: T; error?: string };\n if (data.error) {\n throw new Error(`Preview gateway Redis command failed: ${data.error}`);\n }\n return data.result as T;\n }\n\n private key(value: string) {\n return `${this.options.prefix || \"farm-preview\"}:${value}`;\n }\n\n private sessionKey(id: string) {\n return this.key(`session:${id}`);\n }\n\n private nameKey(name: string) {\n return this.key(`name:${name}`);\n }\n\n private queueKey(id: string) {\n return this.key(`queue:${id}`);\n }\n\n private responseKey(sessionId: string, requestId: string) {\n return this.key(`response:${sessionId}:${requestId}`);\n }\n}\n\nasync function createSession(\n request: Request,\n store: PreviewGatewayStore,\n config: PreviewGatewayRuntimeConfig,\n) {\n const input = (await request.json().catch(() => ({}))) as {\n name?: string;\n localUrl?: string;\n };\n const requestedName = sanitizePreviewName(input.name);\n let name = requestedName || randomPreviewName();\n\n // A name that is still owned by a live session cannot be claimed by a new\n // one — otherwise any caller could repoint an active preview's public URL\n // to their own session. Stale sessions (missed heartbeats) may be taken\n // over, matching how public routing treats them.\n for (let attempt = 0; ; attempt++) {\n const existing = await store.getSessionByName(name);\n if (!existing || !isPreviewClientOnline(existing, config)) {\n break;\n }\n if (requestedName) {\n return text(\n `A Farm preview named \"${name}\" is already active. Choose another name or stop the running preview.`,\n 409,\n );\n }\n if (attempt >= 4) {\n return text(\"Could not allocate a unique preview name. Try again.\", 503);\n }\n name = randomPreviewName();\n }\n\n const hostname = `${name}.${config.domain}`;\n const session: PreviewGatewaySession = {\n id: randomId(\"sess\"),\n name,\n hostname,\n publicUrl: createPublicUrl(request, config, name),\n token: randomId(\"token\"),\n localUrl: input.localUrl,\n createdAt: Date.now(),\n expiresAt: Date.now() + config.sessionTtlMs,\n lastHeartbeatAt: Date.now(),\n };\n\n await store.createSession(session, config.sessionTtlMs);\n\n return json({\n id: session.id,\n name: session.name,\n token: session.token,\n publicUrl: session.publicUrl,\n expiresAt: session.expiresAt,\n });\n}\n\nasync function handleSessionRoute(\n request: Request,\n url: URL,\n store: PreviewGatewayStore,\n config: PreviewGatewayRuntimeConfig,\n route: { sessionId: string; action?: string; requestId?: string },\n) {\n const session = await requireSession(store, route.sessionId, url.searchParams.get(\"token\"));\n\n if (request.method === \"GET\" && route.action === \"requests\") {\n return await pollSessionRequests(url, store, config, session);\n }\n\n if (request.method === \"POST\" && route.action === \"responses\" && route.requestId) {\n let response: PreviewGatewayResponse;\n try {\n response = await parsePreviewResponse(request, config.maxResponseBodyBytes);\n } catch (error) {\n if (error instanceof GatewayHttpError && error.status === 413) {\n await store.saveResponse(\n session.id,\n route.requestId,\n createOversizedPreviewResponse(config.maxResponseBodyBytes),\n config.sessionTtlMs,\n );\n }\n throw error;\n }\n await store.saveResponse(session.id, route.requestId, response, config.sessionTtlMs);\n await markSessionOnline(store, config, session);\n return json({ ok: true });\n }\n\n if (request.method === \"POST\" && route.action === \"heartbeat\") {\n await markSessionOnline(store, config, session);\n return json({ ok: true, expiresAt: Date.now() + config.sessionTtlMs });\n }\n\n if (request.method === \"DELETE\" && !route.action) {\n await store.deleteSession(session);\n return json({ ok: true });\n }\n\n return text(\"Preview gateway route not found.\", 404);\n}\n\nasync function parsePreviewResponse(request: Request, maxBodyBytes: number) {\n const maxEncodedBytes = Math.ceil(maxBodyBytes / 3) * 4 + 64 * 1024;\n const contentLength = Number(request.headers.get(\"content-length\") || 0);\n if (Number.isFinite(contentLength) && contentLength > maxEncodedBytes) {\n await request.body?.cancel();\n throw new GatewayHttpError(413, \"Preview response body is too large.\");\n }\n\n const bytes = await readBodyWithLimit(\n request,\n maxEncodedBytes,\n \"Preview response body is too large.\",\n );\n let response: PreviewGatewayResponse;\n try {\n response = JSON.parse(bytes.toString(\"utf8\")) as PreviewGatewayResponse;\n } catch {\n throw new GatewayHttpError(400, \"Preview response body must be valid JSON.\");\n }\n\n if (getPreviewResponseBodySize(response) > maxBodyBytes) {\n throw new GatewayHttpError(413, \"Preview response body is too large.\");\n }\n return response;\n}\n\nfunction getPreviewResponseBodySize(response: PreviewGatewayResponse) {\n if (!response.body) return 0;\n return Buffer.byteLength(response.body, response.encoding === \"base64\" ? \"base64\" : \"utf8\");\n}\n\nfunction createOversizedPreviewResponse(maxBodyBytes: number): PreviewGatewayResponse {\n return {\n status: 502,\n headers: { \"content-type\": \"text/plain; charset=utf-8\" },\n body: Buffer.from(\n `The local preview response exceeded the ${maxBodyBytes} byte limit.`,\n ).toString(\"base64\"),\n encoding: \"base64\",\n };\n}\n\nasync function pollSessionRequests(\n url: URL,\n store: PreviewGatewayStore,\n config: PreviewGatewayRuntimeConfig,\n session: PreviewGatewaySession,\n) {\n const waitMs = clamp(Number(url.searchParams.get(\"wait\") || config.pollTimeoutMs), 1000, 25000);\n const deadline = Date.now() + waitMs;\n\n await markSessionOnline(store, config, session);\n\n while (Date.now() < deadline) {\n const requests = await store.takeRequests(session.id, DEFAULT_POLL_REQUEST_LIMIT);\n if (requests.length) {\n await markSessionOnline(store, config, session);\n return json({ requests });\n }\n await delay(config.pollIntervalMs);\n }\n\n await markSessionOnline(store, config, session);\n return new Response(null, { status: 204 });\n}\n\nasync function proxyPublicRequest(\n request: Request,\n url: URL,\n store: PreviewGatewayStore,\n config: PreviewGatewayRuntimeConfig,\n route: { name: string; path: string },\n) {\n const session = await store.getSessionByName(route.name);\n if (!session) {\n return text(`No active Farm preview is running for \"${route.name}\".`, 404);\n }\n if (!isPreviewClientOnline(session, config)) {\n await store.deleteSession(session);\n return text(`No active Farm preview is running for \"${route.name}\".`, 404);\n }\n\n const previewRequest = await serializePreviewRequest(\n request,\n url,\n route.path,\n config.maxBodyBytes,\n );\n await store.enqueueRequest(session.id, previewRequest, config.sessionTtlMs);\n await store.touchSession(session, config.sessionTtlMs);\n\n const response = await waitForPreviewResponse(\n store,\n config,\n session,\n previewRequest.id,\n request.signal,\n );\n if (response === \"cancelled\") {\n await store.enqueueRequest(\n session.id,\n {\n ...previewRequest,\n body: undefined,\n encoding: undefined,\n cancelled: true,\n createdAt: Date.now(),\n },\n config.sessionTtlMs,\n );\n return new Response(null, { status: 499 });\n }\n if (response === \"stale\") {\n return text(`No active Farm preview is running for \"${route.name}\".`, 404);\n }\n if (!response) {\n return text(\"The local Farm preview did not respond before the gateway timed out.\", 504);\n }\n\n const headers = new Headers();\n for (const [key, value] of Object.entries(response.headers || {})) {\n const normalized = key.toLowerCase();\n if (HOP_BY_HOP_HEADERS.has(normalized)) {\n continue;\n }\n // Append multi-valued headers (Set-Cookie) so every value reaches the\n // visitor; Headers.set would keep only the last.\n for (const single of Array.isArray(value) ? value : [value]) {\n headers.append(key, single);\n }\n }\n headers.set(\"cache-control\", \"no-store\");\n headers.set(\"x-farm-preview\", session.name);\n\n return new Response(\n response.body\n ? Buffer.from(response.body, response.encoding === \"base64\" ? \"base64\" : \"utf8\")\n : null,\n {\n status: response.status || 200,\n headers,\n },\n );\n}\n\nasync function waitForPreviewResponse(\n store: PreviewGatewayStore,\n config: PreviewGatewayRuntimeConfig,\n session: PreviewGatewaySession,\n requestId: string,\n signal: AbortSignal,\n) {\n const deadline = Date.now() + config.requestTimeoutMs;\n let nextLivenessCheckAt = 0;\n\n while (Date.now() < deadline) {\n if (signal.aborted) return \"cancelled\";\n if (Date.now() >= nextLivenessCheckAt) {\n const latestSession = await store.getSessionById(session.id);\n if (!latestSession || !isPreviewClientOnline(latestSession, config)) {\n if (latestSession) await store.deleteSession(latestSession);\n return \"stale\";\n }\n nextLivenessCheckAt = Date.now() + 1000;\n }\n\n const response = await store.getResponse(session.id, requestId);\n if (response) {\n await store.deleteResponse(session.id, requestId);\n return response;\n }\n try {\n await delay(config.pollIntervalMs, undefined, { signal });\n } catch {\n if (signal.aborted) return \"cancelled\";\n throw new Error(\"Preview response polling was interrupted.\");\n }\n }\n\n return undefined;\n}\n\nasync function markSessionOnline(\n store: PreviewGatewayStore,\n config: PreviewGatewayRuntimeConfig,\n session: PreviewGatewaySession,\n) {\n session.lastHeartbeatAt = Date.now();\n await store.touchSession(session, config.sessionTtlMs);\n}\n\nfunction isPreviewClientOnline(\n session: PreviewGatewaySession,\n config: PreviewGatewayRuntimeConfig,\n) {\n const lastHeartbeatAt = session.lastHeartbeatAt || session.createdAt;\n return Date.now() - lastHeartbeatAt <= config.clientHeartbeatTimeoutMs;\n}\n\nasync function serializePreviewRequest(\n request: Request,\n url: URL,\n routePath: string,\n maxBodyBytes: number,\n): Promise<PreviewGatewayRequest> {\n const method = request.method.toUpperCase();\n const headers: Record<string, string> = {};\n request.headers.forEach((value, key) => {\n const normalized = key.toLowerCase();\n if (\n !HOP_BY_HOP_HEADERS.has(normalized) &&\n normalized !== \"forwarded\" &&\n !normalized.startsWith(\"x-forwarded-\")\n ) {\n headers[key] = value;\n }\n });\n\n headers[\"x-forwarded-host\"] = request.headers.get(\"host\") || url.host;\n headers[\"x-forwarded-proto\"] = url.protocol.replace(\":\", \"\");\n\n let body: string | undefined;\n if (method !== \"GET\" && method !== \"HEAD\") {\n const contentLength = Number(request.headers.get(\"content-length\") || 0);\n if (contentLength > maxBodyBytes) {\n throw new GatewayHttpError(413, \"Preview request body is too large.\");\n }\n\n const buffer = await readBodyWithLimit(\n request,\n maxBodyBytes,\n \"Preview request body is too large.\",\n );\n body = buffer.toString(\"base64\");\n }\n\n return {\n id: randomId(\"req\"),\n method,\n path: `${routePath}${url.search}`,\n headers,\n body,\n encoding: body ? \"base64\" : undefined,\n createdAt: Date.now(),\n };\n}\n\nasync function readBodyWithLimit(request: Request, maxBytes: number, message: string) {\n if (!request.body) return Buffer.alloc(0);\n const reader = request.body.getReader();\n const chunks: Uint8Array[] = [];\n let size = 0;\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n size += value.byteLength;\n if (size > maxBytes) {\n await reader.cancel();\n throw new GatewayHttpError(413, message);\n }\n chunks.push(value);\n }\n } catch (error) {\n await reader.cancel().catch(() => undefined);\n throw error;\n }\n return Buffer.concat(chunks, size);\n}\n\nasync function requireSession(store: PreviewGatewayStore, sessionId: string, token: string | null) {\n const session = await store.getSessionById(sessionId);\n if (!session || !token || token !== session.token) {\n throw new GatewayHttpError(401, \"Invalid preview gateway session.\");\n }\n return session;\n}\n\nfunction resolvePublicPreviewRoute(request: Request, url: URL, domain: string) {\n const pathMatch = url.pathname.match(/^\\/__preview\\/([^/]+)(\\/.*)?$/);\n if (pathMatch) {\n return {\n name: pathMatch[1],\n path: pathMatch[2] || \"/\",\n };\n }\n\n const host = (request.headers.get(\"host\") || url.host).split(\":\")[0];\n if (!host || host === domain || !host.endsWith(`.${domain}`)) {\n return undefined;\n }\n\n return {\n name: host.slice(0, -(domain.length + 1)),\n path: url.pathname || \"/\",\n };\n}\n\nfunction matchSessionRoute(pathname: string) {\n const match = pathname.match(\n /^\\/api\\/sessions\\/([^/]+)(?:\\/(requests|heartbeat|responses)(?:\\/([^/]+))?)?$/,\n );\n if (!match) return undefined;\n return {\n sessionId: match[1],\n action: match[2],\n requestId: match[3],\n };\n}\n\nfunction nodeRequestToWebRequest(req: IncomingMessage, signal?: AbortSignal) {\n const host = headerValue(req.headers, \"host\") || \"localhost\";\n // This adapter is the public trust boundary. A visitor-controlled forwarded\n // protocol must not influence the authority serialized to the local app.\n const protocol = isLocalHost(host.split(\":\")[0]) ? \"http\" : \"https\";\n const url = `${protocol}://${host}${req.url || \"/\"}`;\n const headers = nodeHeadersToWebHeaders(req.headers);\n const method = req.method || \"GET\";\n const init: RequestInit & { duplex?: \"half\" } = {\n method,\n headers,\n signal,\n };\n\n if (method !== \"GET\" && method !== \"HEAD\") {\n init.body = req as unknown as BodyInit;\n init.duplex = \"half\";\n }\n\n return new Request(url, init);\n}\n\nfunction nodeHeadersToWebHeaders(headers: IncomingHttpHeaders) {\n const next = new Headers();\n for (const [key, value] of Object.entries(headers)) {\n if (value === undefined) continue;\n if (Array.isArray(value)) {\n for (const item of value) next.append(key, item);\n } else {\n next.set(key, value);\n }\n }\n return next;\n}\n\nfunction headerValue(headers: IncomingHttpHeaders, key: string) {\n const value = headers[key];\n return Array.isArray(value) ? value[0] : value;\n}\n\nfunction createPublicUrl(request: Request, config: PreviewGatewayRuntimeConfig, name: string) {\n if (config.baseUrl) {\n const base = new URL(config.baseUrl);\n if (isLocalHost(base.hostname)) {\n return `${config.baseUrl.replace(/\\/+$/, \"\")}/__preview/${name}`;\n }\n }\n\n const host = request.headers.get(\"host\") || new URL(request.url).host;\n if (isLocalHost(host.split(\":\")[0])) {\n return `${new URL(request.url).origin}/__preview/${name}`;\n }\n\n return `https://${name}.${config.domain}`;\n}\n\nfunction json(value: unknown, status = 200) {\n return new Response(JSON.stringify(value), {\n status,\n headers: {\n \"content-type\": \"application/json\",\n \"cache-control\": \"no-store\",\n },\n });\n}\n\nfunction text(value: string, status = 200) {\n return new Response(value, {\n status,\n headers: {\n \"content-type\": \"text/plain; charset=utf-8\",\n \"cache-control\": \"no-store\",\n },\n });\n}\n\nfunction sanitizePreviewName(value: string | undefined) {\n if (!value) return undefined;\n return value\n .toLowerCase()\n .replace(/[^a-z0-9-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 48);\n}\n\nfunction randomPreviewName() {\n return `farm-${Math.random().toString(36).slice(2, 8)}`;\n}\n\nfunction randomId(prefix: string) {\n return `${prefix}_${crypto.randomUUID().replace(/-/g, \"\")}`;\n}\n\nfunction normalizeDomain(value: string) {\n return value\n .replace(/^https?:\\/\\//, \"\")\n .replace(/^\\.*/, \"\")\n .replace(/\\/*$/, \"\");\n}\n\nfunction responseKey(sessionId: string, requestId: string) {\n return `${sessionId}:${requestId}`;\n}\n\nfunction clamp(value: number, min: number, max: number) {\n if (!Number.isFinite(value)) return min;\n return Math.max(min, Math.min(max, value));\n}\n\nfunction isLocalHost(hostname: string) {\n return hostname === \"localhost\" || hostname === \"127.0.0.1\" || hostname === \"::1\";\n}\n\nclass GatewayHttpError extends Error {\n constructor(\n public status: number,\n message: string,\n ) {\n super(message);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["delay"],"sources":["../src/index.ts"],"sourcesContent":["import type { IncomingHttpHeaders, IncomingMessage, ServerResponse } from \"node:http\";\nimport { setTimeout as delay } from \"node:timers/promises\";\n\nexport interface PreviewGatewayOptions {\n domain?: string;\n baseUrl?: string;\n store?: PreviewGatewayStore;\n sessionTtlMs?: number;\n clientHeartbeatTimeoutMs?: number;\n requestTimeoutMs?: number;\n pollTimeoutMs?: number;\n pollIntervalMs?: number;\n maxBodyBytes?: number;\n maxResponseBodyBytes?: number;\n}\n\nexport interface PreviewGatewaySession {\n id: string;\n name: string;\n hostname: string;\n publicUrl: string;\n token: string;\n localUrl?: string;\n createdAt: number;\n expiresAt: number;\n lastHeartbeatAt?: number;\n}\n\nexport interface PreviewGatewayRequest {\n id: string;\n method: string;\n path: string;\n headers: Record<string, string>;\n body?: string | null;\n encoding?: \"base64\";\n createdAt: number;\n cancelled?: true;\n}\n\nexport interface PreviewGatewayResponse {\n status: number;\n // A header may carry multiple values (notably Set-Cookie: a login response\n // commonly sets a session cookie and a CSRF cookie). Those must survive as\n // an array through the store and back onto the public response.\n headers?: Record<string, string | string[]>;\n body?: string | null;\n encoding?: \"base64\";\n}\n\nexport interface PreviewGatewayStore {\n createSession(session: PreviewGatewaySession, ttlMs: number): Promise<void>;\n getSessionByName(name: string): Promise<PreviewGatewaySession | undefined>;\n getSessionById(id: string): Promise<PreviewGatewaySession | undefined>;\n touchSession(session: PreviewGatewaySession, ttlMs: number): Promise<void>;\n enqueueRequest(sessionId: string, request: PreviewGatewayRequest, ttlMs: number): Promise<void>;\n takeRequests(sessionId: string, limit: number): Promise<PreviewGatewayRequest[]>;\n saveResponse(\n sessionId: string,\n requestId: string,\n response: PreviewGatewayResponse,\n ttlMs: number,\n ): Promise<void>;\n getResponse(sessionId: string, requestId: string): Promise<PreviewGatewayResponse | undefined>;\n deleteResponse(sessionId: string, requestId: string): Promise<void>;\n deleteSession(session: PreviewGatewaySession): Promise<void>;\n}\n\ninterface PreviewGatewayRuntimeConfig {\n domain: string;\n baseUrl?: string;\n sessionTtlMs: number;\n clientHeartbeatTimeoutMs: number;\n requestTimeoutMs: number;\n pollTimeoutMs: number;\n pollIntervalMs: number;\n maxBodyBytes: number;\n maxResponseBodyBytes: number;\n}\n\nconst DEFAULT_DOMAIN = \"preview.farming-labs.dev\";\nconst DEFAULT_SESSION_TTL_MS = 1000 * 60 * 30;\nconst DEFAULT_CLIENT_HEARTBEAT_TIMEOUT_MS = 1000 * 20;\nconst DEFAULT_REQUEST_TIMEOUT_MS = 25000;\nconst DEFAULT_POLL_TIMEOUT_MS = 15000;\nconst DEFAULT_POLL_INTERVAL_MS = 100;\nconst DEFAULT_MAX_BODY_BYTES = 1024 * 1024 * 5;\nconst DEFAULT_MAX_RESPONSE_BODY_BYTES = 1024 * 1024 * 5;\nconst DEFAULT_POLL_REQUEST_LIMIT = 50;\nconst HOP_BY_HOP_HEADERS = new Set([\n \"connection\",\n \"content-length\",\n \"keep-alive\",\n \"proxy-authenticate\",\n \"proxy-authorization\",\n \"te\",\n \"trailer\",\n \"transfer-encoding\",\n \"upgrade\",\n]);\n\nfunction getHopByHopHeaderNames(headers: Headers | Record<string, string | string[]>): Set<string> {\n const names = new Set(HOP_BY_HOP_HEADERS);\n const connection =\n headers instanceof Headers\n ? headers.get(\"connection\")\n : Object.entries(headers).find(([name]) => name.toLowerCase() === \"connection\")?.[1];\n const values = Array.isArray(connection) ? connection : connection ? [connection] : [];\n for (const value of values) {\n for (const name of value.split(\",\")) {\n const normalized = name.trim().toLowerCase();\n if (normalized) names.add(normalized);\n }\n }\n return names;\n}\n\nexport function createPreviewGatewayHandler(options: PreviewGatewayOptions = {}) {\n const store = options.store || createPreviewGatewayStoreFromEnv();\n const config = {\n domain: normalizeDomain(options.domain || process.env.FARM_PREVIEW_DOMAIN || DEFAULT_DOMAIN),\n baseUrl: options.baseUrl || process.env.FARM_PREVIEW_GATEWAY_URL,\n sessionTtlMs: options.sessionTtlMs ?? DEFAULT_SESSION_TTL_MS,\n clientHeartbeatTimeoutMs:\n options.clientHeartbeatTimeoutMs ?? DEFAULT_CLIENT_HEARTBEAT_TIMEOUT_MS,\n requestTimeoutMs: options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,\n pollTimeoutMs: options.pollTimeoutMs ?? DEFAULT_POLL_TIMEOUT_MS,\n pollIntervalMs: options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,\n maxBodyBytes: options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES,\n maxResponseBodyBytes: options.maxResponseBodyBytes ?? DEFAULT_MAX_RESPONSE_BODY_BYTES,\n };\n\n return async function handlePreviewGatewayRequest(request: Request): Promise<Response> {\n try {\n const url = new URL(request.url);\n\n if (request.method === \"GET\" && url.pathname === \"/api/health\") {\n return json({\n ok: true,\n domain: config.domain,\n store: store.constructor.name || \"PreviewGatewayStore\",\n });\n }\n\n if (request.method === \"POST\" && url.pathname === \"/api/sessions\") {\n return await createSession(request, store, config);\n }\n\n const sessionRoute = matchSessionRoute(url.pathname);\n if (sessionRoute) {\n return await handleSessionRoute(request, url, store, config, sessionRoute);\n }\n\n const publicRoute = resolvePublicPreviewRoute(request, url, config.domain);\n if (publicRoute) {\n return await proxyPublicRequest(request, url, store, config, publicRoute);\n }\n\n return json(\n {\n ok: true,\n message: \"Farm Preview Gateway\",\n createSession: \"/api/sessions\",\n health: \"/api/health\",\n },\n 200,\n );\n } catch (error) {\n if (error instanceof GatewayHttpError) {\n return text(error.message, error.status);\n }\n return text(\n error instanceof Error ? error.message : \"Unexpected preview gateway error.\",\n 500,\n );\n }\n };\n}\n\nexport function createNodePreviewGatewayHandler(options: PreviewGatewayOptions = {}) {\n const handler = createPreviewGatewayHandler(options);\n\n return async function nodePreviewGatewayHandler(req: IncomingMessage, res: ServerResponse) {\n const controller = new AbortController();\n const abort = () => controller.abort();\n const abortOnClose = () => {\n if (!res.writableEnded) abort();\n };\n req.once(\"aborted\", abort);\n res.once(\"close\", abortOnClose);\n try {\n const response = await handler(nodeRequestToWebRequest(req, controller.signal));\n if (controller.signal.aborted || res.destroyed) {\n await response.body?.cancel();\n return;\n }\n res.statusCode = response.status;\n // Headers.forEach folds repeated headers into one comma-joined value, which\n // corrupts multiple Set-Cookie. Emit those separately as an array so each\n // cookie becomes its own header line.\n const setCookies = response.headers.getSetCookie?.() ?? [];\n response.headers.forEach((value, key) => {\n if (key.toLowerCase() === \"set-cookie\") return;\n res.setHeader(key, value);\n });\n if (setCookies.length > 0) {\n res.setHeader(\"set-cookie\", setCookies);\n }\n res.end(Buffer.from(await response.arrayBuffer()));\n } finally {\n req.off(\"aborted\", abort);\n res.off(\"close\", abortOnClose);\n }\n };\n}\n\nexport function createPreviewGatewayStoreFromEnv(): PreviewGatewayStore {\n const redisUrl = process.env.UPSTASH_REDIS_REST_URL || process.env.KV_REST_API_URL;\n const redisToken = process.env.UPSTASH_REDIS_REST_TOKEN || process.env.KV_REST_API_TOKEN;\n\n if (redisUrl && redisToken) {\n return new RedisRestPreviewGatewayStore({\n url: redisUrl,\n token: redisToken,\n });\n }\n\n return new MemoryPreviewGatewayStore();\n}\n\nexport class MemoryPreviewGatewayStore implements PreviewGatewayStore {\n private sessions = new Map<string, PreviewGatewaySession>();\n private names = new Map<string, string>();\n private queues = new Map<string, PreviewGatewayRequest[]>();\n private responses = new Map<string, PreviewGatewayResponse>();\n\n async createSession(session: PreviewGatewaySession): Promise<void> {\n this.sessions.set(session.id, session);\n this.names.set(session.name, session.id);\n this.queues.set(session.id, []);\n }\n\n async getSessionByName(name: string): Promise<PreviewGatewaySession | undefined> {\n const id = this.names.get(name);\n return id ? this.getSessionById(id) : undefined;\n }\n\n async getSessionById(id: string): Promise<PreviewGatewaySession | undefined> {\n const session = this.sessions.get(id);\n if (!session) return undefined;\n if (session.expiresAt <= Date.now()) {\n await this.deleteSession(session);\n return undefined;\n }\n return session;\n }\n\n async touchSession(session: PreviewGatewaySession, ttlMs: number): Promise<void> {\n // A concurrent delete (the preview was stopped) must win: a poll handler\n // that outlives the DELETE must never resurrect a session that is no\n // longer stored, or its name would look \"online\" and block a restart.\n if (!this.sessions.has(session.id)) return;\n session.expiresAt = Date.now() + ttlMs;\n this.sessions.set(session.id, session);\n const nameOwner = this.names.get(session.name);\n if (!nameOwner || nameOwner === session.id) {\n this.names.set(session.name, session.id);\n }\n }\n\n async enqueueRequest(sessionId: string, request: PreviewGatewayRequest): Promise<void> {\n const queue = this.queues.get(sessionId) || [];\n queue.push(request);\n this.queues.set(sessionId, queue);\n }\n\n async takeRequests(sessionId: string, limit: number): Promise<PreviewGatewayRequest[]> {\n const queue = this.queues.get(sessionId) || [];\n return queue.splice(0, limit);\n }\n\n async saveResponse(\n sessionId: string,\n requestId: string,\n response: PreviewGatewayResponse,\n ): Promise<void> {\n this.responses.set(responseKey(sessionId, requestId), response);\n }\n\n async getResponse(\n sessionId: string,\n requestId: string,\n ): Promise<PreviewGatewayResponse | undefined> {\n return this.responses.get(responseKey(sessionId, requestId));\n }\n\n async deleteResponse(sessionId: string, requestId: string): Promise<void> {\n this.responses.delete(responseKey(sessionId, requestId));\n }\n\n async deleteSession(session: PreviewGatewaySession): Promise<void> {\n this.sessions.delete(session.id);\n // The name may have been taken over by a newer session; only remove the\n // mapping if it still points at the session being deleted.\n if (this.names.get(session.name) === session.id) {\n this.names.delete(session.name);\n }\n this.queues.delete(session.id);\n }\n}\n\nexport class RedisRestPreviewGatewayStore implements PreviewGatewayStore {\n constructor(private options: { url: string; token: string; prefix?: string }) {}\n\n async createSession(session: PreviewGatewaySession, ttlMs: number): Promise<void> {\n await this.command(\"SET\", this.sessionKey(session.id), JSON.stringify(session), \"PX\", ttlMs);\n await this.command(\"SET\", this.nameKey(session.name), session.id, \"PX\", ttlMs);\n await this.command(\"DEL\", this.queueKey(session.id));\n }\n\n async getSessionByName(name: string): Promise<PreviewGatewaySession | undefined> {\n const id = await this.command<string | null>(\"GET\", this.nameKey(name));\n return id ? this.getSessionById(id) : undefined;\n }\n\n async getSessionById(id: string): Promise<PreviewGatewaySession | undefined> {\n const value = await this.command<string | null>(\"GET\", this.sessionKey(id));\n return value ? JSON.parse(value) : undefined;\n }\n\n async touchSession(session: PreviewGatewaySession, ttlMs: number): Promise<void> {\n const nextSession = {\n ...session,\n expiresAt: Date.now() + ttlMs,\n };\n // XX only refreshes a session key that still exists. A poll handler that\n // outlives a DELETE must not recreate the session (nor its name mapping),\n // which would otherwise block reclaiming the name on restart.\n const result = await this.command<string | null>(\n \"SET\",\n this.sessionKey(session.id),\n JSON.stringify(nextSession),\n \"PX\",\n ttlMs,\n \"XX\",\n );\n if (result === null) return;\n const nameOwner = await this.command<string | null>(\"GET\", this.nameKey(session.name));\n if (!nameOwner || nameOwner === session.id) {\n await this.command(\"SET\", this.nameKey(session.name), session.id, \"PX\", ttlMs);\n }\n await this.command(\"EXPIRE\", this.queueKey(session.id), Math.ceil(ttlMs / 1000));\n }\n\n async enqueueRequest(\n sessionId: string,\n request: PreviewGatewayRequest,\n ttlMs: number,\n ): Promise<void> {\n await this.command(\"RPUSH\", this.queueKey(sessionId), JSON.stringify(request));\n await this.command(\"EXPIRE\", this.queueKey(sessionId), Math.ceil(ttlMs / 1000));\n }\n\n async takeRequests(sessionId: string, limit: number): Promise<PreviewGatewayRequest[]> {\n const values = await this.command<string[] | string | null>(\n \"LPOP\",\n this.queueKey(sessionId),\n limit,\n );\n if (!values) return [];\n const list = Array.isArray(values) ? values : [values];\n return list.map((value) => JSON.parse(value));\n }\n\n async saveResponse(\n sessionId: string,\n requestId: string,\n response: PreviewGatewayResponse,\n ttlMs: number,\n ): Promise<void> {\n await this.command(\n \"SET\",\n this.responseKey(sessionId, requestId),\n JSON.stringify(response),\n \"PX\",\n ttlMs,\n );\n }\n\n async getResponse(\n sessionId: string,\n requestId: string,\n ): Promise<PreviewGatewayResponse | undefined> {\n const value = await this.command<string | null>(\"GET\", this.responseKey(sessionId, requestId));\n return value ? JSON.parse(value) : undefined;\n }\n\n async deleteResponse(sessionId: string, requestId: string): Promise<void> {\n await this.command(\"DEL\", this.responseKey(sessionId, requestId));\n }\n\n async deleteSession(session: PreviewGatewaySession): Promise<void> {\n const keys = [this.sessionKey(session.id), this.queueKey(session.id)];\n // The name may have been taken over by a newer session; only remove the\n // mapping if it still points at the session being deleted.\n const nameOwner = await this.command<string | null>(\"GET\", this.nameKey(session.name));\n if (nameOwner === session.id) {\n keys.push(this.nameKey(session.name));\n }\n await this.command(\"DEL\", ...keys);\n }\n\n private async command<T = unknown>(...args: Array<string | number>) {\n const response = await fetch(this.options.url, {\n method: \"POST\",\n headers: {\n authorization: `Bearer ${this.options.token}`,\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify(args),\n });\n\n if (!response.ok) {\n throw new Error(\n `Preview gateway Redis command failed (${response.status}): ${await response.text()}`,\n );\n }\n\n const data = (await response.json()) as { result?: T; error?: string };\n if (data.error) {\n throw new Error(`Preview gateway Redis command failed: ${data.error}`);\n }\n return data.result as T;\n }\n\n private key(value: string) {\n return `${this.options.prefix || \"farm-preview\"}:${value}`;\n }\n\n private sessionKey(id: string) {\n return this.key(`session:${id}`);\n }\n\n private nameKey(name: string) {\n return this.key(`name:${name}`);\n }\n\n private queueKey(id: string) {\n return this.key(`queue:${id}`);\n }\n\n private responseKey(sessionId: string, requestId: string) {\n return this.key(`response:${sessionId}:${requestId}`);\n }\n}\n\nasync function createSession(\n request: Request,\n store: PreviewGatewayStore,\n config: PreviewGatewayRuntimeConfig,\n) {\n const input = (await request.json().catch(() => ({}))) as {\n name?: string;\n localUrl?: string;\n };\n const requestedName = sanitizePreviewName(input.name);\n let name = requestedName || randomPreviewName();\n\n // A name that is still owned by a live session cannot be claimed by a new\n // one — otherwise any caller could repoint an active preview's public URL\n // to their own session. Stale sessions (missed heartbeats) may be taken\n // over, matching how public routing treats them.\n for (let attempt = 0; ; attempt++) {\n const existing = await store.getSessionByName(name);\n if (!existing || !isPreviewClientOnline(existing, config)) {\n break;\n }\n if (requestedName) {\n return text(\n `A Farm preview named \"${name}\" is already active. Choose another name or stop the running preview.`,\n 409,\n );\n }\n if (attempt >= 4) {\n return text(\"Could not allocate a unique preview name. Try again.\", 503);\n }\n name = randomPreviewName();\n }\n\n const hostname = `${name}.${config.domain}`;\n const session: PreviewGatewaySession = {\n id: randomId(\"sess\"),\n name,\n hostname,\n publicUrl: createPublicUrl(request, config, name),\n token: randomId(\"token\"),\n localUrl: input.localUrl,\n createdAt: Date.now(),\n expiresAt: Date.now() + config.sessionTtlMs,\n lastHeartbeatAt: Date.now(),\n };\n\n await store.createSession(session, config.sessionTtlMs);\n\n return json({\n id: session.id,\n name: session.name,\n token: session.token,\n publicUrl: session.publicUrl,\n expiresAt: session.expiresAt,\n });\n}\n\nasync function handleSessionRoute(\n request: Request,\n url: URL,\n store: PreviewGatewayStore,\n config: PreviewGatewayRuntimeConfig,\n route: { sessionId: string; action?: string; requestId?: string },\n) {\n const session = await requireSession(store, route.sessionId, url.searchParams.get(\"token\"));\n\n if (request.method === \"GET\" && route.action === \"requests\") {\n return await pollSessionRequests(url, store, config, session);\n }\n\n if (request.method === \"POST\" && route.action === \"responses\" && route.requestId) {\n let response: PreviewGatewayResponse;\n try {\n response = await parsePreviewResponse(request, config.maxResponseBodyBytes);\n } catch (error) {\n if (error instanceof GatewayHttpError && error.status === 413) {\n await store.saveResponse(\n session.id,\n route.requestId,\n createOversizedPreviewResponse(config.maxResponseBodyBytes),\n config.sessionTtlMs,\n );\n }\n throw error;\n }\n await store.saveResponse(session.id, route.requestId, response, config.sessionTtlMs);\n await markSessionOnline(store, config, session);\n return json({ ok: true });\n }\n\n if (request.method === \"POST\" && route.action === \"heartbeat\") {\n await markSessionOnline(store, config, session);\n return json({ ok: true, expiresAt: Date.now() + config.sessionTtlMs });\n }\n\n if (request.method === \"DELETE\" && !route.action) {\n await store.deleteSession(session);\n return json({ ok: true });\n }\n\n return text(\"Preview gateway route not found.\", 404);\n}\n\nasync function parsePreviewResponse(request: Request, maxBodyBytes: number) {\n const maxEncodedBytes = Math.ceil(maxBodyBytes / 3) * 4 + 64 * 1024;\n const contentLength = Number(request.headers.get(\"content-length\") || 0);\n if (Number.isFinite(contentLength) && contentLength > maxEncodedBytes) {\n await request.body?.cancel();\n throw new GatewayHttpError(413, \"Preview response body is too large.\");\n }\n\n const bytes = await readBodyWithLimit(\n request,\n maxEncodedBytes,\n \"Preview response body is too large.\",\n );\n let response: PreviewGatewayResponse;\n try {\n response = JSON.parse(bytes.toString(\"utf8\")) as PreviewGatewayResponse;\n } catch {\n throw new GatewayHttpError(400, \"Preview response body must be valid JSON.\");\n }\n\n if (getPreviewResponseBodySize(response) > maxBodyBytes) {\n throw new GatewayHttpError(413, \"Preview response body is too large.\");\n }\n return response;\n}\n\nfunction getPreviewResponseBodySize(response: PreviewGatewayResponse) {\n if (!response.body) return 0;\n return Buffer.byteLength(response.body, response.encoding === \"base64\" ? \"base64\" : \"utf8\");\n}\n\nfunction createOversizedPreviewResponse(maxBodyBytes: number): PreviewGatewayResponse {\n return {\n status: 502,\n headers: { \"content-type\": \"text/plain; charset=utf-8\" },\n body: Buffer.from(\n `The local preview response exceeded the ${maxBodyBytes} byte limit.`,\n ).toString(\"base64\"),\n encoding: \"base64\",\n };\n}\n\nasync function pollSessionRequests(\n url: URL,\n store: PreviewGatewayStore,\n config: PreviewGatewayRuntimeConfig,\n session: PreviewGatewaySession,\n) {\n const waitMs = clamp(Number(url.searchParams.get(\"wait\") || config.pollTimeoutMs), 1000, 25000);\n const deadline = Date.now() + waitMs;\n\n await markSessionOnline(store, config, session);\n\n while (Date.now() < deadline) {\n const requests = await store.takeRequests(session.id, DEFAULT_POLL_REQUEST_LIMIT);\n if (requests.length) {\n await markSessionOnline(store, config, session);\n return json({ requests });\n }\n await delay(config.pollIntervalMs);\n }\n\n await markSessionOnline(store, config, session);\n return new Response(null, { status: 204 });\n}\n\nasync function proxyPublicRequest(\n request: Request,\n url: URL,\n store: PreviewGatewayStore,\n config: PreviewGatewayRuntimeConfig,\n route: { name: string; path: string },\n) {\n const session = await store.getSessionByName(route.name);\n if (!session) {\n return text(`No active Farm preview is running for \"${route.name}\".`, 404);\n }\n if (!isPreviewClientOnline(session, config)) {\n await store.deleteSession(session);\n return text(`No active Farm preview is running for \"${route.name}\".`, 404);\n }\n\n const previewRequest = await serializePreviewRequest(\n request,\n url,\n route.path,\n config.maxBodyBytes,\n );\n await store.enqueueRequest(session.id, previewRequest, config.sessionTtlMs);\n await store.touchSession(session, config.sessionTtlMs);\n\n const response = await waitForPreviewResponse(\n store,\n config,\n session,\n previewRequest.id,\n request.signal,\n );\n if (response === \"cancelled\") {\n await store.enqueueRequest(\n session.id,\n {\n ...previewRequest,\n body: undefined,\n encoding: undefined,\n cancelled: true,\n createdAt: Date.now(),\n },\n config.sessionTtlMs,\n );\n return new Response(null, { status: 499 });\n }\n if (response === \"stale\") {\n return text(`No active Farm preview is running for \"${route.name}\".`, 404);\n }\n if (!response) {\n return text(\"The local Farm preview did not respond before the gateway timed out.\", 504);\n }\n\n const headers = new Headers();\n const responseHopByHopHeaders = getHopByHopHeaderNames(response.headers || {});\n for (const [key, value] of Object.entries(response.headers || {})) {\n const normalized = key.toLowerCase();\n if (responseHopByHopHeaders.has(normalized) || normalized === \"content-encoding\") {\n continue;\n }\n // Append multi-valued headers (Set-Cookie) so every value reaches the\n // visitor; Headers.set would keep only the last.\n for (const single of Array.isArray(value) ? value : [value]) {\n headers.append(key, single);\n }\n }\n headers.set(\"cache-control\", \"no-store\");\n headers.set(\"x-farm-preview\", session.name);\n\n return new Response(\n response.body\n ? Buffer.from(response.body, response.encoding === \"base64\" ? \"base64\" : \"utf8\")\n : null,\n {\n status: response.status || 200,\n headers,\n },\n );\n}\n\nasync function waitForPreviewResponse(\n store: PreviewGatewayStore,\n config: PreviewGatewayRuntimeConfig,\n session: PreviewGatewaySession,\n requestId: string,\n signal: AbortSignal,\n) {\n const deadline = Date.now() + config.requestTimeoutMs;\n let nextLivenessCheckAt = 0;\n\n while (Date.now() < deadline) {\n if (signal.aborted) return \"cancelled\";\n if (Date.now() >= nextLivenessCheckAt) {\n const latestSession = await store.getSessionById(session.id);\n if (!latestSession || !isPreviewClientOnline(latestSession, config)) {\n if (latestSession) await store.deleteSession(latestSession);\n return \"stale\";\n }\n nextLivenessCheckAt = Date.now() + 1000;\n }\n\n const response = await store.getResponse(session.id, requestId);\n if (response) {\n await store.deleteResponse(session.id, requestId);\n return response;\n }\n try {\n await delay(config.pollIntervalMs, undefined, { signal });\n } catch {\n if (signal.aborted) return \"cancelled\";\n throw new Error(\"Preview response polling was interrupted.\");\n }\n }\n\n return undefined;\n}\n\nasync function markSessionOnline(\n store: PreviewGatewayStore,\n config: PreviewGatewayRuntimeConfig,\n session: PreviewGatewaySession,\n) {\n session.lastHeartbeatAt = Date.now();\n await store.touchSession(session, config.sessionTtlMs);\n}\n\nfunction isPreviewClientOnline(\n session: PreviewGatewaySession,\n config: PreviewGatewayRuntimeConfig,\n) {\n const lastHeartbeatAt = session.lastHeartbeatAt || session.createdAt;\n return Date.now() - lastHeartbeatAt <= config.clientHeartbeatTimeoutMs;\n}\n\nasync function serializePreviewRequest(\n request: Request,\n url: URL,\n routePath: string,\n maxBodyBytes: number,\n): Promise<PreviewGatewayRequest> {\n const method = request.method.toUpperCase();\n const headers: Record<string, string> = {};\n const requestHopByHopHeaders = getHopByHopHeaderNames(request.headers);\n request.headers.forEach((value, key) => {\n const normalized = key.toLowerCase();\n if (\n !requestHopByHopHeaders.has(normalized) &&\n normalized !== \"forwarded\" &&\n !normalized.startsWith(\"x-forwarded-\")\n ) {\n headers[key] = value;\n }\n });\n\n headers[\"x-forwarded-host\"] = request.headers.get(\"host\") || url.host;\n headers[\"x-forwarded-proto\"] = url.protocol.replace(\":\", \"\");\n\n let body: string | undefined;\n if (method !== \"GET\" && method !== \"HEAD\") {\n const contentLength = Number(request.headers.get(\"content-length\") || 0);\n if (contentLength > maxBodyBytes) {\n throw new GatewayHttpError(413, \"Preview request body is too large.\");\n }\n\n const buffer = await readBodyWithLimit(\n request,\n maxBodyBytes,\n \"Preview request body is too large.\",\n );\n body = buffer.toString(\"base64\");\n }\n\n return {\n id: randomId(\"req\"),\n method,\n path: `${routePath}${url.search}`,\n headers,\n body,\n encoding: body ? \"base64\" : undefined,\n createdAt: Date.now(),\n };\n}\n\nasync function readBodyWithLimit(request: Request, maxBytes: number, message: string) {\n if (!request.body) return Buffer.alloc(0);\n const reader = request.body.getReader();\n const chunks: Uint8Array[] = [];\n let size = 0;\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n size += value.byteLength;\n if (size > maxBytes) {\n await reader.cancel();\n throw new GatewayHttpError(413, message);\n }\n chunks.push(value);\n }\n } catch (error) {\n await reader.cancel().catch(() => undefined);\n throw error;\n }\n return Buffer.concat(chunks, size);\n}\n\nasync function requireSession(store: PreviewGatewayStore, sessionId: string, token: string | null) {\n const session = await store.getSessionById(sessionId);\n if (!session || !token || token !== session.token) {\n throw new GatewayHttpError(401, \"Invalid preview gateway session.\");\n }\n return session;\n}\n\nfunction resolvePublicPreviewRoute(request: Request, url: URL, domain: string) {\n const pathMatch = url.pathname.match(/^\\/__preview\\/([^/]+)(\\/.*)?$/);\n if (pathMatch) {\n return {\n name: pathMatch[1],\n path: pathMatch[2] || \"/\",\n };\n }\n\n const host = (request.headers.get(\"host\") || url.host).split(\":\")[0];\n if (!host || host === domain || !host.endsWith(`.${domain}`)) {\n return undefined;\n }\n\n return {\n name: host.slice(0, -(domain.length + 1)),\n path: url.pathname || \"/\",\n };\n}\n\nfunction matchSessionRoute(pathname: string) {\n const match = pathname.match(\n /^\\/api\\/sessions\\/([^/]+)(?:\\/(requests|heartbeat|responses)(?:\\/([^/]+))?)?$/,\n );\n if (!match) return undefined;\n return {\n sessionId: match[1],\n action: match[2],\n requestId: match[3],\n };\n}\n\nfunction nodeRequestToWebRequest(req: IncomingMessage, signal?: AbortSignal) {\n const host = headerValue(req.headers, \"host\") || \"localhost\";\n // This adapter is the public trust boundary. A visitor-controlled forwarded\n // protocol must not influence the authority serialized to the local app.\n const protocol = isLocalHost(host.split(\":\")[0]) ? \"http\" : \"https\";\n const url = `${protocol}://${host}${req.url || \"/\"}`;\n const headers = nodeHeadersToWebHeaders(req.headers);\n const method = req.method || \"GET\";\n const init: RequestInit & { duplex?: \"half\" } = {\n method,\n headers,\n signal,\n };\n\n if (method !== \"GET\" && method !== \"HEAD\") {\n init.body = req as unknown as BodyInit;\n init.duplex = \"half\";\n }\n\n return new Request(url, init);\n}\n\nfunction nodeHeadersToWebHeaders(headers: IncomingHttpHeaders) {\n const next = new Headers();\n for (const [key, value] of Object.entries(headers)) {\n if (value === undefined) continue;\n if (Array.isArray(value)) {\n for (const item of value) next.append(key, item);\n } else {\n next.set(key, value);\n }\n }\n return next;\n}\n\nfunction headerValue(headers: IncomingHttpHeaders, key: string) {\n const value = headers[key];\n return Array.isArray(value) ? value[0] : value;\n}\n\nfunction createPublicUrl(request: Request, config: PreviewGatewayRuntimeConfig, name: string) {\n if (config.baseUrl) {\n const base = new URL(config.baseUrl);\n if (isLocalHost(base.hostname)) {\n return `${config.baseUrl.replace(/\\/+$/, \"\")}/__preview/${name}`;\n }\n }\n\n const host = request.headers.get(\"host\") || new URL(request.url).host;\n if (isLocalHost(host.split(\":\")[0])) {\n return `${new URL(request.url).origin}/__preview/${name}`;\n }\n\n return `https://${name}.${config.domain}`;\n}\n\nfunction json(value: unknown, status = 200) {\n return new Response(JSON.stringify(value), {\n status,\n headers: {\n \"content-type\": \"application/json\",\n \"cache-control\": \"no-store\",\n },\n });\n}\n\nfunction text(value: string, status = 200) {\n return new Response(value, {\n status,\n headers: {\n \"content-type\": \"text/plain; charset=utf-8\",\n \"cache-control\": \"no-store\",\n },\n });\n}\n\nfunction sanitizePreviewName(value: string | undefined) {\n if (!value) return undefined;\n return value\n .toLowerCase()\n .replace(/[^a-z0-9-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 48);\n}\n\nfunction randomPreviewName() {\n return `farm-${Math.random().toString(36).slice(2, 8)}`;\n}\n\nfunction randomId(prefix: string) {\n return `${prefix}_${crypto.randomUUID().replace(/-/g, \"\")}`;\n}\n\nfunction normalizeDomain(value: string) {\n return value\n .replace(/^https?:\\/\\//, \"\")\n .replace(/^\\.*/, \"\")\n .replace(/\\/*$/, \"\");\n}\n\nfunction responseKey(sessionId: string, requestId: string) {\n return `${sessionId}:${requestId}`;\n}\n\nfunction clamp(value: number, min: number, max: number) {\n if (!Number.isFinite(value)) return min;\n return Math.max(min, Math.min(max, value));\n}\n\nfunction isLocalHost(hostname: string) {\n return hostname === \"localhost\" || hostname === \"127.0.0.1\" || hostname === \"::1\";\n}\n\nclass GatewayHttpError extends Error {\n constructor(\n public status: number,\n message: string,\n ) {\n super(message);\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farm.js/preview-gateway",
3
- "version": "0.1.0-beta.92",
3
+ "version": "0.1.0-beta.94",
4
4
  "description": "Farm.js preview gateway for Vercel-hosted instant preview URLs",
5
5
  "license": "MIT",
6
6
  "repository": {