function isLoopbackHostname(hostname: string) { return hostname === 'localhost' || hostname === '127.0.0.1'; } function normalizeBaseUrl(baseUrl: string) { return baseUrl.replace(/\/+$/, ''); } function resolveApiBaseUrl() { const configuredBaseUrl = (import.meta.env.VITE_API_BASE_URL ?? '').trim(); if (typeof window === 'undefined') { return normalizeBaseUrl(configuredBaseUrl); } const fallbackBaseUrl = `${window.location.protocol}//${window.location.hostname}:4000/api`; if (!configuredBaseUrl) { return fallbackBaseUrl; } try { const configuredUrl = new URL(configuredBaseUrl); if (isLoopbackHostname(configuredUrl.hostname) && !isLoopbackHostname(window.location.hostname)) { configuredUrl.hostname = window.location.hostname; return normalizeBaseUrl(configuredUrl.toString()); } return normalizeBaseUrl(configuredUrl.toString()); } catch { return normalizeBaseUrl(configuredBaseUrl); } } const apiBaseUrl = resolveApiBaseUrl(); export const hasApiConfig = Boolean(apiBaseUrl); export async function apiRequest(path: string, init?: RequestInit): Promise { if (!apiBaseUrl) { throw new Error('VITE_API_BASE_URL is not configured.'); } let response: Response; try { response = await fetch(`${apiBaseUrl}${path}`, { credentials: 'include', headers: { 'Content-Type': 'application/json', ...(init?.headers ?? {}), }, ...init, }); } catch (error) { throw new Error( `Failed to reach the local API at ${apiBaseUrl}. Check that the API server is running and that VITE_API_BASE_URL matches the host you opened in the browser.`, { cause: error }, ); } const contentType = response.headers.get('content-type') || ''; const payload = contentType.includes('application/json') ? ((await response.json()) as unknown) : null; if (!response.ok) { const message = payload && typeof payload === 'object' && 'error' in payload ? String(payload.error) : 'Request failed.'; throw new Error(message); } return payload as T; }