import type { Handle, HandleFetch } from '@sveltejs/kit/hooks'; import { KNOTLESS_SERVE } from '$app/env/private'; import { NIGHT, NIGHT_PALETTE, PALETTE, THEME, customOf, halvesStyle, paletteOf, styleOf, themeOf } from '#lib/theme.ts'; /** What `` says of the theme the cookies name: a theme of the viewer's own only with its palette, which is only ever colours as #rrggbb. */ function drawn(theme: string | null, said: string | undefined): string { const palette = theme?.startsWith('custom-') ? paletteOf(said) : null; if (palette) return ` data-theme="${customOf(palette)}" style="${styleOf(palette)}"`; return theme && !theme.startsWith('custom-') ? ` data-theme="${theme}"` : ''; } /** A half of a pair, as the cookies name it: its theme, and its palette for one of the viewer's own. */ function half(theme: string | null, said: string | undefined) { const palette = theme?.startsWith('custom-') ? paletteOf(said) : null; return theme && (palette || !theme.startsWith('custom-')) ? { id: theme, palette } : null; } /** What `` says of a pair, by day and at night, and the ` : '' }; } export const handle: Handle = async ({ event, resolve }) => { // A page is drawn in the theme its cookie names, from the first paint; // or in a pair's, as the system is light or dark. const both = pair( half(themeOf(event.cookies.get(THEME)), event.cookies.get(PALETTE)), half(themeOf(event.cookies.get(NIGHT)), event.cookies.get(NIGHT_PALETTE)) ); const theme = both?.html ?? drawn(themeOf(event.cookies.get(THEME)), event.cookies.get(PALETTE)); const res = await resolve(event, { filterSerializedResponseHeaders: (name) => name === 'cache-control', transformPageChunk: theme ? ({ html }) => html .replace('', ``) .replace('', `${both?.head ?? ''}`) : undefined }); // A page's times are put in the zone its cookie names, and its colours in its theme. res.headers.append('vary', 'cookie'); // Someone logged in is shown what is theirs: a page for no cache but theirs, and not kept. if (event.cookies.get('knotless_session')) res.headers.set('cache-control', 'private, no-store'); return res; }; /** * Asked of this site, XRPC queries are asked of knotless-serve * directly, and with the global fetch: knotless-serve can have the same * origin as the site it fronts, which Kit's own would answer itself. */ export const handleFetch: HandleFetch = ({ event, request, fetch }) => { const url = new URL(request.url); if (url.origin === event.url.origin && url.pathname.startsWith('/xrpc/')) { // The site it was asked of, for the addresses it answers in full. const asked = new Request(`${KNOTLESS_SERVE}${url.pathname}${url.search}`, request); asked.headers.set('x-forwarded-host', event.url.host); asked.headers.set('x-forwarded-proto', event.url.protocol.slice(0, -1)); // Who asks, as the session cookie of someone logged in says: the // global fetch does not send the page's cookies itself. const cookie = event.request.headers.get('cookie'); if (cookie) asked.headers.set('cookie', cookie); // The trace knotless-serve started for the page, for what it asks // to be logged under the same one. const trace = event.request.headers.get('traceparent'); if (trace) asked.headers.set('traceparent', trace); // Named as the pages, which knotless-serve counts apart from the // clients that ask it: node would call itself `node`, as scripts do. asked.headers.set('user-agent', 'knotless-pages'); return globalThis.fetch(asked); } return fetch(request); };