| 1 | /** |
| 2 | * A screenshot of a project's production, for its overview. Members only, |
| 3 | * like the deployments it shows. The overview asks with `?v=<commit>`, so a |
| 4 | * screenshot of that commit is kept by the browser for good; one of an |
| 5 | * older deploy, while the new one is being taken, only for a minute. |
| 6 | */ |
| 7 | import { env } from "cloudflare:workers"; |
| 8 | |
| 9 | import type { Route } from "./+types/production-screenshot"; |
| 10 | import { deployments } from "../../lib/services.server"; |
| 11 | import { getViewer } from "../../lib/session.server"; |
| 12 | |
| 13 | const LONG = "private, max-age=31536000, immutable"; |
| 14 | const BRIEF = "private, max-age=60"; |
| 15 | |
| 16 | export async function loader({ params, context, request }: Route.LoaderArgs) { |
| 17 | const viewer = getViewer(context); |
| 18 | const none = (status: number) => new Response(null, { status, headers: { "cache-control": BRIEF } }); |
| 19 | if (!viewer || !env.SCREENSHOTS) return none(404); |
| 20 | const list = await deployments.list({ workspace: params.owner, slug: params.repo }, viewer).catch(() => null); |
| 21 | const production = list?.ok ? list.value.live.find((app) => app.kind === "production") : undefined; |
| 22 | if (!production) return none(404); |
| 23 | const shot = await env.SCREENSHOTS.image({ host: new URL(production.url).host, commit: production.commit }).catch( |
| 24 | (error: unknown) => (console.warn("screenshot:", error), null), |
| 25 | ); |
| 26 | if (!shot) return none(404); |
| 27 | const asked = new URL(request.url).searchParams.get("v"); |
| 28 | const current = shot.commit === production.commit && asked === production.commit; |
| 29 | return new Response(shot.body, { |
| 30 | headers: { |
| 31 | "content-type": shot.contentType, |
| 32 | "cache-control": current ? LONG : BRIEF, |
| 33 | "x-content-type-options": "nosniff", |
| 34 | "last-modified": new Date(shot.capturedAt).toUTCString(), |
| 35 | }, |
| 36 | }); |
| 37 | } |