import { supabase } from '../lib/supabase' const API_BASE = import.meta.env['VITE_API_URL'] ?? '' // Refresh token if it expires within this many seconds const TOKEN_EXPIRY_BUFFER_SECONDS = 60 export class ApiError extends Error { status: number constructor(status: number, message: string) { super(message) this.name = 'ApiError' this.status = status } } function isTokenExpiringSoon(expiresAt: number): boolean { const nowSeconds = Math.floor(Date.now() / 1000) return expiresAt - nowSeconds < TOKEN_EXPIRY_BUFFER_SECONDS } async function getValidAccessToken(): Promise { const { data } = await supabase.auth.getSession() const session = data.session if (!session) { return null } // If token is expired or expiring soon, refresh it if (isTokenExpiringSoon(session.expires_at ?? 0)) { const { data: refreshed, error } = await supabase.auth.refreshSession() if (error || !refreshed.session) { return null } return refreshed.session.access_token } return session.access_token } async function getAuthHeaders(): Promise> { const token = await getValidAccessToken() if (token) { return { Authorization: `Bearer ${token}` } } return {} } async function request(path: string, options?: RequestInit, isRetry = false): Promise { const authHeaders = await getAuthHeaders() const res = await fetch(`${API_BASE}/api/v1${path}`, { ...options, headers: { 'Content-Type': 'application/json', ...authHeaders, ...options?.headers, }, }) // On 401, try refreshing the token and retry once if (res.status === 401 && !isRetry) { const { data: refreshed, error } = await supabase.auth.refreshSession() if (!error && refreshed.session) { return request(path, options, true) } } if (!res.ok) { const body = await res.json().catch(() => ({})) throw new ApiError(res.status, body.detail ?? res.statusText) } if (res.status === 204) return undefined as T return res.json() } export const api = { get: (path: string) => request(path), post: (path: string, body: unknown) => request(path, { method: 'POST', body: JSON.stringify(body), }), patch: (path: string, body: unknown) => request(path, { method: 'PATCH', body: JSON.stringify(body), }), put: (path: string, body: unknown) => request(path, { method: 'PUT', body: JSON.stringify(body), }), del: (path: string) => request(path, { method: 'DELETE' }), }