websocket-pro-client
Advanced tools
| import { WebSocketConfig } from '../types'; | ||
| export declare const DEFAULT_CONFIG: Required<WebSocketConfig>; |
| export declare const deepMerge: (target: any, source: any) => any; | ||
| export declare const isEqual: (target: any, source: any) => boolean; |
@@ -0,14 +1,16 @@ | ||
| import { HeartbeatConfig, WebSocketConfig } from '../types'; | ||
| import { EventEmitter } from "./EventEmitter"; | ||
| export declare class Heartbeat extends EventEmitter { | ||
| private config; | ||
| private readonly sendPing; | ||
| private intervalMs; | ||
| private timeoutMs; | ||
| private lastPongTime; | ||
| private intervalId?; | ||
| private timeoutId?; | ||
| constructor(intervalMs: number, timeoutMs: number, sendPing: () => void); | ||
| constructor(config: HeartbeatConfig, sendPing: () => void); | ||
| start(): void; | ||
| stop(): void; | ||
| handleDefaultTimeout(cb?: () => void): void; | ||
| recordPong(): void; | ||
| getLastPongTime(): number; | ||
| updateConfig(config?: WebSocketConfig): void; | ||
| } |
| export declare class TaskScheduler { | ||
| private readonly maxConcurrent; | ||
| private maxConcurrent; | ||
| private readonly onTaskError?; | ||
@@ -10,2 +10,3 @@ private queue; | ||
| clear(): void; | ||
| updateThresholds(maxConcurrent?: number): void; | ||
| } |
@@ -6,3 +6,4 @@ import { EventEmitter } from "./EventEmitter"; | ||
| private readonly protocols; | ||
| private readonly config; | ||
| private config; | ||
| private currentConfig; | ||
| private socket; | ||
@@ -12,5 +13,8 @@ private reconnectAttempts; | ||
| private readonly messageQueue; | ||
| private readonly heartbeat; | ||
| private heartbeat?; | ||
| private readonly scheduler; | ||
| private isUpdatingConfig; | ||
| private configQueue; | ||
| constructor(url: string, protocols: string[], config: Required<WebSocketConfig>); | ||
| private initHeartbeat; | ||
| private connect; | ||
@@ -23,2 +27,8 @@ private sendRaw; | ||
| reconnect(): void; | ||
| updateConfig(newConfig: WebSocketConfig): Promise<void>; | ||
| private applyConfigSafely; | ||
| private handleConfigChange; | ||
| private applyConfig; | ||
| private reInitHeartbeat; | ||
| private resetReconnectTimer; | ||
| } |
@@ -11,2 +11,12 @@ export interface Serializer<T = any> { | ||
| } | ||
| export type HeartbeatConfig = { | ||
| /** 心跳间隔(ms) (默认: 25000) */ | ||
| interval?: number; | ||
| /** 心跳超时(ms) (默认: 10000) */ | ||
| timeout?: number; | ||
| /** 心跳消息 (默认: "PING") */ | ||
| message?: string; | ||
| /** 自定义超时处理 */ | ||
| onTimeout?: () => void; | ||
| }; | ||
| export interface WebSocketConfig { | ||
@@ -21,8 +31,6 @@ /** 最大重连尝试次数 (默认: 10) */ | ||
| maxReconnectDelay?: number; | ||
| /** 心跳间隔(ms) (默认: 25000) */ | ||
| heartbeatInterval?: number; | ||
| /** 心跳超时(ms) (默认: 10000) */ | ||
| heartbeatTimeout?: number; | ||
| /** 连接池大小 (默认: 5) */ | ||
| connectionPoolSize?: number; | ||
| /** 最大并行任务数 (默认: 1) */ | ||
| maxConcurrent?: number; | ||
| /** 默认消息优先级 (默认: 1) */ | ||
@@ -34,2 +42,6 @@ defaultPriority?: number; | ||
| serializer?: Serializer; | ||
| /** 是否需要心跳 (默认: true) */ | ||
| isNeedHeartbeat?: boolean; | ||
| /** 心跳配置 */ | ||
| heartbeat?: HeartbeatConfig; | ||
| } | ||
@@ -36,0 +48,0 @@ export type WebSocketEvent = "open" | "message" | "close" | "error" | "reconnect" | "heartbeat" | "latency"; |
+179
-87
@@ -1,2 +0,22 @@ | ||
| class c { | ||
| const h = { | ||
| maxReconnectAttempts: 10, | ||
| reconnectDelay: 1e3, | ||
| reconnectExponent: 1.5, | ||
| maxReconnectDelay: 3e4, | ||
| connectionPoolSize: 5, | ||
| maxConcurrent: 1, | ||
| defaultPriority: 1, | ||
| enableCompression: !1, | ||
| serializer: { | ||
| serialize: JSON.stringify, | ||
| deserialize: JSON.parse | ||
| }, | ||
| isNeedHeartbeat: !0, | ||
| heartbeat: { | ||
| interval: 25e3, | ||
| timeout: 1e4, | ||
| message: "PING" | ||
| } | ||
| }; | ||
| class u { | ||
| constructor() { | ||
@@ -9,10 +29,10 @@ this.events = {}; | ||
| off(e, t) { | ||
| this.events[e] && (this.events[e] = this.events[e].filter((s) => s !== t)); | ||
| this.events[e] && (this.events[e] = this.events[e].filter((i) => i !== t)); | ||
| } | ||
| emit(e, ...t) { | ||
| this.events[e] && this.events[e].forEach((s) => { | ||
| this.events[e] && this.events[e].forEach((i) => { | ||
| try { | ||
| s(...t); | ||
| } catch (i) { | ||
| console.error(`Event "${e}" listener error:`, i); | ||
| i(...t); | ||
| } catch (s) { | ||
| console.error(`Event "${e}" listener error:`, s); | ||
| } | ||
@@ -22,6 +42,6 @@ }); | ||
| once(e, t) { | ||
| const s = (...i) => { | ||
| this.off(e, s), t(...i); | ||
| const i = (...s) => { | ||
| this.off(e, i), t(...s); | ||
| }; | ||
| this.on(e, s); | ||
| this.on(e, i); | ||
| } | ||
@@ -32,12 +52,12 @@ removeAllListeners(e) { | ||
| } | ||
| class l extends c { | ||
| constructor(e, t, s) { | ||
| super(), this.sendPing = s, this.lastPongTime = 0, this.intervalMs = e, this.timeoutMs = t; | ||
| class f extends u { | ||
| constructor(e = {}, t) { | ||
| super(), this.config = e, this.sendPing = t, this.lastPongTime = 0; | ||
| } | ||
| start() { | ||
| this.stop(), this.lastPongTime = Date.now(), this.intervalId = setInterval(() => { | ||
| this.config || console.log("[Heartbeat] config is empty"), this.stop(), this.lastPongTime = Date.now(), this.intervalId = setInterval(() => { | ||
| this.sendPing(), this.timeoutId = setTimeout(() => { | ||
| this.emit("timeout"); | ||
| }, this.timeoutMs); | ||
| }, this.intervalMs); | ||
| this.handleDefaultTimeout(this.config.onTimeout); | ||
| }, this.config.timeout); | ||
| }, this.config.interval); | ||
| } | ||
@@ -47,2 +67,5 @@ stop() { | ||
| } | ||
| handleDefaultTimeout(e) { | ||
| this.stop(), e && e(), this.emit("timeout"); | ||
| } | ||
| recordPong() { | ||
@@ -54,4 +77,11 @@ this.lastPongTime = Date.now(), clearTimeout(this.timeoutId), this.emit("pong", Date.now() - this.lastPongTime); | ||
| } | ||
| updateConfig(e) { | ||
| if (!(e != null && e.isNeedHeartbeat)) { | ||
| this.stop(); | ||
| return; | ||
| } | ||
| this.config = { ...this.config, ...e.heartbeat }, this.stop(), this.start(); | ||
| } | ||
| } | ||
| class u { | ||
| class g { | ||
| constructor(e, t) { | ||
@@ -61,12 +91,12 @@ this.maxConcurrent = e, this.onTaskError = t, this.queue = [], this.runningCount = 0; | ||
| add(e, t) { | ||
| return new Promise((s, i) => { | ||
| const n = async () => { | ||
| return new Promise((i, s) => { | ||
| const r = async () => { | ||
| var o; | ||
| try { | ||
| await e(), s(); | ||
| await e(), i(); | ||
| } catch (a) { | ||
| (o = this.onTaskError) == null || o.call(this, a), i(a); | ||
| (o = this.onTaskError) == null || o.call(this, a), s(a); | ||
| } | ||
| }; | ||
| this.queue.push({ task: n, priority: t }), this.queue.sort((o, a) => a.priority - o.priority), this.run(); | ||
| this.queue.push({ task: r, priority: t }), this.queue.sort((o, a) => a.priority - o.priority), this.run(); | ||
| }); | ||
@@ -85,17 +115,57 @@ } | ||
| } | ||
| updateThresholds(e) { | ||
| e !== void 0 && (this.maxConcurrent = e), this.run(); | ||
| } | ||
| } | ||
| class m extends c { | ||
| constructor(e, t, s) { | ||
| super(), this.url = e, this.protocols = t, this.config = s, this.socket = null, this.reconnectAttempts = 0, this.messageQueue = [], this.heartbeat = new l( | ||
| s.heartbeatInterval, | ||
| s.heartbeatTimeout, | ||
| () => this.sendRaw("ping") | ||
| ), this.scheduler = new u(1, (i) => this.emit("error", i)), this.connect(); | ||
| const c = (n, e) => { | ||
| const t = { ...n }; | ||
| for (const i in e) | ||
| e[i] instanceof Object && !Array.isArray(e[i]) ? t[i] = c(n[i] || {}, e[i]) : t[i] = e[i]; | ||
| return t; | ||
| }, l = (n, e) => { | ||
| if (n === e) | ||
| return !0; | ||
| if (n == null || e == null || typeof n != "object" || typeof e != "object") | ||
| return n === e; | ||
| if (Array.isArray(n) && Array.isArray(e)) { | ||
| if (n.length !== e.length) | ||
| return !1; | ||
| for (let s = 0; s < n.length; s++) | ||
| if (!l(n[s], e[s])) | ||
| return !1; | ||
| return !0; | ||
| } | ||
| if (Array.isArray(n) || Array.isArray(e)) | ||
| return !1; | ||
| const t = Object.keys(n), i = Object.keys(e); | ||
| if (t.length !== i.length) | ||
| return !1; | ||
| for (const s of t) | ||
| if (!e.hasOwnProperty(s) || !l(n[s], e[s])) | ||
| return !1; | ||
| return !0; | ||
| }; | ||
| class m extends u { | ||
| constructor(e, t, i) { | ||
| super(), this.url = e, this.protocols = t, this.config = i, this.socket = null, this.reconnectAttempts = 0, this.messageQueue = [], this.isUpdatingConfig = !1, this.configQueue = [], this.currentConfig = c(h, this.config), this.initHeartbeat(), this.scheduler = new g( | ||
| this.currentConfig.maxConcurrent, | ||
| (s) => this.emit("error", s) | ||
| ), this.connect(); | ||
| } | ||
| // 初始化心跳 | ||
| initHeartbeat() { | ||
| this.currentConfig.isNeedHeartbeat && (this.heartbeat = new f(this.currentConfig.heartbeat, () => { | ||
| var e; | ||
| this.send(((e = this.currentConfig.heartbeat) == null ? void 0 : e.message) || "PING"); | ||
| }), this.heartbeat.on("timeout", () => { | ||
| var e; | ||
| console.log("Heartbeat timeout, triggering reconnect..."), ((e = this.socket) == null ? void 0 : e.readyState) === WebSocket.OPEN && this.close(1e3, "heartbeat timeout"), this.scheduleReconnect(); | ||
| })); | ||
| } | ||
| connect() { | ||
| this.socket = new WebSocket(this.url, this.protocols), this.socket.binaryType = "arraybuffer", this.socket.onopen = (e) => { | ||
| this.reconnectAttempts = 0, this.heartbeat.start(), this.flushMessageQueue(), this.emit("open", e); | ||
| this.reconnectAttempts = 0, clearTimeout(this.reconnectTimer), this.heartbeat && this.heartbeat.start(), this.flushMessageQueue(), this.emit("open", e); | ||
| }, this.socket.onmessage = (e) => { | ||
| if (e.data === "pong") { | ||
| this.heartbeat.recordPong(); | ||
| this.heartbeat && this.heartbeat.recordPong(); | ||
| return; | ||
@@ -105,5 +175,5 @@ } | ||
| }, this.socket.onclose = (e) => { | ||
| this.heartbeat.stop(), this.emit("close", e); | ||
| this.heartbeat && this.heartbeat.stop(), this.emit("close", e); | ||
| }, this.socket.onerror = (e) => { | ||
| this.emit("error", e), this.scheduleReconnect(); | ||
| this.heartbeat && this.heartbeat.stop(), this.emit("error", e), this.scheduleReconnect(); | ||
| }; | ||
@@ -116,33 +186,35 @@ } | ||
| scheduleReconnect() { | ||
| if (this.reconnectAttempts >= this.config.maxReconnectAttempts) | ||
| if (this.reconnectAttempts >= this.currentConfig.maxReconnectAttempts) { | ||
| this.emit("overMaxReconnectAttempts"); | ||
| return; | ||
| } | ||
| const e = Math.min( | ||
| this.config.reconnectDelay * Math.pow(this.config.reconnectExponent, this.reconnectAttempts), | ||
| this.config.maxReconnectDelay | ||
| ), s = e * 0.2 * (Math.random() * 2 - 1), i = Math.max(1e3, e + s); | ||
| this.currentConfig.reconnectDelay * Math.pow(this.currentConfig.reconnectExponent, this.reconnectAttempts), | ||
| this.currentConfig.maxReconnectDelay | ||
| ), i = e * 0.2 * (Math.random() * 2 - 1), s = Math.max(1e3, e + i); | ||
| this.reconnectTimer = setTimeout(() => { | ||
| this.reconnectAttempts++, this.connect(); | ||
| }, i); | ||
| }, s); | ||
| } | ||
| flushMessageQueue() { | ||
| for (; this.messageQueue.length > 0; ) { | ||
| const { data: e, resolve: t, reject: s } = this.messageQueue.shift(); | ||
| this.send(e).then(t).catch(s); | ||
| const { data: e, resolve: t, reject: i } = this.messageQueue.shift(); | ||
| this.send(e).then(t).catch(i); | ||
| } | ||
| } | ||
| send(e, t = this.config.defaultPriority) { | ||
| var s; | ||
| return ((s = this.socket) == null ? void 0 : s.readyState) === WebSocket.OPEN ? this.scheduler.add(() => new Promise((i, n) => { | ||
| send(e, t = this.currentConfig.defaultPriority) { | ||
| var i; | ||
| return ((i = this.socket) == null ? void 0 : i.readyState) === WebSocket.OPEN ? this.scheduler.add(() => new Promise((s, r) => { | ||
| try { | ||
| this.sendRaw(e), i(); | ||
| this.sendRaw(e), s(); | ||
| } catch (o) { | ||
| n(o); | ||
| r(o); | ||
| } | ||
| }), t) : new Promise((i, n) => { | ||
| this.messageQueue.push({ data: e, priority: t, resolve: i, reject: n }); | ||
| }), t) : new Promise((s, r) => { | ||
| this.messageQueue.push({ data: e, priority: t, resolve: s, reject: r }); | ||
| }); | ||
| } | ||
| close(e, t) { | ||
| var s; | ||
| clearTimeout(this.reconnectTimer), this.heartbeat.stop(), (s = this.socket) == null || s.close(e, t), this.socket = null; | ||
| var i; | ||
| clearTimeout(this.reconnectTimer), this.heartbeat && this.heartbeat.stop(), (i = this.socket) == null || i.close(e, t), this.socket = null; | ||
| } | ||
@@ -152,4 +224,38 @@ reconnect() { | ||
| } | ||
| // 配置更新方法 | ||
| async updateConfig(e) { | ||
| if (this.configQueue.push(e), !this.isUpdatingConfig) { | ||
| for (this.isUpdatingConfig = !0; this.configQueue.length > 0; ) { | ||
| const t = this.configQueue.shift(); | ||
| await this.applyConfigSafely(t); | ||
| } | ||
| this.isUpdatingConfig = !1; | ||
| } | ||
| } | ||
| applyConfigSafely(e) { | ||
| const t = { ...this.currentConfig }; | ||
| this.currentConfig = c(this.currentConfig, e), this.handleConfigChange(t, this.currentConfig), this.applyConfig(); | ||
| } | ||
| // 处理特定配置变更 | ||
| handleConfigChange(e, t) { | ||
| l(e.heartbeat, t.heartbeat) || this.reInitHeartbeat(), (e.maxReconnectAttempts !== t.maxReconnectAttempts || e.reconnectDelay !== t.reconnectDelay || e.reconnectExponent !== t.reconnectExponent || e.maxReconnectDelay !== t.maxReconnectDelay) && this.resetReconnectTimer(); | ||
| } | ||
| // 应用新配置到各模块 | ||
| applyConfig() { | ||
| var e, t; | ||
| (e = this.heartbeat) == null || e.updateConfig(this.currentConfig), (t = this.scheduler) == null || t.updateThresholds(this.currentConfig.maxConcurrent); | ||
| } | ||
| reInitHeartbeat() { | ||
| var e; | ||
| if (!this.currentConfig.isNeedHeartbeat) { | ||
| (e = this.heartbeat) == null || e.stop(), this.heartbeat = void 0; | ||
| return; | ||
| } | ||
| this.heartbeat ? (this.heartbeat.stop(), this.heartbeat.start()) : this.initHeartbeat(); | ||
| } | ||
| resetReconnectTimer() { | ||
| this.reconnectTimer && (clearTimeout(this.reconnectTimer), this.scheduleReconnect()); | ||
| } | ||
| } | ||
| class g extends c { | ||
| class d extends u { | ||
| constructor(e) { | ||
@@ -159,49 +265,35 @@ super(), this.config = e, this.clients = /* @__PURE__ */ new Map(); | ||
| connect(e, t = []) { | ||
| const s = `${e}|${t.join(",")}`; | ||
| if (this.clients.has(s)) | ||
| return this.clients.get(s); | ||
| const i = new m(e, t, this.config); | ||
| this.clients.set(s, i); | ||
| const n = (o) => (a) => { | ||
| const i = `${e}|${t.join(",")}`; | ||
| if (this.clients.has(i)) | ||
| return this.clients.get(i); | ||
| const s = new m(e, t, this.config); | ||
| this.clients.set(i, s); | ||
| const r = (o) => (a) => { | ||
| this.emit(o, { url: e, protocols: t, data: a }); | ||
| }; | ||
| return i.on("open", n("open")), i.on("message", n("message")), i.on("close", n("close")), i.on("error", n("error")), i; | ||
| return s.on("open", r("open")), s.on("message", r("message")), s.on("close", r("close")), s.on("error", r("error")), s; | ||
| } | ||
| closeAll(e, t) { | ||
| this.clients.forEach((s) => s.close(e, t)), this.clients.clear(); | ||
| this.clients.forEach((i) => i.close(e, t)), this.clients.clear(); | ||
| } | ||
| getClient(e, t) { | ||
| const s = `${e}|${(t == null ? void 0 : t.join(",")) || ""}`; | ||
| return this.clients.get(s); | ||
| const i = `${e}|${(t == null ? void 0 : t.join(",")) || ""}`; | ||
| return this.clients.get(i); | ||
| } | ||
| } | ||
| const h = { | ||
| maxReconnectAttempts: 10, | ||
| reconnectDelay: 1e3, | ||
| reconnectExponent: 1.5, | ||
| maxReconnectDelay: 3e4, | ||
| heartbeatInterval: 25e3, | ||
| heartbeatTimeout: 1e4, | ||
| connectionPoolSize: 5, | ||
| defaultPriority: 1, | ||
| enableCompression: !1, | ||
| serializer: { | ||
| serialize: JSON.stringify, | ||
| deserialize: JSON.parse | ||
| } | ||
| }, d = (r = {}) => { | ||
| const p = (n = {}) => { | ||
| const e = { | ||
| ...h, | ||
| ...r, | ||
| ...n, | ||
| serializer: { | ||
| ...h.serializer, | ||
| ...r.serializer | ||
| ...n.serializer | ||
| } | ||
| }; | ||
| return new g(e); | ||
| }, p = { | ||
| return new d(e); | ||
| }, y = { | ||
| serialize: JSON.stringify, | ||
| deserialize: JSON.parse | ||
| }, f = { | ||
| serialize: (r) => { | ||
| }, b = { | ||
| serialize: (n) => { | ||
| throw new Error( | ||
@@ -211,3 +303,3 @@ "MsgPack serializer requires @msgpack/msgpack installation" | ||
| }, | ||
| deserialize: (r) => { | ||
| deserialize: (n) => { | ||
| throw new Error( | ||
@@ -219,8 +311,8 @@ "MsgPack serializer requires @msgpack/msgpack installation" | ||
| export { | ||
| c as EventEmitter, | ||
| p as JsonSerializer, | ||
| f as MsgPackSerializer, | ||
| u as EventEmitter, | ||
| y as JsonSerializer, | ||
| b as MsgPackSerializer, | ||
| m as WebSocketClient, | ||
| g as WebSocketManager, | ||
| d as createWebSocketManager | ||
| d as WebSocketManager, | ||
| p as createWebSocketManager | ||
| }; |
@@ -1,1 +0,1 @@ | ||
| (function(n,c){typeof exports=="object"&&typeof module<"u"?c(exports):typeof define=="function"&&define.amd?define(["exports"],c):(n=typeof globalThis<"u"?globalThis:n||self,c(n.WebSocketPro={}))})(this,function(n){"use strict";class c{constructor(){this.events={}}on(e,t){return this.events[e]||(this.events[e]=[]),this.events[e].push(t),()=>this.off(e,t)}off(e,t){this.events[e]&&(this.events[e]=this.events[e].filter(s=>s!==t))}emit(e,...t){this.events[e]&&this.events[e].forEach(s=>{try{s(...t)}catch(i){console.error(`Event "${e}" listener error:`,i)}})}once(e,t){const s=(...i)=>{this.off(e,s),t(...i)};this.on(e,s)}removeAllListeners(e){e?delete this.events[e]:this.events={}}}class g extends c{constructor(e,t,s){super(),this.sendPing=s,this.lastPongTime=0,this.intervalMs=e,this.timeoutMs=t}start(){this.stop(),this.lastPongTime=Date.now(),this.intervalId=setInterval(()=>{this.sendPing(),this.timeoutId=setTimeout(()=>{this.emit("timeout")},this.timeoutMs)},this.intervalMs)}stop(){clearInterval(this.intervalId),clearTimeout(this.timeoutId)}recordPong(){this.lastPongTime=Date.now(),clearTimeout(this.timeoutId),this.emit("pong",Date.now()-this.lastPongTime)}getLastPongTime(){return this.lastPongTime}}class d{constructor(e,t){this.maxConcurrent=e,this.onTaskError=t,this.queue=[],this.runningCount=0}add(e,t){return new Promise((s,i)=>{const r=async()=>{var a;try{await e(),s()}catch(h){(a=this.onTaskError)==null||a.call(this,h),i(h)}};this.queue.push({task:r,priority:t}),this.queue.sort((a,h)=>h.priority-a.priority),this.run()})}run(){for(;this.runningCount<this.maxConcurrent&&this.queue.length>0;){const{task:e}=this.queue.shift();this.runningCount++,e().finally(()=>{this.runningCount--,this.run()})}}clear(){this.queue=[]}}class l extends c{constructor(e,t,s){super(),this.url=e,this.protocols=t,this.config=s,this.socket=null,this.reconnectAttempts=0,this.messageQueue=[],this.heartbeat=new g(s.heartbeatInterval,s.heartbeatTimeout,()=>this.sendRaw("ping")),this.scheduler=new d(1,i=>this.emit("error",i)),this.connect()}connect(){this.socket=new WebSocket(this.url,this.protocols),this.socket.binaryType="arraybuffer",this.socket.onopen=e=>{this.reconnectAttempts=0,this.heartbeat.start(),this.flushMessageQueue(),this.emit("open",e)},this.socket.onmessage=e=>{if(e.data==="pong"){this.heartbeat.recordPong();return}this.emit("message",e.data)},this.socket.onclose=e=>{this.heartbeat.stop(),this.emit("close",e)},this.socket.onerror=e=>{this.emit("error",e),this.scheduleReconnect()}}sendRaw(e){var t;((t=this.socket)==null?void 0:t.readyState)===WebSocket.OPEN&&this.socket.send(e)}scheduleReconnect(){if(this.reconnectAttempts>=this.config.maxReconnectAttempts)return;const e=Math.min(this.config.reconnectDelay*Math.pow(this.config.reconnectExponent,this.reconnectAttempts),this.config.maxReconnectDelay),s=e*.2*(Math.random()*2-1),i=Math.max(1e3,e+s);this.reconnectTimer=setTimeout(()=>{this.reconnectAttempts++,this.connect()},i)}flushMessageQueue(){for(;this.messageQueue.length>0;){const{data:e,resolve:t,reject:s}=this.messageQueue.shift();this.send(e).then(t).catch(s)}}send(e,t=this.config.defaultPriority){var s;return((s=this.socket)==null?void 0:s.readyState)===WebSocket.OPEN?this.scheduler.add(()=>new Promise((i,r)=>{try{this.sendRaw(e),i()}catch(a){r(a)}}),t):new Promise((i,r)=>{this.messageQueue.push({data:e,priority:t,resolve:i,reject:r})})}close(e,t){var s;clearTimeout(this.reconnectTimer),this.heartbeat.stop(),(s=this.socket)==null||s.close(e,t),this.socket=null}reconnect(){clearTimeout(this.reconnectTimer),this.reconnectAttempts=0,this.close(),this.connect()}}class u extends c{constructor(e){super(),this.config=e,this.clients=new Map}connect(e,t=[]){const s=`${e}|${t.join(",")}`;if(this.clients.has(s))return this.clients.get(s);const i=new l(e,t,this.config);this.clients.set(s,i);const r=a=>h=>{this.emit(a,{url:e,protocols:t,data:h})};return i.on("open",r("open")),i.on("message",r("message")),i.on("close",r("close")),i.on("error",r("error")),i}closeAll(e,t){this.clients.forEach(s=>s.close(e,t)),this.clients.clear()}getClient(e,t){const s=`${e}|${(t==null?void 0:t.join(","))||""}`;return this.clients.get(s)}}const m={maxReconnectAttempts:10,reconnectDelay:1e3,reconnectExponent:1.5,maxReconnectDelay:3e4,heartbeatInterval:25e3,heartbeatTimeout:1e4,connectionPoolSize:5,defaultPriority:1,enableCompression:!1,serializer:{serialize:JSON.stringify,deserialize:JSON.parse}},f=(o={})=>{const e={...m,...o,serializer:{...m.serializer,...o.serializer}};return new u(e)},p={serialize:JSON.stringify,deserialize:JSON.parse},k={serialize:o=>{throw new Error("MsgPack serializer requires @msgpack/msgpack installation")},deserialize:o=>{throw new Error("MsgPack serializer requires @msgpack/msgpack installation")}};n.EventEmitter=c,n.JsonSerializer=p,n.MsgPackSerializer=k,n.WebSocketClient=l,n.WebSocketManager=u,n.createWebSocketManager=f,Object.defineProperty(n,Symbol.toStringTag,{value:"Module"})}); | ||
| (function(r,h){typeof exports=="object"&&typeof module<"u"?h(exports):typeof define=="function"&&define.amd?define(["exports"],h):(r=typeof globalThis<"u"?globalThis:r||self,h(r.WebSocketPro={}))})(this,function(r){"use strict";const h={maxReconnectAttempts:10,reconnectDelay:1e3,reconnectExponent:1.5,maxReconnectDelay:3e4,connectionPoolSize:5,maxConcurrent:1,defaultPriority:1,enableCompression:!1,serializer:{serialize:JSON.stringify,deserialize:JSON.parse},isNeedHeartbeat:!0,heartbeat:{interval:25e3,timeout:1e4,message:"PING"}};class l{constructor(){this.events={}}on(e,t){return this.events[e]||(this.events[e]=[]),this.events[e].push(t),()=>this.off(e,t)}off(e,t){this.events[e]&&(this.events[e]=this.events[e].filter(i=>i!==t))}emit(e,...t){this.events[e]&&this.events[e].forEach(i=>{try{i(...t)}catch(s){console.error(`Event "${e}" listener error:`,s)}})}once(e,t){const i=(...s)=>{this.off(e,i),t(...s)};this.on(e,i)}removeAllListeners(e){e?delete this.events[e]:this.events={}}}class d extends l{constructor(e={},t){super(),this.config=e,this.sendPing=t,this.lastPongTime=0}start(){this.config||console.log("[Heartbeat] config is empty"),this.stop(),this.lastPongTime=Date.now(),this.intervalId=setInterval(()=>{this.sendPing(),this.timeoutId=setTimeout(()=>{this.handleDefaultTimeout(this.config.onTimeout)},this.config.timeout)},this.config.interval)}stop(){clearInterval(this.intervalId),clearTimeout(this.timeoutId)}handleDefaultTimeout(e){this.stop(),e&&e(),this.emit("timeout")}recordPong(){this.lastPongTime=Date.now(),clearTimeout(this.timeoutId),this.emit("pong",Date.now()-this.lastPongTime)}getLastPongTime(){return this.lastPongTime}updateConfig(e){if(!(e!=null&&e.isNeedHeartbeat)){this.stop();return}this.config={...this.config,...e.heartbeat},this.stop(),this.start()}}class p{constructor(e,t){this.maxConcurrent=e,this.onTaskError=t,this.queue=[],this.runningCount=0}add(e,t){return new Promise((i,s)=>{const o=async()=>{var a;try{await e(),i()}catch(c){(a=this.onTaskError)==null||a.call(this,c),s(c)}};this.queue.push({task:o,priority:t}),this.queue.sort((a,c)=>c.priority-a.priority),this.run()})}run(){for(;this.runningCount<this.maxConcurrent&&this.queue.length>0;){const{task:e}=this.queue.shift();this.runningCount++,e().finally(()=>{this.runningCount--,this.run()})}}clear(){this.queue=[]}updateThresholds(e){e!==void 0&&(this.maxConcurrent=e),this.run()}}const u=(n,e)=>{const t={...n};for(const i in e)e[i]instanceof Object&&!Array.isArray(e[i])?t[i]=u(n[i]||{},e[i]):t[i]=e[i];return t},f=(n,e)=>{if(n===e)return!0;if(n==null||e==null||typeof n!="object"||typeof e!="object")return n===e;if(Array.isArray(n)&&Array.isArray(e)){if(n.length!==e.length)return!1;for(let s=0;s<n.length;s++)if(!f(n[s],e[s]))return!1;return!0}if(Array.isArray(n)||Array.isArray(e))return!1;const t=Object.keys(n),i=Object.keys(e);if(t.length!==i.length)return!1;for(const s of t)if(!e.hasOwnProperty(s)||!f(n[s],e[s]))return!1;return!0};class g extends l{constructor(e,t,i){super(),this.url=e,this.protocols=t,this.config=i,this.socket=null,this.reconnectAttempts=0,this.messageQueue=[],this.isUpdatingConfig=!1,this.configQueue=[],this.currentConfig=u(h,this.config),this.initHeartbeat(),this.scheduler=new p(this.currentConfig.maxConcurrent,s=>this.emit("error",s)),this.connect()}initHeartbeat(){this.currentConfig.isNeedHeartbeat&&(this.heartbeat=new d(this.currentConfig.heartbeat,()=>{var e;this.send(((e=this.currentConfig.heartbeat)==null?void 0:e.message)||"PING")}),this.heartbeat.on("timeout",()=>{var e;console.log("Heartbeat timeout, triggering reconnect..."),((e=this.socket)==null?void 0:e.readyState)===WebSocket.OPEN&&this.close(1e3,"heartbeat timeout"),this.scheduleReconnect()}))}connect(){this.socket=new WebSocket(this.url,this.protocols),this.socket.binaryType="arraybuffer",this.socket.onopen=e=>{this.reconnectAttempts=0,clearTimeout(this.reconnectTimer),this.heartbeat&&this.heartbeat.start(),this.flushMessageQueue(),this.emit("open",e)},this.socket.onmessage=e=>{if(e.data==="pong"){this.heartbeat&&this.heartbeat.recordPong();return}this.emit("message",e.data)},this.socket.onclose=e=>{this.heartbeat&&this.heartbeat.stop(),this.emit("close",e)},this.socket.onerror=e=>{this.heartbeat&&this.heartbeat.stop(),this.emit("error",e),this.scheduleReconnect()}}sendRaw(e){var t;((t=this.socket)==null?void 0:t.readyState)===WebSocket.OPEN&&this.socket.send(e)}scheduleReconnect(){if(this.reconnectAttempts>=this.currentConfig.maxReconnectAttempts){this.emit("overMaxReconnectAttempts");return}const e=Math.min(this.currentConfig.reconnectDelay*Math.pow(this.currentConfig.reconnectExponent,this.reconnectAttempts),this.currentConfig.maxReconnectDelay),i=e*.2*(Math.random()*2-1),s=Math.max(1e3,e+i);this.reconnectTimer=setTimeout(()=>{this.reconnectAttempts++,this.connect()},s)}flushMessageQueue(){for(;this.messageQueue.length>0;){const{data:e,resolve:t,reject:i}=this.messageQueue.shift();this.send(e).then(t).catch(i)}}send(e,t=this.currentConfig.defaultPriority){var i;return((i=this.socket)==null?void 0:i.readyState)===WebSocket.OPEN?this.scheduler.add(()=>new Promise((s,o)=>{try{this.sendRaw(e),s()}catch(a){o(a)}}),t):new Promise((s,o)=>{this.messageQueue.push({data:e,priority:t,resolve:s,reject:o})})}close(e,t){var i;clearTimeout(this.reconnectTimer),this.heartbeat&&this.heartbeat.stop(),(i=this.socket)==null||i.close(e,t),this.socket=null}reconnect(){clearTimeout(this.reconnectTimer),this.reconnectAttempts=0,this.close(),this.connect()}async updateConfig(e){if(this.configQueue.push(e),!this.isUpdatingConfig){for(this.isUpdatingConfig=!0;this.configQueue.length>0;){const t=this.configQueue.shift();await this.applyConfigSafely(t)}this.isUpdatingConfig=!1}}applyConfigSafely(e){const t={...this.currentConfig};this.currentConfig=u(this.currentConfig,e),this.handleConfigChange(t,this.currentConfig),this.applyConfig()}handleConfigChange(e,t){f(e.heartbeat,t.heartbeat)||this.reInitHeartbeat(),(e.maxReconnectAttempts!==t.maxReconnectAttempts||e.reconnectDelay!==t.reconnectDelay||e.reconnectExponent!==t.reconnectExponent||e.maxReconnectDelay!==t.maxReconnectDelay)&&this.resetReconnectTimer()}applyConfig(){var e,t;(e=this.heartbeat)==null||e.updateConfig(this.currentConfig),(t=this.scheduler)==null||t.updateThresholds(this.currentConfig.maxConcurrent)}reInitHeartbeat(){var e;if(!this.currentConfig.isNeedHeartbeat){(e=this.heartbeat)==null||e.stop(),this.heartbeat=void 0;return}this.heartbeat?(this.heartbeat.stop(),this.heartbeat.start()):this.initHeartbeat()}resetReconnectTimer(){this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.scheduleReconnect())}}class m extends l{constructor(e){super(),this.config=e,this.clients=new Map}connect(e,t=[]){const i=`${e}|${t.join(",")}`;if(this.clients.has(i))return this.clients.get(i);const s=new g(e,t,this.config);this.clients.set(i,s);const o=a=>c=>{this.emit(a,{url:e,protocols:t,data:c})};return s.on("open",o("open")),s.on("message",o("message")),s.on("close",o("close")),s.on("error",o("error")),s}closeAll(e,t){this.clients.forEach(i=>i.close(e,t)),this.clients.clear()}getClient(e,t){const i=`${e}|${(t==null?void 0:t.join(","))||""}`;return this.clients.get(i)}}const y=(n={})=>{const e={...h,...n,serializer:{...h.serializer,...n.serializer}};return new m(e)},b={serialize:JSON.stringify,deserialize:JSON.parse},C={serialize:n=>{throw new Error("MsgPack serializer requires @msgpack/msgpack installation")},deserialize:n=>{throw new Error("MsgPack serializer requires @msgpack/msgpack installation")}};r.EventEmitter=l,r.JsonSerializer=b,r.MsgPackSerializer=C,r.WebSocketClient=g,r.WebSocketManager=m,r.createWebSocketManager=y,Object.defineProperty(r,Symbol.toStringTag,{value:"Module"})}); |
+1
-1
| { | ||
| "name": "websocket-pro-client", | ||
| "version": "1.0.1", | ||
| "version": "1.0.2-beta.1", | ||
| "description": "High-performance WebSocket client with auto-reconnect, heartbeat and priority messaging", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
+14
-2
@@ -10,2 +10,14 @@ # WebSocket Pro Client | ||
| ### v1.0.2-Beta | ||
| - 支持自定义心跳消息 | ||
| - 支持动态更新部分 websocket 配置 | ||
| 下个版本迭代内容: | ||
| 1. 配置校验以及支持回滚 | ||
| 2. 完善的配置动态更新支持 | ||
| ### v1.0.1 | ||
| - 🚀 自动重连 + 智能退避算法 | ||
@@ -40,3 +52,3 @@ - 💓 可配置心跳检测 | ||
| client.send({ action: "subscribe" }); | ||
| client.send("hello word"); | ||
| ``` | ||
@@ -60,3 +72,3 @@ | ||
| 1. Fork 仓库 | ||
| 2. 创建特性分支 (`git checkout -b dev/feature/fix-xxx`) | ||
| 2. 创建分支 (`git checkout -b dev/feature/fix-xxx`) | ||
| 3. 提交更改 (`git commit -am 'feat/fix xxx'`) | ||
@@ -63,0 +75,0 @@ 4. 推送到分支 (`git push origin dev/feature/fix-xxx`) |
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
28172
36.68%15
15.38%520
33.68%78
18.18%2
100%