mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-09 15:39:24 +08:00
236 lines
7.3 KiB
TypeScript
236 lines
7.3 KiB
TypeScript
/**
|
|
* This file is part of the NocoBase (R) project.
|
|
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
* Authors: NocoBase Team.
|
|
*
|
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
*/
|
|
|
|
import { APIClient as APIClientSDK } from '@nocobase/sdk';
|
|
import { Result } from 'ahooks/es/useRequest/src/types';
|
|
import { notification } from 'antd';
|
|
import React from 'react';
|
|
import { Application } from '../application';
|
|
|
|
function notify(type, messages, instance) {
|
|
if (!messages?.length) {
|
|
return;
|
|
}
|
|
instance[type]({
|
|
message: messages.map?.((item: any, index) => {
|
|
return React.createElement(
|
|
'div',
|
|
{ key: `${index}_${item.message}` },
|
|
typeof item === 'string' ? item : item.message,
|
|
);
|
|
}),
|
|
});
|
|
}
|
|
|
|
const handleErrorMessage = (error, notification) => {
|
|
const reader = new FileReader();
|
|
reader.readAsText(error?.response?.data, 'utf-8');
|
|
reader.onload = function () {
|
|
const messages = JSON.parse(reader.result as string).errors;
|
|
notify('error', messages, notification);
|
|
};
|
|
};
|
|
|
|
function offsetToTimeZone(offset) {
|
|
const hours = Math.floor(Math.abs(offset));
|
|
const minutes = Math.abs((offset % 1) * 60);
|
|
|
|
const formattedHours = (hours < 10 ? '0' : '') + hours;
|
|
const formattedMinutes = (minutes < 10 ? '0' : '') + minutes;
|
|
|
|
const sign = offset >= 0 ? '+' : '-';
|
|
return sign + formattedHours + ':' + formattedMinutes;
|
|
}
|
|
|
|
const getCurrentTimezone = () => {
|
|
const timezoneOffset = new Date().getTimezoneOffset() / -60;
|
|
return offsetToTimeZone(timezoneOffset);
|
|
};
|
|
|
|
const errorCache = new Map();
|
|
export class APIClient extends APIClientSDK {
|
|
services: Record<string, Result<any, any>> = {};
|
|
silence = false;
|
|
app: Application;
|
|
/** 该值会在 AntdAppProvider 中被重新赋值 */
|
|
notification: any = notification;
|
|
|
|
cloneInstance() {
|
|
const api = new APIClient(this.options);
|
|
api.options = this.options;
|
|
api.services = this.services;
|
|
api.storage = this.storage;
|
|
api.app = this.app;
|
|
api.auth = this.auth;
|
|
api.storagePrefix = this.storagePrefix;
|
|
api.notification = this.notification;
|
|
const handlers = [];
|
|
for (const handler of this.axios.interceptors.response['handlers']) {
|
|
if (handler.rejected['_name'] === 'handleNotificationError') {
|
|
handlers.push({
|
|
...handler,
|
|
rejected: api.handleNotificationError.bind(api),
|
|
});
|
|
} else {
|
|
handlers.push(handler);
|
|
}
|
|
}
|
|
api.axios.interceptors.response['handlers'] = handlers;
|
|
return api;
|
|
}
|
|
|
|
getHeaders() {
|
|
const headers = super.getHeaders();
|
|
const appName = this.app?.getName();
|
|
if (appName) {
|
|
headers['X-App'] = appName;
|
|
}
|
|
headers['X-Timezone'] = getCurrentTimezone();
|
|
headers['X-Hostname'] = window?.location?.hostname;
|
|
return headers;
|
|
}
|
|
|
|
service(uid: string) {
|
|
return this.services[uid];
|
|
}
|
|
|
|
interceptors() {
|
|
this.axios.interceptors.request.use((config) => {
|
|
config.headers['X-With-ACL-Meta'] = true;
|
|
const headers = this.getHeaders();
|
|
Object.keys(headers).forEach((key) => {
|
|
config.headers[key] = config.headers[key] || headers[key];
|
|
});
|
|
return config;
|
|
});
|
|
super.interceptors();
|
|
this.useNotificationMiddleware();
|
|
this.axios.interceptors.response.use(
|
|
(response) => {
|
|
return response;
|
|
},
|
|
(error) => {
|
|
const errs = this.toErrMessages(error);
|
|
// Hard code here temporarily
|
|
// TODO(yangqia): improve error code and message
|
|
if (errs.find((error: { code?: string }) => error.code === 'ROLE_NOT_FOUND_ERR')) {
|
|
this.auth.setRole(null);
|
|
window.location.reload();
|
|
}
|
|
if (errs.find((error: { code?: string }) => error.code === 'TOKEN_INVALID' || error.code === 'USER_LOCKED')) {
|
|
this.auth.setToken(null);
|
|
}
|
|
if (errs.find((error: { code?: string }) => error.code === 'ROLE_NOT_FOUND_FOR_USER')) {
|
|
this.auth.setRole(null);
|
|
window.location.reload();
|
|
}
|
|
throw error;
|
|
},
|
|
);
|
|
}
|
|
|
|
toErrMessages(error) {
|
|
if (typeof error?.response?.data === 'string') {
|
|
const tempElement = document.createElement('div');
|
|
tempElement.innerHTML = error?.response?.data;
|
|
let message = tempElement.textContent || tempElement.innerText;
|
|
if (message.includes('Error occurred while trying')) {
|
|
message = 'The application may be starting up. Please try again later.';
|
|
return [{ code: 'APP_WARNING', message }];
|
|
}
|
|
if (message.includes('502 Bad Gateway')) {
|
|
message = 'The application may be starting up. Please try again later.';
|
|
return [{ code: 'APP_WARNING', message }];
|
|
}
|
|
return [{ message }];
|
|
}
|
|
if (error?.response?.data?.error) {
|
|
return [error?.response?.data?.error];
|
|
}
|
|
return (
|
|
error?.response?.data?.errors ||
|
|
error?.response?.data?.messages ||
|
|
error?.response?.error || [{ message: error.message || 'Server error' }]
|
|
);
|
|
}
|
|
|
|
async handleNotificationError(error) {
|
|
if (this.silence) {
|
|
// console.error(error);
|
|
// return;
|
|
throw error;
|
|
}
|
|
const skipNotify: boolean | ((error: any) => boolean) = error.config?.skipNotify;
|
|
if (skipNotify && ((typeof skipNotify === 'function' && skipNotify(error)) || skipNotify === true)) {
|
|
throw error;
|
|
}
|
|
const redirectTo = error?.response?.data?.redirectTo;
|
|
if (redirectTo) {
|
|
return (window.location.href = redirectTo);
|
|
}
|
|
if (error?.response?.data?.type === 'application/json') {
|
|
handleErrorMessage(error, this.notification);
|
|
} else {
|
|
if (errorCache.size > 10) {
|
|
errorCache.clear();
|
|
}
|
|
const maintaining = !!error?.response?.data?.error?.maintaining;
|
|
if (this.app.maintaining !== maintaining) {
|
|
this.app.maintaining = maintaining;
|
|
}
|
|
if (this.app.maintaining) {
|
|
this.app.error = error?.response?.data?.error;
|
|
throw error;
|
|
} else if (this.app.error) {
|
|
this.app.error = null;
|
|
}
|
|
let errs = this.toErrMessages(error);
|
|
errs = errs.filter((error) => {
|
|
const lastTime = errorCache.get(error.message);
|
|
if (lastTime && new Date().getTime() - lastTime < 500) {
|
|
return false;
|
|
}
|
|
errorCache.set(error.message, new Date().getTime());
|
|
return true;
|
|
});
|
|
if (errs.length === 0) {
|
|
throw error;
|
|
}
|
|
|
|
notify('error', errs, this.notification);
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
useNotificationMiddleware() {
|
|
const errorHandler = this.handleNotificationError.bind(this);
|
|
errorHandler['_name'] = 'handleNotificationError';
|
|
this.axios.interceptors.response.use((response) => {
|
|
if (response.data?.messages?.length) {
|
|
const messages = response.data.messages.filter((item) => {
|
|
const lastTime = errorCache.get(typeof item === 'string' ? item : item.message);
|
|
if (lastTime && new Date().getTime() - lastTime < 500) {
|
|
return false;
|
|
}
|
|
errorCache.set(item.message, new Date().getTime());
|
|
return true;
|
|
});
|
|
notify('success', messages, this.notification);
|
|
}
|
|
return response;
|
|
}, errorHandler);
|
|
}
|
|
|
|
silent() {
|
|
const api = this.cloneInstance();
|
|
api.silence = true;
|
|
return api;
|
|
}
|
|
}
|