Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(core): fix refresh token when meet multi requests #31

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ VITE_SERVICE_LOGOUT_CODES=8888,8889
VITE_SERVICE_MODAL_LOGOUT_CODES=7777,7778

# token expired codes of backend service, when the code is received, it will refresh the token and resend the request
VITE_SERVICE_EXPIRED_TOKEN_CODES=9999,9998
VITE_SERVICE_EXPIRED_TOKEN_CODES=9999,9998,3333

# when the route mode is static, the defined super role
VITE_STATIC_SUPER_ROLE=R_SUPER
Expand Down
24 changes: 9 additions & 15 deletions src/service/request/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { getServiceBaseURL } from '@/utils/service'
import { localStg } from '@/utils/storage'
import { BACKEND_ERROR_CODE, createFlatRequest, createRequest } from '@sa/axios'
import type { AxiosResponse } from 'axios'
import { handleRefreshToken, showErrorMsg } from './shared'
import { getAuthorization, handleExpiredRequest, showErrorMsg } from './shared'
import type { RequestInstanceState } from './type'

const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y'
Expand All @@ -19,12 +19,8 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt
},
{
async onRequest(config) {
const { headers } = config

// set token
const token = localStg.get('token')
const Authorization = token ? `Bearer ${token}` : null
Object.assign(headers, { Authorization })
const Authorization = getAuthorization()
Object.assign(config.headers, { Authorization })

return config
},
Expand Down Expand Up @@ -83,15 +79,13 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt
// when the backend response code is in `expiredTokenCodes`, it means the token is expired, and refresh token
// the api `refreshToken` can not return error code in `expiredTokenCodes`, otherwise it will be a dead loop, should return `logoutCodes` or `modalLogoutCodes`
const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || []
if (expiredTokenCodes.includes(responseCode) && !request.state.isRefreshingToken) {
request.state.isRefreshingToken = true

const refreshConfig = await handleRefreshToken(response.config)

request.state.isRefreshingToken = false
if (expiredTokenCodes.includes(responseCode)) {
const success = await handleExpiredRequest(request.state)
if (success) {
const Authorization = getAuthorization()
Object.assign(response.config.headers, { Authorization })

if (refreshConfig) {
return instance.request(refreshConfig) as Promise<AxiosResponse>
return instance.request(response.config) as Promise<AxiosResponse>
}
}

Expand Down
42 changes: 26 additions & 16 deletions src/service/request/shared.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,44 @@
import { useAuthStore } from '@/store/modules/auth'
import { localStg } from '@/utils/storage'
import type { AxiosRequestConfig } from 'axios'
import { fetchRefreshToken } from '../api'
import type { RequestInstanceState } from './type'

/**
* refresh token
*
* @param axiosConfig - request config when the token is expired
*/
export async function handleRefreshToken(axiosConfig: AxiosRequestConfig) {
export function getAuthorization() {
const token = localStg.get('token')
const Authorization = token ? `Bearer ${token}` : null

return Authorization
}

/** refresh token */
export async function handleRefreshToken() {
const { resetStore } = useAuthStore()

const refreshToken = localStg.get('refreshToken') || ''
const { error, data } = await fetchRefreshToken(refreshToken)
const rToken = localStg.get('refreshToken') || ''
const { error, data } = await fetchRefreshToken(rToken)
if (!error) {
localStg.set('token', data.token)
localStg.set('refreshToken', data.refreshToken)
return true
}

const config = { ...axiosConfig }
if (config.headers) {
config.headers.Authorization = data.token
}
resetStore()

return config
return false
}

export async function handleExpiredRequest(state: RequestInstanceState) {
if (!state.refreshTokenFn) {
state.refreshTokenFn = handleRefreshToken()
}

resetStore()
const success = await state.refreshTokenFn

setTimeout(() => {
state.refreshTokenFn = null
}, 1000)

return null
return success
}

export function showErrorMsg(state: RequestInstanceState, message: string) {
Expand Down
2 changes: 1 addition & 1 deletion src/service/request/type.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export interface RequestInstanceState {
/** whether the request is refreshing token */
isRefreshingToken: boolean
refreshTokenFn: Promise<boolean> | null
/** the request error message stack */
errMsgStack: string[]
}
Loading