import axios, { AxiosError } from "axios"; import { useAuthStore } from "@/store/auth"; // withCredentials so the httpOnly refresh cookie rides along on /api/auth/refresh. const api = axios.create({ baseURL: "/", withCredentials: true, // Arrays as `binds=a&binds=b`, which is what FastAPI reads as a list. Axios' // default `binds[]=a` is a different parameter name to it, so the folders and // volumes picked in the backup dialog were silently ignored on download. paramsSerializer: { indexes: null }, }); api.interceptors.request.use((config) => { const token = useAuthStore.getState().accessToken; if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); let refreshing: Promise | null = null; api.interceptors.response.use( (res) => res, async (error: AxiosError) => { const original = error.config as any; if (error.response?.status === 401 && original && !original._retry) { original._retry = true; if (!refreshing) { refreshing = useAuthStore .getState() .refresh() .finally(() => { refreshing = null; }); } const newToken = await refreshing; if (newToken) { original.headers.Authorization = `Bearer ${newToken}`; return api(original); } // Refresh failed: the session is genuinely over (expired, password // changed, account disabled). refresh() has already cleared the store. } return Promise.reject(error); } ); /** * The server's JSON error body behind a `responseType: "blob"` request (a * backup download), which axios hands back as a Blob. Without this such a * failure read "Request failed with status code 409" instead of saying the * stack was busy. */ export async function withBlobError(request: Promise): Promise { try { return await request; } catch (err) { const e = err as AxiosError; const data = e?.response?.data; if (e?.response && typeof Blob !== "undefined" && data instanceof Blob) { try { e.response.data = JSON.parse(await data.text()); } catch { /* not JSON — keep the generic message */ } } throw err; } } export function apiErrorMessage(err: unknown): string { const e = err as AxiosError; const detail = e?.response?.data?.detail; if (typeof detail === "string") return detail; if (detail?.detail) return `${detail.error}: ${detail.detail}`; if (detail?.error) return detail.error; return e?.message || "Unexpected error"; } export default api;