63 lines
1.7 KiB
TypeScript
63 lines
1.7 KiB
TypeScript
import axios from 'axios'
|
|
import { useAuthStore } from '../store/auth'
|
|
|
|
export const api = axios.create({
|
|
baseURL: '/',
|
|
paramsSerializer: (params) => {
|
|
const sp = new URLSearchParams()
|
|
for (const [key, val] of Object.entries(params)) {
|
|
if (val === undefined || val === null) continue
|
|
if (Array.isArray(val)) {
|
|
val.forEach((v) => sp.append(key, String(v)))
|
|
} else {
|
|
sp.append(key, String(val))
|
|
}
|
|
}
|
|
return sp.toString()
|
|
},
|
|
})
|
|
|
|
// Inject access token on every request
|
|
api.interceptors.request.use((config) => {
|
|
const token = useAuthStore.getState().accessToken
|
|
if (token) config.headers.Authorization = `Bearer ${token}`
|
|
return config
|
|
})
|
|
|
|
// On 401 — try refresh once, then logout
|
|
let refreshing: Promise<string> | null = null
|
|
|
|
api.interceptors.response.use(
|
|
(r) => r,
|
|
async (error) => {
|
|
const original = error.config
|
|
if (error.response?.status !== 401 || original._retried) {
|
|
return Promise.reject(error)
|
|
}
|
|
original._retried = true
|
|
|
|
const { refreshToken, setTokens, clearTokens, username } = useAuthStore.getState()
|
|
if (!refreshToken) {
|
|
clearTokens()
|
|
return Promise.reject(error)
|
|
}
|
|
|
|
if (!refreshing) {
|
|
refreshing = axios
|
|
.post('/api/v1/auth/refresh', { refresh_token: refreshToken })
|
|
.then((r) => {
|
|
setTokens(r.data.access_token, r.data.refresh_token, username ?? '')
|
|
return r.data.access_token
|
|
})
|
|
.catch(() => {
|
|
clearTokens()
|
|
throw new Error('Session expired')
|
|
})
|
|
.finally(() => { refreshing = null })
|
|
}
|
|
|
|
const newToken = await refreshing
|
|
original.headers.Authorization = `Bearer ${newToken}`
|
|
return api(original)
|
|
}
|
|
)
|