import axios, { AxiosError, type InternalAxiosRequestConfig } from 'axios' export const TOKEN_KEY = 'solution-erp-admin-token' export const REFRESH_KEY = 'solution-erp-admin-refresh' export const USER_KEY = 'solution-erp-admin-user' // Dev: Vite proxy /api → :5443 (vite.config.ts) // Prod: VITE_API_BASE_URL = https://api.solutions.com.vn (env.production) const BASE_URL = (import.meta.env.VITE_API_BASE_URL ?? '') + '/api' export const api = axios.create({ baseURL: BASE_URL, timeout: 30000, }) api.interceptors.request.use(config => { const token = localStorage.getItem(TOKEN_KEY) if (token) config.headers.Authorization = `Bearer ${token}` return config }) // Refresh token flow: khi 401 → thử refresh → retry request gốc. Queue các request song song // để chỉ 1 refresh call chạy, các request khác chờ token mới. type QueueItem = { resolve: (value: unknown) => void reject: (reason?: unknown) => void config: InternalAxiosRequestConfig } let isRefreshing = false let queue: QueueItem[] = [] function processQueue(error: unknown, token: string | null) { queue.forEach(({ resolve, reject, config }) => { if (error || !token) { reject(error) } else { config.headers.Authorization = `Bearer ${token}` resolve(api(config)) } }) queue = [] } function redirectLogin() { localStorage.removeItem(TOKEN_KEY) localStorage.removeItem(REFRESH_KEY) localStorage.removeItem(USER_KEY) if (!window.location.pathname.startsWith('/login')) { window.location.href = '/login' } } api.interceptors.response.use( response => response, async (error: AxiosError) => { const original = error.config as InternalAxiosRequestConfig & { _retry?: boolean } if (error.response?.status !== 401 || !original || original._retry) { return Promise.reject(error) } // Login/refresh endpoint 401 → không retry (tránh infinite loop) if (original.url?.includes('/auth/login') || original.url?.includes('/auth/refresh')) { redirectLogin() return Promise.reject(error) } original._retry = true const refreshToken = localStorage.getItem(REFRESH_KEY) if (!refreshToken) { redirectLogin() return Promise.reject(error) } if (isRefreshing) { return new Promise((resolve, reject) => { queue.push({ resolve, reject, config: original }) }) } isRefreshing = true try { const res = await axios.post<{ accessToken: string; refreshToken: string }>( '/api/auth/refresh', { refreshToken }, ) const newToken = res.data.accessToken localStorage.setItem(TOKEN_KEY, newToken) localStorage.setItem(REFRESH_KEY, res.data.refreshToken) processQueue(null, newToken) original.headers.Authorization = `Bearer ${newToken}` return api(original) } catch (refreshErr) { processQueue(refreshErr, null) redirectLogin() return Promise.reject(refreshErr) } finally { isRefreshing = false } }, )