File size: 1,675 Bytes
29bcc71
 
a17d02d
 
8887fa5
4b8da13
 
a4301b6
29bcc71
 
 
04ba4b7
 
 
 
29bcc71
1878f24
29bcc71
 
 
 
 
 
 
 
a17d02d
29bcc71
 
 
 
 
 
04ba4b7
 
 
 
 
 
78865a4
04ba4b7
78865a4
 
 
 
04ba4b7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29bcc71
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
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