const BASE = "/api/v1"; export function setTokens(accessToken: string, refreshToken: string) { localStorage.setItem("accessToken", accessToken); localStorage.setItem("refreshToken", refreshToken); } export function clearTokens() { localStorage.removeItem("accessToken"); localStorage.removeItem("refreshToken"); } function getToken(): string | null { return localStorage.getItem("accessToken"); } // Single in-flight refresh promise so concurrent 401s don't fire multiple refreshes let refreshPromise: Promise | null = null; async function tryRefresh(): Promise { const refreshToken = localStorage.getItem("refreshToken"); if (!refreshToken) return null; try { const res = await fetch(`${BASE}/auth/refresh`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ refreshToken }), }); if (!res.ok) { clearTokens(); return null; } const data = await res.json() as { accessToken: string; refreshToken: string }; setTokens(data.accessToken, data.refreshToken); return data.accessToken; } catch { clearTokens(); return null; } } async function request(path: string, options: RequestInit = {}): Promise { const doFetch = async (token: string | null) => { const headers: Record = { "Content-Type": "application/json", ...(options.headers as Record), }; if (token) headers["Authorization"] = `Bearer ${token}`; return fetch(`${BASE}${path}`, { ...options, headers }); }; let res = await doFetch(getToken()); // On 401, attempt one token refresh then retry if (res.status === 401) { if (!refreshPromise) { refreshPromise = tryRefresh().finally(() => { refreshPromise = null; }); } const newToken = await refreshPromise; if (!newToken) { // Refresh failed — signal the app to go to login window.dispatchEvent(new CustomEvent("auth:logout")); throw new Error("Session expired. Please sign in again."); } res = await doFetch(newToken); } if (!res.ok) { const body = await res.json().catch(() => ({})); const message = (body as { error?: { message?: string } })?.error?.message ?? `HTTP ${res.status}`; throw new Error(message); } return res.json() as Promise; } export const api = { get: (path: string) => request(path), post: (path: string, body: unknown) => request(path, { method: "POST", body: JSON.stringify(body) }), put: (path: string, body: unknown) => request(path, { method: "PUT", body: JSON.stringify(body) }), patch: (path: string, body: unknown) => request(path, { method: "PATCH", body: JSON.stringify(body) }), delete: (path: string) => request(path, { method: "DELETE" }), };