import axios from "axios"; import { useNavigate } from "react-router-dom"; import axiosRetry from "axios-retry"; import showToast from "../services/toastService"; import { startSignalR, stopSignalR } from "../services/signalRService"; import { BASE_URL } from "./constants"; const base_Url = BASE_URL; export const axiosClient = axios.create({ baseURL: base_Url, withCredentials: false, headers: { "Content-Type": "application/json", }, }); // Auto retry failed requests (e.g., network issues) axiosRetry(axiosClient, { retries: 3 }); // Request Interceptor — Add Bearer token if required axiosClient.interceptors.request.use( async (config) => { const requiresAuth = config.authRequired !== false; // default to true if (requiresAuth) { const token = localStorage.getItem("jwtToken") || sessionStorage.getItem("jwtToken"); if (token) { config.headers["Authorization"] = `Bearer ${token}`; config._retry = true; } else { config._retry = false; } } return config; }, (error) => Promise.reject(error) ); // 🔄 Response Interceptor — Handle 401, refresh token, etc. axiosClient.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config; // Skip retry for public requests or already retried ones if ( (!originalRequest && originalRequest._retry) || originalRequest.authRequired === false ) { return Promise.reject(error); } // Avoid showing multiple toasts if (!originalRequest._toastShown) { originalRequest._toastShown = true; if (error.code === "ERR_CONNECTION_REFUSED") { showToast( "Unable to connect to the server. Please try again later.", "error" ); } else if (error.code === "ERR_NETWORK") { showToast("Network error. Please check your connection.", "error"); redirectToLogin(); } else if (error.code === "ECONNABORTED") { showToast("Request timed out. Please try again.", "error"); } else if (error.response) { const status = error.response.status; const isRefreshRequest = error.config.url.includes("refresh-token"); if (status === 401 && !isRefreshRequest) { originalRequest._retry = true; const refreshToken = localStorage.getItem("refreshToken") || sessionStorage.getItem("refreshToken"); if ( !refreshToken || error.response.data?.errors === "Invalid or expired refresh token." ) { redirectToLogin(); return Promise.reject(error); } stopSignalR(); try { // Refresh token call const res = await axiosClient.post("/api/Auth/refresh-token", { token: localStorage.getItem("jwtToken") || sessionStorage.getItem("jwtToken"), refreshToken, }); const { token, refreshToken: newRefreshToken } = res.data.data; // Save updated tokens if (localStorage.getItem("jwtToken")) { localStorage.setItem("jwtToken", token); localStorage.setItem("refreshToken", newRefreshToken); } else { sessionStorage.setItem("jwtToken", token); sessionStorage.setItem("refreshToken", newRefreshToken); } startSignalR(); // Set Authorization header originalRequest.headers["Authorization"] = `Bearer ${token}`; return axiosClient(originalRequest); } catch (refreshError) { redirectToLogin(); return Promise.reject(refreshError); } } } else { showToast("An unknown error occurred.", "error"); } } return Promise.reject(error); } ); // Generic API function const apiRequest = async (method, url, data = {}, config = {}) => { try { const response = await axiosClient({ method, url, data: method !== "get" ? data : undefined, params: method === "get" ? data : undefined, ...config, }); return response.data; } catch (error) { throw error; } }; // Exported API wrapper export const api = { // Public routes (no token required) postPublic: (url, data = {}, customHeaders = {}) => apiRequest("post", url, data, { headers: { ...customHeaders }, authRequired: false, }), // Authenticated routes get: (url, params = {}, customHeaders = {}) => apiRequest("get", url, params, { headers: { ...customHeaders }, authRequired: true, }), post: (url, data = {}, customHeaders = {}) => apiRequest("post", url, data, { headers: { ...customHeaders }, authRequired: true, }), put: (url, data = {}, customHeaders = {}) => apiRequest("put", url, data, { headers: { ...customHeaders }, authRequired: true, }), delete: (url, data = {}, customHeaders = {}) => apiRequest("delete", url, data, { headers: { ...customHeaders }, authRequired: true, }), }; // Redirect helper function redirectToLogin() { window.location.href = "/auth/login"; }