// Request routing for the SW, as a pure function over request facts. /~/ is the // runtime's reserved namespace, and each /~/ URL names its owning host page. Same-origin // requests outside it belong to a guest app only when the *requesting client* does // (guest pages use absolute paths like /app.js, /api, /@vite/client that would // otherwise hit the real static server). import { httpUrl, parseVirtualPath, VIRTUAL_PREFIX, WS_SHIM_PATH, type HostToken } from "../shared/vurls"; export interface RequestFacts { url: URL; selfOrigin: string; mode: RequestMode; destination: RequestDestination; clientId: string; resultingClientId: string; referrer: string; } /** A guest server: which host page's runtime, and which port in it. */ export interface AppRef { host: HostToken; port: number; } export type Route = | { kind: "network" } | { kind: "missing" } | { kind: "shim" } | { kind: "module"; host: HostToken } | { kind: "http"; app: AppRef; path: string; adopt: string | null } | { kind: "redirect"; location: string; status: 307 }; const NETWORK: Route = { kind: "network" }; export function route(facts: RequestFacts, clientApps: ReadonlyMap): Route { const { url, selfOrigin, mode, clientId, resultingClientId } = facts; if (url.origin !== selfOrigin) return NETWORK; const own = url.pathname + url.search; if (url.pathname === WS_SHIM_PATH) return { kind: "shim" }; if (url.pathname.startsWith(VIRTUAL_PREFIX)) { const virtual = parseVirtualPath(url.pathname); if (!virtual) return { kind: "missing" }; if (virtual.kind !== "http") return { kind: "module", host: virtual.host }; const app = { host: virtual.host, port: virtual.port }; return { kind: "http", app, path: virtual.path + url.search, adopt: resultingClientId || null }; } const referrerApp = appOf(facts.referrer, selfOrigin); // Navigations carry no clientId; redirecting (rather than serving in place) keeps // the new document inside the prefix so its own navigations stay attributable. if (mode === "navigate") { return referrerApp === null ? NETWORK : { kind: "redirect", location: httpUrl(referrerApp.host, referrerApp.port) + own.slice(1), status: 307 }; } const app = clientApps.get(clientId); if (app !== undefined) return { kind: "http", app, path: own, adopt: resultingClientId || null }; // Re-learns clients after an idle-killed SW dropped its table. if (referrerApp !== null) return { kind: "http", app: referrerApp, path: own, adopt: clientId || null }; return NETWORK; } /** Facts about a raw connection a page asked for (its WebSocket's pathname, its own URL). */ export interface ConnectionFacts { path: string; page: string; selfOrigin: string; clientId: string; } /** The guest server a page's connection goes to: the one its URL names, else the * page's own app (by client, then by the page URL, which re-learns after an idle kill). */ export function connectionApp(facts: ConnectionFacts, clientApps: ReadonlyMap): AppRef | null { const virtual = parseVirtualPath(facts.path); if (virtual?.kind === "http") return { host: virtual.host, port: virtual.port }; return clientApps.get(facts.clientId) ?? appOf(facts.page, facts.selfOrigin); } function appOf(referrer: string, selfOrigin: string): AppRef | null { if (!referrer) return null; let ref: URL; try { ref = new URL(referrer); } catch { return null; } if (ref.origin !== selfOrigin) return null; const parsed = parseVirtualPath(ref.pathname); return parsed?.kind === "http" ? { host: parsed.host, port: parsed.port } : null; }