@rlanz/socket 0.0.1-6 → 0.0.1-8
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 +186 -147
- package/build/{types-DiYaFvgi.d.ts → base_channel-DPOnqned.d.ts} +185 -171
- package/build/chunk-6PJ4ALJD.js +115 -0
- package/build/chunk-6PJ4ALJD.js.map +1 -0
- package/build/chunk-E6ILCRL3.js +182 -0
- package/build/chunk-E6ILCRL3.js.map +1 -0
- package/build/{chunk-SHH6U4CI.js → chunk-G75J233Z.js} +1 -1
- package/build/chunk-G75J233Z.js.map +1 -0
- package/build/{chunk-B4Y3TNDI.js → chunk-IUQ3GYZZ.js} +5 -24
- package/build/chunk-IUQ3GYZZ.js.map +1 -0
- package/build/chunk-K243S3QP.js +131 -0
- package/build/chunk-K243S3QP.js.map +1 -0
- package/build/chunk-QNOOQXYY.js +899 -0
- package/build/chunk-QNOOQXYY.js.map +1 -0
- package/build/{chunk-FVPY6HZW.js → chunk-YVC7IMDN.js} +21 -5
- package/build/chunk-YVC7IMDN.js.map +1 -0
- package/build/framework-B8pJ4cCA.d.ts +13 -0
- package/build/index-DjBYUhFR.d.ts +150 -0
- package/build/index.d.ts +23 -12
- package/build/index.js +12 -14
- package/build/index.js.map +1 -1
- package/build/providers/socket_provider.d.ts +2 -2
- package/build/providers/socket_provider.js +478 -215
- package/build/providers/socket_provider.js.map +1 -1
- package/build/services/socket.d.ts +2 -2
- package/build/{socket_service-D3jKrleE.d.ts → socket_service-BcC9ASCE.d.ts} +8 -7
- package/build/src/assembler_hook.js +33 -59
- package/build/src/assembler_hook.js.map +1 -1
- package/build/src/client/index.d.ts +3 -147
- package/build/src/client/index.js +4 -837
- package/build/src/client/index.js.map +1 -1
- package/build/src/client/react.d.ts +13 -5
- package/build/src/client/react.js +27 -11
- package/build/src/client/react.js.map +1 -1
- package/build/src/client/types.d.ts +30 -36
- package/build/src/client/vue.d.ts +17 -6
- package/build/src/client/vue.js +31 -13
- package/build/src/client/vue.js.map +1 -1
- package/build/src/decorators.d.ts +3 -2
- package/build/src/decorators.js +1 -1
- package/build/src/health_check.d.ts +2 -2
- package/build/src/health_check.js +14 -3
- package/build/src/health_check.js.map +1 -1
- package/build/src/otel.js +0 -1
- package/build/src/otel.js.map +1 -1
- package/build/src/testing.d.ts +3 -12
- package/build/src/testing.js +1 -1
- package/build/src/types/tracing_channels.d.ts +1 -2
- package/build/src/types.d.ts +1 -1
- package/package.json +3 -9
- package/build/chunk-B4Y3TNDI.js.map +0 -1
- package/build/chunk-FVPY6HZW.js.map +0 -1
- package/build/chunk-HK7Z65DA.js +0 -19
- package/build/chunk-HK7Z65DA.js.map +0 -1
- package/build/chunk-SFAY2ZA4.js +0 -28
- package/build/chunk-SFAY2ZA4.js.map +0 -1
- package/build/chunk-SHH6U4CI.js.map +0 -1
- package/build/chunk-YAX5EHHB.js +0 -453
- package/build/chunk-YAX5EHHB.js.map +0 -1
- package/build/framework-DuW6zpPk.d.ts +0 -14
- package/build/src/client/svelte.d.ts +0 -23
- package/build/src/client/svelte.js +0 -82
- package/build/src/client/svelte.js.map +0 -1
|
@@ -1,842 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
// src/client/channel.ts
|
|
7
|
-
var CLIENT_EVENT_PREFIX = "client:";
|
|
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
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Channel name.
|
|
30
|
-
*/
|
|
31
|
-
name;
|
|
32
|
-
/**
|
|
33
|
-
* Socket client transport.
|
|
34
|
-
*/
|
|
35
|
-
#transport;
|
|
36
|
-
/**
|
|
37
|
-
* Whether the channel should stay subscribed.
|
|
38
|
-
*/
|
|
39
|
-
#subscribed = false;
|
|
40
|
-
#active = false;
|
|
41
|
-
/**
|
|
42
|
-
* Event handlers grouped by event type.
|
|
43
|
-
*/
|
|
44
|
-
#eventHandlers = /* @__PURE__ */ new Map();
|
|
45
|
-
#managedEventHandlers = /* @__PURE__ */ new Map();
|
|
46
|
-
/**
|
|
47
|
-
* Current presence data.
|
|
48
|
-
*/
|
|
49
|
-
#presence = null;
|
|
50
|
-
/**
|
|
51
|
-
* Present user IDs used to detect join and leave events.
|
|
52
|
-
*/
|
|
53
|
-
#presentUserIds = /* @__PURE__ */ new Set();
|
|
54
|
-
/**
|
|
55
|
-
* Handler registered through .here().
|
|
56
|
-
*/
|
|
57
|
-
#hereHandler = null;
|
|
58
|
-
/**
|
|
59
|
-
* Handler registered through .joining().
|
|
60
|
-
*/
|
|
61
|
-
#joiningHandler = null;
|
|
62
|
-
/**
|
|
63
|
-
* Handler registered through .leaving().
|
|
64
|
-
*/
|
|
65
|
-
#leavingHandler = null;
|
|
66
|
-
/**
|
|
67
|
-
* Internal transport handlers to clean up on unsubscribe.
|
|
68
|
-
*/
|
|
69
|
-
#boundPresenceHandler = null;
|
|
70
|
-
#boundEventHandler = null;
|
|
71
|
-
#boundDisconnectHandler = null;
|
|
72
|
-
#listenersAttached = false;
|
|
73
|
-
#pendingSubscribe = null;
|
|
74
|
-
#subscribeToken = 0;
|
|
75
|
-
#managedSubscriptions = 0;
|
|
76
|
-
#pendingUnsubscribe = null;
|
|
77
|
-
constructor(name, transport) {
|
|
78
|
-
this.name = name;
|
|
79
|
-
this.#transport = transport;
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Whether the channel should stay subscribed.
|
|
83
|
-
*/
|
|
84
|
-
get 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;
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Whether the channel is active on the server.
|
|
93
|
-
*/
|
|
94
|
-
get active() {
|
|
95
|
-
return this.#active;
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* List of present users.
|
|
99
|
-
*/
|
|
100
|
-
get users() {
|
|
101
|
-
return this.#presence?.users ?? [];
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Number of present users.
|
|
105
|
-
*/
|
|
106
|
-
get count() {
|
|
107
|
-
return this.#presence?.count ?? 0;
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Registers a handler called with the present users.
|
|
111
|
-
* Called immediately on subscribe and after each presence change.
|
|
112
|
-
*/
|
|
113
|
-
here(handler) {
|
|
114
|
-
this.#hereHandler = handler;
|
|
115
|
-
return this;
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Registers a handler called when a user joins.
|
|
119
|
-
*/
|
|
120
|
-
joining(handler) {
|
|
121
|
-
this.#joiningHandler = handler;
|
|
122
|
-
return this;
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* Registers a handler called when a user leaves.
|
|
126
|
-
*/
|
|
127
|
-
leaving(handler) {
|
|
128
|
-
this.#leavingHandler = handler;
|
|
129
|
-
return this;
|
|
130
|
-
}
|
|
131
|
-
listen(event, handler) {
|
|
132
|
-
const handlers = this.#eventHandlers.get(event) ?? /* @__PURE__ */ new Set();
|
|
133
|
-
handlers.add(handler);
|
|
134
|
-
this.#eventHandlers.set(event, handlers);
|
|
135
|
-
return this;
|
|
136
|
-
}
|
|
137
|
-
stopListening(event, handler) {
|
|
138
|
-
if (handler) {
|
|
139
|
-
this.#eventHandlers.get(event)?.delete(handler);
|
|
140
|
-
} else {
|
|
141
|
-
this.#eventHandlers.delete(event);
|
|
142
|
-
}
|
|
143
|
-
return this;
|
|
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
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Listens for a client event whispered by another channel member.
|
|
159
|
-
*/
|
|
160
|
-
listenForWhisper(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;
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* Removes a whispered client event listener.
|
|
168
|
-
*/
|
|
169
|
-
stopListeningForWhisper(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;
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* Subscribes to the channel.
|
|
177
|
-
*
|
|
178
|
-
* client listeners -> subscribe ack -> active channel
|
|
179
|
-
* | | |
|
|
180
|
-
* v v v
|
|
181
|
-
* events ready presence sync send/listen
|
|
182
|
-
*/
|
|
183
|
-
async subscribe(options = {}) {
|
|
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;
|
|
211
|
-
if (this.#pendingSubscribe) {
|
|
212
|
-
await this.#pendingSubscribe.promise;
|
|
213
|
-
return this;
|
|
214
|
-
}
|
|
215
|
-
if (this.#active) {
|
|
216
|
-
return this;
|
|
217
|
-
}
|
|
218
|
-
const timeout = options.timeout ?? 5e3;
|
|
219
|
-
if (!this.#listenersAttached) {
|
|
220
|
-
this.#setupSocketListeners();
|
|
221
|
-
}
|
|
222
|
-
const token = ++this.#subscribeToken;
|
|
223
|
-
const deferred = Promise.withResolvers();
|
|
224
|
-
this.#pendingSubscribe = {
|
|
225
|
-
promise: deferred.promise,
|
|
226
|
-
resolve: deferred.resolve,
|
|
227
|
-
reject: deferred.reject,
|
|
228
|
-
token
|
|
229
|
-
};
|
|
230
|
-
const { resolve, reject } = this.#pendingSubscribe;
|
|
231
|
-
const timer = setTimeout(() => {
|
|
232
|
-
if (token !== this.#subscribeToken) return;
|
|
233
|
-
this.#subscribeToken++;
|
|
234
|
-
this.#pendingSubscribe = null;
|
|
235
|
-
this.#resetConnectionState();
|
|
236
|
-
reject(new Error(`Subscribe timeout for channel: ${this.name}`));
|
|
237
|
-
}, timeout);
|
|
238
|
-
this.#transport.sendRequest({ type: "subscribe", channel: this.name }, timeout).then((response) => {
|
|
239
|
-
if (token !== this.#subscribeToken) return;
|
|
240
|
-
clearTimeout(timer);
|
|
241
|
-
this.#pendingSubscribe = null;
|
|
242
|
-
if (response.ok) {
|
|
243
|
-
this.#active = true;
|
|
244
|
-
const presenceData = _Channel.#parsePresenceData(
|
|
245
|
-
response.data?.presenceData ?? response.presenceData
|
|
246
|
-
);
|
|
247
|
-
if (presenceData) {
|
|
248
|
-
this.#handleInitialPresence(presenceData);
|
|
249
|
-
}
|
|
250
|
-
resolve(this);
|
|
251
|
-
} else {
|
|
252
|
-
this.#resetConnectionState();
|
|
253
|
-
reject(new Error(response.error ?? "Subscribe failed"));
|
|
254
|
-
}
|
|
255
|
-
}).catch((error) => {
|
|
256
|
-
if (token !== this.#subscribeToken) return;
|
|
257
|
-
clearTimeout(timer);
|
|
258
|
-
this.#pendingSubscribe = null;
|
|
259
|
-
this.#resetConnectionState();
|
|
260
|
-
reject(error instanceof Error ? error : new Error("Subscribe failed"));
|
|
261
|
-
});
|
|
262
|
-
await deferred.promise;
|
|
263
|
-
return this;
|
|
264
|
-
}
|
|
265
|
-
/**
|
|
266
|
-
* Unsubscribes from the channel.
|
|
267
|
-
*/
|
|
268
|
-
async unsubscribe(options = {}) {
|
|
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;
|
|
277
|
-
this.#subscribeToken++;
|
|
278
|
-
if (this.#pendingSubscribe) {
|
|
279
|
-
this.#pendingSubscribe.reject(new Error(`Unsubscribed from channel: ${this.name}`));
|
|
280
|
-
this.#pendingSubscribe = null;
|
|
281
|
-
}
|
|
282
|
-
if (!hadPendingSubscribe && !this.#active) {
|
|
283
|
-
this.#cleanupTerminal();
|
|
284
|
-
return Promise.resolve();
|
|
285
|
-
}
|
|
286
|
-
this.#active = false;
|
|
287
|
-
const pending = new Promise((resolve) => {
|
|
288
|
-
const timeout = options.timeout ?? 5e3;
|
|
289
|
-
const timer = setTimeout(() => {
|
|
290
|
-
resolve();
|
|
291
|
-
}, timeout);
|
|
292
|
-
this.#transport.sendRequest({ type: "unsubscribe", channel: this.name }, timeout).then(() => {
|
|
293
|
-
clearTimeout(timer);
|
|
294
|
-
resolve();
|
|
295
|
-
}).catch(() => {
|
|
296
|
-
clearTimeout(timer);
|
|
297
|
-
resolve();
|
|
298
|
-
});
|
|
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;
|
|
309
|
-
}
|
|
310
|
-
send(event, data) {
|
|
311
|
-
if (!this.#active) {
|
|
312
|
-
console.warn(`Cannot send to channel ${this.name}: not subscribed`);
|
|
313
|
-
return this;
|
|
314
|
-
}
|
|
315
|
-
const payload = {
|
|
316
|
-
channel: this.name,
|
|
317
|
-
event,
|
|
318
|
-
data
|
|
319
|
-
};
|
|
320
|
-
this.#transport.send({ type: "message", ...payload });
|
|
321
|
-
return this;
|
|
322
|
-
}
|
|
323
|
-
/**
|
|
324
|
-
* Relays a client event to other members of this channel.
|
|
325
|
-
*/
|
|
326
|
-
whisper(event, data) {
|
|
327
|
-
if (!this.#active) {
|
|
328
|
-
console.warn(`Cannot whisper to channel ${this.name}: not subscribed`);
|
|
329
|
-
return this;
|
|
330
|
-
}
|
|
331
|
-
const payload = {
|
|
332
|
-
channel: this.name,
|
|
333
|
-
event,
|
|
334
|
-
data
|
|
335
|
-
};
|
|
336
|
-
this.#transport.send({ type: "whisper", ...payload });
|
|
337
|
-
return this;
|
|
338
|
-
}
|
|
339
|
-
async sendWithAck(event, data) {
|
|
340
|
-
if (!this.#active) {
|
|
341
|
-
throw new Error(`Cannot send to channel ${this.name}: not subscribed`);
|
|
342
|
-
}
|
|
343
|
-
const payload = {
|
|
344
|
-
channel: this.name,
|
|
345
|
-
event,
|
|
346
|
-
data
|
|
347
|
-
};
|
|
348
|
-
const response = await this.#transport.sendRequest({
|
|
349
|
-
type: "message",
|
|
350
|
-
...payload
|
|
351
|
-
});
|
|
352
|
-
if (!response?.ok) {
|
|
353
|
-
throw new Error(response?.error ?? `Channel handler failed: ${event}`);
|
|
354
|
-
}
|
|
355
|
-
return response.data;
|
|
356
|
-
}
|
|
357
|
-
/**
|
|
358
|
-
* Configures transport listeners.
|
|
359
|
-
*/
|
|
360
|
-
#setupSocketListeners() {
|
|
361
|
-
this.#boundPresenceHandler = (data) => {
|
|
362
|
-
const presenceData = _Channel.#parsePresenceData(data);
|
|
363
|
-
if (!presenceData) return;
|
|
364
|
-
this.#handlePresenceUpdate(presenceData);
|
|
365
|
-
};
|
|
366
|
-
this.#transport.on(`channel:${this.name}:presence:update`, this.#boundPresenceHandler);
|
|
367
|
-
this.#boundEventHandler = (data) => {
|
|
368
|
-
if (!data || typeof data !== "object") return;
|
|
369
|
-
const payload = data;
|
|
370
|
-
if (!payload.event) return;
|
|
371
|
-
const handlers = this.#eventHandlers.get(payload.event);
|
|
372
|
-
handlers?.forEach((handler) => callClientHandler(handler, payload.data));
|
|
373
|
-
const managedHandlers = this.#managedEventHandlers.get(payload.event);
|
|
374
|
-
managedHandlers?.forEach((handler) => callClientHandler(handler, payload.data));
|
|
375
|
-
};
|
|
376
|
-
this.#transport.on(`channel:${this.name}:event`, this.#boundEventHandler);
|
|
377
|
-
this.#boundDisconnectHandler = () => {
|
|
378
|
-
this.#resetConnectionState();
|
|
379
|
-
};
|
|
380
|
-
this.#transport.on("disconnect", this.#boundDisconnectHandler);
|
|
381
|
-
this.#listenersAttached = true;
|
|
382
|
-
}
|
|
383
|
-
/**
|
|
384
|
-
* Handles the initial presence snapshot.
|
|
385
|
-
*/
|
|
386
|
-
#handleInitialPresence(data) {
|
|
387
|
-
this.#presence = data;
|
|
388
|
-
this.#presentUserIds = new Set(data.users.map((u) => u.id));
|
|
389
|
-
if (this.#hereHandler) {
|
|
390
|
-
callClientHandler(this.#hereHandler, data.users);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
/**
|
|
394
|
-
* Handles a presence update.
|
|
395
|
-
*
|
|
396
|
-
* previous IDs current IDs
|
|
397
|
-
* | |
|
|
398
|
-
* +-- diff in -----+--> joining / leaving callbacks
|
|
399
|
-
*/
|
|
400
|
-
#handlePresenceUpdate(data) {
|
|
401
|
-
const previousIds = this.#presentUserIds;
|
|
402
|
-
const currentIds = new Set(data.users.map((u) => u.id));
|
|
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);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
for (const user of leavingUsers) {
|
|
413
|
-
if (this.#leavingHandler) {
|
|
414
|
-
callClientHandler(this.#leavingHandler, user);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
if (this.#hereHandler) {
|
|
418
|
-
callClientHandler(this.#hereHandler, data.users);
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
#resetConnectionState() {
|
|
422
|
-
this.#active = false;
|
|
423
|
-
this.#presence = null;
|
|
424
|
-
this.#presentUserIds.clear();
|
|
425
|
-
}
|
|
426
|
-
#detachTransportListeners() {
|
|
427
|
-
if (this.#boundPresenceHandler) {
|
|
428
|
-
this.#transport.off(`channel:${this.name}:presence:update`, this.#boundPresenceHandler);
|
|
429
|
-
this.#boundPresenceHandler = null;
|
|
430
|
-
}
|
|
431
|
-
if (this.#boundEventHandler) {
|
|
432
|
-
this.#transport.off(`channel:${this.name}:event`, this.#boundEventHandler);
|
|
433
|
-
this.#boundEventHandler = null;
|
|
434
|
-
}
|
|
435
|
-
if (this.#boundDisconnectHandler) {
|
|
436
|
-
this.#transport.off("disconnect", this.#boundDisconnectHandler);
|
|
437
|
-
this.#boundDisconnectHandler = null;
|
|
438
|
-
}
|
|
439
|
-
this.#listenersAttached = false;
|
|
440
|
-
}
|
|
441
|
-
#clearUserCallbacks() {
|
|
442
|
-
this.#eventHandlers.clear();
|
|
443
|
-
this.#hereHandler = null;
|
|
444
|
-
this.#joiningHandler = null;
|
|
445
|
-
this.#leavingHandler = null;
|
|
446
|
-
}
|
|
447
|
-
#clearCallbacks() {
|
|
448
|
-
this.#clearUserCallbacks();
|
|
449
|
-
this.#managedEventHandlers.clear();
|
|
450
|
-
}
|
|
451
|
-
/**
|
|
452
|
-
* Performs terminal cleanup after the user leaves or the socket is disposed.
|
|
453
|
-
*/
|
|
454
|
-
#cleanupTerminal() {
|
|
455
|
-
this.#resetConnectionState();
|
|
456
|
-
this.#detachTransportListeners();
|
|
457
|
-
this.#clearCallbacks();
|
|
458
|
-
}
|
|
459
|
-
};
|
|
460
|
-
|
|
461
|
-
// src/client/socket_session.ts
|
|
462
|
-
var ClientSocketSession = class {
|
|
463
|
-
#ws = null;
|
|
464
|
-
#state = "disconnected";
|
|
465
|
-
#connectPromise = null;
|
|
466
|
-
#cancelConnect = null;
|
|
467
|
-
#stateHandlers = /* @__PURE__ */ new Set();
|
|
468
|
-
#eventHandlers = /* @__PURE__ */ new Map();
|
|
469
|
-
#pendingRequests = /* @__PURE__ */ new Map();
|
|
470
|
-
#requestId = 0;
|
|
471
|
-
#reconnectAttempts = 0;
|
|
472
|
-
#reconnectTimer = null;
|
|
473
|
-
#manualDisconnect = false;
|
|
474
|
-
#buildUrl;
|
|
475
|
-
#createWebSocket;
|
|
476
|
-
#autoReconnect;
|
|
477
|
-
#reconnectDelay;
|
|
478
|
-
#reconnectMaxDelay;
|
|
479
|
-
#onConnected;
|
|
480
|
-
constructor(options) {
|
|
481
|
-
this.#buildUrl = options.buildUrl;
|
|
482
|
-
this.#createWebSocket = options.createWebSocket ?? ((url) => new WebSocket(url));
|
|
483
|
-
this.#autoReconnect = options.autoReconnect ?? true;
|
|
484
|
-
this.#reconnectDelay = Math.max(0, options.reconnectDelay ?? 250);
|
|
485
|
-
this.#reconnectMaxDelay = Math.max(this.#reconnectDelay, options.reconnectMaxDelay ?? 5e3);
|
|
486
|
-
this.#onConnected = options.onConnected;
|
|
487
|
-
}
|
|
488
|
-
get state() {
|
|
489
|
-
return this.#state;
|
|
490
|
-
}
|
|
491
|
-
get connected() {
|
|
492
|
-
return this.#state === "connected";
|
|
493
|
-
}
|
|
494
|
-
connect() {
|
|
495
|
-
if (this.#state === "connected") {
|
|
496
|
-
return Promise.resolve();
|
|
497
|
-
}
|
|
498
|
-
if (this.#connectPromise) {
|
|
499
|
-
return this.#connectPromise;
|
|
500
|
-
}
|
|
501
|
-
this.#manualDisconnect = false;
|
|
502
|
-
this.#setState("connecting");
|
|
503
|
-
let setupFailed = false;
|
|
504
|
-
const connectPromise = new Promise((resolve, reject) => {
|
|
505
|
-
let ws;
|
|
506
|
-
try {
|
|
507
|
-
ws = this.#createWebSocket(this.#buildUrl());
|
|
508
|
-
} catch (error) {
|
|
509
|
-
setupFailed = true;
|
|
510
|
-
reject(error);
|
|
511
|
-
return;
|
|
512
|
-
}
|
|
513
|
-
this.#ws = ws;
|
|
514
|
-
let settled = false;
|
|
515
|
-
const cleanup = () => {
|
|
516
|
-
ws.removeEventListener("open", handleOpen);
|
|
517
|
-
ws.removeEventListener("error", handleError);
|
|
518
|
-
if (this.#connectPromise === connectPromise) {
|
|
519
|
-
this.#connectPromise = null;
|
|
520
|
-
}
|
|
521
|
-
if (this.#cancelConnect === cancelConnect) {
|
|
522
|
-
this.#cancelConnect = null;
|
|
523
|
-
}
|
|
524
|
-
};
|
|
525
|
-
const cancelConnect = (error) => {
|
|
526
|
-
if (settled) {
|
|
527
|
-
return;
|
|
528
|
-
}
|
|
529
|
-
settled = true;
|
|
530
|
-
cleanup();
|
|
531
|
-
reject(error);
|
|
532
|
-
};
|
|
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
|
-
}
|
|
541
|
-
settled = true;
|
|
542
|
-
cleanup();
|
|
543
|
-
this.#reconnectAttempts = 0;
|
|
544
|
-
this.#emitLocal("connect");
|
|
545
|
-
if (this.#onConnected) {
|
|
546
|
-
this.#callHandler(this.#onConnected);
|
|
547
|
-
}
|
|
548
|
-
resolve();
|
|
549
|
-
};
|
|
550
|
-
const handleError = () => {
|
|
551
|
-
cancelConnect(new Error("WebSocket connection failed"));
|
|
552
|
-
if (this.#ws === ws) {
|
|
553
|
-
this.#setState("disconnected");
|
|
554
|
-
}
|
|
555
|
-
};
|
|
556
|
-
const handleClose = () => {
|
|
557
|
-
cancelConnect(new Error("WebSocket connection failed"));
|
|
558
|
-
if (this.#ws === ws) {
|
|
559
|
-
this.#handleClose();
|
|
560
|
-
}
|
|
561
|
-
};
|
|
562
|
-
this.#cancelConnect = cancelConnect;
|
|
563
|
-
ws.addEventListener("open", handleOpen);
|
|
564
|
-
ws.addEventListener("error", handleError);
|
|
565
|
-
ws.addEventListener("message", (event) => {
|
|
566
|
-
if (this.#ws === ws) {
|
|
567
|
-
this.#handleIncoming(event.data);
|
|
568
|
-
}
|
|
569
|
-
});
|
|
570
|
-
ws.addEventListener("close", handleClose);
|
|
571
|
-
});
|
|
572
|
-
this.#connectPromise = connectPromise;
|
|
573
|
-
if (setupFailed) {
|
|
574
|
-
this.#connectPromise = null;
|
|
575
|
-
this.#setState("disconnected");
|
|
576
|
-
}
|
|
577
|
-
return connectPromise;
|
|
578
|
-
}
|
|
579
|
-
disconnect() {
|
|
580
|
-
this.#manualDisconnect = true;
|
|
581
|
-
if (this.#reconnectTimer) {
|
|
582
|
-
clearTimeout(this.#reconnectTimer);
|
|
583
|
-
this.#reconnectTimer = null;
|
|
584
|
-
}
|
|
585
|
-
this.#cancelConnect?.(new Error("Socket disconnected"));
|
|
586
|
-
this.#reconnectAttempts = 0;
|
|
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();
|
|
595
|
-
this.#rejectPending(new Error("Socket disconnected"));
|
|
596
|
-
}
|
|
597
|
-
onStateChange(handler) {
|
|
598
|
-
this.#stateHandlers.add(handler);
|
|
599
|
-
return () => {
|
|
600
|
-
this.#stateHandlers.delete(handler);
|
|
601
|
-
};
|
|
602
|
-
}
|
|
603
|
-
on(event, handler) {
|
|
604
|
-
if (!this.#eventHandlers.has(event)) {
|
|
605
|
-
this.#eventHandlers.set(event, /* @__PURE__ */ new Set());
|
|
606
|
-
}
|
|
607
|
-
this.#eventHandlers.get(event).add(handler);
|
|
608
|
-
return () => {
|
|
609
|
-
this.off(event, handler);
|
|
610
|
-
};
|
|
611
|
-
}
|
|
612
|
-
off(event, handler) {
|
|
613
|
-
this.#eventHandlers.get(event)?.delete(handler);
|
|
614
|
-
}
|
|
615
|
-
send(message) {
|
|
616
|
-
const ws = this.#ws;
|
|
617
|
-
if (ws?.readyState !== WebSocket.OPEN) {
|
|
618
|
-
return;
|
|
619
|
-
}
|
|
620
|
-
ws.send(JSON.stringify(message));
|
|
621
|
-
}
|
|
622
|
-
sendRequest(message, timeout = 5e3) {
|
|
623
|
-
const ws = this.#ws;
|
|
624
|
-
if (ws?.readyState !== WebSocket.OPEN) {
|
|
625
|
-
return Promise.reject(new Error("Socket is not connected"));
|
|
626
|
-
}
|
|
627
|
-
const id = String(++this.#requestId);
|
|
628
|
-
const payload = { ...message, id };
|
|
629
|
-
return new Promise((resolve, reject) => {
|
|
630
|
-
const timer = setTimeout(() => {
|
|
631
|
-
this.#pendingRequests.delete(id);
|
|
632
|
-
reject(new Error("Socket request timed out"));
|
|
633
|
-
}, timeout);
|
|
634
|
-
this.#pendingRequests.set(id, {
|
|
635
|
-
resolve,
|
|
636
|
-
reject,
|
|
637
|
-
timer
|
|
638
|
-
});
|
|
639
|
-
ws.send(JSON.stringify(payload));
|
|
640
|
-
});
|
|
641
|
-
}
|
|
642
|
-
#handleIncoming(raw) {
|
|
643
|
-
const message = this.#parseMessage(raw);
|
|
644
|
-
if (!message) {
|
|
645
|
-
return;
|
|
646
|
-
}
|
|
647
|
-
if (message.type === "ack") {
|
|
648
|
-
this.#resolveAck(message);
|
|
649
|
-
return;
|
|
650
|
-
}
|
|
651
|
-
if (message.type === "event") {
|
|
652
|
-
if (message.channel) {
|
|
653
|
-
if (message.event === "presence:update") {
|
|
654
|
-
this.#emitLocal(`channel:${message.channel}:presence:update`, message.data);
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
|
-
this.#emitLocal(`channel:${message.channel}:event`, {
|
|
658
|
-
event: message.event,
|
|
659
|
-
data: message.data
|
|
660
|
-
});
|
|
661
|
-
return;
|
|
662
|
-
}
|
|
663
|
-
this.#emitLocal(message.event, message.data);
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
#parseMessage(raw) {
|
|
667
|
-
try {
|
|
668
|
-
const data = typeof raw === "string" ? raw : String(raw);
|
|
669
|
-
const message = JSON.parse(data);
|
|
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;
|
|
680
|
-
} catch {
|
|
681
|
-
return null;
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
#resolveAck(message) {
|
|
685
|
-
if (!message.id) {
|
|
686
|
-
return;
|
|
687
|
-
}
|
|
688
|
-
const pending = this.#pendingRequests.get(message.id);
|
|
689
|
-
if (!pending) {
|
|
690
|
-
return;
|
|
691
|
-
}
|
|
692
|
-
clearTimeout(pending.timer);
|
|
693
|
-
this.#pendingRequests.delete(message.id);
|
|
694
|
-
if (message.ok) {
|
|
695
|
-
pending.resolve({ ok: true, data: message.data });
|
|
696
|
-
} else {
|
|
697
|
-
pending.resolve({ ok: false, error: message.error });
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
#handleClose() {
|
|
701
|
-
const shouldEmitDisconnect = this.#state !== "disconnected";
|
|
702
|
-
this.#ws = null;
|
|
703
|
-
this.#setState("disconnected");
|
|
704
|
-
if (shouldEmitDisconnect) {
|
|
705
|
-
this.#emitLocal("disconnect");
|
|
706
|
-
}
|
|
707
|
-
this.#rejectPending(new Error("Socket disconnected"));
|
|
708
|
-
this.#scheduleReconnect();
|
|
709
|
-
}
|
|
710
|
-
#scheduleReconnect() {
|
|
711
|
-
if (this.#manualDisconnect || !this.#autoReconnect || this.#reconnectTimer) {
|
|
712
|
-
return;
|
|
713
|
-
}
|
|
714
|
-
const delay = Math.min(
|
|
715
|
-
this.#reconnectDelay * 2 ** this.#reconnectAttempts,
|
|
716
|
-
this.#reconnectMaxDelay
|
|
717
|
-
);
|
|
718
|
-
this.#reconnectAttempts += 1;
|
|
719
|
-
this.#reconnectTimer = setTimeout(() => {
|
|
720
|
-
this.#reconnectTimer = null;
|
|
721
|
-
this.connect().catch(() => {
|
|
722
|
-
if (this.#state === "disconnected") {
|
|
723
|
-
this.#scheduleReconnect();
|
|
724
|
-
}
|
|
725
|
-
});
|
|
726
|
-
}, delay);
|
|
727
|
-
}
|
|
728
|
-
#rejectPending(error) {
|
|
729
|
-
for (const [id, pending] of this.#pendingRequests) {
|
|
730
|
-
clearTimeout(pending.timer);
|
|
731
|
-
pending.reject(error);
|
|
732
|
-
this.#pendingRequests.delete(id);
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
#emitLocal(event, data) {
|
|
736
|
-
this.#eventHandlers.get(event)?.forEach((handler) => this.#callHandler(handler, data));
|
|
737
|
-
}
|
|
738
|
-
#setState(state) {
|
|
739
|
-
if (this.#state === state) {
|
|
740
|
-
return;
|
|
741
|
-
}
|
|
742
|
-
this.#state = state;
|
|
743
|
-
this.#stateHandlers.forEach((handler) => this.#callHandler(handler, state));
|
|
744
|
-
}
|
|
745
|
-
#callHandler(handler, data) {
|
|
746
|
-
callClientHandler(handler, data);
|
|
747
|
-
}
|
|
748
|
-
};
|
|
749
|
-
|
|
750
|
-
// src/client/socket.ts
|
|
751
|
-
var Socket = class {
|
|
752
|
-
#channels = /* @__PURE__ */ new Map();
|
|
753
|
-
#url;
|
|
754
|
-
#path;
|
|
755
|
-
#session;
|
|
756
|
-
constructor(options = {}) {
|
|
757
|
-
const browserWindow = globalThis;
|
|
758
|
-
const browserOrigin = typeof globalThis === "object" && browserWindow.window?.location?.origin ? browserWindow.window.location.origin : "";
|
|
759
|
-
this.#url = options.url ?? browserOrigin;
|
|
760
|
-
this.#path = options.path ?? "/socket";
|
|
761
|
-
this.#session = new ClientSocketSession({
|
|
762
|
-
buildUrl: () => this.#buildUrl(),
|
|
763
|
-
autoReconnect: options.autoReconnect,
|
|
764
|
-
reconnectDelay: options.reconnectDelay,
|
|
765
|
-
reconnectMaxDelay: options.reconnectMaxDelay,
|
|
766
|
-
onConnected: () => {
|
|
767
|
-
void this.#resubscribeChannels();
|
|
768
|
-
}
|
|
769
|
-
});
|
|
770
|
-
}
|
|
771
|
-
get state() {
|
|
772
|
-
return this.#session.state;
|
|
773
|
-
}
|
|
774
|
-
get connected() {
|
|
775
|
-
return this.#session.connected;
|
|
776
|
-
}
|
|
777
|
-
connect() {
|
|
778
|
-
return this.#session.connect();
|
|
779
|
-
}
|
|
780
|
-
disconnect() {
|
|
781
|
-
for (const channel of this.#channels.values()) {
|
|
782
|
-
channel.unsubscribe().catch(() => {
|
|
783
|
-
});
|
|
784
|
-
}
|
|
785
|
-
this.#session.disconnect();
|
|
786
|
-
}
|
|
787
|
-
channel(name) {
|
|
788
|
-
if (!this.#channels.has(name)) {
|
|
789
|
-
this.#channels.set(name, new Channel(name, this));
|
|
790
|
-
}
|
|
791
|
-
return this.#channels.get(name);
|
|
792
|
-
}
|
|
793
|
-
async leave(name) {
|
|
794
|
-
const channel = this.#channels.get(name);
|
|
795
|
-
if (channel) {
|
|
796
|
-
await channel.unsubscribe();
|
|
797
|
-
if (this.#channels.get(name) === channel && !channel.subscribed) {
|
|
798
|
-
this.#channels.delete(name);
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
onStateChange(handler) {
|
|
803
|
-
return this.#session.onStateChange(handler);
|
|
804
|
-
}
|
|
805
|
-
on(event, handler) {
|
|
806
|
-
return this.#session.on(event, handler);
|
|
807
|
-
}
|
|
808
|
-
off(event, handler) {
|
|
809
|
-
this.#session.off(event, handler);
|
|
810
|
-
}
|
|
811
|
-
send(message) {
|
|
812
|
-
this.#session.send(message);
|
|
813
|
-
}
|
|
814
|
-
sendRequest(message, timeout = 5e3) {
|
|
815
|
-
return this.#session.sendRequest(message, timeout);
|
|
816
|
-
}
|
|
817
|
-
#buildUrl() {
|
|
818
|
-
const browserGlobal = globalThis;
|
|
819
|
-
const url = new URL(this.#url || browserGlobal.location?.origin || "http://localhost");
|
|
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
|
-
}
|
|
827
|
-
url.pathname = this.#path;
|
|
828
|
-
return url.toString();
|
|
829
|
-
}
|
|
830
|
-
async #resubscribeChannels() {
|
|
831
|
-
for (const channel of this.#channels.values()) {
|
|
832
|
-
if (channel.subscribed && !channel.active) {
|
|
833
|
-
await channel.$resubscribe().catch(reportClientError);
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
};
|
|
2
|
+
Socket
|
|
3
|
+
} from "../../chunk-QNOOQXYY.js";
|
|
4
|
+
import "../../chunk-6PJ4ALJD.js";
|
|
5
|
+
import "../../chunk-E6ILCRL3.js";
|
|
838
6
|
export {
|
|
839
|
-
Channel,
|
|
840
7
|
Socket
|
|
841
8
|
};
|
|
842
9
|
//# sourceMappingURL=index.js.map
|