Serve surfaces/shell's built Dashboard from apps/api on the same origin as /api (ADR 0044), exactly as the display host is served: a closed set of files read once at startup, never a static directory, never a path built from request input. - The shell exports @ekklesia/shell/dashboard-assets (the exact built index.html and script URLs), emits the script at assets/ekklesia-dashboard/app.js, and index.html names that absolute path so dev and production serve the same URLs. - GET / and GET /assets/ekklesia-dashboard/app.js answer from a frozen snapshot: no-cache with a strong ETag and 304 on match. Source maps are never served; every other path is the ordinary sanitized 404. - The page sets nosniff, X-Frame-Options: DENY, Content-Security-Policy: frame-ancestors 'none' (that directive only), and Referrer-Policy: no-referrer, on 200 and 304 alike. - A missing or empty shell build fails startup with a fixed DashboardHostAssetError before any module code is imported. - The If-None-Match comparison moves to conditional-request.ts, shared with the display host routes. - The emailed-link token stripping moves from browser/main.tsx into consumeAuthLanding (location and history injected) so it is tested; behavior is unchanged. Co-Authored-By: Claude Opus 5.5 <[email protected]>
17 lines
572 B
TypeScript
17 lines
572 B
TypeScript
/**
|
|
* RFC 9110 weak comparison for a GET/HEAD `If-None-Match` precondition, shared by the host-owned
|
|
* asset routes that answer `304` against a strong validator they computed from their own bytes.
|
|
*/
|
|
export function etagMatches(headerValue: string | undefined, etag: string): boolean {
|
|
if (headerValue === undefined) {
|
|
return false;
|
|
}
|
|
if (headerValue.trim() === "*") {
|
|
return true;
|
|
}
|
|
return headerValue.split(",").some((candidate) => {
|
|
const value = candidate.trim();
|
|
return (value.startsWith("W/") ? value.slice(2) : value) === etag;
|
|
});
|
|
}
|