import { AxiosInstance, AxiosRequestConfig } from 'axios' import { refreshAccessToken } from '../services/authApi' import { getUserRole } from './role' import { clearAuthTokens, getAccessToken, getRefreshToken, storeAuthTokens } from './authToken' interface RetriableRequestConfig extends AxiosRequestConfig { _retried?: boolean } // shared across every axios instance — concurrent 401s while a refresh is // already in flight must wait on the same promise, not trigger separate refreshes let refreshPromise: Promise | null = null function refreshAndStoreAccessToken(): Promise { const refreshToken = getRefreshToken() if (!refreshToken) return Promise.resolve(null) if (!refreshPromise) { refreshPromise = refreshAccessToken(refreshToken) .then((tokens) => { storeAuthTokens(tokens.access_token, tokens.refresh_token) return tokens.access_token }) .catch(() => { clearAuthTokens() return null }) .finally(() => { refreshPromise = null }) } return refreshPromise } export function attachAuthInterceptors(instance: AxiosInstance) { instance.interceptors.request.use((config) => { const token = getAccessToken() if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) instance.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config as RetriableRequestConfig | undefined if (error.response?.status === 401 && originalRequest && !originalRequest._retried) { originalRequest._retried = true const newAccessToken = await refreshAndStoreAccessToken() if (newAccessToken) { originalRequest.headers = { ...originalRequest.headers, Authorization: `Bearer ${newAccessToken}` } return instance(originalRequest) } // refresh token is gone/invalid — nothing left to do but send the user back to login window.location.href = getUserRole() === 'cafe_owner' ? '/cafe/login' : '/login' } return Promise.reject(error) }, ) }