const configuredApiBase = (import.meta.env.VITE_API_BASE_URL as string | undefined)?.trim(); function resolveApiBaseUrl(): string { if (configuredApiBase) { // Relative /api only works in dev (Vite proxy). In production it hits the web host. if (import.meta.env.PROD && configuredApiBase === '/api') { return 'https://api.eventbuzz.live/api'; } return configuredApiBase.replace(/\/$/, ''); } return import.meta.env.DEV ? '/api' : 'https://api.eventbuzz.live/api'; } export const API_BASE_URL = resolveApiBaseUrl(); export const PUBLIC_WEB_BASE = (import.meta.env.VITE_PUBLIC_WEB_BASE as string | undefined)?.replace(/\/$/, '') || (typeof window !== 'undefined' ? window.location.origin : 'http://localhost:5175'); const STORAGE_KEYS = { access: 'eventbuzz_access_token', refresh: 'eventbuzz_refresh_token', } as const; let accessToken: string | null = null; let refreshToken: string | null = null; function restoreTokensFromStorage() { try { const access = window.localStorage.getItem(STORAGE_KEYS.access); const refresh = window.localStorage.getItem(STORAGE_KEYS.refresh); if (access) accessToken = access; if (refresh) refreshToken = refresh; } catch { // ignore storage issues } } restoreTokensFromStorage(); export function setTokens(access: string, refresh: string) { accessToken = access; refreshToken = refresh; try { window.localStorage.setItem(STORAGE_KEYS.access, access); window.localStorage.setItem(STORAGE_KEYS.refresh, refresh); } catch { // ignore storage issues } } export function clearTokens() { accessToken = null; refreshToken = null; try { window.localStorage.removeItem(STORAGE_KEYS.access); window.localStorage.removeItem(STORAGE_KEYS.refresh); } catch { // ignore storage issues } } async function doRefresh(): Promise { if (!refreshToken) return false; const base = API_BASE_URL.replace(/\/api\/?$/, ''); try { const res = await fetch(`${base}/api/auth/refresh`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refreshToken }), }); const body = await res.json().catch(() => null); if (res.ok && body?.success && body?.data?.token) { setTokens(body.data.token, refreshToken); return true; } } catch { // ignore refresh failures and proceed with clear state } clearTokens(); return false; } type ApiEnvelope = { success?: boolean; data?: T; error?: { message?: string; code?: string }; }; export async function apiGet(path: string): Promise { const url = path.startsWith('http') ? path : `${API_BASE_URL}${path.startsWith('/') ? path : `/${path}`}`; let res: Response; try { res = await fetch(url, { headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : undefined, }); } catch { throw new Error('Could not reach the API. If this persists, the server may be down or blocking cross-origin requests.'); } let body: ApiEnvelope; try { body = await res.json(); } catch { if (res.status === 502 || res.status === 503 || res.status === 504) { throw new Error('The API is temporarily unavailable. Please try again in a few minutes.'); } throw new Error(`Unexpected server response (${res.status}).`); } if (!body.success || body.data === undefined) { throw new Error(body.error?.message || 'Request failed'); } return body.data; } export async function apiPost(path: string, payload: unknown): Promise { const url = path.startsWith('http') ? path : `${API_BASE_URL}${path.startsWith('/') ? path : `/${path}`}`; let res: Response; try { res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}), }, body: JSON.stringify(payload), }); } catch { throw new Error('Could not reach the API. If this persists, the server may be down or blocking cross-origin requests.'); } let body: ApiEnvelope; try { body = await res.json(); } catch { if (res.status === 502 || res.status === 503 || res.status === 504) { throw new Error('The API is temporarily unavailable. Please try again in a few minutes.'); } throw new Error(`Unexpected server response (${res.status}).`); } if (!body.success) { throw new Error(body.error?.message || 'Request failed'); } return body.data as T; } export async function fetchWithAuth(path: string, init: RequestInit = {}): Promise { const url = path.startsWith('http') ? path : `${API_BASE_URL}${path.startsWith('/') ? path : `/${path}`}`; const headers = new Headers(init.headers || {}); if (accessToken) { headers.set('Authorization', `Bearer ${accessToken}`); } if (typeof FormData !== 'undefined' && init.body instanceof FormData) { headers.delete('Content-Type'); } let res = await fetch(url, { ...init, headers }); if (res.status === 401 && refreshToken) { const refreshed = await doRefresh(); if (refreshed && accessToken) { headers.set('Authorization', `Bearer ${accessToken}`); res = await fetch(url, { ...init, headers }); } } return res; }