feat:项目界面补充
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import ky, {HTTPError, type Input, type KyInstance, type Options} from 'ky'
|
||||
import ky, {HTTPError, type AfterResponseHook, type Input, type KyInstance, type Options} from 'ky'
|
||||
|
||||
import {notifyError, resolveToastMessage} from '@/features/notifications'
|
||||
import {useUserStore} from '@/store/user.ts'
|
||||
import type {ValidateTokenData} from '@/types/auth.type.ts'
|
||||
import {objectToFormData} from './tool'
|
||||
|
||||
type RequestMethod = 'get' | 'post' | 'put' | 'patch' | 'delete'
|
||||
type ResponseType = 'json' | 'text' | 'blob' | 'arrayBuffer' | 'formData' | 'response'
|
||||
@@ -44,13 +46,97 @@ const isApiEnvelope = (value: unknown): value is ApiEnvelope =>
|
||||
|
||||
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? '/api/'
|
||||
const REQUEST_TIMEOUT = 10_000
|
||||
const AUTH_RETRY_HEADER = 'x-auth-retried'
|
||||
const VERIFY_TOKEN_PATH = '/v1/mall/verifyToken'
|
||||
|
||||
let accessTokenFormatter: TokenFormatter = (token) => `Bearer ${token}`
|
||||
let refreshAuthInfoPromise: Promise<ValidateTokenData> | null = null
|
||||
|
||||
export const setAccessTokenFormatter = (formatter?: TokenFormatter) => {
|
||||
accessTokenFormatter = formatter ?? ((token) => `Bearer ${token}`)
|
||||
}
|
||||
|
||||
const authRefreshClient = ky.create({
|
||||
baseUrl: API_BASE_URL,
|
||||
timeout: REQUEST_TIMEOUT,
|
||||
retry: 0,
|
||||
headers: {
|
||||
lang: 'zh',
|
||||
},
|
||||
})
|
||||
|
||||
const handleUnauthorizedRetry: AfterResponseHook = async ({request, response}) => {
|
||||
if (!shouldRefreshOnUnauthorized(request, response)) {
|
||||
return response
|
||||
}
|
||||
|
||||
try {
|
||||
const authInfo = await refreshAuthInfo()
|
||||
const nextRequest = withRefreshedSessionId(request, authInfo.session_id)
|
||||
return await requestClient(nextRequest)
|
||||
} catch {
|
||||
useUserStore.getState().clearUserInfo()
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshAuthInfo() {
|
||||
if (refreshAuthInfoPromise) {
|
||||
return refreshAuthInfoPromise
|
||||
}
|
||||
|
||||
refreshAuthInfoPromise = (async () => {
|
||||
const token = useUserStore.getState().userInfo?.token?.trim()
|
||||
if (!token) {
|
||||
throw new RequestError('Unauthorized')
|
||||
}
|
||||
|
||||
const response = await authRefreshClient.post('v1/mall/verifyToken', {
|
||||
body: objectToFormData({token}),
|
||||
}).json<{
|
||||
code: number
|
||||
msg?: string
|
||||
data?: ValidateTokenData
|
||||
}>()
|
||||
|
||||
if (!response || (response.code !== 1 && response.code !== 200) || !response.data?.session_id) {
|
||||
throw new RequestError(resolveToastMessage(response, 'Unauthorized'))
|
||||
}
|
||||
|
||||
useUserStore.getState().setAuthInfo(response.data)
|
||||
return response.data
|
||||
})()
|
||||
|
||||
try {
|
||||
return await refreshAuthInfoPromise
|
||||
} finally {
|
||||
refreshAuthInfoPromise = null
|
||||
}
|
||||
}
|
||||
|
||||
function shouldRefreshOnUnauthorized(request: Request, response: Response) {
|
||||
if (response.status !== 401) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (request.headers.get(AUTH_RETRY_HEADER) === '1') {
|
||||
return false
|
||||
}
|
||||
|
||||
return !request.url.includes(VERIFY_TOKEN_PATH)
|
||||
}
|
||||
|
||||
function withRefreshedSessionId(request: Request, sessionId: string) {
|
||||
const nextUrl = new URL(request.url)
|
||||
if (nextUrl.searchParams.has('session_id')) {
|
||||
nextUrl.searchParams.set('session_id', sessionId)
|
||||
}
|
||||
|
||||
const nextRequest = new Request(nextUrl, request.clone())
|
||||
nextRequest.headers.set(AUTH_RETRY_HEADER, '1')
|
||||
return nextRequest
|
||||
}
|
||||
|
||||
const requestClient = ky.create({
|
||||
baseUrl: API_BASE_URL,
|
||||
timeout: REQUEST_TIMEOUT,
|
||||
@@ -69,6 +155,9 @@ const requestClient = ky.create({
|
||||
request.headers.set('Authorization', accessTokenFormatter(token))
|
||||
},
|
||||
],
|
||||
afterResponse: [
|
||||
handleUnauthorizedRetry,
|
||||
],
|
||||
beforeError: [
|
||||
async ({error}) => {
|
||||
if (error instanceof HTTPError) {
|
||||
|
||||
Reference in New Issue
Block a user