🎩 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.2-beta.1
to
1.0.2-beta.2
+11
dist/constants/errors.d.ts
export declare enum WebSocketErrorCode {
MsgPackNotInstalled = "MSG_PACK_NOT_INSTALLED",
AckTimeout = "ACK_TIMEOUT",
AckMaxRetries = "ACK_MAX_RETRIES",
ClosedBeforeAck = "CLOSED_BEFORE_ACK"
}
export declare const WebSocketErrorMessage: Record<WebSocketErrorCode, string>;
export declare class WebSocketClientError extends Error {
code: WebSocketErrorCode;
constructor(code: WebSocketErrorCode);
}
export declare enum WebSocketEvent {
Open = "open",
Message = "message",
Close = "close",
Error = "error",
Reconnect = "reconnect",
Heartbeat = "heartbeat",
Latency = "latency",
OverMaxReconnectAttempts = "overMaxReconnectAttempts"
}
export declare const CORE_WEB_SOCKET_EVENTS: WebSocketEvent[];
export declare const ALL_WEB_SOCKET_EVENTS: WebSocketEvent[];
export declare enum HeartbeatMessage {
Ping = "PING",
Pong = "PONG"
}
/** Heartbeat 内部发出的事件名 */
export declare enum HeartbeatEvent {
Timeout = "timeout",
Pong = "PONG"
}
export type LogLevel = "debug" | "info" | "warn" | "error";
export interface Logger {
debug: (...args: any[]) => void;
info: (...args: any[]) => void;
warn: (...args: any[]) => void;
error: (...args: any[]) => void;
}
export declare const getLogger: () => Logger;
export declare const setLogger: (logger: Logger) => void;
+1
-1

@@ -1,2 +0,2 @@

