// ============== API client ============== // Fetch wrapper that handles bearer auth + automatic refresh-on-401. const API_BASE = (window.W3J_API_BASE || "/api").replace(/\/$/, ""); const TOKEN_KEY = "w3j.access"; const REFRESH_KEY = "w3j.refresh"; const tokens = { get access() { return localStorage.getItem(TOKEN_KEY); }, get refresh() { return localStorage.getItem(REFRESH_KEY); }, set({ access_token, refresh_token }) { if (access_token) localStorage.setItem(TOKEN_KEY, access_token); if (refresh_token) localStorage.setItem(REFRESH_KEY, refresh_token); window.dispatchEvent(new Event("w3j:auth-changed")); }, clear() { localStorage.removeItem(TOKEN_KEY); localStorage.removeItem(REFRESH_KEY); window.dispatchEvent(new Event("w3j:auth-changed")); }, }; async function tryRefresh() { const rt = tokens.refresh; if (!rt) return false; const r = await fetch(`${API_BASE}/auth/refresh`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ refresh_token: rt }), }); if (!r.ok) { tokens.clear(); return false; } tokens.set(await r.json()); return true; } async function apiFetch(path, { method = "GET", body, headers = {}, isForm = false, auth = true } = {}) { const url = path.startsWith("http") ? path : `${API_BASE}${path}`; const init = { method, headers: { ...headers } }; if (auth && tokens.access) init.headers.Authorization = `Bearer ${tokens.access}`; if (body !== undefined) { if (isForm) { init.body = body; } else { init.headers["Content-Type"] = "application/json"; init.body = JSON.stringify(body); } } let res = await fetch(url, init); if (res.status === 401 && auth && tokens.refresh && !path.includes("/auth/")) { if (await tryRefresh()) { init.headers.Authorization = `Bearer ${tokens.access}`; res = await fetch(url, init); } } if (res.status === 204) return null; const text = await res.text(); const data = text ? JSON.parse(text) : null; if (!res.ok) { const err = new Error((data && (data.detail || data.message)) || `HTTP ${res.status}`); err.status = res.status; err.data = data; throw err; } return data; } window.W3J_API = { apiFetch, tokens, API_BASE };