import { csrfTokenFor } from "../../src/lib/csrf.ts"; import { buildApp } from "../../src/server/app.ts"; const app = buildApp(); interface FetchOptions { cookie?: string; body?: FormData | string; contentType?: string; } function didFromCookie(cookie: string): string | null { const match = cookie.match(/(?:^|;\s*)did=([^;]+)/); return match?.[1] ? decodeURIComponent(match[1]) : null; } /** Send a Request through the app without binding a port. */ export function fetch( method: string, path: string, opts: FetchOptions = {}, ): Promise { const headers: Record = {}; if (opts.cookie) headers["cookie"] = opts.cookie; if (opts.contentType) headers["content-type"] = opts.contentType; const m = method.toUpperCase(); const unsafe = m === "POST" || m === "PUT" || m === "PATCH" || m === "DELETE"; const did = opts.cookie ? didFromCookie(opts.cookie) : null; if (unsafe && did) { const token = csrfTokenFor(did); headers["x-csrf-token"] = token; if (opts.body instanceof FormData && !opts.body.has("_csrf")) { opts.body.append("_csrf", token); } } const init: RequestInit = { method, headers }; if (opts.body !== undefined) init.body = opts.body; return app.handle(new Request(`http://localhost${path}`, init)); } const cookieCache = new Map(); /** * Exercise /dev/login/:handle and return the `did=...` cookie. Cached per handle * since the result is deterministic. */ export async function loginCookie(handle: string): Promise { const cached = cookieCache.get(handle); if (cached) return cached; const res = await fetch("GET", `/dev/login/${handle}`); if (res.status !== 302) { throw new Error(`/dev/login/${handle} returned ${res.status}`); } const cookies = res.headers.getSetCookie(); const didCookie = cookies.find((c) => c.startsWith("did=")); if (!didCookie) { throw new Error( `No did cookie in /dev/login response: ${cookies.join(",")}`, ); } const value = didCookie.split(";")[0]; if (!value) { throw new Error(`Malformed cookie: ${didCookie}`); } cookieCache.set(handle, value); return value; } export function form(fields: Record): FormData { const fd = new FormData(); for (const [key, value] of Object.entries(fields)) { fd.append(key, value); } return fd; }