change snapshot commit to new format

This commit is contained in:
taDuc
2026-05-08 01:44:17 +07:00
parent 8b1df73797
commit ce4bc4f2a5
14 changed files with 988 additions and 256 deletions
+79 -11
View File
@@ -1,10 +1,18 @@
import axios from "axios"
import { API_URL_ROOT } from "../../api"
import {
clearStoredTokens,
extractTokensFromResponsePayload,
getAccessToken,
getRefreshToken,
setStoredTokens,
} from "@/auth/tokenStore"
const baseURL = API_URL_ROOT || "https://history-api.kain.id.vn"
const api = axios.create({
baseURL,
// Support both cookie-based auth (httpOnly) and Bearer JWT.
withCredentials: true
})
@@ -19,12 +27,36 @@ const processQueue = (error?: any) => {
queue = []
}
api.interceptors.request.use((config) => {
const token = getAccessToken()
if (token) {
const headers: any = config.headers || {}
// Do not override if caller set Authorization explicitly (case-insensitive).
const already =
typeof headers.get === "function"
? headers.get("Authorization")
: headers.Authorization || headers.authorization
if (!already) {
if (typeof headers.set === "function") headers.set("Authorization", `Bearer ${token}`)
else headers.Authorization = `Bearer ${token}`
}
config.headers = headers
}
return config
})
api.interceptors.response.use(
(res) => res,
(res) => {
// Opportunistically persist tokens from signin/refresh responses.
const tokens = extractTokensFromResponsePayload(res?.data)
if (tokens) setStoredTokens(tokens)
return res
},
async (err) => {
const originalRequest = err.config
if (err.response?.status === 401 && !originalRequest._retry) {
const url = String(originalRequest?.url || "")
if (err.response?.status === 401 && !originalRequest._retry && !url.includes("/auth/")) {
if (isRefreshing) {
return new Promise((resolve, reject) => {
queue.push({
@@ -38,19 +70,55 @@ api.interceptors.response.use(
isRefreshing = true
try {
await axios.post(
`${baseURL}/auth/refresh`,
{},
{ withCredentials: true }
)
const refreshToken = getRefreshToken()
const tryHeaderRefresh = async () => {
if (!refreshToken) return null
return axios.post(
`${baseURL}/auth/refresh`,
{},
{ headers: { Authorization: `Bearer ${refreshToken}` } }
)
}
const tryCookieRefresh = async () => {
return axios.post(`${baseURL}/auth/refresh`, {}, { withCredentials: true })
}
let refreshRes: any = null
try {
refreshRes = (await tryHeaderRefresh()) || (await tryCookieRefresh())
} catch (e: any) {
// If header-based refresh fails (wrong token type), fall back to cookie refresh.
if (refreshToken && e?.response?.status === 401) {
refreshRes = await tryCookieRefresh()
} else {
throw e
}
}
const nextTokens = extractTokensFromResponsePayload(refreshRes?.data)
if (nextTokens) setStoredTokens(nextTokens)
// Some backends may return only a new access token; keep refresh token.
else {
const maybeAccess = (refreshRes?.data?.data?.access_token ??
refreshRes?.data?.access_token) as unknown
if (typeof maybeAccess === "string" && maybeAccess.trim()) {
// Keep refresh token if we have one; otherwise rely on cookies.
if (refreshToken) setStoredTokens({ access_token: maybeAccess, refresh_token: refreshToken })
}
}
processQueue()
return api(originalRequest)
} catch (refreshErr) {
} catch (refreshErr: any) {
processQueue(refreshErr)
window.location.href = "/signin"
// Only force logout when refresh token/session is truly invalid (401).
if (refreshErr?.response?.status === 401) {
clearStoredTokens()
window.location.href = "/signin"
}
return Promise.reject(refreshErr)
} finally {
@@ -62,4 +130,4 @@ api.interceptors.response.use(
}
)
export default api
export default api