@rlanz/socket 0.0.1-9 → 0.1.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/README.md +556 -556
- package/build/{base_channel-DPOnqned.d.ts → base_channel-CasBQV5I.d.ts} +7 -25
- package/build/{chunk-6PJ4ALJD.js → chunk-DM3UQMWW.js} +5 -3
- package/build/chunk-DM3UQMWW.js.map +1 -0
- package/build/{chunk-QNOOQXYY.js → chunk-DR6GWQUW.js} +242 -199
- package/build/chunk-DR6GWQUW.js.map +1 -0
- package/build/chunk-NUPBNOP3.js +61 -0
- package/build/chunk-NUPBNOP3.js.map +1 -0
- package/build/{chunk-YVC7IMDN.js → chunk-UE75J3JE.js} +2 -2
- package/build/chunk-YSW4MNFR.js +7 -0
- package/build/chunk-YSW4MNFR.js.map +1 -0
- package/build/{framework-B8pJ4cCA.d.ts → framework-Cb71JA5f.d.ts} +1 -1
- package/build/{index-DjBYUhFR.d.ts → index-DCPtfkJL.d.ts} +1 -1
- package/build/index.d.ts +3 -2
- package/build/providers/socket_provider.d.ts +3 -2
- package/build/providers/socket_provider.js +312 -130
- package/build/providers/socket_provider.js.map +1 -1
- package/build/services/socket.d.ts +3 -2
- package/build/shared_types-B11CaOr5.d.ts +26 -0
- package/build/{socket_service-BcC9ASCE.d.ts → socket_service-C2YRbhrO.d.ts} +1 -1
- package/build/src/assembler_hook.js +54 -30
- package/build/src/assembler_hook.js.map +1 -1
- package/build/src/client/index.d.ts +2 -7
- package/build/src/client/index.js +3 -2
- package/build/src/client/react.d.ts +3 -8
- package/build/src/client/react.js +4 -3
- package/build/src/client/react.js.map +1 -1
- package/build/src/client/types.d.ts +9 -13
- package/build/src/client/vue.d.ts +3 -8
- package/build/src/client/vue.js +4 -3
- package/build/src/client/vue.js.map +1 -1
- package/build/src/decorators.d.ts +2 -1
- package/build/src/health_check.d.ts +3 -2
- package/build/src/otel.d.ts +1 -0
- package/build/src/otel.js +211 -27
- package/build/src/otel.js.map +1 -1
- package/build/src/types.d.ts +2 -1
- package/package.json +31 -7
- package/build/chunk-6PJ4ALJD.js.map +0 -1
- package/build/chunk-D3HUBCBW.js +0 -18
- package/build/chunk-D3HUBCBW.js.map +0 -1
- package/build/chunk-QNOOQXYY.js.map +0 -1
- /package/build/{chunk-YVC7IMDN.js.map → chunk-UE75J3JE.js.map} +0 -0
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SERVER_DISCONNECT_CODE
|
|
3
|
+
} from "./chunk-YSW4MNFR.js";
|
|
1
4
|
import {
|
|
2
5
|
BindableChannelPattern
|
|
3
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-DM3UQMWW.js";
|
|
4
7
|
|
|
5
8
|
// src/client/callback.ts
|
|
6
9
|
function reportClientError(error) {
|
|
@@ -86,7 +89,7 @@ var Channel = class {
|
|
|
86
89
|
* Whether the channel should stay subscribed.
|
|
87
90
|
*/
|
|
88
91
|
#subscribed = false;
|
|
89
|
-
#
|
|
92
|
+
#remoteSynchronization = { status: "idle" };
|
|
90
93
|
/**
|
|
91
94
|
* Event handlers grouped by event type.
|
|
92
95
|
*/
|
|
@@ -114,11 +117,7 @@ var Channel = class {
|
|
|
114
117
|
#boundPresenceHandler = null;
|
|
115
118
|
#boundEventHandler = null;
|
|
116
119
|
#boundDisconnectHandler = null;
|
|
117
|
-
#listenersAttached = false;
|
|
118
|
-
#pendingSubscribe = null;
|
|
119
|
-
#subscribeToken = 0;
|
|
120
120
|
#managedSubscriptions = 0;
|
|
121
|
-
#pendingUnsubscribe = null;
|
|
122
121
|
constructor(name, transport) {
|
|
123
122
|
this.name = name;
|
|
124
123
|
this.#transport = transport;
|
|
@@ -137,7 +136,7 @@ var Channel = class {
|
|
|
137
136
|
* Whether the channel is active on the server.
|
|
138
137
|
*/
|
|
139
138
|
get active() {
|
|
140
|
-
return this.#active;
|
|
139
|
+
return this.#remoteSynchronization.status === "active";
|
|
141
140
|
}
|
|
142
141
|
/**
|
|
143
142
|
* List of present users.
|
|
@@ -237,7 +236,9 @@ var Channel = class {
|
|
|
237
236
|
return {
|
|
238
237
|
ready,
|
|
239
238
|
release: () => {
|
|
240
|
-
if (released)
|
|
239
|
+
if (released) {
|
|
240
|
+
return this.#remoteSynchronization.status === "unsubscribing" ? this.#remoteSynchronization.promise : Promise.resolve();
|
|
241
|
+
}
|
|
241
242
|
released = true;
|
|
242
243
|
this.#managedSubscriptions--;
|
|
243
244
|
return this.#unsubscribeIfUnused();
|
|
@@ -249,43 +250,41 @@ var Channel = class {
|
|
|
249
250
|
return this.#ensureSubscribed(options);
|
|
250
251
|
}
|
|
251
252
|
async #ensureSubscribed(options) {
|
|
252
|
-
if (this.#
|
|
253
|
-
await this.#
|
|
253
|
+
if (this.#remoteSynchronization.status === "unsubscribing") {
|
|
254
|
+
await this.#remoteSynchronization.promise;
|
|
254
255
|
}
|
|
255
256
|
if (!this.subscribed) return this;
|
|
256
|
-
if (this.#
|
|
257
|
-
await this.#
|
|
257
|
+
if (this.#remoteSynchronization.status === "subscribing") {
|
|
258
|
+
await this.#remoteSynchronization.pending.promise;
|
|
258
259
|
return this;
|
|
259
260
|
}
|
|
260
|
-
if (this.#active) {
|
|
261
|
+
if (this.#remoteSynchronization.status === "active") {
|
|
261
262
|
return this;
|
|
262
263
|
}
|
|
263
264
|
const timeout = options.timeout ?? 5e3;
|
|
264
|
-
if (
|
|
265
|
+
if (this.#remoteSynchronization.status === "idle" || this.#remoteSynchronization.status === "remoteSubscriptionUncertain") {
|
|
265
266
|
this.#setupSocketListeners();
|
|
266
267
|
}
|
|
267
|
-
const token = ++this.#subscribeToken;
|
|
268
268
|
const deferred = Promise.withResolvers();
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
token
|
|
269
|
+
const synchronization = {
|
|
270
|
+
status: "subscribing",
|
|
271
|
+
pending: deferred,
|
|
272
|
+
connected: true
|
|
274
273
|
};
|
|
275
|
-
|
|
274
|
+
this.#remoteSynchronization = synchronization;
|
|
276
275
|
const timer = setTimeout(() => {
|
|
277
|
-
if (
|
|
278
|
-
this.#
|
|
279
|
-
this.#pendingSubscribe = null;
|
|
276
|
+
if (this.#remoteSynchronization !== synchronization) return;
|
|
277
|
+
this.#remoteSynchronization = { status: "waitingForReconnect" };
|
|
280
278
|
this.#resetConnectionState();
|
|
281
|
-
reject(new Error(`Subscribe timeout for channel: ${this.name}`));
|
|
279
|
+
synchronization.pending.reject(new Error(`Subscribe timeout for channel: ${this.name}`));
|
|
280
|
+
void this.#transport.sendRequest({ type: "unsubscribe", channel: this.name }, timeout).catch(() => {
|
|
281
|
+
});
|
|
282
282
|
}, timeout);
|
|
283
283
|
this.#transport.sendRequest({ type: "subscribe", channel: this.name }, timeout).then((response) => {
|
|
284
|
-
if (
|
|
284
|
+
if (this.#remoteSynchronization !== synchronization) return;
|
|
285
285
|
clearTimeout(timer);
|
|
286
|
-
this.#pendingSubscribe = null;
|
|
287
286
|
if (response.ok) {
|
|
288
|
-
this.#
|
|
287
|
+
this.#remoteSynchronization = { status: "active" };
|
|
289
288
|
const presenceData = ClientPresenceSnapshot.fromTransport(
|
|
290
289
|
response.data?.presenceData,
|
|
291
290
|
this.name
|
|
@@ -293,17 +292,20 @@ var Channel = class {
|
|
|
293
292
|
if (presenceData) {
|
|
294
293
|
this.#handleInitialPresence(presenceData);
|
|
295
294
|
}
|
|
296
|
-
resolve(this);
|
|
295
|
+
synchronization.pending.resolve(this);
|
|
297
296
|
} else {
|
|
297
|
+
this.#remoteSynchronization = { status: "waitingForReconnect" };
|
|
298
298
|
this.#resetConnectionState();
|
|
299
|
-
reject(new Error(response.error
|
|
299
|
+
synchronization.pending.reject(new Error(response.error));
|
|
300
300
|
}
|
|
301
301
|
}).catch((error) => {
|
|
302
|
-
if (
|
|
302
|
+
if (this.#remoteSynchronization !== synchronization) return;
|
|
303
303
|
clearTimeout(timer);
|
|
304
|
-
this.#
|
|
304
|
+
this.#remoteSynchronization = synchronization.connected ? { status: "subscriptionUncertain" } : { status: "waitingForReconnect" };
|
|
305
305
|
this.#resetConnectionState();
|
|
306
|
-
reject(
|
|
306
|
+
synchronization.pending.reject(
|
|
307
|
+
error instanceof Error ? error : new Error("Subscribe failed")
|
|
308
|
+
);
|
|
307
309
|
});
|
|
308
310
|
await deferred.promise;
|
|
309
311
|
return this;
|
|
@@ -318,35 +320,45 @@ var Channel = class {
|
|
|
318
320
|
}
|
|
319
321
|
#unsubscribeIfUnused(options = {}) {
|
|
320
322
|
if (this.subscribed) return Promise.resolve();
|
|
321
|
-
if (this.#
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
323
|
+
if (this.#remoteSynchronization.status === "unsubscribing") {
|
|
324
|
+
return this.#remoteSynchronization.promise;
|
|
325
|
+
}
|
|
326
|
+
const previousSynchronization = this.#remoteSynchronization;
|
|
327
|
+
const hadPendingSubscribe = previousSynchronization.status === "subscribing";
|
|
328
|
+
if (previousSynchronization.status === "subscribing") {
|
|
329
|
+
previousSynchronization.pending.reject(new Error(`Unsubscribed from channel: ${this.name}`));
|
|
330
|
+
}
|
|
331
|
+
const remoteSubscriptionPossible = previousSynchronization.status === "active" || previousSynchronization.status === "subscriptionUncertain" || previousSynchronization.status === "remoteSubscriptionUncertain";
|
|
332
|
+
if (!hadPendingSubscribe && !remoteSubscriptionPossible) {
|
|
329
333
|
this.#cleanupTerminal();
|
|
330
334
|
return Promise.resolve();
|
|
331
335
|
}
|
|
332
|
-
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
336
|
+
const timeout = options.timeout ?? 5e3;
|
|
337
|
+
const deferred = Promise.withResolvers();
|
|
338
|
+
const synchronization = {
|
|
339
|
+
status: "unsubscribing",
|
|
340
|
+
promise: deferred.promise
|
|
341
|
+
};
|
|
342
|
+
this.#remoteSynchronization = synchronization;
|
|
343
|
+
let remoteMayRemain = true;
|
|
344
|
+
let terminalSynchronization = null;
|
|
345
|
+
const timer = setTimeout(() => deferred.resolve(), timeout);
|
|
346
|
+
this.#transport.sendRequest({ type: "unsubscribe", channel: this.name }, timeout).then(() => {
|
|
347
|
+
remoteMayRemain = false;
|
|
348
|
+
clearTimeout(timer);
|
|
349
|
+
deferred.resolve();
|
|
350
|
+
if (this.#remoteSynchronization === terminalSynchronization) {
|
|
351
|
+
this.#remoteSynchronization = { status: "idle" };
|
|
352
|
+
}
|
|
353
|
+
}).catch(() => {
|
|
354
|
+
clearTimeout(timer);
|
|
355
|
+
deferred.resolve();
|
|
345
356
|
});
|
|
346
|
-
|
|
357
|
+
const pending = deferred.promise;
|
|
347
358
|
void pending.finally(() => {
|
|
348
|
-
if (this.#
|
|
349
|
-
|
|
359
|
+
if (this.#remoteSynchronization !== synchronization) return;
|
|
360
|
+
terminalSynchronization = remoteMayRemain ? { status: "remoteSubscriptionUncertain" } : { status: "idle" };
|
|
361
|
+
this.#remoteSynchronization = terminalSynchronization;
|
|
350
362
|
this.#resetConnectionState();
|
|
351
363
|
this.#detachTransportListeners();
|
|
352
364
|
if (!this.subscribed) this.#clearCallbacks();
|
|
@@ -354,7 +366,7 @@ var Channel = class {
|
|
|
354
366
|
return pending;
|
|
355
367
|
}
|
|
356
368
|
send(event, data) {
|
|
357
|
-
if (!this
|
|
369
|
+
if (!this.active) {
|
|
358
370
|
console.warn(`Cannot send to channel ${this.name}: not subscribed`);
|
|
359
371
|
return this;
|
|
360
372
|
}
|
|
@@ -370,7 +382,7 @@ var Channel = class {
|
|
|
370
382
|
* Relays a client event to other members of this channel.
|
|
371
383
|
*/
|
|
372
384
|
whisper(event, data) {
|
|
373
|
-
if (!this
|
|
385
|
+
if (!this.active) {
|
|
374
386
|
console.warn(`Cannot whisper to channel ${this.name}: not subscribed`);
|
|
375
387
|
return this;
|
|
376
388
|
}
|
|
@@ -383,7 +395,7 @@ var Channel = class {
|
|
|
383
395
|
return this;
|
|
384
396
|
}
|
|
385
397
|
async sendWithAck(event, data) {
|
|
386
|
-
if (!this
|
|
398
|
+
if (!this.active) {
|
|
387
399
|
throw new Error(`Cannot send to channel ${this.name}: not subscribed`);
|
|
388
400
|
}
|
|
389
401
|
const payload = {
|
|
@@ -421,10 +433,16 @@ var Channel = class {
|
|
|
421
433
|
};
|
|
422
434
|
this.#transport.on(`channel:${this.name}:event`, this.#boundEventHandler);
|
|
423
435
|
this.#boundDisconnectHandler = () => {
|
|
436
|
+
if (this.#remoteSynchronization.status === "subscribing") {
|
|
437
|
+
this.#remoteSynchronization.connected = false;
|
|
438
|
+
} else if (this.#remoteSynchronization.status === "active" || this.#remoteSynchronization.status === "subscriptionUncertain") {
|
|
439
|
+
this.#remoteSynchronization = { status: "waitingForReconnect" };
|
|
440
|
+
} else if (this.#remoteSynchronization.status === "remoteSubscriptionUncertain") {
|
|
441
|
+
this.#remoteSynchronization = { status: "idle" };
|
|
442
|
+
}
|
|
424
443
|
this.#resetConnectionState();
|
|
425
444
|
};
|
|
426
445
|
this.#transport.on("disconnect", this.#boundDisconnectHandler);
|
|
427
|
-
this.#listenersAttached = true;
|
|
428
446
|
}
|
|
429
447
|
/**
|
|
430
448
|
* Handles the initial presence snapshot.
|
|
@@ -460,7 +478,6 @@ var Channel = class {
|
|
|
460
478
|
}
|
|
461
479
|
}
|
|
462
480
|
#resetConnectionState() {
|
|
463
|
-
this.#active = false;
|
|
464
481
|
this.#presence = null;
|
|
465
482
|
}
|
|
466
483
|
#detachTransportListeners() {
|
|
@@ -476,7 +493,6 @@ var Channel = class {
|
|
|
476
493
|
this.#transport.off("disconnect", this.#boundDisconnectHandler);
|
|
477
494
|
this.#boundDisconnectHandler = null;
|
|
478
495
|
}
|
|
479
|
-
this.#listenersAttached = false;
|
|
480
496
|
}
|
|
481
497
|
#clearUserCallbacks() {
|
|
482
498
|
this.#eventHandlers.clear();
|
|
@@ -492,6 +508,7 @@ var Channel = class {
|
|
|
492
508
|
* Performs terminal cleanup after the user leaves or the socket is disposed.
|
|
493
509
|
*/
|
|
494
510
|
#cleanupTerminal() {
|
|
511
|
+
this.#remoteSynchronization = { status: "idle" };
|
|
495
512
|
this.#resetConnectionState();
|
|
496
513
|
this.#detachTransportListeners();
|
|
497
514
|
this.#clearCallbacks();
|
|
@@ -500,22 +517,17 @@ var Channel = class {
|
|
|
500
517
|
|
|
501
518
|
// src/client/socket_session.ts
|
|
502
519
|
var ClientSocketSession = class {
|
|
503
|
-
#
|
|
504
|
-
#state = "disconnected";
|
|
505
|
-
#connectPromise = null;
|
|
506
|
-
#cancelConnect = null;
|
|
520
|
+
#lifecycle = { kind: "disconnected", reconnectAttempt: 0 };
|
|
507
521
|
#stateHandlers = /* @__PURE__ */ new Set();
|
|
508
522
|
#eventHandlers = /* @__PURE__ */ new Map();
|
|
509
523
|
#pendingRequests = /* @__PURE__ */ new Map();
|
|
510
524
|
#requestId = 0;
|
|
511
|
-
#reconnectAttempts = 0;
|
|
512
|
-
#reconnectTimer = null;
|
|
513
|
-
#manualDisconnect = false;
|
|
514
525
|
#buildUrl;
|
|
515
526
|
#createWebSocket;
|
|
516
527
|
#autoReconnect;
|
|
517
528
|
#reconnectDelay;
|
|
518
529
|
#reconnectMaxDelay;
|
|
530
|
+
#shouldReconnect;
|
|
519
531
|
#onConnected;
|
|
520
532
|
constructor(options) {
|
|
521
533
|
this.#buildUrl = options.buildUrl;
|
|
@@ -523,115 +535,109 @@ var ClientSocketSession = class {
|
|
|
523
535
|
this.#autoReconnect = options.autoReconnect ?? true;
|
|
524
536
|
this.#reconnectDelay = Math.max(0, options.reconnectDelay ?? 250);
|
|
525
537
|
this.#reconnectMaxDelay = Math.max(this.#reconnectDelay, options.reconnectMaxDelay ?? 5e3);
|
|
538
|
+
this.#shouldReconnect = options.shouldReconnect ?? ((event) => event.code !== SERVER_DISCONNECT_CODE);
|
|
526
539
|
this.#onConnected = options.onConnected;
|
|
527
540
|
}
|
|
528
541
|
get state() {
|
|
529
|
-
|
|
542
|
+
switch (this.#lifecycle.kind) {
|
|
543
|
+
case "opening":
|
|
544
|
+
case "connecting":
|
|
545
|
+
return "connecting";
|
|
546
|
+
case "opened":
|
|
547
|
+
case "connected":
|
|
548
|
+
return "connected";
|
|
549
|
+
case "disconnected":
|
|
550
|
+
case "waiting":
|
|
551
|
+
case "failed":
|
|
552
|
+
return "disconnected";
|
|
553
|
+
}
|
|
530
554
|
}
|
|
531
555
|
get connected() {
|
|
532
|
-
return this
|
|
556
|
+
return this.state === "connected";
|
|
533
557
|
}
|
|
534
558
|
connect() {
|
|
535
|
-
if (this.#
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
this.#
|
|
542
|
-
this.#
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
}
|
|
581
|
-
settled = true;
|
|
582
|
-
cleanup();
|
|
583
|
-
this.#reconnectAttempts = 0;
|
|
584
|
-
this.#emitLocal("connect");
|
|
585
|
-
if (this.#onConnected) {
|
|
586
|
-
this.#callHandler(this.#onConnected);
|
|
587
|
-
}
|
|
588
|
-
resolve();
|
|
589
|
-
};
|
|
590
|
-
const handleError = () => {
|
|
591
|
-
cancelConnect(new Error("WebSocket connection failed"));
|
|
592
|
-
if (this.#ws === ws) {
|
|
593
|
-
this.#setState("disconnected");
|
|
594
|
-
}
|
|
595
|
-
};
|
|
596
|
-
const handleClose = () => {
|
|
597
|
-
cancelConnect(new Error("WebSocket connection failed"));
|
|
598
|
-
if (this.#ws === ws) {
|
|
599
|
-
this.#handleClose();
|
|
600
|
-
}
|
|
559
|
+
if (this.#lifecycle.kind === "connected") return Promise.resolve();
|
|
560
|
+
if (this.#lifecycle.kind === "opening" || this.#lifecycle.kind === "connecting") {
|
|
561
|
+
return this.#lifecycle.deferred.promise;
|
|
562
|
+
}
|
|
563
|
+
if (this.#lifecycle.kind === "opened") return this.#lifecycle.deferred.promise;
|
|
564
|
+
const reconnectAttempt = this.#reconnectAttempt;
|
|
565
|
+
const failedSocket = this.#lifecycle.kind === "failed" ? this.#lifecycle.connection.socket : null;
|
|
566
|
+
if (this.#lifecycle.kind === "waiting") clearTimeout(this.#lifecycle.timer);
|
|
567
|
+
const connection = this.#startConnection(reconnectAttempt);
|
|
568
|
+
failedSocket?.close();
|
|
569
|
+
return connection;
|
|
570
|
+
}
|
|
571
|
+
#startConnection(reconnectAttempt) {
|
|
572
|
+
const deferred = Promise.withResolvers();
|
|
573
|
+
const opening = { kind: "opening", deferred, reconnectAttempt };
|
|
574
|
+
this.#setLifecycle(opening);
|
|
575
|
+
if (this.#lifecycle !== opening) return deferred.promise;
|
|
576
|
+
let socket;
|
|
577
|
+
try {
|
|
578
|
+
socket = this.#createWebSocket(this.#buildUrl());
|
|
579
|
+
} catch (error) {
|
|
580
|
+
this.#setLifecycle({ kind: "disconnected", reconnectAttempt });
|
|
581
|
+
deferred.reject(error);
|
|
582
|
+
return deferred.promise;
|
|
583
|
+
}
|
|
584
|
+
const connection = { socket };
|
|
585
|
+
const connecting = {
|
|
586
|
+
kind: "connecting",
|
|
587
|
+
connection,
|
|
588
|
+
deferred,
|
|
589
|
+
reconnectAttempt
|
|
590
|
+
};
|
|
591
|
+
this.#lifecycle = connecting;
|
|
592
|
+
const cleanup = () => {
|
|
593
|
+
socket.removeEventListener("open", handleOpen);
|
|
594
|
+
socket.removeEventListener("error", handleError);
|
|
595
|
+
};
|
|
596
|
+
const handleOpen = () => {
|
|
597
|
+
if (this.#lifecycle !== connecting) return;
|
|
598
|
+
cleanup();
|
|
599
|
+
const opened = {
|
|
600
|
+
kind: "opened",
|
|
601
|
+
connection,
|
|
602
|
+
deferred,
|
|
603
|
+
reconnectAttempt
|
|
601
604
|
};
|
|
602
|
-
this.#
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
605
|
+
this.#setLifecycle(opened);
|
|
606
|
+
if (!this.#isCurrent(opened)) return;
|
|
607
|
+
this.#lifecycle = { kind: "connected", connection };
|
|
608
|
+
this.#emitLocal("connect");
|
|
609
|
+
if (this.#onConnected) this.#callHandler(this.#onConnected);
|
|
610
|
+
deferred.resolve();
|
|
611
|
+
};
|
|
612
|
+
const handleError = () => {
|
|
613
|
+
if (this.#lifecycle !== connecting) return;
|
|
614
|
+
cleanup();
|
|
615
|
+
deferred.reject(new Error("WebSocket connection failed"));
|
|
616
|
+
this.#setLifecycle({ kind: "failed", connection, reconnectAttempt });
|
|
617
|
+
};
|
|
618
|
+
socket.addEventListener("open", handleOpen);
|
|
619
|
+
socket.addEventListener("error", handleError);
|
|
620
|
+
socket.addEventListener("message", (event) => {
|
|
621
|
+
if (this.#owns(connection)) this.#handleIncoming(event.data);
|
|
611
622
|
});
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
this.#
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
return connectPromise;
|
|
623
|
+
socket.addEventListener("close", (event) => {
|
|
624
|
+
cleanup();
|
|
625
|
+
if (this.#owns(connection)) this.#handleClose(event);
|
|
626
|
+
});
|
|
627
|
+
return deferred.promise;
|
|
618
628
|
}
|
|
619
629
|
disconnect() {
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
this.#
|
|
627
|
-
const ws = this.#ws;
|
|
628
|
-
const shouldEmitDisconnect = this.#state !== "disconnected";
|
|
629
|
-
this.#ws = null;
|
|
630
|
-
this.#setState("disconnected");
|
|
630
|
+
const lifecycle = this.#lifecycle;
|
|
631
|
+
const shouldEmitDisconnect = this.state !== "disconnected";
|
|
632
|
+
if (lifecycle.kind === "waiting") clearTimeout(lifecycle.timer);
|
|
633
|
+
if (lifecycle.kind === "opening" || lifecycle.kind === "connecting" || lifecycle.kind === "opened") {
|
|
634
|
+
lifecycle.deferred.reject(new Error("Socket disconnected"));
|
|
635
|
+
}
|
|
636
|
+
this.#setLifecycle({ kind: "disconnected", reconnectAttempt: 0 });
|
|
631
637
|
if (shouldEmitDisconnect) {
|
|
632
638
|
this.#emitLocal("disconnect");
|
|
633
639
|
}
|
|
634
|
-
|
|
640
|
+
if ("connection" in lifecycle) lifecycle.connection.socket.close();
|
|
635
641
|
this.#rejectPending(new Error("Socket disconnected"));
|
|
636
642
|
}
|
|
637
643
|
onStateChange(handler) {
|
|
@@ -653,15 +659,15 @@ var ClientSocketSession = class {
|
|
|
653
659
|
this.#eventHandlers.get(event)?.delete(handler);
|
|
654
660
|
}
|
|
655
661
|
send(message) {
|
|
656
|
-
const
|
|
657
|
-
if (
|
|
662
|
+
const socket = this.#connection?.socket;
|
|
663
|
+
if (socket?.readyState !== WebSocket.OPEN) {
|
|
658
664
|
return;
|
|
659
665
|
}
|
|
660
|
-
|
|
666
|
+
socket.send(JSON.stringify(message));
|
|
661
667
|
}
|
|
662
668
|
sendRequest(message, timeout = 5e3) {
|
|
663
|
-
const
|
|
664
|
-
if (
|
|
669
|
+
const socket = this.#connection?.socket;
|
|
670
|
+
if (socket?.readyState !== WebSocket.OPEN) {
|
|
665
671
|
return Promise.reject(new Error("Socket is not connected"));
|
|
666
672
|
}
|
|
667
673
|
const id = String(++this.#requestId);
|
|
@@ -676,7 +682,7 @@ var ClientSocketSession = class {
|
|
|
676
682
|
reject,
|
|
677
683
|
timer
|
|
678
684
|
});
|
|
679
|
-
|
|
685
|
+
socket.send(JSON.stringify(payload));
|
|
680
686
|
});
|
|
681
687
|
}
|
|
682
688
|
#handleIncoming(raw) {
|
|
@@ -710,7 +716,7 @@ var ClientSocketSession = class {
|
|
|
710
716
|
if (!message || typeof message !== "object" || Array.isArray(message)) {
|
|
711
717
|
return null;
|
|
712
718
|
}
|
|
713
|
-
if (message.type === "ack" && typeof message.id === "string" &&
|
|
719
|
+
if (message.type === "ack" && typeof message.id === "string" && (message.ok === true && message.error === void 0 || message.ok === false && typeof message.error === "string" && message.data === void 0)) {
|
|
714
720
|
return message;
|
|
715
721
|
}
|
|
716
722
|
if (message.type === "event" && typeof message.event === "string" && (message.channel === void 0 || typeof message.channel === "string")) {
|
|
@@ -737,33 +743,75 @@ var ClientSocketSession = class {
|
|
|
737
743
|
pending.resolve({ ok: false, error: message.error });
|
|
738
744
|
}
|
|
739
745
|
}
|
|
740
|
-
#handleClose() {
|
|
741
|
-
const
|
|
742
|
-
|
|
743
|
-
this
|
|
746
|
+
#handleClose(event) {
|
|
747
|
+
const lifecycle = this.#lifecycle;
|
|
748
|
+
const reconnectAttempt = "reconnectAttempt" in lifecycle ? lifecycle.reconnectAttempt : 0;
|
|
749
|
+
const shouldEmitDisconnect = this.state !== "disconnected";
|
|
750
|
+
if (lifecycle.kind === "connecting") {
|
|
751
|
+
lifecycle.deferred.reject(new Error("WebSocket connection failed"));
|
|
752
|
+
}
|
|
753
|
+
const disconnected = { kind: "disconnected", reconnectAttempt };
|
|
754
|
+
this.#setLifecycle(disconnected);
|
|
744
755
|
if (shouldEmitDisconnect) {
|
|
745
756
|
this.#emitLocal("disconnect");
|
|
746
757
|
}
|
|
747
758
|
this.#rejectPending(new Error("Socket disconnected"));
|
|
748
|
-
this.#
|
|
749
|
-
|
|
750
|
-
#scheduleReconnect() {
|
|
751
|
-
if (this.#manualDisconnect || !this.#autoReconnect || this.#reconnectTimer) {
|
|
752
|
-
return;
|
|
759
|
+
if (this.#shouldReconnect(event) && this.#lifecycle === disconnected) {
|
|
760
|
+
this.#scheduleReconnect(disconnected);
|
|
753
761
|
}
|
|
762
|
+
}
|
|
763
|
+
#scheduleReconnect(disconnected) {
|
|
764
|
+
if (!this.#autoReconnect || this.#lifecycle !== disconnected) return;
|
|
754
765
|
const delay = Math.min(
|
|
755
|
-
this.#reconnectDelay * 2 **
|
|
766
|
+
this.#reconnectDelay * 2 ** disconnected.reconnectAttempt,
|
|
756
767
|
this.#reconnectMaxDelay
|
|
757
768
|
);
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
if (this.#
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
769
|
+
const waiting = {
|
|
770
|
+
kind: "waiting",
|
|
771
|
+
reconnectAttempt: disconnected.reconnectAttempt + 1,
|
|
772
|
+
timer: setTimeout(() => {
|
|
773
|
+
if (this.#lifecycle !== waiting) return;
|
|
774
|
+
this.#startConnection(waiting.reconnectAttempt).catch(() => {
|
|
775
|
+
if (this.#lifecycle.kind === "failed") {
|
|
776
|
+
const failed = this.#lifecycle;
|
|
777
|
+
const disconnected2 = {
|
|
778
|
+
kind: "disconnected",
|
|
779
|
+
reconnectAttempt: failed.reconnectAttempt
|
|
780
|
+
};
|
|
781
|
+
this.#setLifecycle(disconnected2);
|
|
782
|
+
failed.connection.socket.close();
|
|
783
|
+
if (this.#isCurrent(disconnected2)) this.#scheduleReconnect(disconnected2);
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
if (this.#lifecycle.kind === "disconnected") {
|
|
787
|
+
this.#scheduleReconnect(this.#lifecycle);
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
}, delay)
|
|
791
|
+
};
|
|
792
|
+
this.#setLifecycle(waiting);
|
|
793
|
+
}
|
|
794
|
+
get #connection() {
|
|
795
|
+
const lifecycle = this.#lifecycle;
|
|
796
|
+
return "connection" in lifecycle ? lifecycle.connection : void 0;
|
|
797
|
+
}
|
|
798
|
+
get #reconnectAttempt() {
|
|
799
|
+
const lifecycle = this.#lifecycle;
|
|
800
|
+
return "reconnectAttempt" in lifecycle ? lifecycle.reconnectAttempt : 0;
|
|
801
|
+
}
|
|
802
|
+
#owns(connection) {
|
|
803
|
+
return this.#connection === connection;
|
|
804
|
+
}
|
|
805
|
+
#isCurrent(lifecycle) {
|
|
806
|
+
return this.#lifecycle === lifecycle;
|
|
807
|
+
}
|
|
808
|
+
#setLifecycle(lifecycle) {
|
|
809
|
+
const previousState = this.state;
|
|
810
|
+
this.#lifecycle = lifecycle;
|
|
811
|
+
const state = this.state;
|
|
812
|
+
if (previousState !== state) {
|
|
813
|
+
this.#stateHandlers.forEach((handler) => this.#callHandler(handler, state));
|
|
814
|
+
}
|
|
767
815
|
}
|
|
768
816
|
#rejectPending(error) {
|
|
769
817
|
for (const [id, pending] of this.#pendingRequests) {
|
|
@@ -775,13 +823,6 @@ var ClientSocketSession = class {
|
|
|
775
823
|
#emitLocal(event, data) {
|
|
776
824
|
this.#eventHandlers.get(event)?.forEach((handler) => this.#callHandler(handler, data));
|
|
777
825
|
}
|
|
778
|
-
#setState(state) {
|
|
779
|
-
if (this.#state === state) {
|
|
780
|
-
return;
|
|
781
|
-
}
|
|
782
|
-
this.#state = state;
|
|
783
|
-
this.#stateHandlers.forEach((handler) => this.#callHandler(handler, state));
|
|
784
|
-
}
|
|
785
826
|
#callHandler(handler, data) {
|
|
786
827
|
callClientHandler(handler, data);
|
|
787
828
|
}
|
|
@@ -811,6 +852,7 @@ var Socket = class {
|
|
|
811
852
|
autoReconnect: options.autoReconnect,
|
|
812
853
|
reconnectDelay: options.reconnectDelay,
|
|
813
854
|
reconnectMaxDelay: options.reconnectMaxDelay,
|
|
855
|
+
shouldReconnect: options.shouldReconnect,
|
|
814
856
|
onConnected: () => {
|
|
815
857
|
void this.#resubscribeChannels();
|
|
816
858
|
}
|
|
@@ -879,6 +921,7 @@ var Socket = class {
|
|
|
879
921
|
throw new Error(`Unsupported socket URL protocol: ${url.protocol}`);
|
|
880
922
|
}
|
|
881
923
|
url.pathname = this.#path;
|
|
924
|
+
url.hash = "";
|
|
882
925
|
return url.toString();
|
|
883
926
|
}
|
|
884
927
|
async #resubscribeChannels() {
|
|
@@ -896,4 +939,4 @@ export {
|
|
|
896
939
|
buildChannelName,
|
|
897
940
|
Socket
|
|
898
941
|
};
|
|
899
|
-
//# sourceMappingURL=chunk-
|
|
942
|
+
//# sourceMappingURL=chunk-DR6GWQUW.js.map
|