Spaces:
Runtime error
Runtime error
| import axios, { AxiosError, InternalAxiosRequestConfig } from 'axios' | |
| const api = axios.create({ | |
| baseURL: '', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| }) | |
| // Flag to prevent multiple refresh attempts | |
| 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 = [] | |
| } | |
| // Helper to get tokens from storage | |
| const getStoredTokens = () => { | |
| const stored = localStorage.getItem('auth-storage') | |
| if (stored) { | |
| try { | |
| const { state } = JSON.parse(stored) | |
| return { | |
| accessToken: state?.token, | |
| refreshToken: state?.refreshToken, | |
| } | |
| } catch { | |
| return { accessToken: null, refreshToken: null } | |
| } | |
| } | |
| return { accessToken: null, refreshToken: null } | |
| } | |
| // Helper to update stored tokens | |
| const updateStoredTokens = (accessToken: string, refreshToken: string) => { | |
| const stored = localStorage.getItem('auth-storage') | |
| if (stored) { | |
| try { | |
| const data = JSON.parse(stored) | |
| data.state.token = accessToken | |
| data.state.refreshToken = refreshToken | |
| localStorage.setItem('auth-storage', JSON.stringify(data)) | |
| } catch { | |
| // Ignore parse errors | |
| } | |
| } | |
| } | |
| // Add auth token to requests | |
| api.interceptors.request.use((config) => { | |
| const { accessToken } = getStoredTokens() | |
| if (accessToken) { | |
| config.headers.Authorization = `Bearer ${accessToken}` | |
| } | |
| return config | |
| }) | |
| // Handle auth errors with automatic refresh | |
| api.interceptors.response.use( | |
| (response) => response, | |
| async (error: AxiosError) => { | |
| const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean } | |
| // If 401 and we haven't retried yet | |
| if (error.response?.status === 401 && !originalRequest._retry) { | |
| // Don't try to refresh if this was the refresh endpoint itself | |
| if (originalRequest.url === '/api/auth/refresh') { | |
| localStorage.removeItem('auth-storage') | |
| window.location.href = '/login' | |
| return Promise.reject(error) | |
| } | |
| if (isRefreshing) { | |
| // If already refreshing, queue this request | |
| return new Promise((resolve, reject) => { | |
| failedQueue.push({ resolve, reject }) | |
| }) | |
| .then((token) => { | |
| originalRequest.headers.Authorization = `Bearer ${token}` | |
| return api(originalRequest) | |
| }) | |
| .catch((err) => Promise.reject(err)) | |
| } | |
| originalRequest._retry = true | |
| isRefreshing = true | |
| const { refreshToken } = getStoredTokens() | |
| if (!refreshToken) { | |
| localStorage.removeItem('auth-storage') | |
| window.location.href = '/login' | |
| return Promise.reject(error) | |
| } | |
| try { | |
| // Call refresh endpoint | |
| const response = await axios.post('/api/auth/refresh', { | |
| refresh_token: refreshToken, | |
| }) | |
| const { access_token, refresh_token: newRefreshToken } = response.data | |
| // Update stored tokens | |
| updateStoredTokens(access_token, newRefreshToken) | |
| // Update auth header for retry | |
| originalRequest.headers.Authorization = `Bearer ${access_token}` | |
| // Process queued requests | |
| processQueue(null, access_token) | |
| // Retry original request | |
| return api(originalRequest) | |
| } catch (refreshError) { | |
| // Refresh failed - logout | |
| processQueue(refreshError as Error, null) | |
| localStorage.removeItem('auth-storage') | |
| window.location.href = '/login' | |
| return Promise.reject(refreshError) | |
| } finally { | |
| isRefreshing = false | |
| } | |
| } | |
| return Promise.reject(error) | |
| } | |
| ) | |
| export default api | |