Files
edr-platform/apps/edr-freight-web/backoffice/src/shared/services/chronicleInstance.ts
2026-07-23 06:21:51 +00:00

105 lines
3.2 KiB
TypeScript

// 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;