import { auth } from '$lib/auth/store.svelte'; import { goto } from '$app/navigation'; const BASE = '/api'; let isRefreshing = false; let refreshPromise: Promise | null = null; async function doRefresh(): Promise { const refreshToken = auth.getRefreshToken(); if (!refreshToken) return null; try { const res = await fetch(`${BASE}/token/refresh`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refresh_token: refreshToken }) }); if (!res.ok) return null; const data = await res.json(); auth.setTokens(data.access_token, data.refresh_token); return data.access_token; } catch { return null; } } async function getValidToken(): Promise { if (auth.accessToken) return auth.accessToken; // Deduplicate concurrent refresh calls if (isRefreshing) return refreshPromise; isRefreshing = true; refreshPromise = doRefresh().finally(() => { isRefreshing = false; refreshPromise = null; }); return refreshPromise; } export async function apiFetch( path: string, options: RequestInit = {}, retry = true ): Promise { const token = await getValidToken(); const headers = new Headers(options.headers); if (token) headers.set('Authorization', `Bearer ${token}`); const res = await fetch(`${BASE}${path}`, { ...options, headers }); if (res.status === 401 && retry) { // Force a refresh and retry once auth.setAccessToken(''); // clear so getValidToken triggers refresh const newToken = await doRefresh(); if (!newToken) { auth.clear(); goto('/login'); return res; } auth.setAccessToken(newToken); headers.set('Authorization', `Bearer ${newToken}`); return fetch(`${BASE}${path}`, { ...options, headers }); } return res; } export async function apiGet(path: string): Promise { const res = await apiFetch(path); if (!res.ok) throw new Error(`GET ${path} failed: ${res.status}`); return res.json(); } export async function apiPost(path: string, body: unknown): Promise { const res = await apiFetch(path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw Object.assign(new Error(`POST ${path} failed: ${res.status}`), { status: res.status, detail: err }); } return res.json(); } export async function apiPatch(path: string, body: unknown): Promise { const res = await apiFetch(path, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); if (!res.ok) throw new Error(`PATCH ${path} failed: ${res.status}`); return res.json(); } export async function apiDelete(path: string): Promise { const res = await apiFetch(path, { method: 'DELETE' }); if (!res.ok) throw new Error(`DELETE ${path} failed: ${res.status}`); }