/** * Shared server-function RPC helpers used by both the dev server (index.ts) * and the production server (prod.ts). Kept in their own module so prod.ts * can import them without creating a circular dependency on index.ts (which * itself re-exports `createProductionServer`/`createProductionHandlers` from * prod.ts). */ import { createContext, runWithRequestContext } from "@wrnexus/core"; /** * CSRF check for `/__wrnexus/rpc` (the server-function RPC endpoint the * browser runtime calls for `server.fn()`). Requires a same-origin request * carrying a matching CSRF cookie + header pair — the standard double-submit * cookie pattern. */ export function validateRpcCsrf(request: Request): boolean { const url = new URL(request.url); const origin = request.headers.get("origin"); if (origin && origin !== url.origin) return false; const cookieHeader = request.headers.get("cookie") ?? ""; const cookieToken = /(?:^|;\s*)wrn-csrf=([^;]+)/.exec(cookieHeader)?.[1] ?? /(?:^|;\s*)wrnexus_csrf=([^;]+)/.exec(cookieHeader)?.[1]; const headerToken = request.headers.get("x-csrf-token") ?? request.headers.get("x-wrnexus-csrf"); return Boolean(cookieToken && headerToken && decodeURIComponent(cookieToken) === headerToken); } /** * Wrap the server-function RPC handler so it runs inside the request's * AsyncLocalStorage context. `/__wrnexus/rpc` is intercepted BEFORE * `handlers.fetch` (fetchHandler) in both dev and production, so a server * function called via `server.fn()` from the browser runs entirely outside * fetchHandler's own context wrap. It runs user code directly, so — like * every other entry point that runs user server code — it needs the request * context too. */ export function withServerFnRequestContext( handler: (request: Request) => Promise, ): (request: Request) => Promise { return (request: Request) => { const url = new URL(request.url); const ctx = createContext(request, url); return runWithRequestContext(ctx, () => handler(request)); }; }