feat(projects): add request refresh token & logout
This commit is contained in:
parent
1ed33dc47a
commit
11a6a3bd80
@ -105,13 +105,13 @@ function createCommonRequest<ResponseData = any>(
|
||||
* @param axiosConfig axios config
|
||||
* @param options request options
|
||||
*/
|
||||
export function createRequest<ResponseData = any>(
|
||||
export function createRequest<ResponseData = any, State = Record<string, unknown>>(
|
||||
axiosConfig?: CreateAxiosDefaults,
|
||||
options?: Partial<RequestOption<ResponseData>>
|
||||
) {
|
||||
const { instance, opts, cancelRequest, cancelAllRequest } = createCommonRequest<ResponseData>(axiosConfig, options);
|
||||
|
||||
const request: RequestInstance = async function request<T = any, R extends ResponseType = 'json'>(
|
||||
const request: RequestInstance<State> = async function request<T = any, R extends ResponseType = 'json'>(
|
||||
config: CustomAxiosRequestConfig
|
||||
) {
|
||||
const response: AxiosResponse<ResponseData> = await instance(config);
|
||||
@ -123,7 +123,7 @@ export function createRequest<ResponseData = any>(
|
||||
}
|
||||
|
||||
return response.data as MappedType<R, T>;
|
||||
} as RequestInstance;
|
||||
} as RequestInstance<State>;
|
||||
|
||||
request.cancelRequest = cancelRequest;
|
||||
request.cancelAllRequest = cancelAllRequest;
|
||||
@ -139,13 +139,13 @@ export function createRequest<ResponseData = any>(
|
||||
* @param axiosConfig axios config
|
||||
* @param options request options
|
||||
*/
|
||||
export function createFlatRequest<ResponseData = any>(
|
||||
export function createFlatRequest<ResponseData = any, State = Record<string, unknown>>(
|
||||
axiosConfig?: CreateAxiosDefaults,
|
||||
options?: Partial<RequestOption<ResponseData>>
|
||||
) {
|
||||
const { instance, opts, cancelRequest, cancelAllRequest } = createCommonRequest<ResponseData>(axiosConfig, options);
|
||||
|
||||
const flatRequest: FlatRequestInstance = async function flatRequest<T = any, R extends ResponseType = 'json'>(
|
||||
const flatRequest: FlatRequestInstance<State> = async function flatRequest<T = any, R extends ResponseType = 'json'>(
|
||||
config: CustomAxiosRequestConfig
|
||||
) {
|
||||
try {
|
||||
@ -163,10 +163,11 @@ export function createFlatRequest<ResponseData = any>(
|
||||
} catch (error) {
|
||||
return { data: null, error };
|
||||
}
|
||||
} as FlatRequestInstance;
|
||||
} as FlatRequestInstance<State>;
|
||||
|
||||
flatRequest.cancelRequest = cancelRequest;
|
||||
flatRequest.cancelAllRequest = cancelAllRequest;
|
||||
flatRequest.state = {} as State;
|
||||
|
||||
return flatRequest;
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ export interface RequestOption<ResponseData = any> {
|
||||
onBackendFail: (
|
||||
response: AxiosResponse<ResponseData>,
|
||||
instance: AxiosInstance
|
||||
) => Promise<AxiosResponse> | Promise<void>;
|
||||
) => Promise<AxiosResponse | null> | Promise<void>;
|
||||
/**
|
||||
* transform backend response when the responseType is json
|
||||
*
|
||||
@ -68,11 +68,16 @@ export type CustomAxiosRequestConfig<R extends ResponseType = 'json'> = Omit<Axi
|
||||
responseType?: R;
|
||||
};
|
||||
|
||||
/** The request instance */
|
||||
export interface RequestInstance {
|
||||
<T = any, R extends ResponseType = 'json'>(config: CustomAxiosRequestConfig<R>): Promise<MappedType<R, T>>;
|
||||
export interface RequestInstanceCommon<T> {
|
||||
cancelRequest: (requestId: string) => void;
|
||||
cancelAllRequest: () => void;
|
||||
/** you can set custom state in the request instance */
|
||||
state: T;
|
||||
}
|
||||
|
||||
/** The request instance */
|
||||
export interface RequestInstance<S = Record<string, unknown>> extends RequestInstanceCommon<S> {
|
||||
<T = any, R extends ResponseType = 'json'>(config: CustomAxiosRequestConfig<R>): Promise<MappedType<R, T>>;
|
||||
}
|
||||
|
||||
export type FlatResponseSuccessData<T = any> = {
|
||||
@ -87,10 +92,8 @@ export type FlatResponseFailData<T = any> = {
|
||||
|
||||
export type FlatResponseData<T = any> = FlatResponseSuccessData<T> | FlatResponseFailData<T>;
|
||||
|
||||
export interface FlatRequestInstance {
|
||||
export interface FlatRequestInstance<S = Record<string, unknown>> extends RequestInstanceCommon<S> {
|
||||
<T = any, R extends ResponseType = 'json'>(
|
||||
config: CustomAxiosRequestConfig<R>
|
||||
): Promise<FlatResponseData<MappedType<R, T>>>;
|
||||
cancelRequest: (requestId: string) => void;
|
||||
cancelAllRequest: () => void;
|
||||
}
|
||||
|
@ -37,13 +37,12 @@ export function fetchRefreshToken(refreshToken: string) {
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchDebug() {
|
||||
return request<string>({
|
||||
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 } });
|
||||
}
|
||||
|
@ -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<App.Service.Response>(
|
||||
interface InstanceState {
|
||||
/** whether the request is refreshing token */
|
||||
isRefreshingToken: boolean;
|
||||
}
|
||||
|
||||
export const request = createFlatRequest<App.Service.Response, InstanceState>(
|
||||
{
|
||||
baseURL,
|
||||
headers: {
|
||||
@ -28,9 +36,36 @@ export const request = createFlatRequest<App.Service.Response>(
|
||||
// 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<AxiosResponse>;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
transformBackendResponse(response) {
|
||||
return response.data.data;
|
||||
|
31
src/service/request/shared.ts
Normal file
31
src/service/request/shared.ts
Normal file
@ -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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user