UNPKG

xxm-test-js

Version:
358 lines 12 kB
"use strict"; /** * WebSocket 客户端封装类 * * 使用场景:用于建立和管理 WebSocket 连接,提供自动重连、心跳检测、消息队列等功能。 * 适用于需要实时双向通信的应用,如聊天应用、实时数据推送、在线协作等。 * * 主要特性: * - 自动重连:连接断开后自动尝试重新连接 * - 心跳检测:定时发送心跳包保持连接活跃 * - 消息队列:连接未建立时缓存消息,连接成功后自动发送 * - 事件监听:支持监听连接、断开、消息、错误等事件 * - 连接状态管理:提供完整的连接状态跟踪 * * @example * ```typescript * const ws = new WebSocketClient('ws://localhost:8080', { * reconnectInterval: 3000, * heartbeatInterval: 30000, * heartbeatMessage: 'ping' * }); * * ws.on('open', () => console.log('连接成功')); * ws.on('message', (data) => console.log('收到消息:', data)); * ws.on('error', (error) => console.error('连接错误:', error)); * ws.on('close', () => console.log('连接关闭')); * * ws.connect(); * ws.send({ type: 'chat', message: 'Hello' }); * ``` */ Object.defineProperty(exports, "__esModule", { value: true }); exports.WebSocketClient = void 0; class WebSocketClient { constructor(url, options = {}) { var _a, _b, _c, _d, _e, _f, _g, _h; this.ws = null; this.reconnectAttempts = 0; this.reconnectTimer = null; this.heartbeatTimer = null; this.connectionTimer = null; this.messageQueue = []; this.eventListeners = new Map(); this.isManualClose = false; this.isReconnectAbandoned = false; this.url = url; this.options = { reconnect: (_a = options.reconnect) !== null && _a !== void 0 ? _a : true, reconnectInterval: (_b = options.reconnectInterval) !== null && _b !== void 0 ? _b : 5000, maxReconnectAttempts: (_c = options.maxReconnectAttempts) !== null && _c !== void 0 ? _c : Infinity, heartbeat: (_d = options.heartbeat) !== null && _d !== void 0 ? _d : true, heartbeatInterval: (_e = options.heartbeatInterval) !== null && _e !== void 0 ? _e : 30000, heartbeatMessage: (_f = options.heartbeatMessage) !== null && _f !== void 0 ? _f : 'ping', connectionTimeout: (_g = options.connectionTimeout) !== null && _g !== void 0 ? _g : 10000, protocols: options.protocols, debug: (_h = options.debug) !== null && _h !== void 0 ? _h : false, }; // 初始化事件监听器集合 ['open', 'close', 'message', 'error', 'reconnect'].forEach((event) => { this.eventListeners.set(event, new Set()); }); } /** * 建立 WebSocket 连接 */ connect() { if (this.ws && this.ws.readyState === WebSocket.OPEN) { this.log('WebSocket 已连接,无需重复连接'); return; } try { this.isManualClose = false; this.isReconnectAbandoned = false; this.log(`正在连接到 ${this.url}...`); // 创建 WebSocket 连接 if (this.options.protocols) { this.ws = new WebSocket(this.url, this.options.protocols); } else { this.ws = new WebSocket(this.url); } // 设置连接超时 this.connectionTimer = setTimeout(() => { if (this.ws && this.ws.readyState !== WebSocket.OPEN) { this.log('连接超时'); this.ws.close(); this.handleReconnect(); } }, this.options.connectionTimeout); // 绑定事件处理器 this.ws.onopen = this.handleOpen.bind(this); this.ws.onclose = this.handleClose.bind(this); this.ws.onmessage = this.handleMessage.bind(this); this.ws.onerror = this.handleError.bind(this); } catch (error) { this.log('连接失败:', error); const errorEvent = error instanceof Error ? error : new Error(String(error)); this.emit('error', errorEvent); this.handleReconnect(); } } /** * 关闭 WebSocket 连接 * @param code 关闭状态码 * @param reason 关闭原因 */ disconnect(code, reason) { this.isManualClose = true; this.clearTimers(); if (this.ws) { this.log('手动关闭连接'); this.ws.close(code, reason); this.ws = null; } } /** * 发送消息 * @param data 要发送的数据(支持字符串、对象等) */ send(data) { if (this.ws && this.ws.readyState === WebSocket.OPEN) { try { const message = typeof data === 'object' && !(data instanceof ArrayBuffer) && !(data instanceof Blob) ? JSON.stringify(data) : data; this.ws.send(message); this.log('发送消息:', data); } catch (error) { this.log('发送消息失败:', error); const errorEvent = error instanceof Error ? error : new Error(String(error)); this.emit('error', errorEvent); } } else { // 检查是否已放弃重连 if (this.isReconnectAbandoned) { this.log('已达到最大重连次数,消息发送失败'); this.emit('error', new Error('WebSocket 连接已失败,无法发送消息')); return; } // 连接未建立,将消息加入队列 this.log('连接未建立,消息已加入队列'); this.messageQueue.push(data); } } /** * 监听事件 * @param event 事件类型 * @param callback 回调函数 */ on(event, callback) { const listeners = this.eventListeners.get(event); if (listeners) { listeners.add(callback); } } /** * 移除事件监听 * @param event 事件类型 * @param callback 回调函数 */ off(event, callback) { const listeners = this.eventListeners.get(event); if (listeners) { listeners.delete(callback); } } /** * 获取当前连接状态 * @returns 连接状态:CONNECTING(0), OPEN(1), CLOSING(2), CLOSED(3) */ getReadyState() { return this.ws ? this.ws.readyState : WebSocket.CLOSED; } /** * 获取连接状态描述 */ getStatus() { const state = this.getReadyState(); const statusMap = { [WebSocket.CONNECTING]: '正在连接', [WebSocket.OPEN]: '已连接', [WebSocket.CLOSING]: '正在关闭', [WebSocket.CLOSED]: '已关闭', }; return statusMap[state] || '未知状态'; } /** * 处理连接打开事件 */ handleOpen(event) { this.log('WebSocket 连接已建立'); // 清除连接超时定时器 if (this.connectionTimer) { clearTimeout(this.connectionTimer); this.connectionTimer = null; } // 重置重连计数 this.reconnectAttempts = 0; // 发送队列中的消息 this.flushMessageQueue(); // 启动心跳 this.startHeartbeat(); this.log('连接已建立'); this.emit('open', event); } /** * 处理连接关闭事件 */ handleClose(event) { this.log(`WebSocket 连接已关闭 [code: ${event.code}, reason: ${event.reason}]`); // 停止心跳 this.stopHeartbeat(); // 触发 close 事件 this.emit('close', event); // 尝试重连 if (!this.isManualClose) { this.handleReconnect(); } } /** * 处理消息接收事件 */ handleMessage(event) { let data = event.data; // 尝试解析 JSON try { data = JSON.parse(event.data); } catch (error) { // 不是 JSON 格式,保持原始数据 console.error('解析 JSON 失败:', error); } this.log('收到消息:', data); this.emit('message', data); } /** * 处理错误事件 */ handleError(error) { this.log('WebSocket 错误:', error); const errorEvent = error instanceof Error ? error : new Error(String(error)); this.emit('error', errorEvent); } /** * 处理重连逻辑 */ handleReconnect() { if (!this.options.reconnect || this.isManualClose) { return; } if (this.reconnectAttempts >= this.options.maxReconnectAttempts) { this.log(`已达到最大重连次数 (${this.options.maxReconnectAttempts}),停止重连`); this.isReconnectAbandoned = true; // 清空消息队列,避免内存泄漏 this.messageQueue = []; return; } this.reconnectAttempts++; this.log(`准备第 ${this.reconnectAttempts} 次重连,${this.options.reconnectInterval}ms 后执行`); this.reconnectTimer = setTimeout(() => { this.emit('reconnect', { attempt: this.reconnectAttempts }); this.connect(); }, this.options.reconnectInterval); } /** * 启动心跳检测 */ startHeartbeat() { if (!this.options.heartbeat) { return; } this.stopHeartbeat(); this.heartbeatTimer = setInterval(() => { if (this.ws && this.ws.readyState === WebSocket.OPEN) { this.send(this.options.heartbeatMessage); this.log('发送心跳'); } }, this.options.heartbeatInterval); } /** * 停止心跳检测 */ stopHeartbeat() { if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = null; } } /** * 发送消息队列中的所有消息 */ flushMessageQueue() { if (this.messageQueue.length > 0) { this.log(`发送队列中的 ${this.messageQueue.length} 条消息`); while (this.messageQueue.length > 0) { const message = this.messageQueue.shift(); if (message !== undefined) { this.send(message); } } } } /** * 清除所有定时器 */ clearTimers() { if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = null; } if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = null; } if (this.connectionTimer) { clearTimeout(this.connectionTimer); this.connectionTimer = null; } } /** * 触发事件 */ emit(event, data) { const listeners = this.eventListeners.get(event); if (listeners) { listeners.forEach((callback) => { try { callback(data); } catch (error) { this.log(`事件 ${event} 的回调函数执行出错:`, error); } }); } } /** * 输出日志 */ log(...args) { if (this.options.debug) { console.log('[WebSocketClient]', ...args); } } /** * 销毁实例,清理所有资源 */ destroy() { this.disconnect(); this.messageQueue = []; this.eventListeners.clear(); this.log('WebSocketClient 实例已销毁'); } } exports.WebSocketClient = WebSocketClient; //# sourceMappingURL=WebSocketClient.js.map