// The one door to the server. Mutations carry the anti-CSRF header the server // requires; errors come back as a stable code the interface translates. export class ApiError extends Error { constructor( public status: number, public code: string, ) { super(code); } } async function call(method: string, path: string, body?: unknown): Promise { const headers: Record = { 'x-requested-with': 'libredecision' }; if (body !== undefined) headers['content-type'] = 'application/json'; let res: Response; try { res = await fetch(`/api${path}`, { method, headers, credentials: 'same-origin', body: body === undefined ? undefined : JSON.stringify(body), }); } catch { throw new ApiError(0, 'network'); } const data = await res.json().catch(() => null); if (!res.ok) throw new ApiError(res.status, (data && typeof data.error === 'string' && data.error) || 'internal'); return data as T; } export const api = { get: (path: string) => call('GET', path), post: (path: string, body: unknown = {}) => call('POST', path, body), patch: (path: string, body: unknown) => call('PATCH', path, body), put: (path: string, body: unknown) => call('PUT', path, body), del: (path: string) => call('DELETE', path), };