import axios, { AxiosError, InternalAxiosRequestConfig } from 'axios'; import { API_BASE_URL } from './config'; // Create axios instance export const apiClient = axios.create({ baseURL: API_BASE_URL, headers: { 'Content-Type': 'application/json', }, }); // Token management const TOKEN_KEY = 'colaflow_access_token'; const REFRESH_TOKEN_KEY = 'colaflow_refresh_token'; export const tokenManager = { getAccessToken: () => { if (typeof window === 'undefined') return null; return localStorage.getItem(TOKEN_KEY); }, setAccessToken: (token: string) => { if (typeof window === 'undefined') return; localStorage.setItem(TOKEN_KEY, token); }, getRefreshToken: () => { if (typeof window === 'undefined') return null; return localStorage.getItem(REFRESH_TOKEN_KEY); }, setRefreshToken: (token: string) => { if (typeof window === 'undefined') return; localStorage.setItem(REFRESH_TOKEN_KEY, token); }, clearTokens: () => { if (typeof window === 'undefined') return; localStorage.removeItem(TOKEN_KEY); localStorage.removeItem(REFRESH_TOKEN_KEY); }, }; // Request interceptor: automatically add Access Token apiClient.interceptors.request.use( (config: InternalAxiosRequestConfig) => { const token = tokenManager.getAccessToken(); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, (error) => Promise.reject(error) ); // Response interceptor: automatically refresh Token let isRefreshing = false; let failedQueue: Array<{ resolve: (value?: unknown) => void; reject: (reason?: unknown) => void; }> = []; const processQueue = (error: unknown, token: string | null = null) => { failedQueue.forEach((prom) => { if (error) { prom.reject(error); } else { prom.resolve(token); } }); failedQueue = []; }; apiClient.interceptors.response.use( (response) => response, async (error: AxiosError) => { const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean; }; // If 401 and not a refresh token request, try to refresh token if (error.response?.status === 401 && !originalRequest._retry) { if (isRefreshing) { // If already refreshing, queue this request return new Promise((resolve, reject) => { failedQueue.push({ resolve, reject }); }).then((token) => { originalRequest.headers.Authorization = `Bearer ${token}`; return apiClient(originalRequest); }); } originalRequest._retry = true; isRefreshing = true; const refreshToken = tokenManager.getRefreshToken(); if (!refreshToken) { tokenManager.clearTokens(); if (typeof window !== 'undefined') { window.location.href = '/login'; } return Promise.reject(error); } try { const { data } = await axios.post(`${API_BASE_URL}/api/auth/refresh`, { refreshToken, }); tokenManager.setAccessToken(data.accessToken); tokenManager.setRefreshToken(data.refreshToken); apiClient.defaults.headers.common.Authorization = `Bearer ${data.accessToken}`; originalRequest.headers.Authorization = `Bearer ${data.accessToken}`; processQueue(null, data.accessToken); return apiClient(originalRequest); } catch (refreshError) { processQueue(refreshError, null); tokenManager.clearTokens(); if (typeof window !== 'undefined') { window.location.href = '/login'; } return Promise.reject(refreshError); } finally { isRefreshing = false; } } return Promise.reject(error); } );