const API_BASE_URL = String(import.meta.env.VITE_API_BASE_URL || '').replace(/\/$/, '') let accessToken = '' let refreshHandler = null export function setAccessToken(token) { accessToken = token || '' } export function clearAccessToken() { accessToken = '' } export function setRefreshHandler(handler) { refreshHandler = handler } function buildUrl(path) { if (!API_BASE_URL) return path if (/^https?:\/\//i.test(path)) return path return `${API_BASE_URL}${path.startsWith('/') ? '' : '/'}${path}` } async function readBody(res) { if (res.status === 204) return null const contentType = res.headers.get('content-type') || '' if (contentType.includes('application/json')) return res.json() return res.text() } function normalizeError(data) { if (!data) return new Error('Request failed') if (typeof data === 'string') return new Error(data) if (typeof data?.message === 'string') return new Error(data.message) return new Error('Request failed') } async function tryRefresh() { if (!refreshHandler) return false try { const token = await refreshHandler() if (!token) { clearAccessToken() return false } setAccessToken(token) return true } catch { clearAccessToken() return false } } export async function apiRequest( path, { method = 'GET', body, headers = {}, skipAuth = false, skipRefresh = false } = {}, ) { const url = buildUrl(path) const finalHeaders = new Headers(headers) if (!skipAuth && accessToken) { finalHeaders.set('Authorization', `Bearer ${accessToken}`) } let finalBody = body if (body && !(body instanceof FormData) && typeof body === 'object') { finalHeaders.set('Content-Type', 'application/json') finalBody = JSON.stringify(body) } const res = await fetch(url, { method, headers: finalHeaders, body: finalBody, credentials: 'include', }) if (res.status === 401 && !skipRefresh) { const refreshed = await tryRefresh() if (refreshed) { return apiRequest(path, { method, body, headers, skipAuth, skipRefresh: true }) } } const data = await readBody(res) if (!res.ok) { throw normalizeError(data) } return data }