All checks were successful
Deploy production / deploy (push) Successful in 1m29s
69 lines
3.5 KiB
TypeScript
69 lines
3.5 KiB
TypeScript
import type { Handle, HandleServerError, ServerInit } from '@sveltejs/kit/hooks';
|
|
import { startMusicPoller } from '#lib/server/music/poller.js';
|
|
import { startGamesPoller } from '#lib/server/games/poller.js';
|
|
import { building } from '$app/env';
|
|
import { auth } from '#lib/server/auth.js';
|
|
import { isOwner, isOwnerOnlyPath, isOwnerWritePath } from '#lib/server/owner.js';
|
|
import { error } from '@sveltejs/kit';
|
|
|
|
const analytics = /<!-- analytics -->[\s\S]*?<!-- \/analytics -->/;
|
|
|
|
// Background jobs that live in the app process: the Spotify scrobbler (idle until connected)
|
|
// and the CS2 / PUBG / R6 match history poller.
|
|
export const init: ServerInit = () => {
|
|
if (!building) {
|
|
startMusicPoller();
|
|
startGamesPoller();
|
|
}
|
|
};
|
|
|
|
export const handle: Handle = async ({ event, resolve }) => {
|
|
// Match the endpoint path here: the dev proxy may terminate HTTPS before Vite.
|
|
// Better Auth validates request origins against its configured public URL.
|
|
if (!building && event.url.pathname.startsWith('/api/auth/')) return auth.handler(event.request);
|
|
const session = building ? null : await auth.api.getSession({ headers: event.request.headers });
|
|
event.locals.user = session?.user ?? null;
|
|
event.locals.session = session?.session ?? null;
|
|
// Owner-only areas (health tracker, book files, reader and book APIs). Page loads,
|
|
// __data.json and endpoints 404 for non-owners in the route itself (layout load /
|
|
// endpoint), which renders the same 404 page as an unknown URL. As a backstop, any other
|
|
// method (form actions, which skip layout loads) is refused here before a route runs,
|
|
// including writes to the otherwise public /trackers/books pages.
|
|
const ownerOnly = isOwnerOnlyPath(event.url.pathname);
|
|
const owner = isOwner(event);
|
|
const read = event.request.method === 'GET' || event.request.method === 'HEAD';
|
|
if (!owner && ((ownerOnly && !read) || (!read && isOwnerWritePath(event.url.pathname))))
|
|
error(404, 'Not Found');
|
|
// Private pages (the /start new-tab dashboard, trackers) skip Google Analytics: less to
|
|
// load, and nothing private gets reported.
|
|
const response = await resolve(
|
|
event,
|
|
ownerOnly ? { transformPageChunk: ({ html }) => html.replace(analytics, '') } : undefined
|
|
);
|
|
if (ownerOnly) {
|
|
response.headers.set('x-robots-tag', 'noindex, nofollow, noarchive');
|
|
// Keep a stricter value a route set itself (the /start/live stream adds no-transform).
|
|
if (!response.headers.get('cache-control')?.includes('no-store'))
|
|
response.headers.set('cache-control', 'private, no-store');
|
|
}
|
|
return response;
|
|
};
|
|
|
|
// Runs for every error (SvelteKit 3 passes a `kind`). Unknown errors (bugs, failing services)
|
|
// get a short id that the error page shows and the log line starts with ("[error ab12cd34]"),
|
|
// so a screenshot or report can be matched to the stack trace; the client only gets the
|
|
// generic "Internal Error", never the stack. 404s get one compact line (Caddy keeps no access
|
|
// log), other error() calls are expected and stay quiet. Query strings are never logged.
|
|
export const handleError: HandleServerError = (input) => {
|
|
const { event } = input;
|
|
const where = `${event.request.method} ${event.url.pathname} route=${event.route.id ?? '(none)'}`;
|
|
if (input.kind === 'unknown') {
|
|
const id = crypto.randomUUID().slice(0, 8);
|
|
console.error(`[error ${id}] 500 ${where}`, input.error);
|
|
return { id };
|
|
}
|
|
if (input.error.status === 404) console.warn(`[404] ${where}`);
|
|
else if (input.error.status >= 500)
|
|
console.error(`[${input.error.status}] ${where} ${input.error.message}`);
|
|
};
|