import { tokenStore } from '../lib/auth' export class AuthError extends Error { override name = 'AuthError' constructor(message: string) { super(message) } } export class APIError extends Error { override name = 'APIError' status: number code: string constructor(message: string, status: number, code: string) { super(message) this.status = status this.code = code } } /** * Build the Authorization header value from the in-memory token store. * Returns undefined when no token is present so the header is omitted (IV5). */ function authHeader(): { Authorization: string } | Record { const token = tokenStore.get() return token !== null ? { Authorization: `Bearer ${token}` } : {} } export async function apiFetch(path: string, init?: RequestInit): Promise { const resp = await fetch(path, { ...init, headers: { Accept: 'application/json', ...authHeader(), ...init?.headers, }, }) if (resp.status === 401) { throw new AuthError('not authenticated') } if (!resp.ok) { const ct = resp.headers.get('Content-Type') ?? '' if (ct.includes('application/problem+json')) { const body = await resp.json() as { detail?: string; title?: string; status?: number; code?: string } throw new APIError(body.detail ?? body.title ?? 'error', body.status ?? resp.status, body.code ?? '') } if (resp.status >= 500) { throw new APIError('server error', resp.status, '') } throw new APIError(`request failed: ${resp.status}`, resp.status, '') } return resp.json() as Promise } /** * apiFetchVoid is a variant of apiFetch for endpoints that return no body * (e.g. DELETE → 204 No Content). It shares the same auth and error handling * as apiFetch but does not attempt to parse the response body. */ export async function apiFetchVoid(path: string, init?: RequestInit): Promise { const resp = await fetch(path, { ...init, headers: { Accept: 'application/json', ...authHeader(), ...init?.headers, }, }) if (resp.status === 401) { throw new AuthError('not authenticated') } if (!resp.ok) { const ct = resp.headers.get('Content-Type') ?? '' if (ct.includes('application/problem+json')) { const body = await resp.json() as { detail?: string; title?: string; status?: number; code?: string } throw new APIError(body.detail ?? body.title ?? 'error', body.status ?? resp.status, body.code ?? '') } if (resp.status >= 500) { throw new APIError('server error', resp.status, '') } throw new APIError(`request failed: ${resp.status}`, resp.status, '') } }