// WebSocket全局配置 const WS_CONFIG = { heartbeatInterval: 30000, // 30秒心跳间隔 heartbeatTimeout: 5000, // 心跳响应超时时间(5秒) maxReconnectAttempts: 5, // 最大重连次数 reconnectBaseDelay: 1000, // 重连基础延迟1秒 maxReconnectDelay: 30000, // 最大重连延迟30秒 heartbeatMessage: 'ping', // 心跳消息 heartbeatResponseType: 'pong', // 心跳响应类型 }; // 增强的WebSocket包装器 class EnhancedWebSocket { private ws: WebSocket; private path: string; private baseUrl: string; private heartbeatTimer?: NodeJS.Timeout; private heartbeatTimeoutTimer?: NodeJS.Timeout; private reconnectTimer?: NodeJS.Timeout; private reconnectAttempts: number = 0; private isManualClose: boolean = false; private isHeartbeatTimeout: boolean = false; private userOnMessage: ((event: MessageEvent) => void) | null = null; private userOnClose: ((event: CloseEvent) => void) | null = null; private userOnError: ((event: Event) => void) | null = null; private userOnOpen: ((event: Event) => void) | null = null; constructor(path: string, baseUrl: string) { this.path = path; this.baseUrl = baseUrl; this.ws = new WebSocket(baseUrl + path); this.setupHandlers(); } // 设置事件处理器 private setupHandlers(): void { this.ws.onopen = (event) => { console.log(`WebSocket连接已建立: ${this.path}`); this.reconnectAttempts = 0; this.clearReconnectTimer(); this.startHeartbeat(); if (this.userOnOpen) { this.userOnOpen(event); } }; this.ws.onmessage = (event) => { const messageData = event.data; // 检查是否为心跳响应(支持字符串和JSON格式) let isHeartbeatResponse = false; // 1. 检查是否为简单字符串心跳响应 if (typeof messageData === 'string' && messageData === WS_CONFIG.heartbeatResponseType) { isHeartbeatResponse = true; } // 2. 检查是否为JSON格式心跳响应 if (!isHeartbeatResponse && typeof messageData === 'string') { try { const data = JSON.parse(messageData); if (data.type === WS_CONFIG.heartbeatResponseType) { isHeartbeatResponse = true; } } catch (e) { // JSON解析失败,不是JSON格式的心跳响应 } } if (isHeartbeatResponse) { // 收到心跳响应,清除超时定时器 this.clearHeartbeatTimeout(); return; } // 传递给业务代码 if (this.userOnMessage) { this.userOnMessage(event); } }; this.ws.onclose = (event) => { console.log(`WebSocket连接关闭: ${this.path}`, event.code, event.reason); this.stopHeartbeat(); // 先调用业务代码的关闭处理 if (this.userOnClose) { this.userOnClose(event); } // 如果不是手动关闭,或者是心跳超时导致的关闭,则重连 if (!this.isManualClose || this.isHeartbeatTimeout) { this.scheduleReconnect(); } // 重置心跳超时标志 this.isHeartbeatTimeout = false; }; this.ws.onerror = (event) => { console.error(`WebSocket连接错误: ${this.path}`, event); this.stopHeartbeat(); if (this.userOnError) { this.userOnError(event); } }; } // 开始心跳检测 private startHeartbeat(): void { this.stopHeartbeat(); console.log(`开始心跳检测: ${this.path}, 间隔: ${WS_CONFIG.heartbeatInterval}ms`); this.heartbeatTimer = setInterval(() => { if (this.ws.readyState === WebSocket.OPEN) { this.ws.send(WS_CONFIG.heartbeatMessage); // 只有在没有进行超时检测时才设置新的超时检测 if (!this.heartbeatTimeoutTimer) { this.startHeartbeatTimeout(); } } }, WS_CONFIG.heartbeatInterval); } // 停止心跳检测 private stopHeartbeat(): void { if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = undefined; } this.clearHeartbeatTimeout(); } // 开始心跳响应超时检测 private startHeartbeatTimeout(): void { // 不再自动清除,只在收到响应时清除 this.heartbeatTimeoutTimer = setTimeout(() => { console.log(`心跳响应超时: ${this.path}, ${WS_CONFIG.heartbeatTimeout}ms内未收到响应,主动断开连接`); // 设置心跳超时标志,触发重连 this.isHeartbeatTimeout = true; this.ws.close(1000, 'Heartbeat timeout'); // 使用正常关闭状态码,通过标志来判断是否重连 }, WS_CONFIG.heartbeatTimeout); } // 清除心跳响应超时检测 private clearHeartbeatTimeout(): void { if (this.heartbeatTimeoutTimer) { clearTimeout(this.heartbeatTimeoutTimer); this.heartbeatTimeoutTimer = undefined; } } // 安排重连 private scheduleReconnect(): void { if (this.isManualClose || this.reconnectAttempts >= WS_CONFIG.maxReconnectAttempts) { console.log(`停止重连: ${this.path}, 手动关闭: ${this.isManualClose}, 重连次数: ${this.reconnectAttempts}`); return; } this.reconnectAttempts++; // 指数退避重连策略 const delay = Math.min( WS_CONFIG.reconnectBaseDelay * Math.pow(2, this.reconnectAttempts - 1), WS_CONFIG.maxReconnectDelay, ); console.log( `WebSocket将在${delay}ms后重连: ${this.path} (${this.reconnectAttempts}/${WS_CONFIG.maxReconnectAttempts})`, ); this.reconnectTimer = setTimeout(() => { this.reconnect(); }, delay); } // 重连逻辑 private reconnect(): void { if (this.isManualClose) return; console.log(`WebSocket重连尝试: ${this.path} (${this.reconnectAttempts}/${WS_CONFIG.maxReconnectAttempts})`); // 创建新的WebSocket连接 this.ws = new WebSocket(this.baseUrl + this.path); this.setupHandlers(); } // 清理重连定时器 private clearReconnectTimer(): void { if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = undefined; } } // 公开的WebSocket属性和方法 get readyState(): number { return this.ws.readyState; } get url(): string { return this.ws.url; } get protocol(): string { return this.ws.protocol; } get extensions(): string { return this.ws.extensions; } get bufferedAmount(): number { return this.ws.bufferedAmount; } get binaryType(): BinaryType { return this.ws.binaryType; } set binaryType(value: BinaryType) { this.ws.binaryType = value; } // 事件处理器属性 get onopen(): ((event: Event) => void) | null { return this.userOnOpen; } set onopen(handler: ((event: Event) => void) | null) { this.userOnOpen = handler; } get onmessage(): ((event: MessageEvent) => void) | null { return this.userOnMessage; } set onmessage(handler: ((event: MessageEvent) => void) | null) { this.userOnMessage = handler; } get onclose(): ((event: CloseEvent) => void) | null { return this.userOnClose; } set onclose(handler: ((event: CloseEvent) => void) | null) { this.userOnClose = handler; } get onerror(): ((event: Event) => void) | null { return this.userOnError; } set onerror(handler: ((event: Event) => void) | null) { this.userOnError = handler; } // WebSocket方法 send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void { this.ws.send(data); } close(code?: number, reason?: string): void { console.log(`手动关闭WebSocket: ${this.path}`); this.isManualClose = true; this.isHeartbeatTimeout = false; // 手动关闭时重置心跳超时标志 this.stopHeartbeat(); this.clearReconnectTimer(); this.ws.close(code, reason); } addEventListener( type: K, listener: (this: WebSocket, ev: WebSocketEventMap[K]) => any, options?: boolean | AddEventListenerOptions, ): void { this.ws.addEventListener(type, listener, options); } removeEventListener( type: K, listener: (this: WebSocket, ev: WebSocketEventMap[K]) => any, options?: boolean | EventListenerOptions, ): void { this.ws.removeEventListener(type, listener, options); } dispatchEvent(event: Event): boolean { return this.ws.dispatchEvent(event); } // 常量 static readonly CONNECTING = WebSocket.CONNECTING; static readonly OPEN = WebSocket.OPEN; static readonly CLOSING = WebSocket.CLOSING; static readonly CLOSED = WebSocket.CLOSED; readonly CONNECTING = WebSocket.CONNECTING; readonly OPEN = WebSocket.OPEN; readonly CLOSING = WebSocket.CLOSING; readonly CLOSED = WebSocket.CLOSED; } function create(path: string): Promise { const baseUrl = import.meta.env.ENV_WEBSOCKET_BASE ?? ''; const ws = new EnhancedWebSocket(path, baseUrl) as any; return new Promise((resolve, reject) => { const timeout = setTimeout(() => { ws.close(); reject(new Error('WebSocket connection timeout')); }, 10000); // 10秒连接超时 ws.addEventListener('open', () => { clearTimeout(timeout); resolve(ws); }); ws.addEventListener('error', (e: any) => { clearTimeout(timeout); reject(e); }); }); } export default { create };