const API_URL = import.meta.env.VITE_EFC_API_URL; // Variable para controlar si ya hay una renovación de token en proceso let isRefreshing = false; let failedQueue = []; // Función para procesar la cola de peticiones fallidas después de renovar el token const processQueue = (error, token = null) => { failedQueue.forEach(prom => { if (error) { prom.reject(error); } else { prom.resolve(token); } }); failedQueue = []; }; // Función para renovar el token usando el refresh token const refreshToken = async () => { try { const refresh = localStorage.getItem('refresh'); if (!refresh) { throw new Error('No refresh token available'); } const response = await fetch(`${API_URL}/auth/token/refresh/`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ refresh: refresh }), }); if (!response.ok) { throw new Error('Failed to refresh token'); } const data = await response.json(); // Guardar el nuevo access token localStorage.setItem('access', data.access); // Si viene un nuevo refresh token, guardarlo también if (data.refresh) { localStorage.setItem('refresh', data.refresh); } return data.access; } catch (error) { // Si falla la renovación, limpiar tokens y redirigir al login localStorage.removeItem('access'); localStorage.removeItem('refresh'); localStorage.removeItem('user_id'); localStorage.removeItem('user_is_importador'); // Redirigir al login después de un pequeño delay setTimeout(() => { window.location.href = '/login'; }, 1000); throw error; } }; // Función principal para hacer peticiones con manejo automático de tokens export const fetchWithAuth = async (url, options = {}) => { // Obtener el token actual let token = localStorage.getItem('access'); // Configurar headers por defecto const defaultHeaders = { 'Content-Type': 'application/json', ...(token && { 'Authorization': `Bearer ${token}` }) }; // Combinar headers const finalOptions = { ...options, headers: { ...defaultHeaders, ...options.headers } }; try { // Hacer la petición inicial let response = await fetch(url, finalOptions); // Si la respuesta es 401 (Unauthorized), intentar renovar el token if (response.status === 401 && !isRefreshing) { isRefreshing = true; try { // Renovar el token const newToken = await refreshToken(); // Procesar la cola de peticiones pendientes processQueue(null, newToken); // Actualizar el header de autorización y reintentar la petición original finalOptions.headers['Authorization'] = `Bearer ${newToken}`; response = await fetch(url, finalOptions); } catch (refreshError) { // Si falla la renovación, procesar la cola con error processQueue(refreshError, null); throw refreshError; } finally { isRefreshing = false; } } // Si todavía hay un 401 después del intento de renovación, redirigir al login if (response.status === 401) { localStorage.removeItem('access'); localStorage.removeItem('refresh'); localStorage.removeItem('user_id'); localStorage.removeItem('user_is_importador'); setTimeout(() => { window.location.href = '/login'; }, 1000); throw new Error('Session expired'); } return response; } catch (error) { // Si hay un error de red o cualquier otro error, propagarlo throw error; } }; // Función auxiliar para hacer peticiones GET con manejo de tokens export const getWithAuth = async (url) => { return fetchWithAuth(url, { method: 'GET' }); }; // Función auxiliar para hacer peticiones POST con manejo de tokens export const postWithAuth = async (url, data) => { return fetchWithAuth(url, { method: 'POST', body: JSON.stringify(data) }); }; // Función auxiliar para hacer peticiones PUT con manejo de tokens export const putWithAuth = async (url, data) => { return fetchWithAuth(url, { method: 'PUT', body: JSON.stringify(data) }); }; // Función auxiliar para hacer peticiones PATCH con manejo de tokens export const patchWithAuth = async (url, data) => { return fetchWithAuth(url, { method: 'PATCH', body: JSON.stringify(data) }); }; // Función auxiliar para hacer peticiones DELETE con manejo de tokens export const deleteWithAuth = async (url) => { return fetchWithAuth(url, { method: 'DELETE' }); }; // Función para hacer peticiones con FormData (para archivos) export const postFormDataWithAuth = async (url, formData) => { let token = localStorage.getItem('access'); const options = { method: 'POST', headers: { ...(token && { 'Authorization': `Bearer ${token}` }) }, body: formData }; try { let response = await fetch(url, options); if (response.status === 401 && !isRefreshing) { isRefreshing = true; try { const newToken = await refreshToken(); processQueue(null, newToken); options.headers['Authorization'] = `Bearer ${newToken}`; response = await fetch(url, options); } catch (refreshError) { processQueue(refreshError, null); throw refreshError; } finally { isRefreshing = false; } } if (response.status === 401) { localStorage.removeItem('access'); localStorage.removeItem('refresh'); localStorage.removeItem('user_id'); localStorage.removeItem('user_is_importador'); setTimeout(() => { window.location.href = '/login'; }, 1000); throw new Error('Session expired'); } return response; } catch (error) { throw error; } }; export default fetchWithAuth;