import { Listener } from "../types";
import { Listener } from '../types';
export declare class EventEmitter {

@@ -3,0 +3,0 @@ private events;

import { HeartbeatConfig, WebSocketConfig } from '../types';
import { EventEmitter } from "./EventEmitter";
import { EventEmitter } from './EventEmitter';
export declare class Heartbeat extends EventEmitter {

@@ -9,3 +9,3 @@ private config;

private timeoutId?;
constructor(config: HeartbeatConfig, sendPing: () => void);
constructor(config: HeartbeatConfig | undefined, sendPing: () => void);
start(): void;

@@ -12,0 +12,0 @@ stop(): void;

@@ -1,3 +0,3 @@

export { EventEmitter } from "./EventEmitter";
export { WebSocketManager } from "./WebSocketManager";
export { WebSocketClient } from "./WebSocketClient";
export { EventEmitter } from './EventEmitter';
export { WebSocketManager } from './WebSocketManager';
export { WebSocketClient } from './WebSocketClient';

@@ -1,3 +0,3 @@

import { EventEmitter } from "./EventEmitter";
import { WebSocketConfig } from "../types";
import { EventEmitter } from './EventEmitter';
import { WebSocketConfig } from '../types';
export declare class WebSocketClient extends EventEmitter {

@@ -14,2 +14,4 @@ private readonly url;

private readonly scheduler;
private readonly pendingAcks;
private lastInboundSeq?;
private isUpdatingConfig;

@@ -23,3 +25,6 @@ private configQueue;

private flushMessageQueue;
private sendInternal;
private handleAckTimeout;
send(data: any, priority?: number): Promise<void>;
sendWithAck(data: any, priority?: number): Promise<void>;
close(code?: number, reason?: string): void;

@@ -26,0 +31,0 @@ reconnect(): void;

@@ -1,4 +0,4 @@

import { WebSocketClient } from "./WebSocketClient";
import { EventEmitter } from "./EventEmitter";
import { WebSocketConfig } from "../types";
import { WebSocketClient } from './WebSocketClient';
import { EventEmitter } from './EventEmitter';
import { WebSocketConfig } from '../types';
export declare class WebSocketManager extends EventEmitter {

@@ -5,0 +5,0 @@ private readonly config;

@@ -1,2 +0,2 @@

import type { WebSocketConfig, WebSocketEvent, IWebSocketManager, IWebSocketClient, Serializer } from "./types";
import { WebSocketConfig, WebSocketEvent, IWebSocketManager, IWebSocketClient, Serializer } from './types';
/**

@@ -38,2 +38,3 @@ * 创建 WebSocket 管理器实例

export type { WebSocketConfig, WebSocketEvent, IWebSocketManager, IWebSocketClient, Serializer, };
export { EventEmitter, WebSocketManager, WebSocketClient } from "./core";
export { HeartbeatMessage, HeartbeatEvent } from './constants/heartbeat';
export { EventEmitter, WebSocketManager, WebSocketClient } from './core';

@@ -0,1 +1,2 @@

import { WebSocketEvent } from '../constants/events';
export interface Serializer<T = any> {

@@ -21,2 +22,45 @@ /**

};
/**
* 消息 ACK 配置
*/
export type AckStrategy = {
/** 是否开启 ACK 机制 (默认: true) */
enabled?: boolean;
/** ACK 超时时间(ms) (默认: 5000) */
timeout?: number;
/** 最大重试次数 (默认: 2) */
maxRetries?: number;
/**
* 生成消息 ID(默认自增)
*/
generateId?: () => string | number;
/**
* 发出消息时的包装
* @example 默认: (id, data) => ({ id, payload: data })
*/
wrapOutbound?: (id: string | number, data: any) => any;
/**
* 从服务端消息中解析 ACK 所属的消息 ID
* 返回 null 表示不是 ACK 消息
*/
extractAckId?: (message: any) => string | number | null;
};
/**
* 消息序列号配置
*/
export type SequenceStrategy = {
/** 是否开启本地序列号 (默认: true) */
enabled?: boolean;
/** 生成序列号(默认自增) */
generateSeq?: () => string | number;
/**
* 对外发数据加上 seq
* @example 默认: (seq, data) => ({ seq, payload: data })
*/
wrapOutbound?: (seq: string | number, data: any) => any;
/**
* 解析入站消息的序列号
*/
extractInboundSeq?: (message: any) => string | number | null;
};
export interface WebSocketConfig {

@@ -41,2 +85,6 @@ /** 最大重连尝试次数 (默认: 10) */

serializer?: Serializer;
/** 消息 ACK 配置 */
ack?: AckStrategy;
/** 消息序列号配置 */
sequence?: SequenceStrategy;
/** 是否需要心跳 (默认: true) */

@@ -47,5 +95,11 @@ isNeedHeartbeat?: boolean;

}
export type WebSocketEvent = "open" | "message" | "close" | "error" | "reconnect" | "heartbeat" | "latency";
export { WebSocketEvent } from '../constants/events';
export interface IWebSocketClient {
send(data: any, priority?: number): Promise<void>;
/**
* 发送消息并等待 ACK
* - 使用全局或自定义的 ACK 配置
* - 返回的 Promise 会在收到 ACK、超时或重试失败后结束
*/
sendWithAck(data: any, priority?: number): Promise<void>;
close(code?: number, reason?: string): void;

@@ -52,0 +106,0 @@ reconnect(): void;

@@ -1,2 +0,2 @@

const h = {
const _ = {
maxReconnectAttempts: 10,

@@ -14,2 +14,30 @@ reconnectDelay: 1e3,

},
ack: {
enabled: !0,
timeout: 5e3,
maxRetries: 2,
generateId: () => {
window.__ws_pro_client_ack_id__ || (window.__ws_pro_client_ack_id__ = 1);
const i = window.__ws_pro_client_ack_id__;
return window.__ws_pro_client_ack_id__ = i + 1, i;
},
wrapOutbound: (i, e) => ({
id: i,
payload: e
}),
extractAckId: (i) => i && typeof i == "object" && "ackId" in i ? i.ackId : null
},
sequence: {
enabled: !0,
generateSeq: () => {
window.__ws_pro_client_seq__ || (window.__ws_pro_client_seq__ = 1);
const i = window.__ws_pro_client_seq__;
return window.__ws_pro_client_seq__ = i + 1, i;
},
wrapOutbound: (i, e) => ({
seq: i,
payload: e
}),
extractInboundSeq: (i) => i && typeof i == "object" && "seq" in i ? i.seq : null
},
isNeedHeartbeat: !0,

@@ -22,3 +50,22 @@ heartbeat: {

};
class u {
var k = /* @__PURE__ */ ((i) => (i.Ping = "PING", i.Pong = "PONG", i))(k || {}), p = /* @__PURE__ */ ((i) => (i.Timeout = "timeout", i.Pong = "PONG", i))(p || {});
class P {
constructor(e = "[WebSocketPro]") {
this.prefix = e;
}
debug(...e) {
}
info(...e) {
console.info(this.prefix, ...e);
}
warn(...e) {
console.warn(this.prefix, ...e);
}
error(...e) {
console.error(this.prefix, ...e);
}
}
let R = new P();
const w = () => R;
class A {
constructor() {

@@ -31,10 +78,10 @@ this.events = {};

off(e, t) {
this.events[e] && (this.events[e] = this.events[e].filter((i) => i !== t));
this.events[e] && (this.events[e] = this.events[e].filter((n) => n !== t));
}
emit(e, ...t) {
this.events[e] && this.events[e].forEach((i) => {
this.events[e] && this.events[e].forEach((n) => {
try {
i(...t);
n(...t);
} catch (s) {
console.error(`Event "${e}" listener error:`, s);
w().error(`Event "${e}" listener error:`, s);
}

@@ -44,6 +91,6 @@ });

once(e, t) {
const i = (...s) => {
this.off(e, i), t(...s);
const n = (...s) => {
this.off(e, n), t(...s);
};
this.on(e, i);
this.on(e, n);
}

@@ -54,3 +101,3 @@ removeAllListeners(e) {

}
class f extends u {
class E extends A {
constructor(e = {}, t) {

@@ -60,3 +107,3 @@ 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.config || w().warn("Heartbeat config is empty"), this.stop(), this.lastPongTime = Date.now(), this.intervalId = setInterval(() => {
this.sendPing(), this.timeoutId = setTimeout(() => {

@@ -71,6 +118,6 @@ this.handleDefaultTimeout(this.config.onTimeout);

handleDefaultTimeout(e) {
this.stop(), e && e(), this.emit("timeout");
this.stop(), e && e(), this.emit(p.Timeout);
}
recordPong() {
this.lastPongTime = Date.now(), clearTimeout(this.timeoutId), this.emit("pong", Date.now() - this.lastPongTime);
this.lastPongTime = Date.now(), clearTimeout(this.timeoutId), this.emit(p.Pong, Date.now() - this.lastPongTime);
}

@@ -88,3 +135,3 @@ getLastPongTime() {

}
class g {
class O {
constructor(e, t) {

@@ -94,12 +141,12 @@ this.maxConcurrent = e, this.onTaskError = t, this.queue = [], this.runningCount = 0;

add(e, t) {
return new Promise((i, s) => {
const r = async () => {
var o;
return new Promise((n, s) => {
const o = async () => {
var r;
try {
await e(), i();
await e(), n();
} catch (a) {
(o = this.onTaskError) == null || o.call(this, a), s(a);
(r = this.onTaskError) == null || r.call(this, a), s(a);
}
};
this.queue.push({ task: r, priority: t }), this.queue.sort((o, a) => a.priority - o.priority), this.run();
this.queue.push({ task: o, priority: t }), this.queue.sort((r, a) => a.priority - r.priority), this.run();
});

@@ -122,35 +169,56 @@ }

}
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];
var f = /* @__PURE__ */ ((i) => (i.Open = "open", i.Message = "message", i.Close = "close", i.Error = "error", i.Reconnect = "reconnect", i.Heartbeat = "heartbeat", i.Latency = "latency", i.OverMaxReconnectAttempts = "overMaxReconnectAttempts", i))(f || {});
const S = [
"open",
"message",
"close",
"error"
/* Error */
], y = (i, e) => {
const t = { ...i };
for (const n in e) {
const s = e[n];
s && typeof s == "object" && !Array.isArray(s) ? t[n] = y(i[n] || {}, s) : t[n] = s;
}
return t;
}, l = (n, e) => {
if (n === e)
}, b = (i, e) => {
if (i === 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)
if (i == null || e == null || typeof i != "object" || typeof e != "object")
return i === e;
if (Array.isArray(i) && Array.isArray(e)) {
if (i.length !== e.length)
return !1;
for (let s = 0; s < n.length; s++)
if (!l(n[s], e[s]))
for (let s = 0; s < i.length; s++)
if (!b(i[s], e[s]))
return !1;
return !0;
}
if (Array.isArray(n) || Array.isArray(e))
if (Array.isArray(i) || Array.isArray(e))
return !1;
const t = Object.keys(n), i = Object.keys(e);
if (t.length !== i.length)
const t = Object.keys(i), n = Object.keys(e);
if (t.length !== n.length)
return !1;
for (const s of t)
if (!e.hasOwnProperty(s) || !l(n[s], e[s]))
if (!e.hasOwnProperty(s) || !b(i[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(
var d = /* @__PURE__ */ ((i) => (i.MsgPackNotInstalled = "MSG_PACK_NOT_INSTALLED", i.AckTimeout = "ACK_TIMEOUT", i.AckMaxRetries = "ACK_MAX_RETRIES", i.ClosedBeforeAck = "CLOSED_BEFORE_ACK", i))(d || {});
const M = {
MSG_PACK_NOT_INSTALLED: "MsgPack serializer requires @msgpack/msgpack installation",
ACK_TIMEOUT: "ACK timeout",
ACK_MAX_RETRIES: "ACK timeout, maximum retry attempts reached",
CLOSED_BEFORE_ACK: "WebSocket connection closed before ACK was received"
};
class m extends Error {
constructor(e) {
super(M[e]), this.code = e, this.name = "WebSocketClientError";
}
}
class N extends A {
constructor(e, t, n) {
super(), this.url = e, this.protocols = t, this.config = n, this.socket = null, this.reconnectAttempts = 0, this.messageQueue = [], this.pendingAcks = /* @__PURE__ */ new Map(), this.isUpdatingConfig = !1, this.configQueue = [], this.currentConfig = y(_, this.config), this.initHeartbeat(), this.scheduler = new O(
this.currentConfig.maxConcurrent,
(s) => this.emit("error", s)
(s) => this.emit(f.Error, s)
), this.connect();

@@ -160,8 +228,8 @@ }

initHeartbeat() {
this.currentConfig.isNeedHeartbeat && (this.heartbeat = new f(this.currentConfig.heartbeat, () => {
this.currentConfig.isNeedHeartbeat && (this.heartbeat = new E(this.currentConfig.heartbeat, () => {
var e;
this.send(((e = this.currentConfig.heartbeat) == null ? void 0 : e.message) || "PING");
}), this.heartbeat.on("timeout", () => {
}), this.heartbeat.on(p.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();
w().warn("Heartbeat timeout, triggering reconnect..."), ((e = this.socket) == null ? void 0 : e.readyState) === WebSocket.OPEN && this.close(1e3, "heartbeat timeout"), this.scheduleReconnect();
}));

@@ -171,13 +239,36 @@ }

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.reconnectAttempts = 0, clearTimeout(this.reconnectTimer), this.heartbeat && this.heartbeat.start(), this.flushMessageQueue(), this.emit(f.Open, e);
}, this.socket.onmessage = (e) => {
if (e.data === "pong") {
if (e.data === k.Pong) {
this.heartbeat && this.heartbeat.recordPong();
return;
}
this.emit("message", e.data);
const t = e.data;
let n = t;
try {
n = this.currentConfig.serializer.deserialize(t);
} catch {
n = t;
}
const s = this.currentConfig.ack;
if (s != null && s.enabled && typeof s.extractAckId == "function") {
const r = s.extractAckId(n);
if (r != null) {
const a = this.pendingAcks.get(r);
if (a) {
clearTimeout(a.timer), this.pendingAcks.delete(r), a.resolve();
return;
}
}
}
const o = this.currentConfig.sequence;
if (o != null && o.enabled && typeof o.extractInboundSeq == "function") {
const r = o.extractInboundSeq(n);
r != null && (this.lastInboundSeq = r);
}
this.emit(f.Message, n);
}, this.socket.onclose = (e) => {
this.heartbeat && this.heartbeat.stop(), this.emit("close", e);
this.heartbeat && this.heartbeat.stop(), this.emit(f.Close, e);
}, this.socket.onerror = (e) => {
this.heartbeat && this.heartbeat.stop(), this.emit("error", e), this.scheduleReconnect();
this.heartbeat && this.heartbeat.stop(), this.emit(f.Error, e), this.scheduleReconnect();
};

@@ -191,3 +282,3 @@ }

if (this.reconnectAttempts >= this.currentConfig.maxReconnectAttempts) {
this.emit("overMaxReconnectAttempts");
this.emit(f.OverMaxReconnectAttempts);
return;

@@ -198,3 +289,3 @@ }

this.currentConfig.maxReconnectDelay
), i = e * 0.2 * (Math.random() * 2 - 1), s = Math.max(1e3, e + i);
), n = e * 0.2 * (Math.random() * 2 - 1), s = Math.max(1e3, e + n);
this.reconnectTimer = setTimeout(() => {

@@ -206,21 +297,71 @@ this.reconnectAttempts++, this.connect();

for (; this.messageQueue.length > 0; ) {
const { data: e, resolve: t, reject: i } = this.messageQueue.shift();
this.send(e).then(t).catch(i);
const { data: e, priority: t, needAck: n, resolve: s, reject: o } = this.messageQueue.shift();
this.sendInternal(e, t, n).then(s).catch(o);
}
}
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) => {
sendInternal(e, t, n) {
var s;
return ((s = this.socket) == null ? void 0 : s.readyState) === WebSocket.OPEN ? this.scheduler.add(() => new Promise((o, r) => {
var a, C;
try {
this.sendRaw(e), s();
} catch (o) {
r(o);
let l = e;
const u = this.currentConfig.sequence;
if (u != null && u.enabled && typeof u.wrapOutbound == "function") {
const h = (a = u.generateSeq) == null ? void 0 : a.call(u);
h !== void 0 && (l = u.wrapOutbound(h, l));
}
const c = this.currentConfig.ack;
let g = null;
if (n && (c != null && c.enabled)) {
const h = (C = c.generateId) == null ? void 0 : C.call(c);
h != null && typeof c.wrapOutbound == "function" && (g = h, l = c.wrapOutbound(h, l));
}
const T = this.currentConfig.serializer.serialize(l);
if (this.sendRaw(T), !n || !(c != null && c.enabled) || g === null) {
o();
return;
}
const x = c.timeout ?? 5e3, q = c.maxRetries ?? 0, I = {
resolve: o,
reject: (h) => r(h),
retries: 0,
rawData: e,
priority: t,
timer: setTimeout(() => {
this.handleAckTimeout(g);
}, x)
};
this.pendingAcks.set(g, I);
} catch (l) {
r(l);
}
}), t) : new Promise((s, r) => {
this.messageQueue.push({ data: e, priority: t, resolve: s, reject: r });
}), t) : new Promise((o, r) => {
this.messageQueue.push({ data: e, priority: t, needAck: n, resolve: o, reject: r });
});
}
handleAckTimeout(e) {
const t = this.currentConfig.ack, n = this.pendingAcks.get(e);
if (!n || !(t != null && t.enabled)) {
n && (this.pendingAcks.delete(e), n.reject(new m(d.AckTimeout)));
return;
}
const s = t.timeout ?? 5e3, o = t.maxRetries ?? 0;
n.retries < o ? (n.retries += 1, this.sendInternal(n.rawData, n.priority, !1).catch(
n.reject
), n.timer = setTimeout(() => {
this.handleAckTimeout(e);
}, s)) : (this.pendingAcks.delete(e), n.reject(new m(d.AckMaxRetries)));
}
send(e, t = this.currentConfig.defaultPriority) {
return this.sendInternal(e, t, !1);
}
// 发送并等待 ACK
sendWithAck(e, t = this.currentConfig.defaultPriority) {
return this.sendInternal(e, t, !0);
}
close(e, t) {
var i;
clearTimeout(this.reconnectTimer), this.heartbeat && this.heartbeat.stop(), (i = this.socket) == null || i.close(e, t), this.socket = null;
var n;
clearTimeout(this.reconnectTimer), this.heartbeat && this.heartbeat.stop(), this.pendingAcks.forEach((s, o) => {
clearTimeout(s.timer), s.reject(new m(d.ClosedBeforeAck)), this.pendingAcks.delete(o);
}), (n = this.socket) == null || n.close(e, t), this.socket = null;
}

@@ -242,7 +383,7 @@ reconnect() {

const t = { ...this.currentConfig };
this.currentConfig = c(this.currentConfig, e), this.handleConfigChange(t, this.currentConfig), this.applyConfig();
this.currentConfig = y(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();
b(e.heartbeat, t.heartbeat) || this.reInitHeartbeat(), (e.maxReconnectAttempts !== t.maxReconnectAttempts || e.reconnectDelay !== t.reconnectDelay || e.reconnectExponent !== t.reconnectExponent || e.maxReconnectDelay !== t.maxReconnectDelay) && this.resetReconnectTimer();
}

@@ -266,3 +407,3 @@ // 应用新配置到各模块

}
class d extends u {
class D extends A {
constructor(e) {

@@ -272,52 +413,52 @@ super(), this.config = e, this.clients = /* @__PURE__ */ new Map();

connect(e, t = []) {
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 });
const n = `${e}|${t.join(",")}`;
if (this.clients.has(n))
return this.clients.get(n);
const s = new N(e, t, this.config);
this.clients.set(n, s);
const o = (r) => (a) => {
this.emit(r, { url: e, protocols: t, data: a });
};
return s.on("open", r("open")), s.on("message", r("message")), s.on("close", r("close")), s.on("error", r("error")), s;
return S.forEach((r) => {
s.on(r, o(r));
}), s;
}
closeAll(e, t) {
this.clients.forEach((i) => i.close(e, t)), this.clients.clear();
this.clients.forEach((n) => n.close(e, t)), this.clients.clear();
}
getClient(e, t) {
const i = `${e}|${(t == null ? void 0 : t.join(",")) || ""}`;
return this.clients.get(i);
const n = `${e}|${(t == null ? void 0 : t.join(",")) || ""}`;
return this.clients.get(n);
}
}
const p = (n = {}) => {
const z = (i = {}) => {
const e = {
...h,
...n,
..._,
...i,
serializer: {
...h.serializer,
...n.serializer
..._.serializer,
...i.serializer
}
};
return new d(e);
}, y = {
return new D(e);
}, j = {
serialize: JSON.stringify,
deserialize: JSON.parse
}, b = {
serialize: (n) => {
throw new Error(
"MsgPack serializer requires @msgpack/msgpack installation"
);
}, v = {
serialize: (i) => {
throw new m(d.MsgPackNotInstalled);
},
deserialize: (n) => {
throw new Error(
"MsgPack serializer requires @msgpack/msgpack installation"
);
deserialize: (i) => {
throw new m(d.MsgPackNotInstalled);
}
};
export {
u as EventEmitter,
y as JsonSerializer,
b as MsgPackSerializer,
m as WebSocketClient,
d as WebSocketManager,
p as createWebSocketManager
A as EventEmitter,
p as HeartbeatEvent,
k as HeartbeatMessage,
j as JsonSerializer,
v as MsgPackSerializer,
N as WebSocketClient,
D as WebSocketManager,
z as createWebSocketManager
};

@@ -1,1 +0,1 @@

(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"})});
(function(a,f){typeof exports=="object"&&typeof module<"u"?f(exports):typeof define=="function"&&define.amd?define(["exports"],f):(a=typeof globalThis<"u"?globalThis:a||self,f(a.WebSocketPro={}))})(this,function(a){"use strict";var f=typeof document<"u"?document.currentScript:null;const w={maxReconnectAttempts:10,reconnectDelay:1e3,reconnectExponent:1.5,maxReconnectDelay:3e4,connectionPoolSize:5,maxConcurrent:1,defaultPriority:1,enableCompression:!1,serializer:{serialize:JSON.stringify,deserialize:JSON.parse},ack:{enabled:!0,timeout:5e3,maxRetries:2,generateId:()=>{window.__ws_pro_client_ack_id__||(window.__ws_pro_client_ack_id__=1);const n=window.__ws_pro_client_ack_id__;return window.__ws_pro_client_ack_id__=n+1,n},wrapOutbound:(n,e)=>({id:n,payload:e}),extractAckId:n=>n&&typeof n=="object"&&"ackId"in n?n.ackId:null},sequence:{enabled:!0,generateSeq:()=>{window.__ws_pro_client_seq__||(window.__ws_pro_client_seq__=1);const n=window.__ws_pro_client_seq__;return window.__ws_pro_client_seq__=n+1,n},wrapOutbound:(n,e)=>({seq:n,payload:e}),extractInboundSeq:n=>n&&typeof n=="object"&&"seq"in n?n.seq:null},isNeedHeartbeat:!0,heartbeat:{interval:25e3,timeout:1e4,message:"PING"}};var C=(n=>(n.Ping="PING",n.Pong="PONG",n))(C||{}),p=(n=>(n.Timeout="timeout",n.Pong="PONG",n))(p||{});class I{constructor(e="[WebSocketPro]"){this.prefix=e}debug(...e){typeof document>"u"&&typeof location>"u"?require("url").pathToFileURL(__filename).href:typeof document>"u"?location.href:f&&f.tagName.toUpperCase()==="SCRIPT"&&f.src||new URL("websocket-pro.umd.js",document.baseURI).href}info(...e){console.info(this.prefix,...e)}warn(...e){console.warn(this.prefix,...e)}error(...e){console.error(this.prefix,...e)}}let E=new I;const k=()=>E;class b{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){k().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 x extends b{constructor(e={},t){super(),this.config=e,this.sendPing=t,this.lastPongTime=0}start(){this.config||k().warn("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(p.Timeout)}recordPong(){this.lastPongTime=Date.now(),clearTimeout(this.timeoutId),this.emit(p.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 O{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 r;try{await e(),i()}catch(c){(r=this.onTaskError)==null||r.call(this,c),s(c)}};this.queue.push({task:o,priority:t}),this.queue.sort((r,c)=>c.priority-r.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()}}var d=(n=>(n.Open="open",n.Message="message",n.Close="close",n.Error="error",n.Reconnect="reconnect",n.Heartbeat="heartbeat",n.Latency="latency",n.OverMaxReconnectAttempts="overMaxReconnectAttempts",n))(d||{});const M=["open","message","close","error"],A=(n,e)=>{const t={...n};for(const i in e){const s=e[i];s&&typeof s=="object"&&!Array.isArray(s)?t[i]=A(n[i]||{},s):t[i]=s}return t},T=(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(!T(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)||!T(n[s],e[s]))return!1;return!0};var g=(n=>(n.MsgPackNotInstalled="MSG_PACK_NOT_INSTALLED",n.AckTimeout="ACK_TIMEOUT",n.AckMaxRetries="ACK_MAX_RETRIES",n.ClosedBeforeAck="CLOSED_BEFORE_ACK",n))(g||{});const N={MSG_PACK_NOT_INSTALLED:"MsgPack serializer requires @msgpack/msgpack installation",ACK_TIMEOUT:"ACK timeout",ACK_MAX_RETRIES:"ACK timeout, maximum retry attempts reached",CLOSED_BEFORE_ACK:"WebSocket connection closed before ACK was received"};class _ extends Error{constructor(e){super(N[e]),this.code=e,this.name="WebSocketClientError"}}class S extends b{constructor(e,t,i){super(),this.url=e,this.protocols=t,this.config=i,this.socket=null,this.reconnectAttempts=0,this.messageQueue=[],this.pendingAcks=new Map,this.isUpdatingConfig=!1,this.configQueue=[],this.currentConfig=A(w,this.config),this.initHeartbeat(),this.scheduler=new O(this.currentConfig.maxConcurrent,s=>this.emit(d.Error,s)),this.connect()}initHeartbeat(){this.currentConfig.isNeedHeartbeat&&(this.heartbeat=new x(this.currentConfig.heartbeat,()=>{var e;this.send(((e=this.currentConfig.heartbeat)==null?void 0:e.message)||"PING")}),this.heartbeat.on(p.Timeout,()=>{var e;k().warn("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(d.Open,e)},this.socket.onmessage=e=>{if(e.data===C.Pong){this.heartbeat&&this.heartbeat.recordPong();return}const t=e.data;let i=t;try{i=this.currentConfig.serializer.deserialize(t)}catch{i=t}const s=this.currentConfig.ack;if(s!=null&&s.enabled&&typeof s.extractAckId=="function"){const r=s.extractAckId(i);if(r!=null){const c=this.pendingAcks.get(r);if(c){clearTimeout(c.timer),this.pendingAcks.delete(r),c.resolve();return}}}const o=this.currentConfig.sequence;if(o!=null&&o.enabled&&typeof o.extractInboundSeq=="function"){const r=o.extractInboundSeq(i);r!=null&&(this.lastInboundSeq=r)}this.emit(d.Message,i)},this.socket.onclose=e=>{this.heartbeat&&this.heartbeat.stop(),this.emit(d.Close,e)},this.socket.onerror=e=>{this.heartbeat&&this.heartbeat.stop(),this.emit(d.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(d.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,priority:t,needAck:i,resolve:s,reject:o}=this.messageQueue.shift();this.sendInternal(e,t,i).then(s).catch(o)}}sendInternal(e,t,i){var s;return((s=this.socket)==null?void 0:s.readyState)===WebSocket.OPEN?this.scheduler.add(()=>new Promise((o,r)=>{var c,R;try{let m=e;const l=this.currentConfig.sequence;if(l!=null&&l.enabled&&typeof l.wrapOutbound=="function"){const u=(c=l.generateSeq)==null?void 0:c.call(l);u!==void 0&&(m=l.wrapOutbound(u,m))}const h=this.currentConfig.ack;let y=null;if(i&&(h!=null&&h.enabled)){const u=(R=h.generateId)==null?void 0:R.call(h);u!=null&&typeof h.wrapOutbound=="function"&&(y=u,m=h.wrapOutbound(u,m))}const z=this.currentConfig.serializer.serialize(m);if(this.sendRaw(z),!i||!(h!=null&&h.enabled)||y===null){o();return}const v=h.timeout??5e3,K=h.maxRetries??0,L={resolve:o,reject:u=>r(u),retries:0,rawData:e,priority:t,timer:setTimeout(()=>{this.handleAckTimeout(y)},v)};this.pendingAcks.set(y,L)}catch(m){r(m)}}),t):new Promise((o,r)=>{this.messageQueue.push({data:e,priority:t,needAck:i,resolve:o,reject:r})})}handleAckTimeout(e){const t=this.currentConfig.ack,i=this.pendingAcks.get(e);if(!i||!(t!=null&&t.enabled)){i&&(this.pendingAcks.delete(e),i.reject(new _(g.AckTimeout)));return}const s=t.timeout??5e3,o=t.maxRetries??0;i.retries<o?(i.retries+=1,this.sendInternal(i.rawData,i.priority,!1).catch(i.reject),i.timer=setTimeout(()=>{this.handleAckTimeout(e)},s)):(this.pendingAcks.delete(e),i.reject(new _(g.AckMaxRetries)))}send(e,t=this.currentConfig.defaultPriority){return this.sendInternal(e,t,!1)}sendWithAck(e,t=this.currentConfig.defaultPriority){return this.sendInternal(e,t,!0)}close(e,t){var i;clearTimeout(this.reconnectTimer),this.heartbeat&&this.heartbeat.stop(),this.pendingAcks.forEach((s,o)=>{clearTimeout(s.timer),s.reject(new _(g.ClosedBeforeAck)),this.pendingAcks.delete(o)}),(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=A(this.currentConfig,e),this.handleConfigChange(t,this.currentConfig),this.applyConfig()}handleConfigChange(e,t){T(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 P extends b{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 S(e,t,this.config);this.clients.set(i,s);const o=r=>c=>{this.emit(r,{url:e,protocols:t,data:c})};return M.forEach(r=>{s.on(r,o(r))}),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 D=(n={})=>{const e={...w,...n,serializer:{...w.serializer,...n.serializer}};return new P(e)},q={serialize:JSON.stringify,deserialize:JSON.parse},j={serialize:n=>{throw new _(g.MsgPackNotInstalled)},deserialize:n=>{throw new _(g.MsgPackNotInstalled)}};a.EventEmitter=b,a.HeartbeatEvent=p,a.HeartbeatMessage=C,a.JsonSerializer=q,a.MsgPackSerializer=j,a.WebSocketClient=S,a.WebSocketManager=P,a.createWebSocketManager=D,Object.defineProperty(a,Symbol.toStringTag,{value:"Module"})});
{
"name": "websocket-pro-client",
"version": "1.0.2-beta.1",
"version": "1.0.2-beta.2",
"description": "High-performance WebSocket client with auto-reconnect, heartbeat and priority messaging",

@@ -49,3 +49,3 @@ "keywords": [

"vite": "^4.0.0",
"vite-plugin-dts": "^2.0.0",
"vite-plugin-dts": "^4.3.0",
"vitest": "^0.25.0"

@@ -52,0 +52,0 @@ },

+284
-27

@@ -1,2 +0,2 @@

# WebSocket Pro Client
## WebSocket Pro Client

@@ -6,24 +6,17 @@ [![npm version](https://img.shields.io/npm/v/websocket-pro-client)](https://www.npmjs.com/package/websocket-pro-client)

高性能 WebSocket 客户端,专为现代 Web 应用设计。
高性能 WebSocket 客户端,专为现代 Web 应用设计,内置自动重连、心跳、消息优先级调度、连接池管理,以及**可配置的消息 ACK 与序列号机制**。
## 特性
### 特性一览
### v1.0.2-Beta
- 🚀 自动重连 + 指数退避算法
- 💓 心跳检测(支持自定义心跳内容与超时处理)
- 🎯 消息优先级调度 & 最大并发控制
- 📦 连接池管理(同一 url/protocol 只维护一个连接实例)
- 🔄 运行时更新配置(心跳、重连策略等)
- ✅ 内置消息 ACK 机制(默认实现 + 完全可自定义)
- 🔢 消息序列号支持(默认自增,可自定义包装与解析)
- 🔍 完整 TypeScript 类型定义
- 支持自定义心跳消息
- 支持动态更新部分 websocket 配置
---
下个版本迭代内容:
1. 配置校验以及支持回滚
2. 完善的配置动态更新支持
### v1.0.1
- 🚀 自动重连 + 智能退避算法
- 💓 可配置心跳检测
- 🎯 消息优先级调度
- 📦 多连接池管理
- 🔍 完整的 TypeScript 支持
## 安装

@@ -37,20 +30,280 @@

---
## 快速开始
```typescript
import { createWebSocketManager } from "websocket-pro-client";
import { createWebSocketManager } from 'websocket-pro-client'
// 1. 创建全局 WebSocket 管理器(可配置重连、心跳、ACK 等)
const manager = createWebSocketManager({
maxReconnectAttempts: 5,
});
})
const client = manager.connect("wss://api.example.com");
// 2. 建立连接(同一 url + protocols 只会创建一个底层连接)
const client = manager.connect('wss://api.example.com')
client.on("message", (data) => {
console.log("Received:", data);
});
// 3. 监听消息
client.on('message', (data) => {
console.log('Received:', data)
})
client.send("hello word");
// 4. 发送消息(不关心 ACK)
client.send({ type: 'ping' })
```
---
## API 说明
### 1. 顶层方法
- **`createWebSocketManager(config?: Partial<WebSocketConfig>): IWebSocketManager`**
- 创建一个 WebSocket 连接管理器。
- 内部会将传入的配置与库内的 `DEFAULT_CONFIG` 深度合并。
- **`JsonSerializer / MsgPackSerializer`**
- `JsonSerializer`: 默认使用 `JSON.stringify/JSON.parse` 的序列化器。
- `MsgPackSerializer`: MessagePack 示例(需要自行安装 `@msgpack/msgpack` 后替换实现)。
---
### 2. WebSocketManager
`IWebSocketManager` 接口:
```ts
export interface IWebSocketManager {
connect(url: string, protocols?: string[]): IWebSocketClient
closeAll(code?: number, reason?: string): void
on(event: WebSocketEvent, listener: (data: any) => void): void
}
```
- **`connect(url, protocols?)`**
- 返回一个 `IWebSocketClient` 实例。
- 同一 `url + protocols` 会复用同一个底层连接。
- **`closeAll(code?, reason?)`**
- 关闭当前 manager 管理的所有连接。
- **`on(event, listener)`**
- 监听所有客户端转发上来的事件(`open/message/close/error` 等),回调中会携带 `{ url, protocols, data }`。
示例:
```ts
import { WebSocketEvent } from 'websocket-pro-client'
manager.on(WebSocketEvent.Error, ({ url, data }) => {
console.error('ws error:', url, data)
})
```
---
### 3. WebSocketClient
`IWebSocketClient` 接口:
```ts
export interface IWebSocketClient {
send(data: any, priority?: number): Promise<void>
sendWithAck(data: any, priority?: number): Promise<void>
close(code?: number, reason?: string): void
reconnect(): void
on(event: WebSocketEvent, listener: (data: any) => void): void
off(event: WebSocketEvent, listener: (data: any) => void): void
}
```
- **`send(data, priority?)`**
- 发送一条消息,不等待 ACK。
- 返回的 Promise 仅表示“客户端已成功发送到 WebSocket”,**不代表服务端处理成功**。
- **`sendWithAck(data, priority?)`**
- 发送一条消息,并基于全局 `ack` 配置等待服务端 ACK。
- 默认行为:
- 发送的数据会被包装成:`{ id, payload: { seq, payload: 原始data } }`
- 要求服务端在处理完成后,发送一条包含 `ackId` 字段的消息(例如 `{ ackId: id }`),表示已确认。
- 支持超时与自动重试(由 `ack.timeout` 和 `ack.maxRetries` 控制)。
- **`close(code?, reason?)`**
- 主动关闭当前连接,并清理所有等待中的 ACK。
- **`reconnect()`**
- 立即重连一次(会重置重连计数和退避延迟)。
- **事件监听**
```ts
import { WebSocketEvent } from 'websocket-pro-client'
client.on(WebSocketEvent.Open, () => {
console.log('ws open')
})
client.on(WebSocketEvent.Message, (data) => {
console.log('message:', data)
})
client.on(WebSocketEvent.Close, (event) => {
console.log('closed:', event)
})
client.on(WebSocketEvent.Error, (err) => {
console.error('ws error:', err)
})
```
---
## 配置说明(WebSocketConfig)
```ts
export interface WebSocketConfig {
// 重连相关
maxReconnectAttempts?: number
reconnectDelay?: number
reconnectExponent?: number
maxReconnectDelay?: number
// 任务调度 & 连接池
connectionPoolSize?: number
maxConcurrent?: number
defaultPriority?: number
// 序列化
enableCompression?: boolean
serializer?: Serializer
// 心跳
isNeedHeartbeat?: boolean
heartbeat?: HeartbeatConfig
// 消息 ACK
ack?: AckStrategy
// 消息序列号
sequence?: SequenceStrategy
}
```
### 1. 心跳配置 HeartbeatConfig
```ts
export type HeartbeatConfig = {
interval?: number // 心跳间隔(ms),默认 25000
timeout?: number // 心跳超时(ms),默认 10000
message?: string // 心跳消息内容,默认 "PING"
onTimeout?: () => void // 心跳超时时的自定义回调
}
```
默认情况下,客户端会周期性发送心跳消息,并在超时时自动触发重连。
### 2. 消息 ACK 配置 AckStrategy
```ts
export type AckStrategy = {
enabled?: boolean
timeout?: number
maxRetries?: number
generateId?: () => string | number
wrapOutbound?: (id: string | number, data: any) => any
extractAckId?: (message: any) => string | number | null
}
```
默认实现(不配置时):
- `enabled: true`
- `timeout: 5000`
- `maxRetries: 2`
- `generateId`: 使用浏览器 `window` 上的自增计数。
- `wrapOutbound`: `({ id, payload })`
- `extractAckId`: 从 `message.ackId` 中提取 ACK 对应的 ID。
> 如需和现有服务端协议对齐,只需要重写 `wrapOutbound` 和 `extractAckId` 即可。
#### 自定义 ACK 协议示例
后端规定:
- 出站:`{ msgId, body }`
- ACK 消息:`{ type: 'ACK', msgId }`
对应配置:
```ts
const manager = createWebSocketManager({
ack: {
enabled: true,
wrapOutbound: (id, data) => ({ msgId: id, body: data }),
extractAckId: (msg) =>
msg && msg.type === 'ACK' && msg.msgId != null ? msg.msgId : null,
},
})
```
### 3. 消息序列号配置 SequenceStrategy
```ts
export type SequenceStrategy = {
enabled?: boolean
generateSeq?: () => string | number
wrapOutbound?: (seq: string | number, data: any) => any
extractInboundSeq?: (message: any) => string | number | null
}
```
默认实现:
- `enabled: true`
- `generateSeq`: 使用浏览器 `window` 上的自增计数。
- `wrapOutbound`: `({ seq, payload })`
- `extractInboundSeq`: 从 `message.seq` 中提取序列号。
> 库内部只负责“生成/包装/解析”序列号,不做强制的乱序丢弃;你可以在 `message` 监听回调中结合 `seq` 做业务上的顺序控制。
---
## 使用 ACK 与序列号的完整示例
```ts
import {
createWebSocketManager,
WebSocketEvent,
} from 'websocket-pro-client'
const manager = createWebSocketManager({
ack: {
enabled: true,
timeout: 3000,
maxRetries: 1,
},
})
const client = manager.connect('wss://api.example.com')
client.on(WebSocketEvent.Open, async () => {
// 发送一条不需要 ACK 的消息
await client.send({ type: 'ping' })
// 发送一条需要 ACK 的消息
try {
await client.sendWithAck({ type: 'update', payload: { id: 1 } })
console.log('update confirmed by server')
} catch (e) {
console.error('update failed (no ACK):', e)
}
})
client.on(WebSocketEvent.Message, (msg) => {
// msg 是反序列化后的对象,例如:
// { seq, payload: { ... } } 或根据你自定义的包装形态
console.log('inbound message:', msg)
})
```
---
## 开发调试

@@ -65,6 +318,8 @@

# 运行demo
# 运行 demo
npm run demo
```
---
## 贡献指南

@@ -78,4 +333,6 @@

---
## 许可证
MIT © 2023 BetaCatPro