import { API_BASE_URL } from '../config'; import { logger } from './logger'; type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'; let tokenGetter: (() => string | null) | null = null; export const setAuthTokenGetter = (getter: () => string | null) => { tokenGetter = getter; }; export interface HttpError extends Error { status: number; data?: any; } async function request( path: string, options: { method?: HttpMethod; body?: any; headers?: Record; contentType?: 'json' | 'form' | 'none'; signal?: AbortSignal; } = {} ): Promise { const { method = 'GET', body, headers = {}, contentType = 'json', signal } = options; const finalHeaders: Record = { ...headers }; const token = tokenGetter?.() || null; if (token) finalHeaders['Authorization'] = `Bearer ${token}`; if (contentType === 'json') finalHeaders['Content-Type'] = 'application/json'; if (contentType === 'form') finalHeaders['Content-Type'] = 'application/x-www-form-urlencoded'; let payload: BodyInit | undefined; if (body != null) { if (contentType === 'json') payload = JSON.stringify(body); else if (contentType === 'form') payload = new URLSearchParams(body).toString(); else payload = body as BodyInit; } const startTime = Date.now(); const url = `${API_BASE_URL}${path}`; // Log outgoing request logger.apiRequest(method, url, { headers: { ...finalHeaders, Authorization: finalHeaders.Authorization ? '[REDACTED]' : undefined }, body: payload ? (typeof payload === 'string' ? payload : '[BLOB]') : undefined }); const res = await fetch(url, { method, headers: finalHeaders, body: payload, signal, }); const duration = Date.now() - startTime; const isJson = res.headers.get('content-type')?.includes('application/json'); const data = isJson ? await res.json().catch(() => undefined) : await res.text(); if (!res.ok) { // Log error response logger.apiError(res.status, method, url, { status: res.status, statusText: res.statusText, data, duration }); const err: HttpError = Object.assign(new Error('HTTP Error'), { status: res.status, data }); throw err; } // Log successful response logger.apiResponse(res.status, method, url, data, duration); return data as T; } export const http = { get: (path: string, options: { headers?: Record; signal?: AbortSignal } = {}) => request(path, { method: 'GET', ...options }), post: ( path: string, body?: any, options: { headers?: Record; contentType?: 'json' | 'form' | 'none'; signal?: AbortSignal } = {} ) => request(path, { method: 'POST', body, ...options }), put: (path: string, body?: any, options: { headers?: Record; signal?: AbortSignal } = {}) => request(path, { method: 'PUT', body, ...options }), patch: (path: string, body?: any, options: { headers?: Record; signal?: AbortSignal } = {}) => request(path, { method: 'PATCH', body, ...options }), delete: (path: string, options: { headers?: Record; signal?: AbortSignal } = {}) => request(path, { method: 'DELETE', ...options }), };