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 axiosConfig axios config
|
||||||
* @param options request options
|
* @param options request options
|
||||||
*/
|
*/
|
||||||
export function createRequest<ResponseData = any>(
|
export function createRequest<ResponseData = any, State = Record<string, unknown>>(
|
||||||
axiosConfig?: CreateAxiosDefaults,
|
axiosConfig?: CreateAxiosDefaults,
|
||||||
options?: Partial<RequestOption<ResponseData>>
|
options?: Partial<RequestOption<ResponseData>>
|
||||||
) {
|
) {
|
||||||
const { instance, opts, cancelRequest, cancelAllRequest } = createCommonRequest<ResponseData>(axiosConfig, options);
|
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
|
config: CustomAxiosRequestConfig
|
||||||
) {
|
) {
|
||||||
const response: AxiosResponse<ResponseData> = await instance(config);
|
const response: AxiosResponse<ResponseData> = await instance(config);
|
||||||
@ -123,7 +123,7 @@ export function createRequest<ResponseData = any>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return response.data as MappedType<R, T>;
|
return response.data as MappedType<R, T>;
|
||||||
} as RequestInstance;
|
} as RequestInstance<State>;
|
||||||
|
|
||||||
request.cancelRequest = cancelRequest;
|
request.cancelRequest = cancelRequest;
|
||||||
request.cancelAllRequest = cancelAllRequest;
|
request.cancelAllRequest = cancelAllRequest;
|
||||||
@ -139,13 +139,13 @@ export function createRequest<ResponseData = any>(
|
|||||||
* @param axiosConfig axios config
|
* @param axiosConfig axios config
|
||||||
* @param options request options
|
* @param options request options
|
||||||
*/
|
*/
|
||||||
export function createFlatRequest<ResponseData = any>(
|
export function createFlatRequest<ResponseData = any, State = Record<string, unknown>>(
|
||||||
axiosConfig?: CreateAxiosDefaults,
|
axiosConfig?: CreateAxiosDefaults,
|
||||||
options?: Partial<RequestOption<ResponseData>>
|
options?: Partial<RequestOption<ResponseData>>
|
||||||
) {
|
) {
|
||||||
const { instance, opts, cancelRequest, cancelAllRequest } = createCommonRequest<ResponseData>(axiosConfig, options);
|
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
|
config: CustomAxiosRequestConfig
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
@ -163,10 +163,11 @@ export function createFlatRequest<ResponseData = any>(
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { data: null, error };
|
return { data: null, error };
|
||||||
}
|
}
|
||||||
} as FlatRequestInstance;
|
} as FlatRequestInstance<State>;
|
||||||
|
|
||||||
flatRequest.cancelRequest = cancelRequest;
|
flatRequest.cancelRequest = cancelRequest;
|
||||||
flatRequest.cancelAllRequest = cancelAllRequest;
|
flatRequest.cancelAllRequest = cancelAllRequest;
|
||||||
|
flatRequest.state = {} as State;
|
||||||
|
|
||||||
return flatRequest;
|
return flatRequest;
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ export interface RequestOption<ResponseData = any> {
|
|||||||
onBackendFail: (
|
onBackendFail: (
|
||||||
response: AxiosResponse<ResponseData>,
|
response: AxiosResponse<ResponseData>,
|
||||||
instance: AxiosInstance
|
instance: AxiosInstance
|
||||||
) => Promise<AxiosResponse> | Promise<void>;
|
) => Promise<AxiosResponse | null> | Promise<void>;
|
||||||
/**
|
/**
|
||||||
* transform backend response when the responseType is json
|
* transform backend response when the responseType is json
|
||||||
*
|
*
|
||||||
@ -68,11 +68,16 @@ export type CustomAxiosRequestConfig<R extends ResponseType = 'json'> = Omit<Axi
|
|||||||
responseType?: R;
|
responseType?: R;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** The request instance */
|
export interface RequestInstanceCommon<T> {
|
||||||
export interface RequestInstance {
|
|
||||||
<T = any, R extends ResponseType = 'json'>(config: CustomAxiosRequestConfig<R>): Promise<MappedType<R, T>>;
|
|
||||||
cancelRequest: (requestId: string) => void;
|
cancelRequest: (requestId: string) => void;
|
||||||
cancelAllRequest: () => 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> = {
|
export type FlatResponseSuccessData<T = any> = {
|
||||||
@ -87,10 +92,8 @@ export type FlatResponseFailData<T = any> = {
|
|||||||
|
|
||||||
export type FlatResponseData<T = any> = FlatResponseSuccessData<T> | FlatResponseFailData<T>;
|
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'>(
|
<T = any, R extends ResponseType = 'json'>(
|
||||||
config: CustomAxiosRequestConfig<R>
|
config: CustomAxiosRequestConfig<R>
|
||||||
): Promise<FlatResponseData<MappedType<R, T>>>;
|
): 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>({
|
* return custom backend error
|
||||||
url: '/debug-post',
|
*
|
||||||
method: 'post',
|
* @param code error code
|
||||||
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
* @param msg error message
|
||||||
data: {
|
*/
|
||||||
a: '1'
|
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 { BACKEND_ERROR_CODE, createFlatRequest, createRequest } from '@sa/axios';
|
||||||
|
import { useAuthStore } from '@/store/modules/auth';
|
||||||
import { localStg } from '@/utils/storage';
|
import { localStg } from '@/utils/storage';
|
||||||
import { getServiceBaseURL } from '@/utils/service';
|
import { getServiceBaseURL } from '@/utils/service';
|
||||||
|
import { handleRefreshToken } from './shared';
|
||||||
|
|
||||||
const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
|
const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
|
||||||
const { baseURL, otherBaseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
|
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,
|
baseURL,
|
||||||
headers: {
|
headers: {
|
||||||
@ -28,9 +36,36 @@ export const request = createFlatRequest<App.Service.Response>(
|
|||||||
// you can change this logic by yourself
|
// you can change this logic by yourself
|
||||||
return response.data.code === '0000';
|
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
|
// 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
|
// 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) {
|
transformBackendResponse(response) {
|
||||||
return response.data.data;
|
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