import { createMiddleware } from "@tanstack/react-start"; import { notFound } from "@tanstack/react-router"; import { sessionViewer } from "~/server/auth"; import { HttpError, unauthorized } from "~/server/errors"; import { clientSafeError } from "~/server/client-safe-error"; import type { Viewer } from "~/lib/types"; import { paymentErrorFor } from "~/server/metering/access"; import { QuotaExceededError } from "~/server/metering/quota"; const HTTP_NOT_FOUND = 404; /** Resolves the session viewer for every server function and maps 404s onto router not-found handling. */ export const viewerMiddleware = createMiddleware({ type: "function" }).server(async ({ next }) => { const viewer = await sessionViewer(); try { return await next({ context: { viewer } }); } catch (error) { if (error instanceof HttpError && error.status === HTTP_NOT_FOUND) throw notFound(); const http = error instanceof QuotaExceededError ? error.toHttpError() : error; if (http instanceof HttpError) throw await paymentErrorFor(http, viewer?.id ?? null); throw http; } }); export function requireViewer(viewer: Viewer | null): Viewer { if (!viewer) throw unauthorized("You must be signed in"); return viewer; } /** * Registered globally in start.ts: a quota error from a server function that skips viewerMiddleware still * reaches the client as a 402 (not a masked 500), with billing context for whoever is signed in, and * unexpected errors are masked by `clientSafeError`. */ export const paymentErrorMiddleware = createMiddleware({ type: "function" }).server(async ({ next }) => { try { return await next(); } catch (error) { if (!(error instanceof QuotaExceededError)) throw clientSafeError(error); throw await paymentErrorFor(error.toHttpError(), (await sessionViewer())?.id ?? null); } });