function resolveApiUrl() { const raw = (import.meta.env.VITE_API_URL || "").trim(); const fallback = "/api/v1"; if (!raw) return fallback; try { const parsed = new URL(raw, window.location.origin); if (window.location.protocol === "https:" && parsed.protocol === "http:") { // Avoid mixed-content when UI is served over HTTPS. parsed.protocol = "https:"; } return parsed.toString().replace(/\/$/, ""); } catch { return fallback; } } const API_URL = resolveApiUrl(); export async function apiFetch(path, options = {}, tokens, onUnauthorized) { const headers = { "Content-Type": "application/json", ...(options.headers || {}), }; if (tokens?.accessToken) { headers.Authorization = `Bearer ${tokens.accessToken}`; } let res = await fetch(`${API_URL}${path}`, { ...options, headers }); if (res.status === 401 && tokens?.refreshToken && onUnauthorized) { const refreshed = await onUnauthorized(); if (refreshed) { headers.Authorization = `Bearer ${refreshed.accessToken}`; res = await fetch(`${API_URL}${path}`, { ...options, headers }); } } if (!res.ok) { const raw = await res.text(); let parsed = null; try { parsed = raw ? JSON.parse(raw) : null; } catch { parsed = null; } const message = parsed?.message || raw || `HTTP ${res.status}`; const err = new Error(message); err.status = res.status; err.code = parsed?.code || null; err.details = parsed?.details || null; err.requestId = parsed?.request_id || res.headers.get("x-request-id") || null; throw err; } if (res.status === 204) return null; return res.json(); } export { API_URL };