import { UseQueryOptions, QueryObserverOptions, } from "@tanstack/react-query"; import axios, { AxiosError, InternalAxiosRequestConfig } from "axios"; import { URL_CONSTANTS } from "@/constants/URLS"; const client = axios.create({ baseURL: import.meta.env.VITE_API_URL, }); function getCookie(name: string): string | undefined { return document.cookie .split("; ") .find((row) => row.startsWith(`${name}=`)) ?.split("=")[1]; } function setCookie(name: string, value: string, days: number) { const expires = new Date(); expires.setDate(expires.getDate() + days); document.cookie = `${name}=${value}; path=/; expires=${expires.toUTCString()}; SameSite=Lax`; } function clearAuthCookies() { ["auth-token", "refresh-token", "auth-user", "current-position-id", "selected-position-id"].forEach( (name) => { document.cookie = `${name}=; Max-Age=0; path=/`; }, ); } // Attach auth token to every request client.interceptors.request.use((config) => { const token = getCookie("auth-token"); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); // Token refresh state let isRefreshing = false; let failedQueue: { resolve: (token: string) => void; reject: (error: unknown) => void; }[] = []; function processQueue(error: unknown, token?: string) { failedQueue.forEach(({ resolve, reject }) => { if (error) { reject(error); } else { resolve(token!); } }); failedQueue = []; } // Handle auth errors globally with token refresh client.interceptors.response.use( (response) => response, async (error: AxiosError) => { const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean; }; // Don't intercept if: // - no response (network error) // - status is not 401 // - already retried // - it's the refresh endpoint itself if ( !error.response || error.response.status !== 401 || originalRequest._retry || originalRequest.url === URL_CONSTANTS.AUTH.REFRESH_TOKEN ) { return Promise.reject(error); } if (isRefreshing) { return new Promise((resolve, reject) => { failedQueue.push({ resolve, reject }); }).then((token) => { originalRequest.headers.Authorization = `Bearer ${token}`; return client(originalRequest); }); } originalRequest._retry = true; isRefreshing = true; const refreshToken = getCookie("refresh-token"); if (!refreshToken) { isRefreshing = false; clearAuthCookies(); if (window.location.pathname !== "/login") { window.location.href = "/login"; } return Promise.reject(error); } try { const { data } = await client.post<{ data: { token: string; refreshToken: string } }>( URL_CONSTANTS.AUTH.REFRESH_TOKEN, { refreshToken }, ); const { token, refreshToken: newRefreshToken } = data.data; setCookie("auth-token", token, 7); setCookie("refresh-token", newRefreshToken, 7); originalRequest.headers.Authorization = `Bearer ${token}`; processQueue(null, token); return client(originalRequest); } catch (refreshError) { processQueue(refreshError, undefined); clearAuthCookies(); if (window.location.pathname !== "/login") { window.location.href = "/login"; } return Promise.reject(refreshError); } finally { isRefreshing = false; } }, ); export { client }; export type { UseQueryOptions, QueryObserverOptions };