import axios, { AxiosRequestConfig } from "axios"; import { normalizeApiError } from "./errors"; const defaultTimeout = 12000; export const searchTimeout = 8000; export const uploadTimeout = 30000; export const api = axios.create({ baseURL: "/api", withCredentials: true, headers: { "Content-Type": "application/json", }, timeout: defaultTimeout, }); const refreshApi = axios.create({ baseURL: "/api", withCredentials: true, headers: { "Content-Type": "application/json", }, timeout: defaultTimeout, }); let accessToken: string | null = null; let refreshToken: string | null = null; let refreshPromise: Promise | null = null; export function setAccessToken(token: string | null) { accessToken = token; if (token) { localStorage.setItem("access_token", token); } else { localStorage.removeItem("access_token"); } } export function getAccessToken(): string | null { if (!accessToken) { accessToken = localStorage.getItem("access_token"); } return accessToken; } export function setRefreshToken(token: string | null) { refreshToken = token; if (token) { sessionStorage.setItem("refresh_token", token); } else { sessionStorage.removeItem("refresh_token"); } } export function getRefreshToken(): string | null { if (!refreshToken) { refreshToken = sessionStorage.getItem("refresh_token"); } return refreshToken; } export function clearRefreshToken() { refreshToken = null; sessionStorage.removeItem("refresh_token"); } async function refreshAccessToken() { const token = getRefreshToken(); if (!token) { return null; } if (!refreshPromise) { refreshPromise = refreshApi .post<{ access_token: string, refresh_token: string, token_type: string }>( "/auth/refresh", { refresh_token: token } ) .then((response) => { setAccessToken(response.data.access_token); setRefreshToken(response.data.refresh_token); return response.data.access_token; }) .catch(() => { setAccessToken(null); clearRefreshToken(); return null; }) .finally(() => { refreshPromise = null; }); } return refreshPromise; } api.interceptors.request.use((config) => { const token = getAccessToken(); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); api.interceptors.response.use( (response) => response, async (error) => { const config = error?.config as (AxiosRequestConfig & { _retry?: boolean }) | undefined; if (error?.response?.status === 401 && config && !config._retry) { config._retry = true; const newToken = await refreshAccessToken(); if (newToken) { config.headers = { ...(config.headers || {}), Authorization: `Bearer ${newToken}` }; return api(config); } setAccessToken(null); clearRefreshToken(); } if (config && !config._retry && (!error.response || (error.response.status >= 500 && error.response.status < 600))) { const method = (config.method || "get").toLowerCase(); if (["get", "head", "options"].includes(method)) { config._retry = true; return api(config); } } return Promise.reject(normalizeApiError(error)); } );