/** * HTTP API client for the Felt backend. * * Auto-detects base URL from current host, attaches JWT from auth store, * handles 401 responses by clearing auth state and redirecting to login. */ import { auth } from '$lib/stores/auth.svelte'; import { goto } from '$app/navigation'; /** Typed API error with status code and message. */ export class ApiError extends Error { constructor( public readonly status: number, public readonly statusText: string, public readonly body: unknown ) { const msg = typeof body === 'object' && body !== null && 'error' in body ? (body as { error: string }).error : statusText; super(msg); this.name = 'ApiError'; } } /** Base URL for API requests — auto-detected from current host. */ function getBaseUrl(): string { return `${window.location.origin}/api/v1`; } /** Build headers with JWT auth and content type. */ function buildHeaders(hasBody: boolean): HeadersInit { const headers: Record = { 'Accept': 'application/json' }; if (hasBody) { headers['Content-Type'] = 'application/json'; } const token = auth.token; if (token) { headers['Authorization'] = `Bearer ${token}`; } return headers; } /** Handle API response — parse JSON, handle errors. */ async function handleResponse(response: Response): Promise { if (response.status === 401) { // Token expired or invalid — clear auth and redirect to login auth.logout(); await goto('/login'); throw new ApiError(401, 'Unauthorized', { error: 'Session expired' }); } if (!response.ok) { let body: unknown; try { body = await response.json(); } catch { body = { error: response.statusText }; } throw new ApiError(response.status, response.statusText, body); } // Handle 204 No Content if (response.status === 204) { return undefined as T; } return response.json() as Promise; } /** Perform an API request. */ async function request(method: string, path: string, body?: unknown): Promise { const url = `${getBaseUrl()}${path}`; const init: RequestInit = { method, headers: buildHeaders(body !== undefined), credentials: 'same-origin' }; if (body !== undefined) { init.body = JSON.stringify(body); } const response = await fetch(url, init); return handleResponse(response); } /** * HTTP API client. * * All methods auto-attach JWT from auth store and handle 401 responses. */ export const api = { /** GET request. */ get(path: string): Promise { return request('GET', path); }, /** POST request with JSON body. */ post(path: string, body?: unknown): Promise { return request('POST', path, body); }, /** PUT request with JSON body. */ put(path: string, body?: unknown): Promise { return request('PUT', path, body); }, /** PATCH request with JSON body. */ patch(path: string, body?: unknown): Promise { return request('PATCH', path, body); }, /** DELETE request. */ delete(path: string): Promise { return request('DELETE', path); } };