import { clearToken, ensureAuthConfig, getToken, hasPortalHandoffFlag, isAuthEnabled, isPortalHandoffCoolingDown, redirectToPortalLogin, } from '@/lib/auth' export class ApiError extends Error { constructor( public status: number, public code: string, message: string, ) { super(message) this.name = 'ApiError' } } async function handoffOnUnauthorized(): Promise { clearToken() const cfg = await ensureAuthConfig() if ( (cfg.required || isAuthEnabled()) && !hasPortalHandoffFlag() && !isPortalHandoffCoolingDown() ) { redirectToPortalLogin(`${window.location.origin}/auth/callback`) return } // Cooldown / recent handoff — stop SSO storm (wrong JWT secret / issuer). if (cfg.required || isAuthEnabled()) { window.location.assign( `${window.location.origin}/auth/callback?error=jwt_rejected`, ) return } if (!cfg.required && !isAuthEnabled()) { window.location.href = '/login' } } async function request(path: string, init?: RequestInit): Promise { const token = getToken() const headers = new Headers(init?.headers) if (init?.body != null && !headers.has('Content-Type')) { headers.set('Content-Type', 'application/json') } if (token) headers.set('Authorization', `Bearer ${token}`) const res = await fetch(path, { ...init, headers }) if (res.status === 401 && !path.includes('/auth/login')) { await handoffOnUnauthorized() throw new ApiError(401, 'UNAUTHORIZED', 'Unauthorized') } if (!res.ok) { const body = await res.json().catch(() => ({})) const err = body?.error throw new ApiError( res.status, err?.code ?? 'UNKNOWN', err?.message ?? res.statusText, ) } if (res.status === 204) return undefined as T return res.json() as Promise } export const api = { get: (path: string) => request(path), post: (path: string, body?: unknown) => request(path, { method: 'POST', body: body ? JSON.stringify(body) : undefined }), patch: (path: string, body: unknown) => request(path, { method: 'PATCH', body: JSON.stringify(body) }), put: (path: string, body: unknown) => request(path, { method: 'PUT', body: JSON.stringify(body) }), delete: (path: string) => request(path, { method: 'DELETE' }), }