const API_BASE = import.meta.env.VITE_API_BASE_URL || (import.meta.env.DEV ? "http://localhost:8000" : ""); const inFlightRequests = new Map(); export const apiRequest = async (endpoint, method = "GET", body = null) => { // Deduplicate concurrent in-flight GET requests if (method === "GET" && !body) { if (inFlightRequests.has(endpoint)) { return inFlightRequests.get(endpoint); } const promise = (async () => { try { return await _performRequest(endpoint, method, body); } finally { inFlightRequests.delete(endpoint); } })(); inFlightRequests.set(endpoint, promise); return promise; } return _performRequest(endpoint, method, body); }; const _performRequest = async (endpoint, method = "GET", body = null) => { const token = localStorage.getItem("token"); const headers = {}; if (!(body instanceof FormData)) { headers["Content-Type"] = "application/json"; } if (token) { headers["Authorization"] = `Bearer ${token}`; } const response = await fetch(API_BASE + endpoint, { method, headers, body: body instanceof FormData ? body : body ? JSON.stringify(body) : null, }); // If unauthorized, clear stale token and redirect to login if (response.status === 401 && endpoint !== "/auth/login" && endpoint !== "/auth/me") { localStorage.removeItem("token"); window.location.href = "/login"; return { detail: "Session expired" }; } // 204 No Content — nothing to parse if (response.status === 204) { return { ok: true }; } return response.json(); }; export const loginRequest = async (email, password) => { const formData = new URLSearchParams(); formData.append("username", email); formData.append("password", password); const response = await fetch(API_BASE + "/auth/login", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: formData, }); return response.json(); }; export const demoLoginRequest = async () => { const response = await fetch(API_BASE + "/auth/demo-login", { method: "POST", headers: { "Content-Type": "application/json" }, }); return response.json(); };