import { env } from "cloudflare:workers"; import { contentType, decodePath, lookup, SECURITY_HEADERS } from "../pages/serve"; import { objectKey } from "../pages/config"; import { manifestKey } from "./config"; import type { SiteManifest } from "./config"; const MANIFEST_CACHE_SECONDS = 3600; const HTTP_OK = 200; export function plain(status: number, body: string, extra: Record = {}): Response { return new Response(body, { status, headers: { ...SECURITY_HEADERS, "content-type": "text/plain; charset=utf-8", "cache-control": "no-store", ...extra, }, }); } export function html(status: number, body: string, extra: Record = {}): Response { return new Response(body, { status, headers: { ...SECURITY_HEADERS, "content-type": "text/html; charset=utf-8", "cache-control": "no-store", "content-security-policy": "default-src 'none'; style-src 'unsafe-inline'; form-action 'self'; frame-ancestors 'none'", ...extra, }, }); } export function loadManifest(deploymentId: string): Promise { return env.KV.get(manifestKey(deploymentId), { type: "json", cacheTtl: MANIFEST_CACHE_SECONDS }); } /** Serves a static asset from a deployment's manifest; identical semantics to Pages. */ export async function serveStatic( request: Request, manifest: SiteManifest, spa: boolean, gated = false, ): Promise { if (request.method !== "GET" && request.method !== "HEAD") return plain(405, "Method not allowed"); const path = decodePath(new URL(request.url).pathname); if (path === null) return plain(400, "Bad path"); const found = lookup({ files: manifest.files }, spa, path, "/"); if (!found) return plain(404, "Not found"); if ("redirect" in found) return new Response(null, { status: 308, headers: { ...SECURITY_HEADERS, location: found.redirect } }); const [sha, size] = found.file; const etag = `"${sha}"`; const headers: Record = { ...SECURITY_HEADERS, "content-type": contentType(found.path), etag, "cache-control": `${gated ? "private" : "public"}, ${found.path.endsWith(".html") ? "max-age=0, must-revalidate" : "max-age=600"}`, }; if (gated) headers.vary = "cookie"; if (found.status === HTTP_OK && request.headers.get("if-none-match") === etag) { return new Response(null, { status: 304, headers }); } if (request.method === "HEAD") { return new Response(null, { status: found.status, headers: { ...headers, "content-length": String(size) } }); } const object = await env.BUCKET.get(objectKey(sha)); if (!object) return plain(503, "Deployment object missing"); return new Response(object.body, { status: found.status, headers: { ...headers, "content-length": String(object.size) }, }); }