@arcblock/ws 1.28.9 → 1.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/_virtual/rolldown_runtime.mjs +7 -0
- package/esm/browser.d.mts +2 -0
- package/esm/browser.mjs +3 -0
- package/esm/client/base.d.mts +80 -0
- package/esm/client/base.mjs +118 -0
- package/esm/client/browser.d.mts +30 -0
- package/esm/client/browser.mjs +9 -0
- package/esm/client/index.d.mts +30 -0
- package/esm/client/index.mjs +10 -0
- package/esm/index.d.mts +3 -0
- package/esm/index.mjs +4 -0
- package/esm/logger.d.mts +13 -0
- package/esm/logger.mjs +16 -0
- package/esm/server/index.d.mts +138 -0
- package/esm/server/index.mjs +474 -0
- package/lib/_virtual/rolldown_runtime.cjs +29 -0
- package/lib/browser.cjs +3 -0
- package/lib/browser.d.cts +2 -0
- package/lib/client/base.cjs +119 -0
- package/lib/client/base.d.cts +80 -0
- package/lib/client/browser.cjs +12 -0
- package/lib/client/browser.d.cts +30 -0
- package/lib/client/index.cjs +14 -0
- package/lib/client/index.d.cts +30 -0
- package/lib/index.cjs +5 -0
- package/lib/index.d.cts +3 -0
- package/lib/logger.cjs +19 -0
- package/lib/logger.d.cts +13 -0
- package/lib/server/index.cjs +479 -0
- package/lib/server/index.d.cts +138 -0
- package/package.json +32 -10
- package/lib/browser.js +0 -5
- package/lib/client/base.js +0 -134
- package/lib/client/browser.js +0 -6
- package/lib/client/index.js +0 -7
- package/lib/index.js +0 -7
- package/lib/logger.js +0 -12
- package/lib/server/index.js +0 -442
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
import { __require } from "../_virtual/rolldown_runtime.mjs";
|
|
2
|
+
import logger_default from "../logger.mjs";
|
|
3
|
+
import EventEmitter from "node:events";
|
|
4
|
+
import WebSocket, { WebSocketServer } from "ws";
|
|
5
|
+
import cluster from "node:cluster";
|
|
6
|
+
import get from "lodash/get.js";
|
|
7
|
+
|
|
8
|
+
//#region src/server/index.ts
|
|
9
|
+
const eventHub = cluster.isMaster ? __require("@arcblock/event-hub/single").default : __require("@arcblock/event-hub").default;
|
|
10
|
+
const nanoid = (length = 16) => [...Array(length)].map(() => Math.random().toString(36)[2]).join("");
|
|
11
|
+
const sleep = (timeout) => new Promise((resolve) => {
|
|
12
|
+
setTimeout(resolve, timeout);
|
|
13
|
+
});
|
|
14
|
+
const reply = ({ socket, topic, event, data = {}, status = "ok", ref = "", joinRef = "" }) => {
|
|
15
|
+
if (socket.readyState === WebSocket.OPEN) {
|
|
16
|
+
const res = JSON.stringify([
|
|
17
|
+
joinRef,
|
|
18
|
+
ref,
|
|
19
|
+
topic,
|
|
20
|
+
event,
|
|
21
|
+
{
|
|
22
|
+
status,
|
|
23
|
+
response: data
|
|
24
|
+
}
|
|
25
|
+
]);
|
|
26
|
+
socket.send(res);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const noop = () => {};
|
|
30
|
+
const defaultHooks = {
|
|
31
|
+
authenticateJoinChannel: noop,
|
|
32
|
+
preJoinChannel: noop,
|
|
33
|
+
postJoinChannel: noop,
|
|
34
|
+
preLeaveChannel: noop,
|
|
35
|
+
postLeaveChannel: noop,
|
|
36
|
+
postBroadcast: noop,
|
|
37
|
+
postSend: noop,
|
|
38
|
+
receiveMessage: noop
|
|
39
|
+
};
|
|
40
|
+
const refreshHeartbeat = (socket) => {
|
|
41
|
+
socket.heartbeatAt = Date.now();
|
|
42
|
+
};
|
|
43
|
+
const HEARTBEAT_TIMEOUT = 300 * 1e3;
|
|
44
|
+
/**
|
|
45
|
+
* Create a websocket server
|
|
46
|
+
*
|
|
47
|
+
* @param {Object} opts
|
|
48
|
+
* @param {String} opts.pathname - which path to mount the socket server
|
|
49
|
+
* @param {Object} opts.authenticate - authentication function to be called on connection
|
|
50
|
+
* @param {Object} opts.hooks - hooks to be called on events
|
|
51
|
+
* @param {Object} opts.logger - logger used to log messages
|
|
52
|
+
* @param {Object} opts.broadcastEventName - used in cluster mode, default is '@arcblock/ws:broadcast'
|
|
53
|
+
* @param {Object} opts.heartbeatTimeout - maximum non-response time of a connection socket
|
|
54
|
+
* @class WsServer
|
|
55
|
+
* @extends {EventEmitter}
|
|
56
|
+
*/
|
|
57
|
+
var WsServer = class extends EventEmitter {
|
|
58
|
+
constructor(opts = {}) {
|
|
59
|
+
super();
|
|
60
|
+
this.pathname = opts.pathname;
|
|
61
|
+
this.authenticate = opts.authenticate;
|
|
62
|
+
this.hooks = Object.assign({}, defaultHooks, opts.hooks || {});
|
|
63
|
+
this.logger = opts.logger || logger_default("server", opts.silent);
|
|
64
|
+
this.skipLogOnHookError = opts.skipLogOnHookError || false;
|
|
65
|
+
this.heartbeatTimeout = opts.heartbeatTimeout || HEARTBEAT_TIMEOUT;
|
|
66
|
+
this.wss = new WebSocketServer({
|
|
67
|
+
noServer: true,
|
|
68
|
+
clientTracking: false
|
|
69
|
+
});
|
|
70
|
+
this.wss.on("connection", this.onWssConnection.bind(this));
|
|
71
|
+
this.wss.on("close", this.onWssClose.bind(this));
|
|
72
|
+
this.wss.on("error", this.onWssError.bind(this));
|
|
73
|
+
this.topics = {};
|
|
74
|
+
this.broadcastEventName = opts.broadcastEventName || "@arcblock/ws:broadcast";
|
|
75
|
+
eventHub.on(this.broadcastEventName, (data) => this._doBroadCast(data));
|
|
76
|
+
}
|
|
77
|
+
attach(server) {
|
|
78
|
+
server.on("upgrade", this.onConnect.bind(this));
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
onConnect(request, socket, head) {
|
|
82
|
+
const { pathname } = new URL(request.url, `http://${request.headers.host || "unknown"}`);
|
|
83
|
+
this.logger.debug("connect attempt", { pathname });
|
|
84
|
+
if (this.pathname && pathname !== this.pathname) {
|
|
85
|
+
socket.write("HTTP/1.1 404 Pathname mismatch\r\n\r\n");
|
|
86
|
+
socket.destroy();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (!this.authenticate) {
|
|
90
|
+
this.wss.handleUpgrade(request, socket, head, (ws) => {
|
|
91
|
+
this.wss.emit("connection", ws, request);
|
|
92
|
+
});
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
this.authenticate(request, (err, authInfo) => {
|
|
96
|
+
if (err) {
|
|
97
|
+
socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
|
|
98
|
+
socket.destroy();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
this.wss.handleUpgrade(request, socket, head, (ws) => {
|
|
102
|
+
ws.authInfo = authInfo;
|
|
103
|
+
this.wss.emit("connection", ws, request);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Broadcast message to all subscribers of a topic, can be used as
|
|
109
|
+
* - broadcast(event, data) ==> broadcast(event, event, data)
|
|
110
|
+
* - broadcast(topic, event, data)
|
|
111
|
+
* - broadcast(topic, event, data, options)
|
|
112
|
+
*/
|
|
113
|
+
async broadcast(...args) {
|
|
114
|
+
let topic;
|
|
115
|
+
let event;
|
|
116
|
+
let data;
|
|
117
|
+
let options = {};
|
|
118
|
+
let cb = () => {};
|
|
119
|
+
if (typeof args[args.length - 1] === "function") cb = args.pop();
|
|
120
|
+
if (args.length < 2) throw new Error("Broadcasting requires at least 2 arguments");
|
|
121
|
+
if (args.length === 2) {
|
|
122
|
+
[event, data] = args;
|
|
123
|
+
topic = event;
|
|
124
|
+
} else if (args.length === 3) [topic, event, data] = args;
|
|
125
|
+
else [topic, event, data, options] = args;
|
|
126
|
+
const enableLog = !!options.enableLog;
|
|
127
|
+
const { socketFilters, noCluster } = options;
|
|
128
|
+
const replyId = nanoid();
|
|
129
|
+
let count = 0;
|
|
130
|
+
if (noCluster) {
|
|
131
|
+
const { count: c } = this._doBroadCast({
|
|
132
|
+
topic,
|
|
133
|
+
event,
|
|
134
|
+
data,
|
|
135
|
+
enableLog,
|
|
136
|
+
socketFilters
|
|
137
|
+
});
|
|
138
|
+
count = c;
|
|
139
|
+
} else {
|
|
140
|
+
eventHub.on(replyId, ({ count: c } = {}) => {
|
|
141
|
+
if (c) count += c;
|
|
142
|
+
});
|
|
143
|
+
eventHub.broadcast(this.broadcastEventName, {
|
|
144
|
+
topic,
|
|
145
|
+
event,
|
|
146
|
+
data,
|
|
147
|
+
options,
|
|
148
|
+
enableLog,
|
|
149
|
+
replyId,
|
|
150
|
+
socketFilters
|
|
151
|
+
});
|
|
152
|
+
await sleep(600);
|
|
153
|
+
eventHub.off(replyId);
|
|
154
|
+
}
|
|
155
|
+
const opts = {
|
|
156
|
+
count,
|
|
157
|
+
topic,
|
|
158
|
+
event,
|
|
159
|
+
data,
|
|
160
|
+
options
|
|
161
|
+
};
|
|
162
|
+
cb(opts);
|
|
163
|
+
try {
|
|
164
|
+
await this.hooks.postBroadcast(opts);
|
|
165
|
+
} catch (error) {
|
|
166
|
+
if (!this.skipLogOnHookError) this.logger.error("postBroadcast error", { error });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
_doBroadCast({ topic, event, data, enableLog, replyId, socketFilters } = {}) {
|
|
170
|
+
try {
|
|
171
|
+
let count = 0;
|
|
172
|
+
if (this.topics[topic]?.size) {
|
|
173
|
+
let conditions = null;
|
|
174
|
+
if (socketFilters && Object.keys(socketFilters).length) conditions = Object.entries(socketFilters);
|
|
175
|
+
this.topics[topic].forEach((socket) => {
|
|
176
|
+
const noHeartbeatTime = Date.now() - socket.heartbeatAt;
|
|
177
|
+
if (noHeartbeatTime > this.heartbeatTimeout) {
|
|
178
|
+
this.logger.error(`Socket has no heartbeat within ${Math.floor(noHeartbeatTime / 1e3)} seconds`, {
|
|
179
|
+
topic,
|
|
180
|
+
id: socket.id
|
|
181
|
+
});
|
|
182
|
+
this.topics[topic].delete(socket);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (conditions && !conditions.every(([key, value]) => get(socket, key) === value)) return;
|
|
186
|
+
count++;
|
|
187
|
+
if (enableLog) this.logger.info("broadcast message to", {
|
|
188
|
+
topic,
|
|
189
|
+
event,
|
|
190
|
+
id: socket.id
|
|
191
|
+
});
|
|
192
|
+
reply({
|
|
193
|
+
socket,
|
|
194
|
+
topic,
|
|
195
|
+
event,
|
|
196
|
+
data
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
if (count > 0 && replyId) eventHub.broadcast(replyId, { count });
|
|
201
|
+
return { count };
|
|
202
|
+
} catch (error) {
|
|
203
|
+
this.logger.error("_doBroadcast error", { error });
|
|
204
|
+
return {
|
|
205
|
+
count: 0,
|
|
206
|
+
error
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Send message to 1 subscriber of a topic, can be used as
|
|
212
|
+
* - send(socket, event, data)
|
|
213
|
+
* - send(socket, topic, event, data)
|
|
214
|
+
* - send(socket, topic, event, data, options)
|
|
215
|
+
*/
|
|
216
|
+
async send(...args) {
|
|
217
|
+
let socket;
|
|
218
|
+
let topic;
|
|
219
|
+
let event;
|
|
220
|
+
let data;
|
|
221
|
+
let options = {};
|
|
222
|
+
if (args.length < 3) throw new Error("send requires at least 3 arguments");
|
|
223
|
+
if (args.length === 3) {
|
|
224
|
+
[socket, event, data] = args;
|
|
225
|
+
topic = event;
|
|
226
|
+
} else if (args.length === 4) [socket, topic, event, data] = args;
|
|
227
|
+
else [socket, topic, event, data, options] = args;
|
|
228
|
+
const opts = {
|
|
229
|
+
enableLog: true,
|
|
230
|
+
...options
|
|
231
|
+
};
|
|
232
|
+
if (!socket) {
|
|
233
|
+
this.logger.error("socket does not exist");
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (opts.enableLog) this.logger.info("send message to", {
|
|
237
|
+
topic,
|
|
238
|
+
event,
|
|
239
|
+
id: socket.id
|
|
240
|
+
});
|
|
241
|
+
reply({
|
|
242
|
+
socket,
|
|
243
|
+
topic,
|
|
244
|
+
event,
|
|
245
|
+
data
|
|
246
|
+
});
|
|
247
|
+
try {
|
|
248
|
+
await this.hooks.postSend({
|
|
249
|
+
topic,
|
|
250
|
+
event,
|
|
251
|
+
data,
|
|
252
|
+
options
|
|
253
|
+
});
|
|
254
|
+
} catch (error) {
|
|
255
|
+
if (!this.skipLogOnHookError) this.logger.error("postSend error", { error });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* private
|
|
260
|
+
*/
|
|
261
|
+
async onWssConnection(socket) {
|
|
262
|
+
const wsSocket = socket;
|
|
263
|
+
wsSocket.id = nanoid();
|
|
264
|
+
wsSocket.channel = {};
|
|
265
|
+
refreshHeartbeat(wsSocket);
|
|
266
|
+
this.logger.debug("socket connected", { id: wsSocket.id });
|
|
267
|
+
wsSocket.on("message", async (msg) => {
|
|
268
|
+
this.logger.debug("socket onmessage", msg.toString());
|
|
269
|
+
let joinRef;
|
|
270
|
+
let ref;
|
|
271
|
+
let topic;
|
|
272
|
+
let event;
|
|
273
|
+
let payload;
|
|
274
|
+
try {
|
|
275
|
+
[joinRef, ref, topic, event, payload] = JSON.parse(msg.toString());
|
|
276
|
+
} catch (err) {
|
|
277
|
+
this.logger.error("parse socket message error", {
|
|
278
|
+
id: wsSocket.id,
|
|
279
|
+
error: err
|
|
280
|
+
});
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if (!topic || !event) {
|
|
284
|
+
this.logger.warn?.("Invalid message format, topic/event fields are required");
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (topic === "phoenix" && event === "heartbeat") {
|
|
288
|
+
reply({
|
|
289
|
+
socket: wsSocket,
|
|
290
|
+
topic,
|
|
291
|
+
event,
|
|
292
|
+
ref
|
|
293
|
+
});
|
|
294
|
+
refreshHeartbeat(wsSocket);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (event === "phx_join") {
|
|
298
|
+
try {
|
|
299
|
+
const authInfo = await this.hooks.authenticateJoinChannel({
|
|
300
|
+
socket: wsSocket,
|
|
301
|
+
joinRef,
|
|
302
|
+
ref,
|
|
303
|
+
topic,
|
|
304
|
+
event,
|
|
305
|
+
payload
|
|
306
|
+
});
|
|
307
|
+
await this.hooks.preJoinChannel({
|
|
308
|
+
socket: wsSocket,
|
|
309
|
+
joinRef,
|
|
310
|
+
ref,
|
|
311
|
+
topic,
|
|
312
|
+
event,
|
|
313
|
+
payload
|
|
314
|
+
});
|
|
315
|
+
wsSocket.channel[topic] = { authInfo };
|
|
316
|
+
} catch (error) {
|
|
317
|
+
if (!this.skipLogOnHookError) this.logger.error("preJoinChannel error", { error });
|
|
318
|
+
reply({
|
|
319
|
+
socket: wsSocket,
|
|
320
|
+
topic,
|
|
321
|
+
event: `chan_reply_${ref}`,
|
|
322
|
+
data: { message: error.message },
|
|
323
|
+
status: "error",
|
|
324
|
+
ref,
|
|
325
|
+
joinRef
|
|
326
|
+
});
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (!this.topics[topic]) this.topics[topic] = /* @__PURE__ */ new Set();
|
|
330
|
+
this.topics[topic].add(wsSocket);
|
|
331
|
+
reply({
|
|
332
|
+
socket: wsSocket,
|
|
333
|
+
topic,
|
|
334
|
+
event: `chan_reply_${ref}`,
|
|
335
|
+
ref,
|
|
336
|
+
joinRef
|
|
337
|
+
});
|
|
338
|
+
this.emit("channel.join", {
|
|
339
|
+
socket: wsSocket,
|
|
340
|
+
topic,
|
|
341
|
+
event,
|
|
342
|
+
payload
|
|
343
|
+
});
|
|
344
|
+
try {
|
|
345
|
+
await this.hooks.postJoinChannel({
|
|
346
|
+
socket: wsSocket,
|
|
347
|
+
joinRef,
|
|
348
|
+
ref,
|
|
349
|
+
topic,
|
|
350
|
+
event,
|
|
351
|
+
payload
|
|
352
|
+
});
|
|
353
|
+
} catch (error) {
|
|
354
|
+
if (!this.skipLogOnHookError) this.logger.error("postJoinChannel error", { error });
|
|
355
|
+
}
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (event === "phx_leave") {
|
|
359
|
+
try {
|
|
360
|
+
await this.hooks.preLeaveChannel({
|
|
361
|
+
socket: wsSocket,
|
|
362
|
+
joinRef,
|
|
363
|
+
ref,
|
|
364
|
+
topic,
|
|
365
|
+
event,
|
|
366
|
+
payload
|
|
367
|
+
});
|
|
368
|
+
} catch (error) {
|
|
369
|
+
if (!this.skipLogOnHookError) this.logger.error("preLeaveChannel error", { error });
|
|
370
|
+
reply({
|
|
371
|
+
socket: wsSocket,
|
|
372
|
+
topic,
|
|
373
|
+
event: `chan_reply_${ref}`,
|
|
374
|
+
data: { message: error.message },
|
|
375
|
+
status: "error",
|
|
376
|
+
ref,
|
|
377
|
+
joinRef
|
|
378
|
+
});
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
this._leaveChannel(wsSocket, topic);
|
|
382
|
+
reply({
|
|
383
|
+
socket: wsSocket,
|
|
384
|
+
topic,
|
|
385
|
+
event: `chan_reply_${ref}`,
|
|
386
|
+
ref,
|
|
387
|
+
joinRef
|
|
388
|
+
});
|
|
389
|
+
try {
|
|
390
|
+
await this.hooks.postLeaveChannel({
|
|
391
|
+
socket: wsSocket,
|
|
392
|
+
joinRef,
|
|
393
|
+
ref,
|
|
394
|
+
topic,
|
|
395
|
+
event,
|
|
396
|
+
payload
|
|
397
|
+
});
|
|
398
|
+
} catch (error) {
|
|
399
|
+
if (!this.skipLogOnHookError) this.logger.error("postLeaveChannel error", { error });
|
|
400
|
+
}
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
try {
|
|
404
|
+
await this.hooks.receiveMessage({
|
|
405
|
+
socket: wsSocket,
|
|
406
|
+
joinRef,
|
|
407
|
+
ref,
|
|
408
|
+
topic,
|
|
409
|
+
event,
|
|
410
|
+
payload
|
|
411
|
+
});
|
|
412
|
+
} catch (error) {
|
|
413
|
+
if (!this.skipLogOnHookError) this.logger.error("receiveMessage error", { error });
|
|
414
|
+
reply({
|
|
415
|
+
socket: wsSocket,
|
|
416
|
+
topic,
|
|
417
|
+
event: `chan_reply_${ref}`,
|
|
418
|
+
data: { message: error.message },
|
|
419
|
+
status: "error",
|
|
420
|
+
ref,
|
|
421
|
+
joinRef
|
|
422
|
+
});
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
reply({
|
|
426
|
+
socket: wsSocket,
|
|
427
|
+
topic,
|
|
428
|
+
event: `chan_reply_${ref}`,
|
|
429
|
+
ref,
|
|
430
|
+
joinRef
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
wsSocket.on("close", () => {
|
|
434
|
+
this.logger.debug("socket onclose", { id: wsSocket.id });
|
|
435
|
+
Object.keys(this.topics).forEach((topic) => this._leaveChannel(wsSocket, topic));
|
|
436
|
+
});
|
|
437
|
+
wsSocket.on("error", (err) => {
|
|
438
|
+
this.logger.error("socket onerror", {
|
|
439
|
+
id: wsSocket.id,
|
|
440
|
+
error: err
|
|
441
|
+
});
|
|
442
|
+
Object.keys(this.topics).forEach((topic) => this._leaveChannel(wsSocket, topic));
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* private
|
|
447
|
+
*/
|
|
448
|
+
onWssClose() {
|
|
449
|
+
this.logger.debug("ws server onclose");
|
|
450
|
+
this.emit("close");
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* private
|
|
454
|
+
*/
|
|
455
|
+
onWssError(error) {
|
|
456
|
+
this.logger.error("ws server error", { error });
|
|
457
|
+
this.emit("error", error);
|
|
458
|
+
}
|
|
459
|
+
_leaveChannel(socket, topic) {
|
|
460
|
+
if (this.topics[topic]) this.topics[topic].delete(socket);
|
|
461
|
+
this.emit("channel.leave", {
|
|
462
|
+
socket,
|
|
463
|
+
topic
|
|
464
|
+
});
|
|
465
|
+
if (!this.topics[topic] || !this.topics[topic].size) this.emit("channel.destroy", {
|
|
466
|
+
socket,
|
|
467
|
+
topic
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
var server_default = WsServer;
|
|
472
|
+
|
|
473
|
+
//#endregion
|
|
474
|
+
export { server_default as default };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region rolldown:runtime
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: ((k) => from[k]).bind(null, key),
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
23
|
+
value: mod,
|
|
24
|
+
enumerable: true
|
|
25
|
+
}) : target, mod));
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
|
|
29
|
+
exports.__toESM = __toESM;
|
package/lib/browser.cjs
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
|
+
const require_logger = require('../logger.cjs');
|
|
3
|
+
|
|
4
|
+
//#region src/client/base.ts
|
|
5
|
+
var base_default = (Socket, EventEmitter, transport) => {
|
|
6
|
+
return class WsClient extends Socket {
|
|
7
|
+
constructor(endpoint, opts = {}) {
|
|
8
|
+
super(endpoint, {
|
|
9
|
+
transport,
|
|
10
|
+
...opts
|
|
11
|
+
});
|
|
12
|
+
this._logger = require_logger.default("client", opts.silent);
|
|
13
|
+
this.emitter = new EventEmitter();
|
|
14
|
+
this.onOpen(() => {
|
|
15
|
+
this._logger.debug("socket open", endpoint);
|
|
16
|
+
});
|
|
17
|
+
this.onClose(() => {
|
|
18
|
+
this._logger.debug("socket close", endpoint);
|
|
19
|
+
});
|
|
20
|
+
this.onError((err) => {
|
|
21
|
+
this._logger.error("socket error", err.error);
|
|
22
|
+
});
|
|
23
|
+
this.onMessage((message) => {
|
|
24
|
+
this._logger.debug("socket message", message);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
on(event, handler, params = {}) {
|
|
28
|
+
this.ensureJoinChannel(event, params);
|
|
29
|
+
this.emitter.on(event, handler);
|
|
30
|
+
}
|
|
31
|
+
off(event, handler) {
|
|
32
|
+
if (handler) this.emitter.off(event, handler);
|
|
33
|
+
else this.emitter.removeAllListeners(event);
|
|
34
|
+
this.ensureLeaveChannel(event);
|
|
35
|
+
}
|
|
36
|
+
disconnect(callback, code, reason) {
|
|
37
|
+
this.emitter.eventNames().forEach((event) => {
|
|
38
|
+
this.emitter.removeAllListeners(event);
|
|
39
|
+
});
|
|
40
|
+
super.disconnect(callback, code, reason);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* private
|
|
44
|
+
*/
|
|
45
|
+
ensureJoinChannel(topic, params = {}) {
|
|
46
|
+
if (this.emitter.listenerCount(topic) > 0) return;
|
|
47
|
+
const channel = this.channel(topic, params);
|
|
48
|
+
channel.join().receive("ok", (message) => {
|
|
49
|
+
this._logger.debug("join success", {
|
|
50
|
+
topic,
|
|
51
|
+
message
|
|
52
|
+
});
|
|
53
|
+
}).receive("error", (error) => {
|
|
54
|
+
this._logger.error("join error", {
|
|
55
|
+
topic,
|
|
56
|
+
error
|
|
57
|
+
});
|
|
58
|
+
}).receive("timeout", () => {
|
|
59
|
+
this._logger.debug("join timeout", { topic });
|
|
60
|
+
});
|
|
61
|
+
channel.on(topic, ({ status, response: data }) => {
|
|
62
|
+
if (status === "ok") this.emitter.emit(topic, data);
|
|
63
|
+
else this._logger.debug("response error", {
|
|
64
|
+
topic,
|
|
65
|
+
status,
|
|
66
|
+
data
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* private
|
|
72
|
+
*/
|
|
73
|
+
ensureLeaveChannel(topic) {
|
|
74
|
+
if (this.emitter.listenerCount(topic) > 0) return;
|
|
75
|
+
const channel = this.channels.find((c) => c.topic === topic);
|
|
76
|
+
if (!channel) return;
|
|
77
|
+
this.remove(channel);
|
|
78
|
+
channel.leave().receive("ok", (message) => {
|
|
79
|
+
this._logger.debug("leave success", {
|
|
80
|
+
topic,
|
|
81
|
+
message
|
|
82
|
+
});
|
|
83
|
+
}).receive("error", (err) => {
|
|
84
|
+
this._logger.error("leave error", {
|
|
85
|
+
topic,
|
|
86
|
+
err
|
|
87
|
+
});
|
|
88
|
+
}).receive("timeout", () => {
|
|
89
|
+
this._logger.debug("leave timeout", { topic });
|
|
90
|
+
});
|
|
91
|
+
channel.off(topic);
|
|
92
|
+
}
|
|
93
|
+
subscribe(topic, params = {}) {
|
|
94
|
+
let channel = this.channels.find((c) => c.topic === topic);
|
|
95
|
+
if (channel) return channel;
|
|
96
|
+
channel = this.channel(topic, params);
|
|
97
|
+
channel.join().receive("ok", (message) => {
|
|
98
|
+
this._logger.debug("join success", {
|
|
99
|
+
topic,
|
|
100
|
+
message
|
|
101
|
+
});
|
|
102
|
+
}).receive("error", (error) => {
|
|
103
|
+
this._logger.error("join error", {
|
|
104
|
+
topic,
|
|
105
|
+
error
|
|
106
|
+
});
|
|
107
|
+
}).receive("timeout", () => {
|
|
108
|
+
this._logger.debug("join timeout", { topic });
|
|
109
|
+
});
|
|
110
|
+
return channel;
|
|
111
|
+
}
|
|
112
|
+
unsubscribe(topic) {
|
|
113
|
+
this.ensureLeaveChannel(topic);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
//#endregion
|
|
119
|
+
exports.default = base_default;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import _default$1 from "../logger.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/client/base.d.ts
|
|
4
|
+
interface SocketConstructor {
|
|
5
|
+
new (endpoint: string, opts: object): SocketInstance;
|
|
6
|
+
}
|
|
7
|
+
interface SocketInstance {
|
|
8
|
+
channels: Channel[];
|
|
9
|
+
connect(): void;
|
|
10
|
+
disconnect(callback?: () => void, code?: number, reason?: string): void;
|
|
11
|
+
isConnected(): boolean;
|
|
12
|
+
onOpen(cb: () => void): void;
|
|
13
|
+
onClose(cb: () => void): void;
|
|
14
|
+
onError(cb: (err: {
|
|
15
|
+
error: Error;
|
|
16
|
+
}) => void): void;
|
|
17
|
+
onMessage(cb: (message: unknown) => void): void;
|
|
18
|
+
channel(topic: string, params?: object | (() => object)): Channel;
|
|
19
|
+
remove(channel: Channel): void;
|
|
20
|
+
}
|
|
21
|
+
interface Channel {
|
|
22
|
+
topic: string;
|
|
23
|
+
join(): ChannelPush;
|
|
24
|
+
leave(): ChannelPush;
|
|
25
|
+
on(event: string, callback: (data: {
|
|
26
|
+
status: string;
|
|
27
|
+
response: unknown;
|
|
28
|
+
}) => void): void;
|
|
29
|
+
off(event: string): void;
|
|
30
|
+
}
|
|
31
|
+
interface ChannelPush {
|
|
32
|
+
receive(status: string, callback: (data: unknown) => void): ChannelPush;
|
|
33
|
+
}
|
|
34
|
+
interface EventEmitterConstructor {
|
|
35
|
+
new (): EventEmitterInstance;
|
|
36
|
+
}
|
|
37
|
+
interface EventEmitterInstance {
|
|
38
|
+
on(event: string, handler: (data: unknown) => void): void;
|
|
39
|
+
off(event: string, handler?: (data: unknown) => void): void;
|
|
40
|
+
emit(event: string, data: unknown): void;
|
|
41
|
+
removeAllListeners(event: string): void;
|
|
42
|
+
listenerCount(event: string): number;
|
|
43
|
+
eventNames(): (string | symbol)[];
|
|
44
|
+
}
|
|
45
|
+
interface WsClientOptions {
|
|
46
|
+
silent?: boolean;
|
|
47
|
+
[key: string]: unknown;
|
|
48
|
+
}
|
|
49
|
+
declare const _default: (Socket: SocketConstructor, EventEmitter: EventEmitterConstructor, transport?: unknown) => {
|
|
50
|
+
new (endpoint: string, opts?: WsClientOptions): {
|
|
51
|
+
_logger: ReturnType<typeof _default$1>;
|
|
52
|
+
emitter: EventEmitterInstance;
|
|
53
|
+
on(event: string, handler: (data: unknown) => void, params?: object): void;
|
|
54
|
+
off(event: string, handler?: (data: unknown) => void): void;
|
|
55
|
+
disconnect(callback?: () => void, code?: number, reason?: string): void;
|
|
56
|
+
/**
|
|
57
|
+
* private
|
|
58
|
+
*/
|
|
59
|
+
ensureJoinChannel(topic: string, params?: object): void;
|
|
60
|
+
/**
|
|
61
|
+
* private
|
|
62
|
+
*/
|
|
63
|
+
ensureLeaveChannel(topic: string): void;
|
|
64
|
+
subscribe(topic: string, params?: object): Channel;
|
|
65
|
+
unsubscribe(topic: string): void;
|
|
66
|
+
channels: Channel[];
|
|
67
|
+
connect(): void;
|
|
68
|
+
isConnected(): boolean;
|
|
69
|
+
onOpen(cb: () => void): void;
|
|
70
|
+
onClose(cb: () => void): void;
|
|
71
|
+
onError(cb: (err: {
|
|
72
|
+
error: Error;
|
|
73
|
+
}) => void): void;
|
|
74
|
+
onMessage(cb: (message: unknown) => void): void;
|
|
75
|
+
channel(topic: string, params?: object | (() => object)): Channel;
|
|
76
|
+
remove(channel: Channel): void;
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
//#endregion
|
|
80
|
+
export { Channel, ChannelPush, EventEmitterConstructor, EventEmitterInstance, SocketConstructor, SocketInstance, WsClientOptions, _default as default };
|