File
Blob: src/worker/lib/http-entry.ts
| 1 | import { matchSiteHost } from "@/worker/lib/host-match"; |
| 2 | |
| 3 | type Awaitable<T> = T | Promise<T>; |
| 4 | |
| 5 | export interface HttpEntryDeps<TEnv> { |
| 6 | handlePartyRequest: (request: Request, env: TEnv) => Awaitable<Response | null>; |
| 7 | handleAppRequest: (request: Request, env: TEnv, ctx: ExecutionContext) => Awaitable<Response>; |
| 8 | handleAssetRequest: (request: Request, env: TEnv) => Awaitable<Response>; |
| 9 | handleShellRequest: (request: Request, env: TEnv) => Awaitable<Response>; |
| 10 | handleSiteRequest: (request: Request, env: TEnv, ctx: ExecutionContext) => Awaitable<Response>; |
| 11 | } |
| 12 | |
| 13 | export function isDirectAssetRequest(pathname: string): boolean { |
| 14 | return /\.\w+$/.test(pathname) && !pathname.endsWith(".html"); |
| 15 | } |
| 16 | |
| 17 | export function isViteDevRuntimeAssetRequest(request: Request): boolean { |
| 18 | if (request.method !== "GET" && request.method !== "HEAD") return false; |
| 19 | |
| 20 | const { pathname } = new URL(request.url); |
| 21 | return pathname.startsWith("/@") || pathname.startsWith("/src/") || pathname.startsWith("/node_modules/"); |
| 22 | } |
| 23 | |
| 24 | export async function handleHttpRequest<TEnv extends Pick<Env, "PUBLISHED_SITE_DOMAIN">>( |
| 25 | request: Request, |
| 26 | env: TEnv, |
| 27 | ctx: ExecutionContext, |
| 28 | deps: HttpEntryDeps<TEnv>, |
| 29 | ): Promise<Response> { |
| 30 | const url = new URL(request.url); |
| 31 | |
| 32 | if (import.meta.env.DEV && isViteDevRuntimeAssetRequest(request)) { |
| 33 | return deps.handleAssetRequest(request, env); |
| 34 | } |
| 35 | |
| 36 | // Site host dispatch runs BEFORE any path-prefix branch so a published |
| 37 | // subdomain or apex request never falls through to /api, /uploads, the |
| 38 | // SPA shell, or the static asset binding. The Sites surface owns every |
| 39 | // path under its host (Class A page/asset OR Class B robots/apex). |
| 40 | const siteMatch = matchSiteHost(url, env); |
| 41 | if (siteMatch.kind !== "none") { |
| 42 | return deps.handleSiteRequest(request, env, ctx); |
| 43 | } |
| 44 | |
| 45 | const { pathname } = url; |
| 46 | const isReadRequest = request.method === "GET" || request.method === "HEAD"; |
| 47 | |
| 48 | if (pathname.startsWith("/parties/")) { |
| 49 | return (await deps.handlePartyRequest(request, env)) ?? deps.handleAppRequest(request, env, ctx); |
| 50 | } |
| 51 | |
| 52 | if (pathname.startsWith("/api/") || pathname.startsWith("/uploads/")) { |
| 53 | return deps.handleAppRequest(request, env, ctx); |
| 54 | } |
| 55 | |
| 56 | if (isReadRequest && isDirectAssetRequest(pathname)) { |
| 57 | return deps.handleAssetRequest(request, env); |
| 58 | } |
| 59 | |
| 60 | if (request.method === "GET") { |
| 61 | return deps.handleShellRequest(request, env); |
| 62 | } |
| 63 | |
| 64 | if (request.method === "HEAD") { |
| 65 | return deps.handleAssetRequest(request, env); |
| 66 | } |
| 67 | |
| 68 | return deps.handleAppRequest(request, env, ctx); |
| 69 | } |