Files
SMATEC-FRONTEND/config/request_dev.ts

194 lines
5.9 KiB
TypeScript

import { HTTPSTATUS } from '@/constants';
import { API_PATH_REFRESH_TOKEN } from '@/constants/api';
import { ROUTE_LOGIN } from '@/constants/routes';
import { apiRefreshToken } from '@/services/master/AuthController';
import { checkRefreshTokenExpired } from '@/utils/jwt';
import {
clearAllData,
getAccessToken,
getRefreshToken,
setAccessToken,
} from '@/utils/storage';
import { history, request, RequestConfig } from '@umijs/max';
import { message } from 'antd';
// Trạng thái dùng chung cho cơ chế refresh token + hàng đợi
let refreshingTokenPromise: Promise<string | null> | null = null;
async function getValidAccessToken(): Promise<string | null> {
const refreshToken = getRefreshToken();
if (!refreshToken || checkRefreshTokenExpired(refreshToken)) {
return null;
}
if (!refreshingTokenPromise) {
refreshingTokenPromise = apiRefreshToken({ refresh_token: refreshToken })
.then((resp) => {
if (resp?.access_token) {
setAccessToken(resp.access_token);
return resp.access_token;
}
return null;
})
.catch((err) => {
// eslint-disable-next-line no-console
console.error('Refresh token failed:', err);
return null;
})
.finally(() => {
refreshingTokenPromise = null;
});
}
return refreshingTokenPromise;
}
// Runtime configuration
export const handleRequestConfig: RequestConfig = {
// Unified request settings
timeout: 20000,
validateStatus: (status) => {
return (
(status >= 200 && status < 300) ||
status === HTTPSTATUS.HTTP_NOTFOUND ||
status === HTTPSTATUS.HTTP_UNAUTHORIZED
);
},
headers: { 'X-Requested-With': 'XMLHttpRequest' },
// Error handling: umi@3's error handling scheme.
errorConfig: {
// Error throwing
errorThrower: (res: any) => {
// console.log('Response from backend:', res);
const { success, data, errorCode, errorMessage, showType } = res;
if (!success) {
const error: any = new Error(errorMessage);
error.name = 'BizError';
error.info = { errorCode, errorMessage, showType, data };
throw error; // Throw custom error
}
},
// Error catching and handling
errorHandler: (error: any) => {
if (error.response) {
const { status } = error.response;
// Ưu tiên: codeMessage → backend message → statusText
// const errMsg =
// codeMessage[status as keyof typeof codeMessage] ||
// data?.message ||
// statusText ||
// 'Unknown error';
if (status === HTTPSTATUS.HTTP_UNAUTHORIZED) {
// 401 đã được xử lý trong responseInterceptors (refresh token + redirect nếu cần)
return;
} else if (status === HTTPSTATUS.HTTP_SERVERERROR) {
// message.error('💥 Internal server error!');
} else {
message.error(`${status}: ${error.message || 'Error'}`);
}
} else if (error.request) {
message.error('🚨 No response from server!');
} else {
message.error(`⚠️ Request setup error: ${error.message}`);
}
},
},
// Request interceptors
requestInterceptors: [
(url: string, options: any) => {
const token = getAccessToken();
// console.log('Token: ', token);
return {
url,
options: {
...options,
headers: {
...options.headers,
...(token && !options.headers.Authorization
? { Authorization: `${token}` }
: {}),
},
},
};
},
],
// Unwrap data from backend response
responseInterceptors: [
async (response: any, options: any) => {
const isRefreshRequest = response.url?.includes(API_PATH_REFRESH_TOKEN);
const alreadyRetried = options?.skipAuthRefresh === true;
// Xử lý 401: đưa request vào hàng đợi, gọi refresh token, rồi gọi lại
if (
response.status === HTTPSTATUS.HTTP_UNAUTHORIZED &&
// Không tự refresh cho chính API refresh token để tránh vòng lặp vô hạn
!isRefreshRequest &&
!alreadyRetried
) {
const newToken = await getValidAccessToken();
console.log('Access Token hết hạn, đang refresh...');
// Không refresh được => xoá dữ liệu, điều hướng về trang login
if (!newToken) {
console.log('Access Token hết hạn và không thể refresh');
const { pathname } = history.location;
clearAllData();
if (pathname !== ROUTE_LOGIN) {
console.log(
'Request dev: Chuyển về trang login và có pathname ',
pathname,
);
history.push(`${ROUTE_LOGIN}?redirect=${pathname}`);
} else {
console.log(
'Request dev: Chuyển về trang login và không có pathname',
);
history.push(ROUTE_LOGIN);
}
return Promise.reject(
new Error('Unauthorized and refresh token is invalid'),
);
}
const newOptions = {
...options,
headers: {
...(options.headers || {}),
Authorization: `${newToken}`,
},
skipAuthRefresh: true,
};
// Gọi lại request gốc với accessToken mới
return request(response.url, newOptions);
}
if (
response.status === HTTPSTATUS.HTTP_UNAUTHORIZED &&
(isRefreshRequest || alreadyRetried)
) {
clearAllData();
history.push(ROUTE_LOGIN);
return Promise.reject(new Error('Unauthorized'));
}
// console.log('Response from server: ', response);
return {
status: response.status,
statusText: response.statusText,
headers: response.headers,
config: response.config,
data: response.data,
};
},
] as any,
};