@rlanz/socket 0.0.1-4 → 0.0.1-6
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 +430 -26
- package/build/chunk-B4Y3TNDI.js +159 -0
- package/build/chunk-B4Y3TNDI.js.map +1 -0
- package/build/{chunk-GKAD2UOA.js → chunk-D3HUBCBW.js} +1 -1
- package/build/chunk-D3HUBCBW.js.map +1 -0
- package/build/chunk-FVPY6HZW.js +136 -0
- package/build/chunk-FVPY6HZW.js.map +1 -0
- package/build/chunk-SFAY2ZA4.js +28 -0
- package/build/chunk-SFAY2ZA4.js.map +1 -0
- package/build/{chunk-XUDFDJME.js → chunk-SHH6U4CI.js} +12 -9
- package/build/chunk-SHH6U4CI.js.map +1 -0
- package/build/chunk-YAX5EHHB.js +453 -0
- package/build/chunk-YAX5EHHB.js.map +1 -0
- package/build/framework-DuW6zpPk.d.ts +14 -0
- package/build/index.d.ts +7 -13
- package/build/index.js +18 -10
- package/build/index.js.map +1 -1
- package/build/providers/socket_provider.d.ts +4 -3
- package/build/providers/socket_provider.js +1780 -57
- package/build/providers/socket_provider.js.map +1 -1
- package/build/services/socket.d.ts +4 -3
- package/build/socket_service-D3jKrleE.d.ts +105 -0
- package/build/src/assembler_hook.d.ts +15 -0
- package/build/src/assembler_hook.js +261 -0
- package/build/src/assembler_hook.js.map +1 -0
- package/build/src/client/index.d.ts +46 -12
- package/build/src/client/index.js +223 -76
- package/build/src/client/index.js.map +1 -1
- package/build/src/client/react.d.ts +27 -0
- package/build/src/client/react.js +69 -0
- package/build/src/client/react.js.map +1 -0
- package/build/src/client/svelte.d.ts +23 -0
- package/build/src/client/svelte.js +82 -0
- package/build/src/client/svelte.js.map +1 -0
- package/build/src/client/types.d.ts +69 -5
- package/build/src/client/vue.d.ts +26 -0
- package/build/src/client/vue.js +81 -0
- package/build/src/client/vue.js.map +1 -0
- package/build/src/decorators.d.ts +4 -4
- package/build/src/decorators.js +1 -1
- package/build/src/health_check.d.ts +4 -3
- package/build/src/otel.js +4 -5
- package/build/src/otel.js.map +1 -1
- package/build/src/testing.d.ts +54 -0
- package/build/src/testing.js +7 -0
- package/build/src/testing.js.map +1 -0
- package/build/src/types.d.ts +2 -2
- package/build/{types-BBLNfcWk.d.ts → types-DiYaFvgi.d.ts} +112 -63
- package/package.json +41 -6
- package/build/chunk-GKAD2UOA.js.map +0 -1
- package/build/chunk-ILDK672E.js +0 -1921
- package/build/chunk-ILDK672E.js.map +0 -1
- package/build/chunk-XUDFDJME.js.map +0 -1
- package/build/shared_types-Dw9AphfO.d.ts +0 -21
- package/build/socket_service-CRmPa74K.d.ts +0 -144
|
@@ -1,6 +1,30 @@
|
|
|
1
|
+
import {
|
|
2
|
+
callClientHandler,
|
|
3
|
+
reportClientError
|
|
4
|
+
} from "../../chunk-SFAY2ZA4.js";
|
|
5
|
+
|
|
1
6
|
// src/client/channel.ts
|
|
2
7
|
var CLIENT_EVENT_PREFIX = "client:";
|
|
3
|
-
var Channel = class {
|
|
8
|
+
var Channel = class _Channel {
|
|
9
|
+
static #parsePresenceData(value) {
|
|
10
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
const data = value;
|
|
14
|
+
if (typeof data.channel !== "string" || !Array.isArray(data.users) || !data.users.every(
|
|
15
|
+
(user) => user !== null && typeof user === "object" && typeof user.id === "string" && typeof user.joinedAt === "string"
|
|
16
|
+
)) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const usersById = /* @__PURE__ */ new Map();
|
|
20
|
+
for (const user of data.users) {
|
|
21
|
+
if (!usersById.has(user.id)) {
|
|
22
|
+
usersById.set(user.id, user);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const users = [...usersById.values()];
|
|
26
|
+
return { ...data, channel: data.channel, users, count: users.length };
|
|
27
|
+
}
|
|
4
28
|
/**
|
|
5
29
|
* Channel name.
|
|
6
30
|
*/
|
|
@@ -18,6 +42,7 @@ var Channel = class {
|
|
|
18
42
|
* Event handlers grouped by event type.
|
|
19
43
|
*/
|
|
20
44
|
#eventHandlers = /* @__PURE__ */ new Map();
|
|
45
|
+
#managedEventHandlers = /* @__PURE__ */ new Map();
|
|
21
46
|
/**
|
|
22
47
|
* Current presence data.
|
|
23
48
|
*/
|
|
@@ -47,6 +72,8 @@ var Channel = class {
|
|
|
47
72
|
#listenersAttached = false;
|
|
48
73
|
#pendingSubscribe = null;
|
|
49
74
|
#subscribeToken = 0;
|
|
75
|
+
#managedSubscriptions = 0;
|
|
76
|
+
#pendingUnsubscribe = null;
|
|
50
77
|
constructor(name, transport) {
|
|
51
78
|
this.name = name;
|
|
52
79
|
this.#transport = transport;
|
|
@@ -55,7 +82,11 @@ var Channel = class {
|
|
|
55
82
|
* Whether the channel should stay subscribed.
|
|
56
83
|
*/
|
|
57
84
|
get subscribed() {
|
|
58
|
-
return this.#subscribed;
|
|
85
|
+
return this.#subscribed || this.#managedSubscriptions > 0;
|
|
86
|
+
}
|
|
87
|
+
/** Whether a framework lifecycle currently owns this channel. @internal */
|
|
88
|
+
get $managed() {
|
|
89
|
+
return this.#managedSubscriptions > 0;
|
|
59
90
|
}
|
|
60
91
|
/**
|
|
61
92
|
* Whether the channel is active on the server.
|
|
@@ -97,19 +128,12 @@ var Channel = class {
|
|
|
97
128
|
this.#leavingHandler = handler;
|
|
98
129
|
return this;
|
|
99
130
|
}
|
|
100
|
-
/**
|
|
101
|
-
* Listens for an event on this channel.
|
|
102
|
-
*/
|
|
103
131
|
listen(event, handler) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
this.#eventHandlers.get(event).add(handler);
|
|
132
|
+
const handlers = this.#eventHandlers.get(event) ?? /* @__PURE__ */ new Set();
|
|
133
|
+
handlers.add(handler);
|
|
134
|
+
this.#eventHandlers.set(event, handlers);
|
|
108
135
|
return this;
|
|
109
136
|
}
|
|
110
|
-
/**
|
|
111
|
-
* Removes an event listener.
|
|
112
|
-
*/
|
|
113
137
|
stopListening(event, handler) {
|
|
114
138
|
if (handler) {
|
|
115
139
|
this.#eventHandlers.get(event)?.delete(handler);
|
|
@@ -118,17 +142,35 @@ var Channel = class {
|
|
|
118
142
|
}
|
|
119
143
|
return this;
|
|
120
144
|
}
|
|
145
|
+
/** Registers a lifecycle-owned event handler. @internal */
|
|
146
|
+
$listen(event, handler) {
|
|
147
|
+
const handlers = this.#managedEventHandlers.get(event) ?? /* @__PURE__ */ new Set();
|
|
148
|
+
handlers.add(handler);
|
|
149
|
+
this.#managedEventHandlers.set(event, handlers);
|
|
150
|
+
}
|
|
151
|
+
/** Removes a lifecycle-owned event handler. @internal */
|
|
152
|
+
$stopListening(event, handler) {
|
|
153
|
+
const handlers = this.#managedEventHandlers.get(event);
|
|
154
|
+
handlers?.delete(handler);
|
|
155
|
+
if (handlers?.size === 0) this.#managedEventHandlers.delete(event);
|
|
156
|
+
}
|
|
121
157
|
/**
|
|
122
158
|
* Listens for a client event whispered by another channel member.
|
|
123
159
|
*/
|
|
124
160
|
listenForWhisper(event, handler) {
|
|
125
|
-
|
|
161
|
+
const handlers = this.#eventHandlers.get(`${CLIENT_EVENT_PREFIX}${event}`) ?? /* @__PURE__ */ new Set();
|
|
162
|
+
handlers.add(handler);
|
|
163
|
+
this.#eventHandlers.set(`${CLIENT_EVENT_PREFIX}${event}`, handlers);
|
|
164
|
+
return this;
|
|
126
165
|
}
|
|
127
166
|
/**
|
|
128
167
|
* Removes a whispered client event listener.
|
|
129
168
|
*/
|
|
130
169
|
stopListeningForWhisper(event, handler) {
|
|
131
|
-
|
|
170
|
+
const name = `${CLIENT_EVENT_PREFIX}${event}`;
|
|
171
|
+
if (handler) this.#eventHandlers.get(name)?.delete(handler);
|
|
172
|
+
else this.#eventHandlers.delete(name);
|
|
173
|
+
return this;
|
|
132
174
|
}
|
|
133
175
|
/**
|
|
134
176
|
* Subscribes to the channel.
|
|
@@ -140,8 +182,35 @@ var Channel = class {
|
|
|
140
182
|
*/
|
|
141
183
|
async subscribe(options = {}) {
|
|
142
184
|
this.#subscribed = true;
|
|
185
|
+
return this.#ensureSubscribed(options);
|
|
186
|
+
}
|
|
187
|
+
/** Acquires subscription intent without overriding a direct consumer. @internal */
|
|
188
|
+
$acquire(options = {}) {
|
|
189
|
+
this.#managedSubscriptions++;
|
|
190
|
+
const ready = this.#transport.connected ? this.#ensureSubscribed(options) : Promise.resolve(this);
|
|
191
|
+
let released = false;
|
|
192
|
+
return {
|
|
193
|
+
ready,
|
|
194
|
+
release: () => {
|
|
195
|
+
if (released) return this.#pendingUnsubscribe ?? Promise.resolve();
|
|
196
|
+
released = true;
|
|
197
|
+
this.#managedSubscriptions--;
|
|
198
|
+
return this.#unsubscribeIfUnused();
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
/** Restores desired subscriptions after the transport reconnects. @internal */
|
|
203
|
+
$resubscribe(options = {}) {
|
|
204
|
+
return this.#ensureSubscribed(options);
|
|
205
|
+
}
|
|
206
|
+
async #ensureSubscribed(options) {
|
|
207
|
+
if (this.#pendingUnsubscribe) {
|
|
208
|
+
await this.#pendingUnsubscribe;
|
|
209
|
+
}
|
|
210
|
+
if (!this.subscribed) return this;
|
|
143
211
|
if (this.#pendingSubscribe) {
|
|
144
|
-
|
|
212
|
+
await this.#pendingSubscribe.promise;
|
|
213
|
+
return this;
|
|
145
214
|
}
|
|
146
215
|
if (this.#active) {
|
|
147
216
|
return this;
|
|
@@ -151,21 +220,17 @@ var Channel = class {
|
|
|
151
220
|
this.#setupSocketListeners();
|
|
152
221
|
}
|
|
153
222
|
const token = ++this.#subscribeToken;
|
|
154
|
-
|
|
155
|
-
let rejectPromise;
|
|
156
|
-
const promise = new Promise((resolve2, reject2) => {
|
|
157
|
-
resolvePromise = resolve2;
|
|
158
|
-
rejectPromise = reject2;
|
|
159
|
-
});
|
|
223
|
+
const deferred = Promise.withResolvers();
|
|
160
224
|
this.#pendingSubscribe = {
|
|
161
|
-
promise,
|
|
162
|
-
resolve:
|
|
163
|
-
reject:
|
|
225
|
+
promise: deferred.promise,
|
|
226
|
+
resolve: deferred.resolve,
|
|
227
|
+
reject: deferred.reject,
|
|
164
228
|
token
|
|
165
229
|
};
|
|
166
230
|
const { resolve, reject } = this.#pendingSubscribe;
|
|
167
231
|
const timer = setTimeout(() => {
|
|
168
232
|
if (token !== this.#subscribeToken) return;
|
|
233
|
+
this.#subscribeToken++;
|
|
169
234
|
this.#pendingSubscribe = null;
|
|
170
235
|
this.#resetConnectionState();
|
|
171
236
|
reject(new Error(`Subscribe timeout for channel: ${this.name}`));
|
|
@@ -176,7 +241,9 @@ var Channel = class {
|
|
|
176
241
|
this.#pendingSubscribe = null;
|
|
177
242
|
if (response.ok) {
|
|
178
243
|
this.#active = true;
|
|
179
|
-
const presenceData =
|
|
244
|
+
const presenceData = _Channel.#parsePresenceData(
|
|
245
|
+
response.data?.presenceData ?? response.presenceData
|
|
246
|
+
);
|
|
180
247
|
if (presenceData) {
|
|
181
248
|
this.#handleInitialPresence(presenceData);
|
|
182
249
|
}
|
|
@@ -192,39 +259,54 @@ var Channel = class {
|
|
|
192
259
|
this.#resetConnectionState();
|
|
193
260
|
reject(error instanceof Error ? error : new Error("Subscribe failed"));
|
|
194
261
|
});
|
|
195
|
-
|
|
262
|
+
await deferred.promise;
|
|
263
|
+
return this;
|
|
196
264
|
}
|
|
197
265
|
/**
|
|
198
266
|
* Unsubscribes from the channel.
|
|
199
267
|
*/
|
|
200
268
|
async unsubscribe(options = {}) {
|
|
201
|
-
if (!this.#subscribed && !this.#active) return;
|
|
202
269
|
this.#subscribed = false;
|
|
270
|
+
this.#clearUserCallbacks();
|
|
271
|
+
return this.#unsubscribeIfUnused(options);
|
|
272
|
+
}
|
|
273
|
+
#unsubscribeIfUnused(options = {}) {
|
|
274
|
+
if (this.subscribed) return Promise.resolve();
|
|
275
|
+
if (this.#pendingUnsubscribe) return this.#pendingUnsubscribe;
|
|
276
|
+
const hadPendingSubscribe = this.#pendingSubscribe !== null;
|
|
203
277
|
this.#subscribeToken++;
|
|
204
278
|
if (this.#pendingSubscribe) {
|
|
205
279
|
this.#pendingSubscribe.reject(new Error(`Unsubscribed from channel: ${this.name}`));
|
|
206
280
|
this.#pendingSubscribe = null;
|
|
207
281
|
}
|
|
208
|
-
|
|
282
|
+
if (!hadPendingSubscribe && !this.#active) {
|
|
283
|
+
this.#cleanupTerminal();
|
|
284
|
+
return Promise.resolve();
|
|
285
|
+
}
|
|
286
|
+
this.#active = false;
|
|
287
|
+
const pending = new Promise((resolve) => {
|
|
209
288
|
const timeout = options.timeout ?? 5e3;
|
|
210
289
|
const timer = setTimeout(() => {
|
|
211
|
-
this.#cleanupTerminal();
|
|
212
290
|
resolve();
|
|
213
291
|
}, timeout);
|
|
214
292
|
this.#transport.sendRequest({ type: "unsubscribe", channel: this.name }, timeout).then(() => {
|
|
215
293
|
clearTimeout(timer);
|
|
216
|
-
this.#cleanupTerminal();
|
|
217
294
|
resolve();
|
|
218
295
|
}).catch(() => {
|
|
219
296
|
clearTimeout(timer);
|
|
220
|
-
this.#cleanupTerminal();
|
|
221
297
|
resolve();
|
|
222
298
|
});
|
|
223
299
|
});
|
|
300
|
+
this.#pendingUnsubscribe = pending;
|
|
301
|
+
void pending.finally(() => {
|
|
302
|
+
if (this.#pendingUnsubscribe !== pending) return;
|
|
303
|
+
this.#pendingUnsubscribe = null;
|
|
304
|
+
this.#resetConnectionState();
|
|
305
|
+
this.#detachTransportListeners();
|
|
306
|
+
if (!this.subscribed) this.#clearCallbacks();
|
|
307
|
+
});
|
|
308
|
+
return pending;
|
|
224
309
|
}
|
|
225
|
-
/**
|
|
226
|
-
* Sends an event to the server.
|
|
227
|
-
*/
|
|
228
310
|
send(event, data) {
|
|
229
311
|
if (!this.#active) {
|
|
230
312
|
console.warn(`Cannot send to channel ${this.name}: not subscribed`);
|
|
@@ -254,9 +336,6 @@ var Channel = class {
|
|
|
254
336
|
this.#transport.send({ type: "whisper", ...payload });
|
|
255
337
|
return this;
|
|
256
338
|
}
|
|
257
|
-
/**
|
|
258
|
-
* Sends an event and waits for a response.
|
|
259
|
-
*/
|
|
260
339
|
async sendWithAck(event, data) {
|
|
261
340
|
if (!this.#active) {
|
|
262
341
|
throw new Error(`Cannot send to channel ${this.name}: not subscribed`);
|
|
@@ -280,8 +359,9 @@ var Channel = class {
|
|
|
280
359
|
*/
|
|
281
360
|
#setupSocketListeners() {
|
|
282
361
|
this.#boundPresenceHandler = (data) => {
|
|
283
|
-
|
|
284
|
-
|
|
362
|
+
const presenceData = _Channel.#parsePresenceData(data);
|
|
363
|
+
if (!presenceData) return;
|
|
364
|
+
this.#handlePresenceUpdate(presenceData);
|
|
285
365
|
};
|
|
286
366
|
this.#transport.on(`channel:${this.name}:presence:update`, this.#boundPresenceHandler);
|
|
287
367
|
this.#boundEventHandler = (data) => {
|
|
@@ -289,7 +369,9 @@ var Channel = class {
|
|
|
289
369
|
const payload = data;
|
|
290
370
|
if (!payload.event) return;
|
|
291
371
|
const handlers = this.#eventHandlers.get(payload.event);
|
|
292
|
-
handlers?.forEach((handler) => handler
|
|
372
|
+
handlers?.forEach((handler) => callClientHandler(handler, payload.data));
|
|
373
|
+
const managedHandlers = this.#managedEventHandlers.get(payload.event);
|
|
374
|
+
managedHandlers?.forEach((handler) => callClientHandler(handler, payload.data));
|
|
293
375
|
};
|
|
294
376
|
this.#transport.on(`channel:${this.name}:event`, this.#boundEventHandler);
|
|
295
377
|
this.#boundDisconnectHandler = () => {
|
|
@@ -304,7 +386,9 @@ var Channel = class {
|
|
|
304
386
|
#handleInitialPresence(data) {
|
|
305
387
|
this.#presence = data;
|
|
306
388
|
this.#presentUserIds = new Set(data.users.map((u) => u.id));
|
|
307
|
-
this.#hereHandler
|
|
389
|
+
if (this.#hereHandler) {
|
|
390
|
+
callClientHandler(this.#hereHandler, data.users);
|
|
391
|
+
}
|
|
308
392
|
}
|
|
309
393
|
/**
|
|
310
394
|
* Handles a presence update.
|
|
@@ -316,21 +400,23 @@ var Channel = class {
|
|
|
316
400
|
#handlePresenceUpdate(data) {
|
|
317
401
|
const previousIds = this.#presentUserIds;
|
|
318
402
|
const currentIds = new Set(data.users.map((u) => u.id));
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
403
|
+
const joiningUsers = data.users.filter((user) => !previousIds.has(user.id));
|
|
404
|
+
const leavingUsers = (this.#presence?.users ?? []).filter((user) => !currentIds.has(user.id));
|
|
405
|
+
this.#presence = data;
|
|
406
|
+
this.#presentUserIds = currentIds;
|
|
407
|
+
for (const user of joiningUsers) {
|
|
408
|
+
if (this.#joiningHandler) {
|
|
409
|
+
callClientHandler(this.#joiningHandler, user);
|
|
322
410
|
}
|
|
323
411
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
this.#leavingHandler?.(user);
|
|
328
|
-
}
|
|
412
|
+
for (const user of leavingUsers) {
|
|
413
|
+
if (this.#leavingHandler) {
|
|
414
|
+
callClientHandler(this.#leavingHandler, user);
|
|
329
415
|
}
|
|
330
416
|
}
|
|
331
|
-
this.#
|
|
332
|
-
|
|
333
|
-
|
|
417
|
+
if (this.#hereHandler) {
|
|
418
|
+
callClientHandler(this.#hereHandler, data.users);
|
|
419
|
+
}
|
|
334
420
|
}
|
|
335
421
|
#resetConnectionState() {
|
|
336
422
|
this.#active = false;
|
|
@@ -358,14 +444,17 @@ var Channel = class {
|
|
|
358
444
|
this.#joiningHandler = null;
|
|
359
445
|
this.#leavingHandler = null;
|
|
360
446
|
}
|
|
447
|
+
#clearCallbacks() {
|
|
448
|
+
this.#clearUserCallbacks();
|
|
449
|
+
this.#managedEventHandlers.clear();
|
|
450
|
+
}
|
|
361
451
|
/**
|
|
362
452
|
* Performs terminal cleanup after the user leaves or the socket is disposed.
|
|
363
453
|
*/
|
|
364
454
|
#cleanupTerminal() {
|
|
365
|
-
this.#subscribed = false;
|
|
366
455
|
this.#resetConnectionState();
|
|
367
456
|
this.#detachTransportListeners();
|
|
368
|
-
this.#
|
|
457
|
+
this.#clearCallbacks();
|
|
369
458
|
}
|
|
370
459
|
};
|
|
371
460
|
|
|
@@ -411,8 +500,16 @@ var ClientSocketSession = class {
|
|
|
411
500
|
}
|
|
412
501
|
this.#manualDisconnect = false;
|
|
413
502
|
this.#setState("connecting");
|
|
503
|
+
let setupFailed = false;
|
|
414
504
|
const connectPromise = new Promise((resolve, reject) => {
|
|
415
|
-
|
|
505
|
+
let ws;
|
|
506
|
+
try {
|
|
507
|
+
ws = this.#createWebSocket(this.#buildUrl());
|
|
508
|
+
} catch (error) {
|
|
509
|
+
setupFailed = true;
|
|
510
|
+
reject(error);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
416
513
|
this.#ws = ws;
|
|
417
514
|
let settled = false;
|
|
418
515
|
const cleanup = () => {
|
|
@@ -434,12 +531,20 @@ var ClientSocketSession = class {
|
|
|
434
531
|
reject(error);
|
|
435
532
|
};
|
|
436
533
|
const handleOpen = () => {
|
|
534
|
+
if (settled || this.#ws !== ws) {
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
this.#setState("connected");
|
|
538
|
+
if (settled || this.#ws !== ws || !this.connected) {
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
437
541
|
settled = true;
|
|
438
542
|
cleanup();
|
|
439
543
|
this.#reconnectAttempts = 0;
|
|
440
|
-
this.#setState("connected");
|
|
441
544
|
this.#emitLocal("connect");
|
|
442
|
-
this.#onConnected
|
|
545
|
+
if (this.#onConnected) {
|
|
546
|
+
this.#callHandler(this.#onConnected);
|
|
547
|
+
}
|
|
443
548
|
resolve();
|
|
444
549
|
};
|
|
445
550
|
const handleError = () => {
|
|
@@ -457,10 +562,18 @@ var ClientSocketSession = class {
|
|
|
457
562
|
this.#cancelConnect = cancelConnect;
|
|
458
563
|
ws.addEventListener("open", handleOpen);
|
|
459
564
|
ws.addEventListener("error", handleError);
|
|
460
|
-
ws.addEventListener("message", (event) =>
|
|
565
|
+
ws.addEventListener("message", (event) => {
|
|
566
|
+
if (this.#ws === ws) {
|
|
567
|
+
this.#handleIncoming(event.data);
|
|
568
|
+
}
|
|
569
|
+
});
|
|
461
570
|
ws.addEventListener("close", handleClose);
|
|
462
571
|
});
|
|
463
572
|
this.#connectPromise = connectPromise;
|
|
573
|
+
if (setupFailed) {
|
|
574
|
+
this.#connectPromise = null;
|
|
575
|
+
this.#setState("disconnected");
|
|
576
|
+
}
|
|
464
577
|
return connectPromise;
|
|
465
578
|
}
|
|
466
579
|
disconnect() {
|
|
@@ -471,7 +584,14 @@ var ClientSocketSession = class {
|
|
|
471
584
|
}
|
|
472
585
|
this.#cancelConnect?.(new Error("Socket disconnected"));
|
|
473
586
|
this.#reconnectAttempts = 0;
|
|
474
|
-
this.#ws
|
|
587
|
+
const ws = this.#ws;
|
|
588
|
+
const shouldEmitDisconnect = this.#state !== "disconnected";
|
|
589
|
+
this.#ws = null;
|
|
590
|
+
this.#setState("disconnected");
|
|
591
|
+
if (shouldEmitDisconnect) {
|
|
592
|
+
this.#emitLocal("disconnect");
|
|
593
|
+
}
|
|
594
|
+
ws?.close();
|
|
475
595
|
this.#rejectPending(new Error("Socket disconnected"));
|
|
476
596
|
}
|
|
477
597
|
onStateChange(handler) {
|
|
@@ -493,24 +613,30 @@ var ClientSocketSession = class {
|
|
|
493
613
|
this.#eventHandlers.get(event)?.delete(handler);
|
|
494
614
|
}
|
|
495
615
|
send(message) {
|
|
496
|
-
|
|
616
|
+
const ws = this.#ws;
|
|
617
|
+
if (ws?.readyState !== WebSocket.OPEN) {
|
|
497
618
|
return;
|
|
498
619
|
}
|
|
499
|
-
|
|
620
|
+
ws.send(JSON.stringify(message));
|
|
500
621
|
}
|
|
501
622
|
sendRequest(message, timeout = 5e3) {
|
|
502
|
-
|
|
623
|
+
const ws = this.#ws;
|
|
624
|
+
if (ws?.readyState !== WebSocket.OPEN) {
|
|
503
625
|
return Promise.reject(new Error("Socket is not connected"));
|
|
504
626
|
}
|
|
505
627
|
const id = String(++this.#requestId);
|
|
506
|
-
const payload = {
|
|
628
|
+
const payload = { ...message, id };
|
|
507
629
|
return new Promise((resolve, reject) => {
|
|
508
630
|
const timer = setTimeout(() => {
|
|
509
631
|
this.#pendingRequests.delete(id);
|
|
510
632
|
reject(new Error("Socket request timed out"));
|
|
511
633
|
}, timeout);
|
|
512
|
-
this.#pendingRequests.set(id, {
|
|
513
|
-
|
|
634
|
+
this.#pendingRequests.set(id, {
|
|
635
|
+
resolve,
|
|
636
|
+
reject,
|
|
637
|
+
timer
|
|
638
|
+
});
|
|
639
|
+
ws.send(JSON.stringify(payload));
|
|
514
640
|
});
|
|
515
641
|
}
|
|
516
642
|
#handleIncoming(raw) {
|
|
@@ -541,7 +667,16 @@ var ClientSocketSession = class {
|
|
|
541
667
|
try {
|
|
542
668
|
const data = typeof raw === "string" ? raw : String(raw);
|
|
543
669
|
const message = JSON.parse(data);
|
|
544
|
-
|
|
670
|
+
if (!message || typeof message !== "object" || Array.isArray(message)) {
|
|
671
|
+
return null;
|
|
672
|
+
}
|
|
673
|
+
if (message.type === "ack" && typeof message.id === "string" && typeof message.ok === "boolean" && (message.error === void 0 || typeof message.error === "string")) {
|
|
674
|
+
return message;
|
|
675
|
+
}
|
|
676
|
+
if (message.type === "event" && typeof message.event === "string" && (message.channel === void 0 || typeof message.channel === "string")) {
|
|
677
|
+
return message;
|
|
678
|
+
}
|
|
679
|
+
return null;
|
|
545
680
|
} catch {
|
|
546
681
|
return null;
|
|
547
682
|
}
|
|
@@ -564,6 +699,7 @@ var ClientSocketSession = class {
|
|
|
564
699
|
}
|
|
565
700
|
#handleClose() {
|
|
566
701
|
const shouldEmitDisconnect = this.#state !== "disconnected";
|
|
702
|
+
this.#ws = null;
|
|
567
703
|
this.#setState("disconnected");
|
|
568
704
|
if (shouldEmitDisconnect) {
|
|
569
705
|
this.#emitLocal("disconnect");
|
|
@@ -582,7 +718,11 @@ var ClientSocketSession = class {
|
|
|
582
718
|
this.#reconnectAttempts += 1;
|
|
583
719
|
this.#reconnectTimer = setTimeout(() => {
|
|
584
720
|
this.#reconnectTimer = null;
|
|
585
|
-
this.connect().catch(() =>
|
|
721
|
+
this.connect().catch(() => {
|
|
722
|
+
if (this.#state === "disconnected") {
|
|
723
|
+
this.#scheduleReconnect();
|
|
724
|
+
}
|
|
725
|
+
});
|
|
586
726
|
}, delay);
|
|
587
727
|
}
|
|
588
728
|
#rejectPending(error) {
|
|
@@ -593,14 +733,17 @@ var ClientSocketSession = class {
|
|
|
593
733
|
}
|
|
594
734
|
}
|
|
595
735
|
#emitLocal(event, data) {
|
|
596
|
-
this.#eventHandlers.get(event)?.forEach((handler) => handler
|
|
736
|
+
this.#eventHandlers.get(event)?.forEach((handler) => this.#callHandler(handler, data));
|
|
597
737
|
}
|
|
598
738
|
#setState(state) {
|
|
599
739
|
if (this.#state === state) {
|
|
600
740
|
return;
|
|
601
741
|
}
|
|
602
742
|
this.#state = state;
|
|
603
|
-
this.#stateHandlers.forEach((handler) => handler
|
|
743
|
+
this.#stateHandlers.forEach((handler) => this.#callHandler(handler, state));
|
|
744
|
+
}
|
|
745
|
+
#callHandler(handler, data) {
|
|
746
|
+
callClientHandler(handler, data);
|
|
604
747
|
}
|
|
605
748
|
};
|
|
606
749
|
|
|
@@ -631,9 +774,6 @@ var Socket = class {
|
|
|
631
774
|
get connected() {
|
|
632
775
|
return this.#session.connected;
|
|
633
776
|
}
|
|
634
|
-
get id() {
|
|
635
|
-
return void 0;
|
|
636
|
-
}
|
|
637
777
|
connect() {
|
|
638
778
|
return this.#session.connect();
|
|
639
779
|
}
|
|
@@ -642,7 +782,6 @@ var Socket = class {
|
|
|
642
782
|
channel.unsubscribe().catch(() => {
|
|
643
783
|
});
|
|
644
784
|
}
|
|
645
|
-
this.#channels.clear();
|
|
646
785
|
this.#session.disconnect();
|
|
647
786
|
}
|
|
648
787
|
channel(name) {
|
|
@@ -655,7 +794,9 @@ var Socket = class {
|
|
|
655
794
|
const channel = this.#channels.get(name);
|
|
656
795
|
if (channel) {
|
|
657
796
|
await channel.unsubscribe();
|
|
658
|
-
this.#channels.
|
|
797
|
+
if (this.#channels.get(name) === channel && !channel.subscribed) {
|
|
798
|
+
this.#channels.delete(name);
|
|
799
|
+
}
|
|
659
800
|
}
|
|
660
801
|
}
|
|
661
802
|
onStateChange(handler) {
|
|
@@ -676,14 +817,20 @@ var Socket = class {
|
|
|
676
817
|
#buildUrl() {
|
|
677
818
|
const browserGlobal = globalThis;
|
|
678
819
|
const url = new URL(this.#url || browserGlobal.location?.origin || "http://localhost");
|
|
679
|
-
|
|
820
|
+
if (url.protocol === "http:") {
|
|
821
|
+
url.protocol = "ws:";
|
|
822
|
+
} else if (url.protocol === "https:") {
|
|
823
|
+
url.protocol = "wss:";
|
|
824
|
+
} else if (url.protocol !== "ws:" && url.protocol !== "wss:") {
|
|
825
|
+
throw new Error(`Unsupported socket URL protocol: ${url.protocol}`);
|
|
826
|
+
}
|
|
680
827
|
url.pathname = this.#path;
|
|
681
828
|
return url.toString();
|
|
682
829
|
}
|
|
683
830
|
async #resubscribeChannels() {
|
|
684
831
|
for (const channel of this.#channels.values()) {
|
|
685
832
|
if (channel.subscribed && !channel.active) {
|
|
686
|
-
await channel
|
|
833
|
+
await channel.$resubscribe().catch(reportClientError);
|
|
687
834
|
}
|
|
688
835
|
}
|
|
689
836
|
}
|