import { ApiError, type ProblemDetails } from './errors' const baseUrl = import.meta.env.VITE_API_BASE_URL ?? '' let tokenProvider: () => string | null = () => null /** Wires the API client to the session store without creating a hard dependency. */ export function setAuthTokenProvider(provider: () => string | null): void { tokenProvider = provider } export interface RequestOptions { method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' body?: unknown headers?: Record signal?: AbortSignal /** Attach the bearer token when available (default: true). */ auth?: boolean } async function parseBody(response: Response): Promise { if (response.status === 204 || response.headers.get('content-length') === '0') { return undefined } const contentType = response.headers.get('content-type') ?? '' if (!contentType.includes('application/json')) { return undefined } try { return await response.json() } catch { return undefined } } /** * Minimal typed fetch wrapper: JSON in/out, bearer auth, RFC 7807 error mapping. * Throws `ApiError` on non-2xx responses. */ export async function request(path: string, options: RequestOptions = {}): Promise { const { method = 'GET', body, headers = {}, signal, auth = true } = options const requestHeaders: Record = { Accept: 'application/json', ...headers } const token = auth ? tokenProvider() : null if (token) { requestHeaders.Authorization = `Bearer ${token}` } if (body !== undefined) { requestHeaders['Content-Type'] = 'application/json' } const response = await fetch(`${baseUrl}${path}`, { method, headers: requestHeaders, body: body === undefined ? undefined : JSON.stringify(body), signal, }) const payload = await parseBody(response) if (!response.ok) { const problem = (payload as ProblemDetails | undefined) ?? null throw new ApiError(response.status, problem) } return payload as T }