Spaces:
Runtime error
Runtime error
File size: 4,030 Bytes
3674b4b | 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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | 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
|