diff --git a/packages/axios/src/index.ts b/packages/axios/src/index.ts index 4d52024c..847856dd 100644 --- a/packages/axios/src/index.ts +++ b/packages/axios/src/index.ts @@ -105,13 +105,13 @@ function createCommonRequest( * @param axiosConfig axios config * @param options request options */ -export function createRequest( +export function createRequest>( axiosConfig?: CreateAxiosDefaults, options?: Partial> ) { const { instance, opts, cancelRequest, cancelAllRequest } = createCommonRequest(axiosConfig, options); - const request: RequestInstance = async function request( + const request: RequestInstance = async function request( config: CustomAxiosRequestConfig ) { const response: AxiosResponse = await instance(config); @@ -123,7 +123,7 @@ export function createRequest( } return response.data as MappedType; - } as RequestInstance; + } as RequestInstance; request.cancelRequest = cancelRequest; request.cancelAllRequest = cancelAllRequest; @@ -139,13 +139,13 @@ export function createRequest( * @param axiosConfig axios config * @param options request options */ -export function createFlatRequest( +export function createFlatRequest>( axiosConfig?: CreateAxiosDefaults, options?: Partial> ) { const { instance, opts, cancelRequest, cancelAllRequest } = createCommonRequest(axiosConfig, options); - const flatRequest: FlatRequestInstance = async function flatRequest( + const flatRequest: FlatRequestInstance = async function flatRequest( config: CustomAxiosRequestConfig ) { try { @@ -163,10 +163,11 @@ export function createFlatRequest( } catch (error) { return { data: null, error }; } - } as FlatRequestInstance; + } as FlatRequestInstance; flatRequest.cancelRequest = cancelRequest; flatRequest.cancelAllRequest = cancelAllRequest; + flatRequest.state = {} as State; return flatRequest; } diff --git a/packages/axios/src/type.ts b/packages/axios/src/type.ts index 35b30956..8a4461c2 100644 --- a/packages/axios/src/type.ts +++ b/packages/axios/src/type.ts @@ -34,7 +34,7 @@ export interface RequestOption { onBackendFail: ( response: AxiosResponse, instance: AxiosInstance - ) => Promise | Promise; + ) => Promise | Promise; /** * transform backend response when the responseType is json * @@ -68,11 +68,16 @@ export type CustomAxiosRequestConfig = Omit(config: CustomAxiosRequestConfig): Promise>; +export interface RequestInstanceCommon { cancelRequest: (requestId: string) => void; cancelAllRequest: () => void; + /** you can set custom state in the request instance */ + state: T; +} + +/** The request instance */ +export interface RequestInstance> extends RequestInstanceCommon { + (config: CustomAxiosRequestConfig): Promise>; } export type FlatResponseSuccessData = { @@ -87,10 +92,8 @@ export type FlatResponseFailData = { export type FlatResponseData = FlatResponseSuccessData | FlatResponseFailData; -export interface FlatRequestInstance { +export interface FlatRequestInstance> extends RequestInstanceCommon { ( config: CustomAxiosRequestConfig ): Promise>>; - cancelRequest: (requestId: string) => void; - cancelAllRequest: () => void; } diff --git a/src/service/api/auth.ts b/src/service/api/auth.ts index 5e57fb4f..1ffcf2bf 100644 --- a/src/service/api/auth.ts +++ b/src/service/api/auth.ts @@ -37,13 +37,12 @@ export function fetchRefreshToken(refreshToken: string) { }); } -export function fetchDebug() { - return request({ - url: '/debug-post', - method: 'post', - headers: { 'content-type': 'application/x-www-form-urlencoded' }, - data: { - a: '1' - } - }); +/** + * return custom backend error + * + * @param code error code + * @param msg error message + */ +export function fetchCustomBackendError(code: string, msg: string) { + return request({ url: '/auth/error', params: { code, msg } }); } diff --git a/src/service/request/index.ts b/src/service/request/index.ts index f4b384a9..82752fbc 100644 --- a/src/service/request/index.ts +++ b/src/service/request/index.ts @@ -1,11 +1,19 @@ +import type { AxiosResponse } from 'axios'; import { BACKEND_ERROR_CODE, createFlatRequest, createRequest } from '@sa/axios'; +import { useAuthStore } from '@/store/modules/auth'; import { localStg } from '@/utils/storage'; import { getServiceBaseURL } from '@/utils/service'; +import { handleRefreshToken } from './shared'; const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y'; const { baseURL, otherBaseURL } = getServiceBaseURL(import.meta.env, isHttpProxy); -export const request = createFlatRequest( +interface InstanceState { + /** whether the request is refreshing token */ + isRefreshingToken: boolean; +} + +export const request = createFlatRequest( { baseURL, headers: { @@ -28,9 +36,36 @@ export const request = createFlatRequest( // you can change this logic by yourself return response.data.code === '0000'; }, - async onBackendFail(_response) { + async onBackendFail(response, instance) { // when the backend response code is not "0000", it means the request is fail // for example: the token is expired, refresh token and retry request + + const authStore = useAuthStore(); + + // when the backend response code is "8888", it means logout the system and redirect to login page + // the following code is an example, you can change it by yourself + const logoutCodes: (string | number)[] = ['8888']; + if (logoutCodes.includes(response.data.code)) { + authStore.resetStore(); + return null; + } + + // when the backend response code is "9999", it means the token is expired, and refresh token + // the api `refreshToken` can not return the code "9999", otherwise it will be a dead loop, can return `logoutCodes` + const refreshTokenCodes: (string | number)[] = ['9999']; + if (refreshTokenCodes.includes(response.data.code) && !request.state.isRefreshingToken) { + request.state.isRefreshingToken = true; + + const refreshConfig = await handleRefreshToken(response.config); + + request.state.isRefreshingToken = false; + + if (refreshConfig) { + return instance.request(refreshConfig) as Promise; + } + } + + return null; }, transformBackendResponse(response) { return response.data.data; diff --git a/src/service/request/shared.ts b/src/service/request/shared.ts new file mode 100644 index 00000000..151a8526 --- /dev/null +++ b/src/service/request/shared.ts @@ -0,0 +1,31 @@ +import type { AxiosRequestConfig } from 'axios'; +import { useAuthStore } from '@/store/modules/auth'; +import { localStg } from '@/utils/storage'; +import { fetchRefreshToken } from '../api'; + +/** + * refresh token + * + * @param axiosConfig - request config when the token is expired + */ +export async function handleRefreshToken(axiosConfig: AxiosRequestConfig) { + const { resetStore } = useAuthStore(); + + const refreshToken = localStg.get('refreshToken') || ''; + const { error, data } = await fetchRefreshToken(refreshToken); + if (!error) { + localStg.set('token', data.token); + localStg.set('refreshToken', data.refreshToken); + + const config = { ...axiosConfig }; + if (config.headers) { + config.headers.Authorization = data.token; + } + + return config; + } + + resetStore(); + + return null; +}