import type { IncomingMessage, ServerResponse } from "node:http"; import type { PageChrome } from "./chrome.ts"; // type-only: no runtime import, so no cycle // The request context threaded to every route handler (plugin + built-in), built once // per request by `buildContext`: the router supplies matched path `params`, the §4 JWT // middleware supplies `user` (null until then). The host's single handler argument. // The authenticated user, projected from verified session JWT claims (§4): // `id` = `sub`, plus `email` and the coarse `roles` carried in the token. export interface User { email: string; id: string; roles: string[]; } export interface RequestContext { // Page chrome (brand/global-nav/user/theme/csrf) a plugin view hands to partials/shell so its // page renders the native app shell; the host builds it per request (anonymous default otherwise). chrome: PageChrome; params: Record; // path params from the route match, e.g. /users/:id → { id } query: URLSearchParams; // alias of url.searchParams, for ctx.query.get("q") req: IncomingMessage; res: ServerResponse; roles: string[]; // user?.roles ?? [] — coarse gate without a null-check url: URL; user: User | null; // Gate a first-party form submission: true iff `submitted` matches this request's signed CSRF // cookie (double-submit). The host binds the secret; a plugin calls it after reading its body. verifyCsrf(submitted: string | null | undefined): boolean; } export interface BuildContextOptions { chrome?: PageChrome; params?: Record; user?: User | null; verifyCsrf?: (submitted: string | null | undefined) => boolean; } // Anonymous default chrome — used until the host supplies a real one (built-in routes, tests). const ANON_CHROME: PageChrome = { brand: { name: "Plainpages" }, csrfToken: "", nav: [], user: { email: "", initials: "G", name: "Guest" } }; export function buildContext( req: IncomingMessage, res: ServerResponse, options: BuildContextOptions = {}, ): RequestContext { const url = new URL(req.url ?? "/", "http://localhost"); const user = options.user ?? null; return { chrome: options.chrome ?? ANON_CHROME, params: options.params ?? {}, query: url.searchParams, req, res, roles: user?.roles ?? [], url, user, verifyCsrf: options.verifyCsrf ?? (() => false), // fail-closed unless the host binds the secret }; }