/** * ════════════════════════════════════════════════════════════ * مُصادَق (Musadaq) — API Client * ════════════════════════════════════════════════════════════ */ import axios from 'axios'; const API_URL = import.meta.env.VITE_API_URL || '/api'; const apiClient = axios.create({ baseURL: API_URL, withCredentials: true, // Required for HttpOnly refresh cookies headers: { 'Content-Type': 'application/json', }, }); // ── Request Interceptor (JWT) ────────────────────────────── apiClient.interceptors.request.use( (config) => { const token = localStorage.getItem('access_token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, (error) => Promise.reject(error), ); // ── Response Interceptor (Token Rotation) ────────────────── apiClient.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config; // If 401 and not already retrying if (error.response?.status === 401 && !originalRequest._retry) { originalRequest._retry = true; try { // Attempt to refresh tokens const { data } = await axios.post( `${API_URL}/auth/refresh`, {}, { withCredentials: true }, ); localStorage.setItem('access_token', data.accessToken); originalRequest.headers.Authorization = `Bearer ${data.accessToken}`; return apiClient(originalRequest); } catch (refreshError) { // If refresh fails, clear and redirect to login localStorage.removeItem('access_token'); window.location.href = '/login'; return Promise.reject(refreshError); } } return Promise.reject(error); }, ); export default apiClient;