forked from lunanting/cangku
118 lines
3.3 KiB
JavaScript
118 lines
3.3 KiB
JavaScript
import axios from 'axios';
|
|
import { message } from 'antd';
|
|
import { getRequestErrorMessage, isAuthSessionFailure } from './authSessionPolicy';
|
|
import { requestDesktopSessionRenewal } from '../utils/desktopEmbedSession';
|
|
|
|
const DEFAULT_REQUEST_TIMEOUT_MS = Math.max(
|
|
Number(import.meta.env.VITE_API_REQUEST_TIMEOUT_MS) || 120000,
|
|
15000
|
|
);
|
|
|
|
const request = axios.create({
|
|
baseURL: '', // Proxy handles the base URL
|
|
timeout: DEFAULT_REQUEST_TIMEOUT_MS,
|
|
});
|
|
const AUTH_USER_STORAGE_KEY = 'auth_user';
|
|
|
|
function clearAuthSession() {
|
|
localStorage.removeItem('token');
|
|
localStorage.removeItem(AUTH_USER_STORAGE_KEY);
|
|
}
|
|
|
|
function parseJwtPayload(token) {
|
|
if (!token) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
const [, payload] = token.split('.');
|
|
if (!payload) {
|
|
return null;
|
|
}
|
|
|
|
const normalized = payload.replace(/-/g, '+').replace(/_/g, '/');
|
|
const padded = normalized.padEnd(normalized.length + ((4 - normalized.length % 4) % 4), '=');
|
|
return JSON.parse(window.atob(padded));
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function isLocalPreviewSession() {
|
|
const isLocalHost = ['localhost', '127.0.0.1'].includes(window.location.hostname);
|
|
if (!isLocalHost) {
|
|
return false;
|
|
}
|
|
|
|
const payload = parseJwtPayload(localStorage.getItem('token'));
|
|
return payload?.authSource === 'local-preview';
|
|
}
|
|
|
|
function isPublicAuthPage() {
|
|
return ['/login', '/callback', '/install'].includes(window.location.pathname);
|
|
}
|
|
|
|
function shouldBypass401Redirect(error) {
|
|
const url = error?.config?.url || '';
|
|
return url.includes('/api/auth/verify') || isLocalPreviewSession();
|
|
}
|
|
|
|
// Request interceptor
|
|
request.interceptors.request.use(
|
|
(config) => {
|
|
const token = localStorage.getItem('token');
|
|
if (token) {
|
|
config.headers['Authorization'] = `Bearer ${token}`;
|
|
}
|
|
return config;
|
|
},
|
|
(error) => {
|
|
return Promise.reject(error);
|
|
}
|
|
);
|
|
|
|
// Response interceptor
|
|
request.interceptors.response.use(
|
|
(response) => {
|
|
const res = response.data;
|
|
// Compatible with backend interfaces that don't return code
|
|
if (res && res.code && res.code !== 200) {
|
|
const shouldBypassRedirect = res.code === 401 && shouldBypass401Redirect(response);
|
|
|
|
if (!shouldBypassRedirect && !response.config?.silentError) {
|
|
message.error(res.message || 'Error');
|
|
}
|
|
|
|
if (res.code === 401 && !shouldBypassRedirect) {
|
|
// Handle unauthorized - 但如果已在登录页则不重定向
|
|
clearAuthSession();
|
|
if (!isPublicAuthPage()) {
|
|
if (!requestDesktopSessionRenewal('api-unauthorized')) {
|
|
window.location.href = '/login';
|
|
}
|
|
}
|
|
}
|
|
return Promise.reject(new Error(res.message || 'Error'));
|
|
}
|
|
return res;
|
|
},
|
|
(error) => {
|
|
const authSessionFailure = isAuthSessionFailure(error.response);
|
|
if (!authSessionFailure && !error.config?.silentError) {
|
|
message.error(getRequestErrorMessage(error));
|
|
}
|
|
if (authSessionFailure && !shouldBypass401Redirect(error)) {
|
|
clearAuthSession();
|
|
// 如果已在登录页则不重定向,避免无限循环
|
|
if (!isPublicAuthPage()) {
|
|
if (!requestDesktopSessionRenewal('api-unauthorized')) {
|
|
window.location.href = '/login';
|
|
}
|
|
}
|
|
}
|
|
return Promise.reject(error);
|
|
}
|
|
);
|
|
|
|
export default request;
|