| 1 | /** Only built public assets can be read from the release bucket. */ |
| 2 | export function archivedAssetKey(pathname: string): string | null { |
| 3 | if (!/^\/assets\/[a-zA-Z0-9_.~-]+\.(?:js|css|woff2?|ttf|otf|png|jpe?g|gif|svg|webp|ico|wasm)$/.test(pathname)) return null; |
| 4 | return `web-assets${pathname}`; |
| 5 | } |
| 6 | |
| 7 | /** Current assets are served by Cloudflare before the Worker; misses land here. */ |
| 8 | export async function serveArchivedAsset(request: Request, bucket: Pick<R2Bucket, "get"> | undefined): Promise<Response> { |
| 9 | const key = archivedAssetKey(new URL(request.url).pathname); |
| 10 | if (!key || !["GET", "HEAD"].includes(request.method)) return new Response("Not found", { status: 404, headers: { "cache-control": "no-store" } }); |
| 11 | try { |
| 12 | if (!bucket) throw new Error("Asset archive is not configured"); |
| 13 | const object = await bucket.get(key); |
| 14 | if (!object) return new Response("Not found", { status: 404, headers: { "cache-control": "no-store" } }); |
| 15 | const headers = new Headers(); |
| 16 | object.writeHttpMetadata(headers); |
| 17 | headers.set("etag", object.httpEtag); |
| 18 | headers.set("cache-control", "public, max-age=31536000, immutable"); |
| 19 | headers.set("x-content-type-options", "nosniff"); |
| 20 | if (request.headers.get("if-none-match") === object.httpEtag) return new Response(null, { status: 304, headers }); |
| 21 | headers.set("content-length", String(object.size)); |
| 22 | return new Response(request.method === "HEAD" ? null : object.body, { headers }); |
| 23 | } catch { |
| 24 | return new Response("Asset temporarily unavailable", { status: 503, headers: { "cache-control": "no-store", "retry-after": "1" } }); |
| 25 | } |
| 26 | } |