feat: 修复 WebSocket 越权与 AI 流 SSRF
This commit is contained in:
@@ -43,24 +43,24 @@ export function getServerUrl(): string {
|
||||
// 保留导出以兼容现有代码(模块加载时的默认值,实际请求请使用 getApiBaseUrl())
|
||||
export const API_BASE_URL = DEFAULT_API_BASE_URL;
|
||||
|
||||
export function getWsUrl(token: string): string {
|
||||
export function getWsUrl(ticket: string): string {
|
||||
const baseUrl = getApiBaseUrl();
|
||||
|
||||
// 如果是完整 URL,从中推导 WS 地址
|
||||
if (baseUrl.startsWith('http')) {
|
||||
const serverUrl = baseUrl.replace(/\/api\/v1\/?$/, '');
|
||||
const wsUrl = serverUrl.replace(/^http/, 'ws');
|
||||
return `${wsUrl}/ws?token=${token}`;
|
||||
return `${wsUrl}/ws?ticket=${ticket}`;
|
||||
}
|
||||
|
||||
// H5 相对路径 - 使用 window.location 推导
|
||||
if (isH5) {
|
||||
const protocol = typeof window !== 'undefined' && window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const host = typeof window !== 'undefined' ? window.location.host : 'localhost:3000';
|
||||
return `${protocol}//${host}/ws?token=${token}`;
|
||||
return `${protocol}//${host}/ws?ticket=${ticket}`;
|
||||
}
|
||||
|
||||
return `ws://100.83.103.1:3000/ws?token=${token}`;
|
||||
return `ws://100.83.103.1:3000/ws?ticket=${ticket}`;
|
||||
}
|
||||
|
||||
export const REQUEST_TIMEOUT = 15000;
|
||||
|
||||
+19
-5
@@ -1,5 +1,6 @@
|
||||
import Taro from '@tarojs/taro';
|
||||
import { getWsUrl } from '@/api/config';
|
||||
import { get } from '@/api/request';
|
||||
|
||||
type MessageHandler = (data: unknown) => void;
|
||||
|
||||
@@ -7,19 +8,20 @@ class WebSocketManager {
|
||||
private connected: boolean = false;
|
||||
private handlers: Map<string, Set<MessageHandler>> = new Map();
|
||||
private token: string = '';
|
||||
private ticket: string = '';
|
||||
private reconnectAttempts: number = 0;
|
||||
private maxReconnectAttempts: number = 5;
|
||||
private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
private isManualClose: boolean = false;
|
||||
|
||||
connect(token: string): void {
|
||||
async connect(token: string): Promise<void> {
|
||||
this.token = token;
|
||||
this.isManualClose = false;
|
||||
this.doConnect();
|
||||
await this.refreshTicketAndConnect();
|
||||
}
|
||||
|
||||
private doConnect(): void {
|
||||
if (!this.token) {
|
||||
if (!this.token || !this.ticket) {
|
||||
console.warn('[WS] 无 token,无法连接 WebSocket');
|
||||
return;
|
||||
}
|
||||
@@ -34,7 +36,7 @@ class WebSocketManager {
|
||||
this.connected = false;
|
||||
}
|
||||
|
||||
const url = getWsUrl(this.token);
|
||||
const url = getWsUrl(this.ticket);
|
||||
console.log('[WS] 正在连接:', url);
|
||||
|
||||
// 注册全局回调(每次调用会替换上一次的回调)
|
||||
@@ -98,10 +100,22 @@ class WebSocketManager {
|
||||
clearTimeout(this.reconnectTimer);
|
||||
}
|
||||
this.reconnectTimer = setTimeout(() => {
|
||||
this.doConnect();
|
||||
this.refreshTicketAndConnect();
|
||||
}, delay);
|
||||
}
|
||||
|
||||
private async refreshTicketAndConnect(): Promise<void> {
|
||||
if (!this.token) return;
|
||||
try {
|
||||
const { ticket } = await get<{ ticket: string }>('/ws/ticket');
|
||||
this.ticket = ticket;
|
||||
this.doConnect();
|
||||
} catch (e) {
|
||||
console.error('[WS] 获取 ticket 失败:', e);
|
||||
this.scheduleReconnect();
|
||||
}
|
||||
}
|
||||
|
||||
on(type: string, handler: MessageHandler): () => void {
|
||||
if (!this.handlers.has(type)) {
|
||||
this.handlers.set(type, new Set());
|
||||
|
||||
Reference in New Issue
Block a user