@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.
Files changed (55) hide show
  1. package/README.md +430 -26
  2. package/build/chunk-B4Y3TNDI.js +159 -0
  3. package/build/chunk-B4Y3TNDI.js.map +1 -0
  4. package/build/{chunk-GKAD2UOA.js → chunk-D3HUBCBW.js} +1 -1
  5. package/build/chunk-D3HUBCBW.js.map +1 -0
  6. package/build/chunk-FVPY6HZW.js +136 -0
  7. package/build/chunk-FVPY6HZW.js.map +1 -0
  8. package/build/chunk-SFAY2ZA4.js +28 -0
  9. package/build/chunk-SFAY2ZA4.js.map +1 -0
  10. package/build/{chunk-XUDFDJME.js → chunk-SHH6U4CI.js} +12 -9
  11. package/build/chunk-SHH6U4CI.js.map +1 -0
  12. package/build/chunk-YAX5EHHB.js +453 -0
  13. package/build/chunk-YAX5EHHB.js.map +1 -0
  14. package/build/framework-DuW6zpPk.d.ts +14 -0
  15. package/build/index.d.ts +7 -13
  16. package/build/index.js +18 -10
  17. package/build/index.js.map +1 -1
  18. package/build/providers/socket_provider.d.ts +4 -3
  19. package/build/providers/socket_provider.js +1780 -57
  20. package/build/providers/socket_provider.js.map +1 -1
  21. package/build/services/socket.d.ts +4 -3
  22. package/build/socket_service-D3jKrleE.d.ts +105 -0
  23. package/build/src/assembler_hook.d.ts +15 -0
  24. package/build/src/assembler_hook.js +261 -0
  25. package/build/src/assembler_hook.js.map +1 -0
  26. package/build/src/client/index.d.ts +46 -12
  27. package/build/src/client/index.js +223 -76
  28. package/build/src/client/index.js.map +1 -1
  29. package/build/src/client/react.d.ts +27 -0
  30. package/build/src/client/react.js +69 -0
  31. package/build/src/client/react.js.map +1 -0
  32. package/build/src/client/svelte.d.ts +23 -0
  33. package/build/src/client/svelte.js +82 -0
  34. package/build/src/client/svelte.js.map +1 -0
  35. package/build/src/client/types.d.ts +69 -5
  36. package/build/src/client/vue.d.ts +26 -0
  37. package/build/src/client/vue.js +81 -0
  38. package/build/src/client/vue.js.map +1 -0
  39. package/build/src/decorators.d.ts +4 -4
  40. package/build/src/decorators.js +1 -1
  41. package/build/src/health_check.d.ts +4 -3
  42. package/build/src/otel.js +4 -5
  43. package/build/src/otel.js.map +1 -1
  44. package/build/src/testing.d.ts +54 -0
  45. package/build/src/testing.js +7 -0
  46. package/build/src/testing.js.map +1 -0
  47. package/build/src/types.d.ts +2 -2
  48. package/build/{types-BBLNfcWk.d.ts → types-DiYaFvgi.d.ts} +112 -63
  49. package/package.json +41 -6
  50. package/build/chunk-GKAD2UOA.js.map +0 -1
  51. package/build/chunk-ILDK672E.js +0 -1921
  52. package/build/chunk-ILDK672E.js.map +0 -1
  53. package/build/chunk-XUDFDJME.js.map +0 -1
  54. package/build/shared_types-Dw9AphfO.d.ts +0 -21
  55. 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
- if (!this.#eventHandlers.has(event)) {
105
- this.#eventHandlers.set(event, /* @__PURE__ */ new Set());
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
- return this.listen(`${CLIENT_EVENT_PREFIX}${event}`, handler);
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
- return this.stopListening(`${CLIENT_EVENT_PREFIX}${event}`, handler);
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
- return this.#pendingSubscribe.promise;
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
- let resolvePromise;
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: resolvePromise,
163
- reject: rejectPromise,
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 = response.data?.presenceData ?? response.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
- return promise;
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
- return new Promise((resolve) => {
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
- if (!data || typeof data !== "object") return;
284
- this.#handlePresenceUpdate(data);
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(payload.data));
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?.(data.users);
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
- for (const user of data.users) {
320
- if (!previousIds.has(user.id)) {
321
- this.#joiningHandler?.(user);
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
- if (this.#presence) {
325
- for (const user of this.#presence.users) {
326
- if (!currentIds.has(user.id)) {
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.#presence = data;
332
- this.#presentUserIds = currentIds;
333
- this.#hereHandler?.(data.users);
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.#clearUserCallbacks();
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
- const ws = this.#createWebSocket(this.#buildUrl());
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) => this.#handleIncoming(event.data));
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?.close();
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
- if (this.#ws?.readyState !== WebSocket.OPEN) {
616
+ const ws = this.#ws;
617
+ if (ws?.readyState !== WebSocket.OPEN) {
497
618
  return;
498
619
  }
499
- this.#ws.send(JSON.stringify(message));
620
+ ws.send(JSON.stringify(message));
500
621
  }
501
622
  sendRequest(message, timeout = 5e3) {
502
- if (this.#ws?.readyState !== WebSocket.OPEN) {
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 = { id, ...message };
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, { resolve, reject, timer });
513
- this.#ws.send(JSON.stringify(payload));
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
- return message && typeof message === "object" ? message : null;
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(() => this.#handleClose());
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(data));
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(state));
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.delete(name);
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
- url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
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.subscribe().catch(console.error);
833
+ await channel.$resubscribe().catch(reportClientError);
687
834
  }
688
835
  }
689
836
  }