Files
Migu2.0/frontends/apps/simple-platform-vue/src/api/http.ts
T
黄兆尉andCursor 3686abdc78 将调度内核标识从 SimpleLite 全面重命名为 Simple3。
配置段/环境变量、Launcher、健康检查 API、OpenAPI 与前后端文案同步;兼容探测旧 SimpleLite 进程名。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-26 17:46:52 +08:00

80 lines
3.5 KiB
TypeScript
Raw 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 axios, { AxiosError, type AxiosInstance, type InternalAxiosRequestConfig } from 'axios'
const baseURL = (import.meta.env.VITE_API_BASE as string | undefined) ?? '/api'
// AR-5 (会话 45)withCredentials=true 让后端下发的 httpOnly Cookie (simple.auth.token)
// 自动随请求带回。Platform.Server `/api/auth/login` 同时下发 Cookie 和返回 token 字段,
// 过渡期内 Authorization: Bearer header 仍然兼容(旧浏览器 / 跨进程脚本)。
const http: AxiosInstance = axios.create({
baseURL,
timeout: 15000,
headers: { 'Content-Type': 'application/json' },
withCredentials: true
})
http.interceptors.request.use((config: InternalAxiosRequestConfig) => {
// FormData 必须由浏览器自动带 multipart boundary;清掉默认 application/json。
if (typeof FormData !== 'undefined' && config.data instanceof FormData) {
config.headers.delete('Content-Type')
}
// 双轨:优先用 localStorage 里的 token(过渡期 fallback),Cookie 会自动带;
// 后端首先看 Authorization: Bearer,没有再看 Cookie,两路任一通过即可。
const token = localStorage.getItem('simple.auth.token')
if (token) {
config.headers.set('Authorization', `Bearer ${token}`)
}
const scope = localStorage.getItem('simple.auth.scope')
if (scope) {
config.headers.set('X-Scope', scope)
}
return config
})
/**
* 把 axios 异常翻译成更友好的中文文案,特别是 Simple3 链路:
*
* /api/sl/projection/** → Platform.Server YARP → Simple3 EmbedIO :8222
*
* 如果 Simple3 没启动 / 端口未监听,YARP 一定回 502;超时一般是 504。把这两种
* 情况单独识别,写成「Simple3 未连接,请先启动 Simple3 (端口 8222)」之类,
* 比直接抛 `Request failed with status code 502` 友好得多,也避免用户怀疑前端 bug。
*/
function describeError(err: AxiosError): string {
const status = err.response?.status
const url = err.config?.url ?? ''
const isSimple3 = url.startsWith('/sl/') || url.startsWith('sl/')
if (status === 502 || status === 504 || err.code === 'ECONNABORTED' || err.code === 'ERR_NETWORK') {
if (isSimple3) {
return 'Simple3 未连接:请先启动 Simple3 后端(监听端口 8222)后重试。'
}
return `网关无法连接到下游服务(${status ?? err.code ?? 'network'})。`
}
// 后端 envelope 模式:{ success: false, message: 'xxx' }
const data = err.response?.data as { message?: string } | undefined
if (data?.message) return data.message
return err.message
}
http.interceptors.response.use(
(resp) => resp,
(err: AxiosError) => {
if (err.response?.status === 401) {
// 会话 45 HC-1401 全量清 state,避免老 user/scope/perm 残留导致 UI 误判。
try {
localStorage.removeItem('simple.auth.token')
localStorage.removeItem('simple.auth.user')
localStorage.removeItem('simple.auth.scope')
localStorage.removeItem('simple.auth.runMode')
localStorage.removeItem('simple.auth.perm')
} catch { /* 某些隐私模式 / iframe 沙箱可能禁用 localStorage */ }
if (location.pathname !== '/login') location.assign('/login')
}
// 用 Object.defineProperty 覆盖 message,避免后续 `(err as Error).message` 还是看到原文。
const friendly = describeError(err)
try { Object.defineProperty(err, 'message', { value: friendly, configurable: true }) } catch { /* ignore */ }
return Promise.reject(err)
}
)
export default http