refactor: Address technical debt in auth refresh implementation

This commit is contained in:
Yunxiao Xu
2026-02-18 14:36:10 -08:00
parent 341bd08176
commit 6131f27142
4 changed files with 49 additions and 25 deletions

View File

@@ -1,4 +1,4 @@
import { useEffect, useCallback } from 'react'
import { useEffect, useCallback, useRef } from 'react'
import { AuthService } from '@/services/auth'
/**
@@ -6,28 +6,42 @@ import { AuthService } from '@/services/auth'
* It proactively refreshes the session to prevent expiration while the user is active.
*/
export function useSilentRefresh(isAuthenticated: boolean) {
const timerRef = useRef<NodeJS.Timeout | null>(null)
const refresh = useCallback(async () => {
try {
console.log('Proactively refreshing session...')
console.debug('[Auth] Proactively refreshing session...')
await AuthService.refreshSession()
console.debug('[Auth] Silent refresh successful.')
} catch (error) {
console.error('Silent refresh failed:', error)
// If refresh fails, we don't necessarily logout here,
// as the reactive interceptor will handle 401s if the session is truly dead.
console.warn('[Auth] Silent refresh failed:', error)
// We don't force logout here; the reactive interceptor in api.ts
// will handle it if a subsequent data request returns a 401.
}
}, [])
useEffect(() => {
if (!isAuthenticated) return
if (!isAuthenticated) {
if (timerRef.current) {
clearInterval(timerRef.current)
timerRef.current = null
}
return
}
// Refresh every 25 minutes (access token defaults to 30 mins)
const REFRESH_INTERVAL = 25 * 60 * 1000
const intervalId = setInterval(refresh, REFRESH_INTERVAL)
// Clear existing timer if any
if (timerRef.current) clearInterval(timerRef.current)
timerRef.current = setInterval(refresh, REFRESH_INTERVAL)
// Also refresh immediately on mount/auth if we want to ensure we have a fresh start
// refresh()
return () => clearInterval(intervalId)
return () => {
if (timerRef.current) {
clearInterval(timerRef.current)
timerRef.current = null
}
}
}, [isAuthenticated, refresh])
}

View File

@@ -69,22 +69,27 @@ api.interceptors.response.use(
console.error("Reactive refresh failed:", refreshError)
// Final failure - session is dead
if (onUnauthorized) {
onUnauthorized()
}
handleUnauthorized()
return Promise.reject(refreshError)
}
}
// If it's the refresh endpoint itself failing with 401, trigger logout
if (error.response?.status === 401 && isRefreshEndpoint) {
if (onUnauthorized) {
onUnauthorized()
}
// If it's a 401 on an endpoint we don't/can't refresh (like refresh itself or login)
if (error.response?.status === 401) {
handleUnauthorized()
}
return Promise.reject(error)
}
)
/**
* Shared helper to trigger logout/unauthorized cleanup.
*/
function handleUnauthorized() {
if (onUnauthorized) {
onUnauthorized()
}
}
export default api