🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

websocket-pro-client

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

websocket-pro-client - npm Package Compare versions

Comparing version
1.0.1
to
1.0.2-beta.1
+2
dist/config/index.d.ts
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;
+5
-3

@@ -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";

@@ -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"})});
{
"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": [

@@ -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`)