329 lines
9.2 KiB
TypeScript
Raw Normal View History

// 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<K extends keyof WebSocketEventMap>(
type: K,
listener: (this: WebSocket, ev: WebSocketEventMap[K]) => any,
options?: boolean | AddEventListenerOptions,
): void {
this.ws.addEventListener(type, listener, options);
}
removeEventListener<K extends keyof WebSocketEventMap>(
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;
}
2025-05-25 00:07:22 +08:00
function create(path: string): Promise<WebSocket> {
const baseUrl = import.meta.env.ENV_WEBSOCKET_BASE ?? '';
const ws = new EnhancedWebSocket(path, baseUrl) as any;
2025-05-25 00:07:22 +08:00
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);
});
2025-05-25 00:07:22 +08:00
});
}
2025-05-25 00:07:22 +08:00
export default { create };