// The Lucia project. // Copyright 2023-2026 DSP, inc. All rights reserved. // Authors: // imacat.yang@dsp.im (imacat), 2023/9/23 /** * @module apiClient Centralized axios instance with request/response * interceptors for authentication token management and automatic * 401 token refresh with request queuing. */ import axios from "axios"; import { getCookie, deleteCookie } from "@/utils/cookieUtil.js"; /** Axios instance configured with auth interceptors. */ const apiClient = axios.create(); // Request interceptor: automatically attach Authorization header apiClient.interceptors.request.use((config) => { const token = getCookie("luciaToken"); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); // Response interceptor: handle 401 by attempting token refresh let isRefreshing = false; let pendingRequests = []; /** * Resolves all pending requests with the new access token. * @param {string} newToken - The refreshed access token. */ function onRefreshSuccess(newToken) { pendingRequests.forEach((cb) => cb(newToken)); pendingRequests = []; } /** * Rejects all pending requests with the refresh error. * @param {Error} error - The token refresh error. */ function onRefreshFailure(error) { pendingRequests.forEach((cb) => cb(null, error)); pendingRequests = []; } apiClient.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config; // Only attempt refresh on 401, and not for auth endpoints or already-retried requests if ( error.response?.status !== 401 || originalRequest._retried || originalRequest.url === "/api/oauth/token" ) { return Promise.reject(error); } if (isRefreshing) { // Queue this request until the refresh completes return new Promise((resolve, reject) => { pendingRequests.push((newToken, err) => { if (err) return reject(err); originalRequest.headers.Authorization = `Bearer ${newToken}`; originalRequest._retried = true; resolve(apiClient(originalRequest)); }); }); } isRefreshing = true; originalRequest._retried = true; try { // Dynamic import to avoid circular dependency with login store const { refreshTokenAndGetNew } = await import("@/api/auth.js"); const newToken = await refreshTokenAndGetNew(); isRefreshing = false; onRefreshSuccess(newToken); originalRequest.headers.Authorization = `Bearer ${newToken}`; return apiClient(originalRequest); } catch (refreshError) { isRefreshing = false; onRefreshFailure(refreshError); // Refresh failed: clear auth and redirect to login deleteCookie("luciaToken"); deleteCookie("luciaRefreshToken"); deleteCookie("isLuciaLoggedIn"); window.location.href = "/login"; return Promise.reject(refreshError); } }, ); export default apiClient;