Files
rogee a0c6969da0
Build and publish Docker images / Build and publish images (push) Successful in 2m21s
fix(frontend): preserve timeout label success toast
2026-09-16 09:45:52 +08:00

265 lines
8.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Cookies from 'js-cookie';
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
import { SESSION_STORAGE_KEYS } from 'dashboard/constants/sessionStorage';
import { LocalStorage } from 'shared/helpers/localStorage';
import SessionStorage from 'shared/helpers/sessionStorage';
import { emitter } from 'shared/helpers/mitt';
import {
ANALYTICS_IDENTITY,
ANALYTICS_RESET,
CHATWOOT_RESET,
CHATWOOT_SET_USER,
} from '../../constants/appEvents';
Cookies.defaults = { sameSite: 'Lax' };
export const getLoadingStatus = state => state.fetchAPIloadingStatus;
export const setLoadingStatus = (state, status) => {
state.fetchAPIloadingStatus = status;
};
export const setUser = user => {
emitter.emit(CHATWOOT_SET_USER, { user });
emitter.emit(ANALYTICS_IDENTITY, { user });
};
export const setAuthCredentials = response => {
setUser(response.data.data);
};
export const clearBrowserSessionCookies = () => {
Cookies.remove('cw_d_session_info');
Cookies.remove('cw_d_session_state');
Cookies.remove('auth_data');
Cookies.remove('user');
['access-token', 'client', 'uid', 'token-type', 'expiry'].forEach(key =>
localStorage.removeItem(key)
);
};
export const clearLocalStorageOnLogout = () => {
LocalStorage.remove(LOCAL_STORAGE_KEYS.DRAFT_MESSAGES);
};
export const clearSessionStorageOnLogout = () => {
SessionStorage.remove(SESSION_STORAGE_KEYS.IMPERSONATION_USER);
};
export const deleteIndexedDBOnLogout = async () => {
let dbs = [];
try {
dbs = await window.indexedDB.databases();
dbs = dbs.map(db => db.name);
} catch (e) {
dbs = JSON.parse(localStorage.getItem('cw-idb-names') || '[]');
}
dbs.forEach(dbName => {
const deleteRequest = window.indexedDB.deleteDatabase(dbName);
deleteRequest.onerror = event => {
// eslint-disable-next-line no-console
console.error(`Error deleting database ${dbName}.`, event);
};
deleteRequest.onsuccess = () => {
// eslint-disable-next-line no-console
console.log(`Database ${dbName} deleted successfully.`);
};
});
localStorage.removeItem('cw-idb-names');
};
const getSafeLogoutRedirectLink = redirectLink => {
try {
const url = new URL(redirectLink || '/', window.location.origin);
if (url.origin !== window.location.origin) return '/';
return `${url.pathname}${url.search}${url.hash}`;
} catch {
return '/';
}
};
export const clearCookiesOnLogout = () => {
emitter.emit(CHATWOOT_RESET);
emitter.emit(ANALYTICS_RESET);
clearBrowserSessionCookies();
clearLocalStorageOnLogout();
clearSessionStorageOnLogout();
const globalConfig = window.globalConfig || {};
const logoutRedirectLink = getSafeLogoutRedirectLink(
globalConfig.LOGOUT_REDIRECT_LINK
);
window.location.assign(logoutRedirectLink);
};
const API_ERROR_MESSAGES = Object.freeze({
400: '请求参数有误,请检查后重试。',
401: '身份验证失败,请重新登录。',
403: '您没有权限执行此操作。',
404: '请求的资源不存在。',
408: '请求超时,请稍后再试。',
409: '操作冲突,请刷新后重试。',
422: '提交的信息有误,请检查后重试。',
429: '操作过于频繁,请稍后再试。',
500: '服务器发生错误,请稍后再试。',
502: '服务暂时不可用,请稍后再试。',
503: '服务暂时不可用,请稍后再试。',
504: '服务暂时不可用,请稍后再试。',
});
const extractStatusFromMessage = message => {
const match = message.match(
/(?:request failed with status code|status code|error)\s*[:=]?\s*([45]\d{2})/i
);
return match ? Number(match[1]) : null;
};
const getStatusMessage = status => {
const numericStatus = Number(status);
if (!Number.isInteger(numericStatus)) return '';
return (
API_ERROR_MESSAGES[numericStatus] ||
(numericStatus >= 500 ? API_ERROR_MESSAGES[500] : '')
);
};
const normalizeAPIErrorText = (message, status) => {
if (typeof message !== 'string') return message;
const text = message.trim();
if (!text) return text;
if (/^(network error|failed to fetch|network request failed)$/i.test(text)) {
return '网络连接失败,请检查网络后重试。';
}
if (
/\b(?:timeout|timed out)\b/i.test(text) ||
/(?:^|(?:请求|连接|操作|网络|服务|服务器|上游)[^,。!?;;::]{0,8})超时(?:[,。!?;;:]|$)/.test(
text
)
) {
return '请求超时,请稍后再试。';
}
if (/^invalid email or password$/i.test(text)) {
return '邮箱或密码错误,请重试。';
}
if (/^email not confirmed\b/i.test(text)) {
return '邮箱尚未验证,请先完成邮箱验证。';
}
if (/^user account is inactive\b/i.test(text)) {
return '账户已停用,请联系管理员。';
}
if (/^this account uses .* authentication/i.test(text)) {
return '此账户使用第三方登录,请通过对应方式登录。';
}
if (
/^(invalid (token|credentials|headers)|unauthorized|unauthenticated|authentication failed)$/i.test(
text
)
) {
return '身份验证失败,请重新登录。';
}
if (/^account not identified$/i.test(text)) {
return '无法识别当前账户,请重新登录。';
}
if (/administrator role required/i.test(text)) {
return '需要管理员权限才能执行此操作。';
}
if (
/^(not authorized|authorization header required|bearer token required|widget_token required)$/i.test(
text
)
) {
return '缺少有效的登录凭证,请重新登录。';
}
if (/^account is suspended$/i.test(text)) {
return '账户已被停用,请联系管理员。';
}
if (/^sso session (expired or invalid|data corrupt)$/i.test(text)) {
return '单点登录会话无效,请重新登录。';
}
if (/^invalid (source )?url/i.test(text)) {
return '请求地址无效,请检查后重试。';
}
const messageStatus = extractStatusFromMessage(text);
if (messageStatus) {
return getStatusMessage(messageStatus) || text;
}
if (
status &&
/^(bad request|unauthorized|unauthenticated|forbidden|not found|request timeout|unprocessable entity|too many requests|internal server error|bad gateway|service unavailable|gateway timeout|invalid credentials|authentication failed)$/i.test(
text
)
) {
return getStatusMessage(status) || text;
}
if (
status >= 500 &&
/\b(error|failed|unavailable|gateway|server)\b/i.test(text)
) {
return getStatusMessage(status) || text;
}
if (/^(axioserror:\s*)?(error:\s*)?\[object object\]$/i.test(text)) {
return '请求失败,请稍后再试。';
}
if (
/^(failed|unable to|could not|cannot|can't|something went wrong|an error occurred)\b/i.test(
text
)
) {
return '请求失败,请稍后再试。';
}
return message;
};
const extractErrorText = value => {
if (typeof value === 'string') return value;
if (value?.message && typeof value.message === 'string') {
return value.message;
}
if (Array.isArray(value)) return extractErrorText(value[0]);
if (value && typeof value === 'object') return JSON.stringify(value);
return '';
};
export const parseAPIErrorResponse = error => {
if (typeof error === 'string') return normalizeAPIErrorText(error);
const status = error?.response?.status ?? error?.status;
if (error?.code === 'ERR_NETWORK') {
return '网络连接失败,请检查网络后重试。';
}
if (error?.code === 'ECONNABORTED') {
return '请求超时,请稍后再试。';
}
const data = error?.response?.data;
const message = extractErrorText(data?.message);
if (message) return normalizeAPIErrorText(message, status);
const apiError = extractErrorText(data?.error);
if (apiError) return normalizeAPIErrorText(apiError, status);
const errors = extractErrorText(data?.errors);
if (errors) return normalizeAPIErrorText(errors, status);
const errorMessage = extractErrorText(error?.message);
if (errorMessage) return normalizeAPIErrorText(errorMessage, status);
return getStatusMessage(status) || '请求失败,请稍后再试。';
};
export const throwErrorMessage = error => {
const errorMessage = parseAPIErrorResponse(error);
throw new Error(errorMessage);
};
export const parseLinearAPIErrorResponse = (error, defaultMessage) => {
const errorData = error.response.data;
const errorMessage = errorData?.error?.errors?.[0]?.message || defaultMessage;
return errorMessage;
};