import axios, { AxiosInstance, AxiosError, InternalAxiosRequestConfig } from 'axios' const API_BASE_URL = import.meta.env.VITE_API_URL || '/api/v1' // Create axios instance const apiClient: AxiosInstance = axios.create({ baseURL: API_BASE_URL, headers: { 'Content-Type': 'application/json', }, timeout: 30000, }) // Token storage const TOKEN_KEY = 'adan_access_token' const REFRESH_TOKEN_KEY = 'adan_refresh_token' export const getAccessToken = (): string | null => { return localStorage.getItem(TOKEN_KEY) } export const getRefreshToken = (): string | null => { return localStorage.getItem(REFRESH_TOKEN_KEY) } export const setTokens = (accessToken: string, refreshToken: string): void => { localStorage.setItem(TOKEN_KEY, accessToken) localStorage.setItem(REFRESH_TOKEN_KEY, refreshToken) } export const clearTokens = (): void => { localStorage.removeItem(TOKEN_KEY) localStorage.removeItem(REFRESH_TOKEN_KEY) } // Request interceptor - Add auth token apiClient.interceptors.request.use( (config: InternalAxiosRequestConfig) => { const token = getAccessToken() if (token && config.headers) { config.headers.Authorization = `Bearer ${token}` } return config }, (error: AxiosError) => { return Promise.reject(error) } ) // Response interceptor - Handle token refresh and errors let isRefreshing = false let failedQueue: Array<{ resolve: (value?: unknown) => void reject: (reason?: unknown) => void }> = [] const processQueue = (error: Error | null, 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 } // Handle 401 Unauthorized if (error.response?.status === 401 && !originalRequest._retry) { if (isRefreshing) { return new Promise((resolve, reject) => { failedQueue.push({ resolve, reject }) }) .then((token) => { if (originalRequest.headers) { originalRequest.headers.Authorization = `Bearer ${token}` } return apiClient(originalRequest) }) .catch((err) => Promise.reject(err)) } originalRequest._retry = true isRefreshing = true const refreshToken = getRefreshToken() if (!refreshToken) { clearTokens() window.location.href = '/login' return Promise.reject(error) } try { const response = await axios.post(`${API_BASE_URL}/auth/refresh`, { refreshToken, }) const { accessToken, refreshToken: newRefreshToken } = response.data setTokens(accessToken, newRefreshToken) processQueue(null, accessToken) if (originalRequest.headers) { originalRequest.headers.Authorization = `Bearer ${accessToken}` } return apiClient(originalRequest) } catch (refreshError) { processQueue(refreshError as Error, null) clearTokens() window.location.href = '/login' return Promise.reject(refreshError) } finally { isRefreshing = false } } // Handle other errors const errorMessage = (error.response?.data as { message?: string })?.message || error.message || 'Error de conexion' return Promise.reject(new Error(errorMessage)) } ) export default apiClient // Helper functions for common HTTP methods export const api = { get: (url: string, params?: object) => apiClient.get(url, { params }).then((res) => res.data), post: (url: string, data?: object) => apiClient.post(url, data).then((res) => res.data), put: (url: string, data?: object) => apiClient.put(url, data).then((res) => res.data), patch: (url: string, data?: object) => apiClient.patch(url, data).then((res) => res.data), delete: (url: string) => apiClient.delete(url).then((res) => res.data), }