Skip to content

Commit

Permalink
feat(projects): synchronize updates to soybean's axios
Browse files Browse the repository at this point in the history
  • Loading branch information
mufeng889 committed Aug 23, 2024
1 parent 0d3cae0 commit 6401f0b
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 35 deletions.
4 changes: 1 addition & 3 deletions packages/axios/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
},
"typesVersions": {
"*": {
"*": [
"./src/*"
]
"*": ["./src/*"]
}
},
"dependencies": {
Expand Down
28 changes: 15 additions & 13 deletions packages/axios/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import axios, { AxiosError } from 'axios';
import type { AxiosResponse, CancelTokenSource, CreateAxiosDefaults, InternalAxiosRequestConfig } from 'axios';
import type { AxiosResponse, CreateAxiosDefaults, InternalAxiosRequestConfig } from 'axios';
import axiosRetry from 'axios-retry';
import { nanoid } from '@sa/utils';
import { createAxiosConfig, createDefaultOptions, createRetryOptions } from './options';
Expand All @@ -22,7 +22,7 @@ function createCommonRequest<ResponseData = any>(
const axiosConf = createAxiosConfig(axiosConfig);
const instance = axios.create(axiosConf);

const cancelTokenSourceMap = new Map<string, CancelTokenSource>();
const abortControllerMap = new Map<string, AbortController>();

// config axios retry
const retryOptions = createRetryOptions(axiosConf);
Expand All @@ -35,10 +35,12 @@ function createCommonRequest<ResponseData = any>(
const requestId = nanoid();
config.headers.set(REQUEST_ID_KEY, requestId);

// config cancel token
const cancelTokenSource = axios.CancelToken.source();
config.cancelToken = cancelTokenSource.token;
cancelTokenSourceMap.set(requestId, cancelTokenSource);
// config abort controller
if (!config.signal) {
const abortController = new AbortController();
config.signal = abortController.signal;
abortControllerMap.set(requestId, abortController);
}

// handle config by hook
const handledConfig = opts.onRequest?.(config) || config;
Expand Down Expand Up @@ -79,18 +81,18 @@ function createCommonRequest<ResponseData = any>(
);

function cancelRequest(requestId: string) {
const cancelTokenSource = cancelTokenSourceMap.get(requestId);
if (cancelTokenSource) {
cancelTokenSource.cancel();
cancelTokenSourceMap.delete(requestId);
const abortController = abortControllerMap.get(requestId);
if (abortController) {
abortController.abort();
abortControllerMap.delete(requestId);
}
}

function cancelAllRequest() {
cancelTokenSourceMap.forEach(cancelTokenSource => {
cancelTokenSource.cancel();
abortControllerMap.forEach(abortController => {
abortController.abort();
});
cancelTokenSourceMap.clear();
abortControllerMap.clear();
}

return {
Expand Down
4 changes: 3 additions & 1 deletion packages/axios/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ export function createDefaultOptions<ResponseData = any>(options?: Partial<Reque

export function createRetryOptions(config?: Partial<CreateAxiosDefaults>) {
const retryConfig: IAxiosRetryConfig = {
retries: 3
retries: 0,
shouldResetTimeout: true,
retryDelay: () => 1000
};

Object.assign(retryConfig, config);
Expand Down
41 changes: 24 additions & 17 deletions src/service/request/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import type { AxiosResponse } from 'axios';
import { BACKEND_ERROR_CODE, createFlatRequest, createRequest } from '@sa/axios';

import { resetStore } from '@/store/slice/auth';
import { store } from '@/store';
import { $t } from '@/locales';
import { localStg } from '@/utils/storage';
import { getServiceBaseURL } from '@/utils/service';
import { handleRefreshToken } from './shared';
import { handleRefreshToken, showErrorMsg } from './shared';
import type { RequestInstanceState } from './type';

const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
const { baseURL, otherBaseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
interface InstanceState {
/** whether the request is refreshing token */
isRefreshingToken: boolean;
}
export const request = createFlatRequest<App.Service.Response, InstanceState>(

export const request = createFlatRequest<App.Service.Response, RequestInstanceState>(
{
baseURL,
headers: {
Expand All @@ -36,35 +35,44 @@ export const request = createFlatRequest<App.Service.Response, InstanceState>(
return String(response.data.code) === import.meta.env.VITE_SERVICE_SUCCESS_CODE;
},
async onBackendFail(response, instance) {
function handleLogout() {}
const responseCode = String(response.data.code);

function handleLogout() {
store.dispatch(resetStore());
}

function logoutAndCleanup() {
handleLogout();
window.removeEventListener('beforeunload', handleLogout);

request.state.errMsgStack = request.state.errMsgStack.filter(msg => msg !== response.data.msg);
}

// when the backend response code is in `logoutCodes`, it means the user will be logged out and redirected to login page
const logoutCodes = import.meta.env.VITE_SERVICE_LOGOUT_CODES?.split(',') || [];
if (logoutCodes.includes(response.data.code)) {
if (logoutCodes.includes(responseCode)) {
handleLogout();
return null;
}

// when the backend response code is in `modalLogoutCodes`, it means the user will be logged out by displaying a modal
const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || [];
if (modalLogoutCodes.includes(response.data.code)) {
if (modalLogoutCodes.includes(responseCode) && !request.state.errMsgStack?.includes(response.data.msg)) {
request.state.errMsgStack = [...(request.state.errMsgStack || []), response.data.msg];

// prevent the user from refreshing the page
window.addEventListener('beforeunload', handleLogout);

window.$modal?.error({
title: 'Error',
content: response.data.code,
title: $t('common.error'),
content: response.data.msg,
okText: $t('common.confirm'),
maskClosable: false,
keyboard: false,
onOk() {
logoutAndCleanup();
},
onCancel() {
onClose() {
logoutAndCleanup();
}
});
Expand All @@ -75,7 +83,7 @@ export const request = createFlatRequest<App.Service.Response, InstanceState>(
// 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(response.data.code) && !request.state.isRefreshingToken) {
if (expiredTokenCodes.includes(responseCode) && !request.state.isRefreshingToken) {
request.state.isRefreshingToken = true;

const refreshConfig = await handleRefreshToken(response.config);
Expand All @@ -101,7 +109,7 @@ export const request = createFlatRequest<App.Service.Response, InstanceState>(
// get backend error message and code
if (error.code === BACKEND_ERROR_CODE) {
message = error.response?.data?.msg || message;
backendErrorCode = error.response?.data?.code || '';
backendErrorCode = String(error.response?.data?.code || '');
}

// the error message is displayed in the modal
Expand All @@ -116,7 +124,7 @@ export const request = createFlatRequest<App.Service.Response, InstanceState>(
return;
}

window.$message?.error?.(message);
showErrorMsg(request.state, message);
}
}
);
Expand Down Expand Up @@ -150,7 +158,6 @@ export const demoRequest = createRequest<App.Service.DemoResponse>(
},
onError(error) {
// when the request is fail, you can show error message
console.log(error);

let message = error.message;

Expand Down
29 changes: 28 additions & 1 deletion src/service/request/shared.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { AxiosRequestConfig } from 'axios';

import { resetStore } from '@/store/slice/auth';
import { store } from '@/store';
import { localStg } from '@/utils/storage';
import { fetchRefreshToken } from '../api';
import type { RequestInstanceState } from './type';

/**
* refresh token
Expand All @@ -23,5 +25,30 @@ export async function handleRefreshToken(axiosConfig: AxiosRequestConfig) {
return config;
}

store.dispatch(resetStore());

return null;
}

export function showErrorMsg(state: RequestInstanceState, message: string) {
if (!state.errMsgStack?.length) {
state.errMsgStack = [];
}

const isExist = state.errMsgStack.includes(message);

if (!isExist) {
state.errMsgStack.push(message);

window.$message?.error({
content: message,
onClose: () => {
state.errMsgStack = state.errMsgStack.filter(msg => msg !== message);

setTimeout(() => {
state.errMsgStack = [];
}, 5000);
}
});
}
}
6 changes: 6 additions & 0 deletions src/service/request/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface RequestInstanceState {
/** whether the request is refreshing token */
isRefreshingToken: boolean;
/** the request error message stack */
errMsgStack: string[];
}

0 comments on commit 6401f0b

Please sign in to comment.