spb/autoscholar
Public
1"use client";23let csrf: string | null = null;45export class ApiError extends Error {6 constructor(7 readonly status: number,8 readonly code: string,9 message: string,10 readonly details?: unknown,11 ) {12 super(message);13 }14}1516export async function getCsrf(force = false): Promise<string> {17 if (csrf && !force) return csrf;18 const r = await fetch("/api/admin/auth/session", { cache: "no-store" });19 if (!r.ok) throw new ApiError(401, "unauthorized", "Session expired. Please sign in again.");20 const j = (await r.json()) as { csrfToken: string };21 csrf = j.csrfToken;22 return csrf;23}24export function setCsrf(token: string | null) {25 csrf = token;26}2728/** Fetch wrapper for admin mutations: attaches the CSRF token, parses uniform errors (401/403/429). */29export async function api<T>(30 path: string,31 init: RequestInit & { json?: unknown; form?: FormData } = {},32): Promise<T> {33 const method = init.method ?? (init.json !== undefined || init.form ? "POST" : "GET");34 const headers: Record<string, string> = { ...(init.headers as Record<string, string>) };35 if (method !== "GET" && method !== "HEAD") headers["x-csrf-token"] = await getCsrf();36 let body: BodyInit | undefined = init.body as BodyInit | undefined;37 if (init.json !== undefined) {38 headers["content-type"] = "application/json";39 body = JSON.stringify(init.json);40 } else if (init.form) body = init.form;41 const r = await fetch(path, { ...init, method, headers, body, cache: "no-store" });42 const text = await r.text();43 let j: unknown = null;44 try {45 j = text ? JSON.parse(text) : null;46 } catch {47 j = null;48 }49 if (!r.ok) {50 const e = (j as { error?: { code?: string; message?: string; details?: unknown } } | null)?.error;51 if (r.status === 401) {52 setCsrf(null);53 if (typeof window !== "undefined") window.location.href = "/admin/login";54 }55 if (r.status === 403 && e?.message?.includes("CSRF")) setCsrf(null);56 throw new ApiError(57 r.status,58 e?.code ?? "error",59 e?.message ?? (r.status === 429 ? "Too many requests. Slow down." : `HTTP ${r.status}`),60 e?.details,61 );62 }63 return j as T;64}65