Spaces:
Running
Running
| import axios from 'axios' | |
| export const AUTH_UNAUTHORIZED_EVENT = 'gateprep:auth-unauthorized' | |
| const rawApiUrl = import.meta.env.VITE_API_URL || 'http://127.0.0.1:8000' | |
| const BASE_URL = import.meta.env.PROD | |
| ? '/api' | |
| : rawApiUrl.split(',')[0].trim() | |
| const api = axios.create({ | |
| baseURL: BASE_URL, | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| 'X-Requested-With': 'XMLHttpRequest', | |
| }, | |
| timeout: 30000, | |
| withCredentials: true, | |
| }) | |
| api.interceptors.response.use( | |
| r => r, | |
| err => { | |
| if (err.response?.status === 401) { | |
| const isAuthRoute = err.config?.url?.includes('/auth/') | |
| if (!isAuthRoute) { | |
| window.dispatchEvent(new CustomEvent(AUTH_UNAUTHORIZED_EVENT)) | |
| } | |
| } | |
| return Promise.reject(err) | |
| } | |
| ) | |
| // ββ Silent token refresh βββββββββββββββββββββββββββββββββββββββββ | |
| // Refreshes the httpOnly auth cookie before it expires. | |
| // Token lifetime is 3 hours; refresh fires every 2.5 hours. | |
| const REFRESH_INTERVAL_MS = 2.5 * 60 * 60 * 1000 // 2.5 hours | |
| let refreshTimer = null | |
| export function startTokenRefresh({ refreshNow = false } = {}) { | |
| stopTokenRefresh() | |
| if (refreshNow) { | |
| // Reset the clock for existing sessions restored from /auth/me. | |
| api.post('/auth/refresh').catch(() => {}) | |
| } | |
| refreshTimer = setInterval(async () => { | |
| try { | |
| await api.post('/auth/refresh') | |
| } catch { | |
| // 401 will be caught by the existing interceptor | |
| } | |
| }, REFRESH_INTERVAL_MS) | |
| } | |
| export function stopTokenRefresh() { | |
| if (refreshTimer) { | |
| clearInterval(refreshTimer) | |
| refreshTimer = null | |
| } | |
| } | |
| export default api | |