2025-06-19 10:48:08 +08:00
|
|
|
|
// WebSocket全局配置
|
|
|
|
|
const WS_CONFIG = {
|
2025-06-20 09:03:15 +08:00
|
|
|
|
heartbeatInterval: 10000, // 30秒心跳间隔
|
|
|
|
|
heartbeatTimeout: 10000, // 心跳响应超时时间(10秒,给服务器更多响应时间)
|
2025-06-19 10:48:08 +08:00
|
|
|
|
maxReconnectAttempts: 5, // 最大重连次数
|
2025-06-20 09:03:15 +08:00
|
|
|
|
reconnectBaseDelay: 500, // 重连基础延迟0.5秒(更快重连)
|
|
|
|
|
maxReconnectDelay: 10000, // 最大重连延迟10秒(减少等待时间)
|
2025-06-19 10:48:08 +08:00
|
|
|
|
heartbeatMessage: 'ping', // 心跳消息
|
|
|
|
|
heartbeatResponseType: 'pong', // 心跳响应类型
|
|
|
|
|
};
|
|
|
|
|
|
2025-06-29 19:18:59 +08:00
|
|
|
|
// WebSocket关闭码说明
|
|
|
|
|
const WS_CLOSE_CODES: Record<number, string> = {
|
|
|
|
|
1000: '正常关闭',
|
|
|
|
|
1001: '端点离开(如页面关闭)',
|
|
|
|
|
1002: '协议错误',
|
|
|
|
|
1003: '接收到不支持的数据类型',
|
|
|
|
|
1004: '保留码',
|
|
|
|
|
1005: '未收到预期的状态码',
|
|
|
|
|
1006: '连接异常关闭',
|
|
|
|
|
1007: '接收到无效的frame payload数据',
|
|
|
|
|
1008: '策略违规(服务器主动关闭)',
|
|
|
|
|
1009: '消息过大',
|
|
|
|
|
1010: '扩展协商失败',
|
|
|
|
|
1011: '服务器遇到意外情况',
|
|
|
|
|
1012: '服务重启',
|
|
|
|
|
1013: '稍后重试',
|
|
|
|
|
1014: '网关超时',
|
|
|
|
|
1015: 'TLS握手失败',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 获取关闭原因描述
|
|
|
|
|
function getCloseReasonDescription(code: number, reason: string): string {
|
|
|
|
|
const codeDescription = WS_CLOSE_CODES[code] || '未知关闭码';
|
|
|
|
|
const reasonText = reason ? ` (原因: ${reason})` : '';
|
|
|
|
|
return `${code} - ${codeDescription}${reasonText}`;
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-19 10:48:08 +08:00
|
|
|
|
// 增强的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;
|
2025-06-29 19:18:59 +08:00
|
|
|
|
private connectionStartTime: number = 0;
|
|
|
|
|
private lastHeartbeatTime: number = 0;
|
|
|
|
|
private heartbeatSentCount: number = 0;
|
|
|
|
|
private heartbeatReceivedCount: number = 0;
|
2025-06-19 10:48:08 +08:00
|
|
|
|
|
|
|
|
|
constructor(path: string, baseUrl: string) {
|
|
|
|
|
this.path = path;
|
|
|
|
|
this.baseUrl = baseUrl;
|
2025-06-29 19:18:59 +08:00
|
|
|
|
this.connectionStartTime = Date.now();
|
|
|
|
|
console.log(`🔗 开始创建WebSocket连接: ${this.path}, 基础URL: ${baseUrl}`);
|
2025-06-19 10:48:08 +08:00
|
|
|
|
this.ws = new WebSocket(baseUrl + path);
|
|
|
|
|
this.setupHandlers();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 设置事件处理器
|
|
|
|
|
private setupHandlers(): void {
|
|
|
|
|
this.ws.onopen = (event) => {
|
2025-06-29 19:18:59 +08:00
|
|
|
|
const connectionDuration = Date.now() - this.connectionStartTime;
|
|
|
|
|
console.log(`✅ WebSocket连接已建立: ${this.path}, 耗时: ${connectionDuration}ms`);
|
2025-06-19 10:48:08 +08:00
|
|
|
|
this.reconnectAttempts = 0;
|
2025-06-29 19:18:59 +08:00
|
|
|
|
this.heartbeatSentCount = 0;
|
|
|
|
|
this.heartbeatReceivedCount = 0;
|
2025-06-19 10:48:08 +08:00
|
|
|
|
this.clearReconnectTimer();
|
2025-06-20 09:03:15 +08:00
|
|
|
|
|
|
|
|
|
// 🔧 优化:连接建立后立即发送一次心跳,然后开始定期心跳
|
|
|
|
|
if (this.ws.readyState === WebSocket.OPEN) {
|
2025-06-29 19:18:59 +08:00
|
|
|
|
console.log(`💓 连接建立后发送初始心跳: ${this.path}`);
|
2025-06-20 09:03:15 +08:00
|
|
|
|
this.ws.send(WS_CONFIG.heartbeatMessage);
|
2025-06-29 19:18:59 +08:00
|
|
|
|
this.heartbeatSentCount++;
|
|
|
|
|
this.lastHeartbeatTime = Date.now();
|
2025-06-20 09:03:15 +08:00
|
|
|
|
this.startHeartbeatTimeout();
|
|
|
|
|
}
|
2025-06-19 10:48:08 +08:00
|
|
|
|
this.startHeartbeat();
|
|
|
|
|
|
|
|
|
|
if (this.userOnOpen) {
|
|
|
|
|
this.userOnOpen(event);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.ws.onmessage = (event) => {
|
|
|
|
|
const messageData = event.data;
|
|
|
|
|
|
2025-06-20 09:03:15 +08:00
|
|
|
|
// 🔧 优化:收到任何消息都说明连接正常,清除心跳超时检测
|
|
|
|
|
this.clearHeartbeatTimeout();
|
|
|
|
|
|
2025-06-19 10:48:08 +08:00
|
|
|
|
// 检查是否为心跳响应(支持字符串和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;
|
|
|
|
|
}
|
2025-06-29 19:18:59 +08:00
|
|
|
|
} catch {
|
2025-06-19 10:48:08 +08:00
|
|
|
|
// JSON解析失败,不是JSON格式的心跳响应
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isHeartbeatResponse) {
|
2025-06-29 19:18:59 +08:00
|
|
|
|
this.heartbeatReceivedCount++;
|
|
|
|
|
const responseTime = Date.now() - this.lastHeartbeatTime;
|
2025-07-18 15:03:35 +08:00
|
|
|
|
console.log(
|
|
|
|
|
`💗 收到心跳响应: ${this.path}, 响应时间: ${responseTime}ms, 已发送: ${this.heartbeatSentCount}, 已接收: ${this.heartbeatReceivedCount}`,
|
|
|
|
|
);
|
2025-06-20 09:03:15 +08:00
|
|
|
|
// 心跳响应,不传递给业务代码
|
2025-06-19 10:48:08 +08:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 传递给业务代码
|
|
|
|
|
if (this.userOnMessage) {
|
|
|
|
|
this.userOnMessage(event);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.ws.onclose = (event) => {
|
2025-06-29 19:18:59 +08:00
|
|
|
|
const connectionDuration = Date.now() - this.connectionStartTime;
|
|
|
|
|
const closeReason = getCloseReasonDescription(event.code, event.reason);
|
2025-07-18 15:03:35 +08:00
|
|
|
|
|
2025-06-29 19:18:59 +08:00
|
|
|
|
console.log(`❌ WebSocket连接关闭: ${this.path}`);
|
|
|
|
|
console.log(` └─ 关闭原因: ${closeReason}`);
|
|
|
|
|
console.log(` └─ 连接持续时间: ${connectionDuration}ms`);
|
|
|
|
|
console.log(` └─ 心跳统计: 发送${this.heartbeatSentCount}次, 接收${this.heartbeatReceivedCount}次`);
|
|
|
|
|
console.log(` └─ 是否手动关闭: ${this.isManualClose}`);
|
|
|
|
|
console.log(` └─ 是否心跳超时: ${this.isHeartbeatTimeout}`);
|
2025-07-18 15:03:35 +08:00
|
|
|
|
|
2025-06-29 19:18:59 +08:00
|
|
|
|
// 分析断连原因
|
|
|
|
|
this.analyzeDisconnectionReason(event.code, event.reason);
|
2025-07-18 15:03:35 +08:00
|
|
|
|
|
2025-06-19 10:48:08 +08:00
|
|
|
|
this.stopHeartbeat();
|
|
|
|
|
|
|
|
|
|
// 先调用业务代码的关闭处理
|
|
|
|
|
if (this.userOnClose) {
|
|
|
|
|
this.userOnClose(event);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果不是手动关闭,或者是心跳超时导致的关闭,则重连
|
|
|
|
|
if (!this.isManualClose || this.isHeartbeatTimeout) {
|
|
|
|
|
this.scheduleReconnect();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 重置心跳超时标志
|
|
|
|
|
this.isHeartbeatTimeout = false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.ws.onerror = (event) => {
|
2025-06-29 19:18:59 +08:00
|
|
|
|
console.error(`🔥 WebSocket连接错误: ${this.path}`, event);
|
|
|
|
|
console.log(` └─ 连接状态: ${this.getReadyStateText()}`);
|
|
|
|
|
console.log(` └─ 连接URL: ${this.ws.url}`);
|
2025-06-19 10:48:08 +08:00
|
|
|
|
this.stopHeartbeat();
|
|
|
|
|
|
|
|
|
|
if (this.userOnError) {
|
|
|
|
|
this.userOnError(event);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-29 19:18:59 +08:00
|
|
|
|
// 分析断连原因
|
|
|
|
|
private analyzeDisconnectionReason(code: number, reason: string): void {
|
|
|
|
|
console.log(`🔍 断连原因分析: ${this.path}`);
|
|
|
|
|
if (reason) {
|
|
|
|
|
console.log(` └─ 服务器提供的关闭原因: ${reason}`);
|
|
|
|
|
}
|
2025-07-18 15:03:35 +08:00
|
|
|
|
|
2025-06-29 19:18:59 +08:00
|
|
|
|
switch (code) {
|
|
|
|
|
case 1000:
|
|
|
|
|
console.log(' └─ 正常关闭,可能是服务器主动关闭或客户端主动关闭');
|
|
|
|
|
break;
|
|
|
|
|
case 1001:
|
|
|
|
|
console.log(' └─ 端点离开,可能是页面关闭或刷新');
|
|
|
|
|
break;
|
|
|
|
|
case 1006:
|
|
|
|
|
console.log(' └─ 连接异常关闭,可能是网络问题或服务器崩溃');
|
|
|
|
|
break;
|
|
|
|
|
case 1008:
|
|
|
|
|
console.log(' └─ 策略违规,服务器主动关闭连接');
|
|
|
|
|
console.log(' └─ 可能原因: 1) 服务器负载过高 2) 连接频率过快 3) 服务器重启 4) 业务逻辑限制');
|
|
|
|
|
break;
|
|
|
|
|
case 1011:
|
|
|
|
|
console.log(' └─ 服务器遇到意外情况,可能是服务器内部错误');
|
|
|
|
|
break;
|
|
|
|
|
case 1012:
|
|
|
|
|
console.log(' └─ 服务重启,服务器正在重启');
|
|
|
|
|
break;
|
|
|
|
|
case 1013:
|
|
|
|
|
console.log(' └─ 稍后重试,服务器暂时无法处理请求');
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
if (code >= 3000 && code <= 3999) {
|
|
|
|
|
console.log(' └─ 应用程序定义的关闭码,可能是业务逻辑关闭');
|
|
|
|
|
} else if (code >= 4000 && code <= 4999) {
|
|
|
|
|
console.log(' └─ 私有关闭码,可能是框架或库定义的关闭原因');
|
|
|
|
|
} else {
|
|
|
|
|
console.log(' └─ 未知关闭码,需要进一步调查');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 心跳统计分析
|
|
|
|
|
if (this.heartbeatSentCount > this.heartbeatReceivedCount) {
|
|
|
|
|
const missedHeartbeats = this.heartbeatSentCount - this.heartbeatReceivedCount;
|
|
|
|
|
console.log(` └─ 心跳分析: 有${missedHeartbeats}个心跳未收到响应,可能存在网络延迟或服务器响应问题`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取连接状态文本
|
|
|
|
|
private getReadyStateText(): string {
|
|
|
|
|
switch (this.ws.readyState) {
|
2025-07-18 15:03:35 +08:00
|
|
|
|
case WebSocket.CONNECTING:
|
|
|
|
|
return 'CONNECTING(0)';
|
|
|
|
|
case WebSocket.OPEN:
|
|
|
|
|
return 'OPEN(1)';
|
|
|
|
|
case WebSocket.CLOSING:
|
|
|
|
|
return 'CLOSING(2)';
|
|
|
|
|
case WebSocket.CLOSED:
|
|
|
|
|
return 'CLOSED(3)';
|
|
|
|
|
default:
|
|
|
|
|
return `UNKNOWN(${this.ws.readyState})`;
|
2025-06-29 19:18:59 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-19 10:48:08 +08:00
|
|
|
|
// 开始心跳检测
|
|
|
|
|
private startHeartbeat(): void {
|
|
|
|
|
this.stopHeartbeat();
|
2025-06-29 19:18:59 +08:00
|
|
|
|
console.log(`💓 开始心跳检测: ${this.path}, 间隔: ${WS_CONFIG.heartbeatInterval}ms`);
|
2025-06-19 10:48:08 +08:00
|
|
|
|
this.heartbeatTimer = setInterval(() => {
|
|
|
|
|
if (this.ws.readyState === WebSocket.OPEN) {
|
2025-06-29 19:18:59 +08:00
|
|
|
|
this.heartbeatSentCount++;
|
|
|
|
|
this.lastHeartbeatTime = Date.now();
|
|
|
|
|
console.log(`💓 发送心跳: ${this.path} (#${this.heartbeatSentCount})`);
|
2025-06-19 10:48:08 +08:00
|
|
|
|
this.ws.send(WS_CONFIG.heartbeatMessage);
|
|
|
|
|
|
|
|
|
|
// 只有在没有进行超时检测时才设置新的超时检测
|
|
|
|
|
if (!this.heartbeatTimeoutTimer) {
|
|
|
|
|
this.startHeartbeatTimeout();
|
|
|
|
|
}
|
2025-06-29 19:18:59 +08:00
|
|
|
|
} else {
|
|
|
|
|
console.log(`⚠️ 心跳检测时发现连接状态异常: ${this.path}, 状态: ${this.getReadyStateText()}`);
|
2025-06-19 10:48:08 +08:00
|
|
|
|
}
|
|
|
|
|
}, WS_CONFIG.heartbeatInterval);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 停止心跳检测
|
|
|
|
|
private stopHeartbeat(): void {
|
|
|
|
|
if (this.heartbeatTimer) {
|
2025-06-29 19:18:59 +08:00
|
|
|
|
console.log(`🛑 停止心跳检测: ${this.path}`);
|
2025-06-19 10:48:08 +08:00
|
|
|
|
clearInterval(this.heartbeatTimer);
|
|
|
|
|
this.heartbeatTimer = undefined;
|
|
|
|
|
}
|
|
|
|
|
this.clearHeartbeatTimeout();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 开始心跳响应超时检测
|
|
|
|
|
private startHeartbeatTimeout(): void {
|
|
|
|
|
// 不再自动清除,只在收到响应时清除
|
|
|
|
|
this.heartbeatTimeoutTimer = setTimeout(() => {
|
2025-06-29 19:18:59 +08:00
|
|
|
|
console.log(`💔 心跳响应超时: ${this.path}, ${WS_CONFIG.heartbeatTimeout}ms内未收到响应,主动断开连接`);
|
|
|
|
|
console.log(` └─ 心跳统计: 发送${this.heartbeatSentCount}次, 接收${this.heartbeatReceivedCount}次`);
|
2025-06-19 10:48:08 +08:00
|
|
|
|
// 设置心跳超时标志,触发重连
|
|
|
|
|
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) {
|
2025-06-29 19:18:59 +08:00
|
|
|
|
console.log(`🚫 停止重连: ${this.path}, 手动关闭: ${this.isManualClose}, 重连次数: ${this.reconnectAttempts}`);
|
2025-06-19 10:48:08 +08:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.reconnectAttempts++;
|
|
|
|
|
|
|
|
|
|
// 指数退避重连策略
|
|
|
|
|
const delay = Math.min(
|
|
|
|
|
WS_CONFIG.reconnectBaseDelay * Math.pow(2, this.reconnectAttempts - 1),
|
|
|
|
|
WS_CONFIG.maxReconnectDelay,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
console.log(
|
2025-06-29 19:18:59 +08:00
|
|
|
|
`🔄 WebSocket将在${delay}ms后重连: ${this.path} (${this.reconnectAttempts}/${WS_CONFIG.maxReconnectAttempts})`,
|
2025-06-19 10:48:08 +08:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
this.reconnectTimer = setTimeout(() => {
|
|
|
|
|
this.reconnect();
|
|
|
|
|
}, delay);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 重连逻辑
|
|
|
|
|
private reconnect(): void {
|
|
|
|
|
if (this.isManualClose) return;
|
|
|
|
|
|
2025-06-29 19:18:59 +08:00
|
|
|
|
console.log(`🔄 WebSocket重连尝试: ${this.path} (${this.reconnectAttempts}/${WS_CONFIG.maxReconnectAttempts})`);
|
|
|
|
|
this.connectionStartTime = Date.now();
|
2025-06-19 10:48:08 +08:00
|
|
|
|
|
|
|
|
|
// 创建新的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 {
|
2025-06-29 19:18:59 +08:00
|
|
|
|
console.log(`👋 手动关闭WebSocket: ${this.path}`);
|
2025-06-19 10:48:08 +08:00
|
|
|
|
this.isManualClose = true;
|
|
|
|
|
this.isHeartbeatTimeout = false; // 手动关闭时重置心跳超时标志
|
|
|
|
|
this.stopHeartbeat();
|
|
|
|
|
this.clearReconnectTimer();
|
|
|
|
|
this.ws.close(code, reason);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addEventListener<K extends keyof WebSocketEventMap>(
|
|
|
|
|
type: K,
|
2025-06-29 19:18:59 +08:00
|
|
|
|
listener: (this: WebSocket, ev: WebSocketEventMap[K]) => void,
|
2025-06-19 10:48:08 +08:00
|
|
|
|
options?: boolean | AddEventListenerOptions,
|
|
|
|
|
): void {
|
|
|
|
|
this.ws.addEventListener(type, listener, options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
removeEventListener<K extends keyof WebSocketEventMap>(
|
|
|
|
|
type: K,
|
2025-06-29 19:18:59 +08:00
|
|
|
|
listener: (this: WebSocket, ev: WebSocketEventMap[K]) => void,
|
2025-06-19 10:48:08 +08:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-25 00:07:22 +08:00
|
|
|
|
function create(path: string): Promise<WebSocket> {
|
2025-07-18 15:03:35 +08:00
|
|
|
|
let baseUrl = '';
|
|
|
|
|
if (path.includes(import.meta.env.ENV_STORAGE_WEBSOCKET_BASE)) {
|
|
|
|
|
baseUrl = '';
|
|
|
|
|
} else {
|
|
|
|
|
baseUrl = import.meta.env.ENV_WEBSOCKET_BASE ?? '';
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-29 19:18:59 +08:00
|
|
|
|
const ws = new EnhancedWebSocket(path, baseUrl) as WebSocket;
|
2025-06-19 10:48:08 +08:00
|
|
|
|
|
2025-05-25 00:07:22 +08:00
|
|
|
|
return new Promise((resolve, reject) => {
|
2025-06-19 10:48:08 +08:00
|
|
|
|
const timeout = setTimeout(() => {
|
|
|
|
|
ws.close();
|
|
|
|
|
reject(new Error('WebSocket connection timeout'));
|
|
|
|
|
}, 10000); // 10秒连接超时
|
|
|
|
|
|
|
|
|
|
ws.addEventListener('open', () => {
|
|
|
|
|
clearTimeout(timeout);
|
|
|
|
|
resolve(ws);
|
|
|
|
|
});
|
|
|
|
|
|
2025-06-29 19:18:59 +08:00
|
|
|
|
ws.addEventListener('error', (error: Event) => {
|
2025-06-19 10:48:08 +08:00
|
|
|
|
clearTimeout(timeout);
|
2025-06-29 19:18:59 +08:00
|
|
|
|
reject(error);
|
2025-06-19 10:48:08 +08:00
|
|
|
|
});
|
2025-05-25 00:07:22 +08:00
|
|
|
|
});
|
|
|
|
|
}
|
2025-06-19 10:48:08 +08:00
|
|
|
|
|
2025-05-25 00:07:22 +08:00
|
|
|
|
export default { create };
|