// src/services/api/axiosInstance.ts import axios from "axios"; import Cookies from "js-cookie"; import { getEnvUrl } from "@/shared/config/env"; import { getRefreshToken } from "../utils/refreshTokenHandler"; import { getRememberMePreference, setAuthCookies, } from "../utils/authPersistence"; import { handleSessionExpiry } from "./sessionExpiry"; // Chronicle/audit-log backend is optional in this deployment — no warning // when unset; the module's screens are simply non-functional without it. const chronicleBaseUrl = getEnvUrl("VITE_CHRONICLE_URL", false) || getEnvUrl("VITE_AUDITLOG_API_URL", false); const chronicleInstance = axios.create({ baseURL: chronicleBaseUrl, }); // Attach auth token and CSRF defence header to every request chronicleInstance.interceptors.request.use((config) => { const token = Cookies.get("auth-token"); if (token) { config.headers["Authorization"] = `Bearer ${token}`; } config.headers["X-Requested-With"] = "XMLHttpRequest"; return config; }); let isRefreshing = false; let failedQueue: any[] = []; const processQueue = (error: any, token: string | null = null) => { failedQueue.forEach((prom) => { if (error) prom.reject(error); else prom.resolve(token); }); failedQueue = []; }; const skipRefreshUrls = ["/auth/login", "/auth/refresh", "/auth/me"]; // Add all URLs to skip chronicleInstance.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config; // 🔒 Skip refresh logic for specific URLs const shouldSkip = skipRefreshUrls.some((url) => originalRequest.url?.includes(url), ); if (shouldSkip) { return Promise.reject(error); } if (error.response?.status === 401 && !originalRequest._retry) { if (isRefreshing) { return new Promise(function (resolve, reject) { failedQueue.push({ resolve, reject }); }) .then((token) => { originalRequest.headers["Authorization"] = "Bearer " + token; return chronicleInstance(originalRequest); }) .catch((err) => Promise.reject(err)); } originalRequest._retry = true; isRefreshing = true; try { const refreshToken = Cookies.get("refresh-token"); if (!refreshToken) { throw new Error("missing-refresh-token"); } const rememberMe = getRememberMePreference(); const newToken = await getRefreshToken(refreshToken); const updatedToken = newToken.data.token; const updatedRefreshToken = newToken.data.refreshToken; processQueue(null, newToken.data.token); setAuthCookies({ token: updatedToken, refreshToken: updatedRefreshToken, rememberMe, }); originalRequest.headers["Authorization"] = `Bearer ${updatedToken}`; return chronicleInstance(originalRequest); } catch (err) { processQueue(err, null); handleSessionExpiry(); return Promise.reject(err); } finally { isRefreshing = false; } } return Promise.reject(error); }, ); export default chronicleInstance;