import type { ApiError } from '@family-budget/shared'; let onUnauthorized: (() => void) | null = null; export function setOnUnauthorized(cb: () => void) { onUnauthorized = cb; } export class ApiException extends Error { constructor( public status: number, public body: ApiError, ) { super(body.message); } } async function request(url: string, options: RequestInit = {}): Promise { const res = await fetch(url, { ...options, headers: { 'Content-Type': 'application/json', ...(options.headers as Record), }, credentials: 'include', }); if (res.status === 401) { let body: ApiError; try { body = await res.json(); } catch { body = { error: 'UNAUTHORIZED', message: 'Сессия истекла' }; } if (!url.includes('/api/auth/login')) { onUnauthorized?.(); } throw new ApiException(401, body); } if (!res.ok) { let body: ApiError; try { body = await res.json(); } catch { body = { error: 'UNKNOWN', message: res.statusText }; } throw new ApiException(res.status, body); } if (res.status === 204) return undefined as T; return res.json(); } async function requestFormData(url: string, formData: FormData): Promise { const res = await fetch(url, { method: 'POST', body: formData, credentials: 'include', // Do not set Content-Type — browser sets multipart/form-data with boundary }); if (res.status === 401) { let body: ApiError; try { body = await res.json(); } catch { body = { error: 'UNAUTHORIZED', message: 'Сессия истекла' }; } if (!url.includes('/api/auth/login')) { onUnauthorized?.(); } throw new ApiException(401, body); } if (!res.ok) { let body: ApiError; try { body = await res.json(); } catch { body = { error: 'UNKNOWN', message: res.statusText }; } throw new ApiException(res.status, body); } return res.json(); } export const api = { get: (url: string) => request(url), post: (url: string, body?: unknown) => request(url, { method: 'POST', body: body != null ? JSON.stringify(body) : undefined, }), postFormData: (url: string, formData: FormData) => requestFormData(url, formData), put: (url: string, body: unknown) => request(url, { method: 'PUT', body: JSON.stringify(body) }), patch: (url: string, body: unknown) => request(url, { method: 'PATCH', body: JSON.stringify(body) }), delete: (url: string) => request(url, { method: 'DELETE' }), };