import axios, { AxiosError, type InternalAxiosRequestConfig } from "axios"; const TOKEN_KEY = "lc_admin_token"; const REFRESH_KEY = "lc_admin_refresh"; export const tokenStore = { get: () => localStorage.getItem(TOKEN_KEY), getRefresh: () => localStorage.getItem(REFRESH_KEY), set: (access: string, refresh?: string) => { localStorage.setItem(TOKEN_KEY, access); if (refresh) localStorage.setItem(REFRESH_KEY, refresh); }, clear: () => { localStorage.removeItem(TOKEN_KEY); localStorage.removeItem(REFRESH_KEY); }, }; const baseURL = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8000/api/v1"; const api = axios.create({ baseURL, timeout: 20000 }); api.interceptors.request.use((config) => { const token = tokenStore.get(); if (token) config.headers.Authorization = `Bearer ${token}`; return config; }); let onUnauthorized: (() => void) | null = null; export const setUnauthorizedHandler = (fn: () => void) => { onUnauthorized = fn; }; // Single-flight refresh: queue concurrent 401s behind one refresh call. let refreshing: Promise | null = null; async function doRefresh(): Promise { const refresh = tokenStore.getRefresh(); if (!refresh) return null; try { const res = await axios.post(`${baseURL}/auth/refresh`, { refresh_token: refresh, }); tokenStore.set(res.data.access_token, res.data.refresh_token); return res.data.access_token as string; } catch { return null; } } api.interceptors.response.use( (res) => res, async (error: AxiosError) => { const original = error.config as InternalAxiosRequestConfig & { _retry?: boolean; }; const isAuthCall = original?.url?.includes("/auth/"); if (error.response?.status === 401 && original && !original._retry && !isAuthCall) { original._retry = true; refreshing = refreshing ?? doRefresh(); const newToken = await refreshing; refreshing = null; if (newToken) { original.headers.Authorization = `Bearer ${newToken}`; return api(original); } tokenStore.clear(); onUnauthorized?.(); } return Promise.reject(error); }, ); export function apiErrorMessage(error: unknown): string { if (axios.isAxiosError(error)) { const detail = error.response?.data?.detail; if (typeof detail === "string") return detail; if (Array.isArray(detail) && detail[0]?.msg) return detail[0].msg as string; if (error.code === "ERR_NETWORK") return "Cannot reach the server."; } return "Something went wrong. Please try again."; } export default api;