@pluv/io 0.0.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/dist/index.js ADDED
@@ -0,0 +1,891 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __defProps = Object.defineProperties;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
9
+ var __getProtoOf = Object.getPrototypeOf;
10
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
11
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
12
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
13
+ var __spreadValues = (a, b) => {
14
+ for (var prop in b || (b = {}))
15
+ if (__hasOwnProp.call(b, prop))
16
+ __defNormalProp(a, prop, b[prop]);
17
+ if (__getOwnPropSymbols)
18
+ for (var prop of __getOwnPropSymbols(b)) {
19
+ if (__propIsEnum.call(b, prop))
20
+ __defNormalProp(a, prop, b[prop]);
21
+ }
22
+ return a;
23
+ };
24
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
25
+ var __objRest = (source, exclude) => {
26
+ var target = {};
27
+ for (var prop in source)
28
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
29
+ target[prop] = source[prop];
30
+ if (source != null && __getOwnPropSymbols)
31
+ for (var prop of __getOwnPropSymbols(source)) {
32
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
33
+ target[prop] = source[prop];
34
+ }
35
+ return target;
36
+ };
37
+ var __export = (target, all) => {
38
+ for (var name in all)
39
+ __defProp(target, name, { get: all[name], enumerable: true });
40
+ };
41
+ var __copyProps = (to, from, except, desc) => {
42
+ if (from && typeof from === "object" || typeof from === "function") {
43
+ for (let key of __getOwnPropNames(from))
44
+ if (!__hasOwnProp.call(to, key) && key !== except)
45
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
46
+ }
47
+ return to;
48
+ };
49
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
50
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
51
+ mod
52
+ ));
53
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
54
+ var __async = (__this, __arguments, generator) => {
55
+ return new Promise((resolve, reject) => {
56
+ var fulfilled = (value) => {
57
+ try {
58
+ step(generator.next(value));
59
+ } catch (e) {
60
+ reject(e);
61
+ }
62
+ };
63
+ var rejected = (value) => {
64
+ try {
65
+ step(generator.throw(value));
66
+ } catch (e) {
67
+ reject(e);
68
+ }
69
+ };
70
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
71
+ step((generator = generator.apply(__this, __arguments)).next());
72
+ });
73
+ };
74
+
75
+ // src/index.ts
76
+ var src_exports = {};
77
+ __export(src_exports, {
78
+ AbstractPersistance: () => AbstractPersistance,
79
+ AbstractPlatform: () => AbstractPlatform,
80
+ AbstractPubSub: () => AbstractPubSub,
81
+ AbstractWebSocket: () => AbstractWebSocket,
82
+ createIO: () => createIO
83
+ });
84
+ module.exports = __toCommonJS(src_exports);
85
+
86
+ // src/AbstractPersistance.ts
87
+ var AbstractPersistance = class {
88
+ };
89
+
90
+ // src/Persistance.ts
91
+ var Persistance = class extends AbstractPersistance {
92
+ constructor() {
93
+ super(...arguments);
94
+ this._storages = /* @__PURE__ */ new Map();
95
+ this._users = /* @__PURE__ */ new Map();
96
+ }
97
+ addUser(room, connectionId, user) {
98
+ var _a;
99
+ const users = (_a = this._users.get(room)) != null ? _a : /* @__PURE__ */ new Map();
100
+ this._users.set(room, users);
101
+ users.set(connectionId, user);
102
+ return Promise.resolve();
103
+ }
104
+ deleteStorageState(room) {
105
+ this._storages.delete(room);
106
+ return Promise.resolve();
107
+ }
108
+ deleteUser(room, connectionId) {
109
+ return __async(this, null, function* () {
110
+ const users = this._users.get(room);
111
+ if (!users)
112
+ return;
113
+ users.delete(connectionId);
114
+ if (!users.size) {
115
+ yield this.deleteUsers(room);
116
+ yield this.deleteStorageState(room);
117
+ }
118
+ return Promise.resolve();
119
+ });
120
+ }
121
+ deleteUsers(room) {
122
+ this._users.delete(room);
123
+ return Promise.resolve();
124
+ }
125
+ getStorageState(room) {
126
+ var _a;
127
+ const storage = (_a = this._storages.get(room)) != null ? _a : null;
128
+ return Promise.resolve(storage);
129
+ }
130
+ getUser(room, connectionId) {
131
+ var _a;
132
+ const user = (_a = this._users.get(room)) == null ? void 0 : _a.get(connectionId);
133
+ return Promise.resolve(user != null ? user : null);
134
+ }
135
+ getUsers(room) {
136
+ const users = this._users.get(room);
137
+ if (!users)
138
+ return Promise.resolve([]);
139
+ return Promise.resolve(Array.from(users.values()));
140
+ }
141
+ setStorageState(room, state) {
142
+ this._storages.set(room, state);
143
+ return Promise.resolve();
144
+ }
145
+ };
146
+
147
+ // src/PubSub.ts
148
+ var import_wonka = require("wonka");
149
+
150
+ // src/AbstractPubSub.ts
151
+ var AbstractPubSub = class {
152
+ };
153
+
154
+ // src/PubSub.ts
155
+ var PubSub = class extends AbstractPubSub {
156
+ constructor() {
157
+ super(...arguments);
158
+ this._currentSubscriptionId = 0;
159
+ this._subjects = /* @__PURE__ */ new Map();
160
+ this._subscriptionsRefs = /* @__PURE__ */ new Map();
161
+ this._unsubscriptions = /* @__PURE__ */ new Map();
162
+ }
163
+ publish(roomName, payload) {
164
+ return __async(this, null, function* () {
165
+ const subject = this._subjects.get(roomName);
166
+ if (!subject)
167
+ return;
168
+ Promise.resolve(subject.next(payload));
169
+ });
170
+ }
171
+ subscribe(roomName, onMessage) {
172
+ var _a;
173
+ const id = this._currentSubscriptionId++;
174
+ const refs = (_a = this._subscriptionsRefs.get(roomName)) != null ? _a : [];
175
+ const subject = (0, import_wonka.makeSubject)();
176
+ this._subjects.set(roomName, subject);
177
+ this._subscriptionsRefs.set(roomName, [...refs, id]);
178
+ const unsubscribe = (0, import_wonka.subscribe)(onMessage)(subject.source).unsubscribe;
179
+ this._unsubscriptions.set(id, [roomName, unsubscribe]);
180
+ return Promise.resolve(id);
181
+ }
182
+ unsubscribe(subscriptionId) {
183
+ var _a;
184
+ const [roomName, unsubscribe] = (_a = this._unsubscriptions.get(subscriptionId)) != null ? _a : [];
185
+ if (!roomName || !unsubscribe)
186
+ return;
187
+ const refs = this._subscriptionsRefs.get(roomName);
188
+ if (!refs)
189
+ return;
190
+ if (refs.length === 1) {
191
+ this._subjects.delete(roomName);
192
+ this._subscriptionsRefs.delete(roomName);
193
+ this._unsubscriptions.delete(subscriptionId);
194
+ return;
195
+ }
196
+ const index = refs.indexOf(subscriptionId);
197
+ this._subscriptionsRefs.set(
198
+ roomName,
199
+ index === -1 ? refs : [...refs.slice(0, index), ...refs.slice(index + 1)]
200
+ );
201
+ unsubscribe();
202
+ this._unsubscriptions.delete(subscriptionId);
203
+ }
204
+ };
205
+
206
+ // src/AbstractPlatform.ts
207
+ var AbstractPlatform = class {
208
+ constructor(config = {}) {
209
+ const { persistance, pubSub } = config;
210
+ this.persistance = persistance != null ? persistance : new Persistance();
211
+ this.pubSub = pubSub != null ? pubSub : new PubSub();
212
+ }
213
+ };
214
+
215
+ // src/AbstractWebSocket.ts
216
+ var AbstractWebSocket = class {
217
+ constructor(config) {
218
+ this.CONNECTING = 0;
219
+ this.OPEN = 1;
220
+ this.CLOSING = 2;
221
+ this.CLOSED = 3;
222
+ const { room } = config;
223
+ this.room = room;
224
+ }
225
+ handleError(params) {
226
+ var _a, _b, _c;
227
+ const {
228
+ error,
229
+ message = error instanceof Error ? error.message : "Unexpected error",
230
+ session
231
+ } = params;
232
+ const stack = error instanceof Error ? (_a = error.stack) != null ? _a : null : null;
233
+ return this.sendMessage({
234
+ connectionId: (_b = session == null ? void 0 : session.id) != null ? _b : null,
235
+ data: { message, stack },
236
+ room: this.room,
237
+ type: "$ERROR",
238
+ user: (_c = session == null ? void 0 : session.user) != null ? _c : null
239
+ });
240
+ }
241
+ sendMessage(data) {
242
+ return this.send(JSON.stringify(data));
243
+ }
244
+ };
245
+
246
+ // src/PluvIO.ts
247
+ var import_chalk2 = __toESM(require("chalk"));
248
+
249
+ // src/authorize.ts
250
+ var import_hkdf = __toESM(require("@panva/hkdf"));
251
+ var import_jose = require("jose");
252
+ var DEFAULT_MAX_AGE_MS = 6e4;
253
+ var getEncryptionKey = (secret) => __async(void 0, null, function* () {
254
+ return yield (0, import_hkdf.default)(
255
+ "sha256",
256
+ secret,
257
+ "",
258
+ "Pluv.io Generated Encryption Key",
259
+ 32
260
+ );
261
+ });
262
+ var now = () => Date.now() / 1e3 | 0;
263
+ var authorize = (params) => {
264
+ const { platform, secret } = params;
265
+ const encode = (encodeParams) => __async(void 0, null, function* () {
266
+ const { maxAge = DEFAULT_MAX_AGE_MS, room, user } = encodeParams;
267
+ const encryptionSecret = yield getEncryptionKey(secret);
268
+ const token = yield new import_jose.EncryptJWT({
269
+ room,
270
+ sub: room ? `${room}|${user.id}` : user.id,
271
+ user
272
+ }).setProtectedHeader({ alg: "dir", enc: "A256GCM" }).setIssuedAt().setExpirationTime(now() + maxAge).setJti(platform.randomUUID()).encrypt(encryptionSecret);
273
+ return token.toString();
274
+ });
275
+ const decode = (jwt) => __async(void 0, null, function* () {
276
+ const encryptionSecret = yield getEncryptionKey(secret);
277
+ const { payload } = yield (0, import_jose.jwtDecrypt)(jwt, encryptionSecret, {
278
+ clockTolerance: 15
279
+ });
280
+ return payload != null ? payload : null;
281
+ });
282
+ return { decode, encode };
283
+ };
284
+
285
+ // src/IORoom.ts
286
+ var import_crdt_yjs = require("@pluv/crdt-yjs");
287
+ var import_chalk = __toESM(require("chalk"));
288
+ var PING_TIMEOUT_MS = 3e4;
289
+ var IORoom = class {
290
+ constructor(room, config) {
291
+ this._doc = (0, import_crdt_yjs.doc)();
292
+ this._sessions = /* @__PURE__ */ new Map();
293
+ this._uninitialize = null;
294
+ this._authorize = null;
295
+ const { authorize: authorize2, context, debug, events, onDestroy, platform } = config;
296
+ this._context = context;
297
+ this._debug = debug;
298
+ this._events = events;
299
+ this._platform = platform;
300
+ this.room = room;
301
+ this._listeners = {
302
+ onDestroy: () => onDestroy == null ? void 0 : onDestroy()
303
+ };
304
+ if (authorize2)
305
+ this._authorize = authorize2;
306
+ }
307
+ broadcast(message) {
308
+ return Promise.resolve(this._broadcast({ message }));
309
+ }
310
+ register(webSocket, options) {
311
+ return __async(this, null, function* () {
312
+ var _a;
313
+ if (!this._uninitialize)
314
+ yield this._initialize();
315
+ const token = options == null ? void 0 : options.token;
316
+ const sessionId = this._platform.randomUUID();
317
+ const pluvWs = this._platform.convertWebSocket(webSocket, {
318
+ room: this.room
319
+ });
320
+ this._logDebug(
321
+ `${import_chalk.default.blue(
322
+ `Registering connection for room ${this.room}:`
323
+ )} ${sessionId}`
324
+ );
325
+ const uninitializeWs = yield pluvWs.initialize();
326
+ const user = yield this._getAuthorizedUser(token);
327
+ const isUnauthorized = !!((_a = this._authorize) == null ? void 0 : _a.required) && !user;
328
+ const isTokenInUse = !!user && Array.from(this._sessions.values()).some(
329
+ (session2) => {
330
+ var _a2;
331
+ return ((_a2 = session2.user) == null ? void 0 : _a2.id) === user.id;
332
+ }
333
+ );
334
+ if (isUnauthorized || isTokenInUse) {
335
+ this._logDebug(
336
+ `${import_chalk.default.blue(
337
+ "Authorization failed for connection:"
338
+ )} ${sessionId}`
339
+ );
340
+ pluvWs.handleError({ error: new Error("Not authorized") });
341
+ pluvWs.close(3e3, "WebSocket unauthorized.");
342
+ return;
343
+ }
344
+ const currentTime = new Date().getTime();
345
+ const session = {
346
+ id: sessionId,
347
+ presence: null,
348
+ quit: false,
349
+ room: this.room,
350
+ timers: {
351
+ ping: currentTime
352
+ },
353
+ webSocket: pluvWs,
354
+ user
355
+ };
356
+ this._sessions.set(session.id, session);
357
+ yield this._platform.persistance.addUser(
358
+ this.room,
359
+ sessionId,
360
+ user != null ? user : {}
361
+ );
362
+ this._emitRegistered(session);
363
+ this._emitStorageReceived(session);
364
+ this._emitUserJoined(session);
365
+ const context = {
366
+ context: this._context,
367
+ doc: this._doc,
368
+ room: this.room,
369
+ session,
370
+ sessions: this._sessions
371
+ };
372
+ pluvWs.addEventListener("message", (event) => {
373
+ if (!this._uninitialize)
374
+ return;
375
+ if (session.quit) {
376
+ session.webSocket.close(1011, "WebSocket broken.");
377
+ return;
378
+ }
379
+ const message = this._parseMessage(event);
380
+ if (!message)
381
+ return;
382
+ const eventConfig = this._getEventConfig(message);
383
+ if (!eventConfig)
384
+ return;
385
+ this._resolveMessage(message, context).then((output) => {
386
+ var _a2, _b;
387
+ if (!output)
388
+ return;
389
+ const eventType = (_b = (_a2 = eventConfig.options) == null ? void 0 : _a2.type) != null ? _b : "broadcast";
390
+ switch (eventType) {
391
+ case "self": {
392
+ Object.entries(output).forEach(([type, data]) => {
393
+ this._sendSelfMessage({ data, type }, session);
394
+ });
395
+ return;
396
+ }
397
+ case "sync": {
398
+ this._platform.pubSub.publish(this.room, __spreadValues({
399
+ connectionId: session.id,
400
+ options: eventConfig.options,
401
+ room: this.room,
402
+ user: session.user
403
+ }, message));
404
+ Object.entries(output).forEach(([type, data]) => {
405
+ this._sendSelfMessage({ data, type }, session);
406
+ });
407
+ return;
408
+ }
409
+ case "broadcast":
410
+ default: {
411
+ Object.entries(output).forEach(([type, data]) => {
412
+ this._broadcast({
413
+ message: { data, type },
414
+ senderId: session.id
415
+ });
416
+ });
417
+ }
418
+ }
419
+ });
420
+ });
421
+ const closeHandler = () => {
422
+ if (!this._uninitialize)
423
+ return;
424
+ session.quit = true;
425
+ this._logDebug(
426
+ `${import_chalk.default.blue(
427
+ `(Unregistering connection for room ${this.room}:`
428
+ )} ${sessionId}`
429
+ );
430
+ this._sessions.delete(session.id);
431
+ this._platform.persistance.deleteUser(this.room, session.id);
432
+ this._broadcast({
433
+ message: {
434
+ type: "$EXIT",
435
+ data: { sessionId: session.id }
436
+ },
437
+ senderId: session.id
438
+ });
439
+ uninitializeWs();
440
+ const size2 = this._getSessionsSize();
441
+ this._logDebug(
442
+ `${import_chalk.default.blue(
443
+ `Unregistered connection for room ${this.room}:`
444
+ )} ${sessionId}`
445
+ );
446
+ this._logDebug(`${import_chalk.default.blue(`Room ${this.room} size:`)} ${size2}`);
447
+ if (size2)
448
+ return;
449
+ this._uninitialize();
450
+ };
451
+ pluvWs.addEventListener("close", closeHandler);
452
+ pluvWs.addEventListener("error", closeHandler);
453
+ this._logDebug(
454
+ `${import_chalk.default.blue(
455
+ `Registered connection for room ${this.room}:`
456
+ )} ${sessionId}`
457
+ );
458
+ const size = this._getSessionsSize();
459
+ this._logDebug(`${import_chalk.default.blue(`Room ${this.room} size:`)} ${size}`);
460
+ });
461
+ }
462
+ _broadcast(params) {
463
+ var _a;
464
+ const { message, senderId } = params;
465
+ const currentTime = new Date().getTime();
466
+ const sender = senderId ? this._sessions.get(senderId) : null;
467
+ const quitters = Array.from(this._sessions.values()).filter(
468
+ (session) => session.quit || currentTime - session.timers.ping > PING_TIMEOUT_MS
469
+ );
470
+ quitters.forEach((session) => {
471
+ this._sessions.delete(session.id);
472
+ });
473
+ this._emitQuitters(quitters);
474
+ this._platform.pubSub.publish(this.room, __spreadValues({
475
+ connectionId: senderId != null ? senderId : null,
476
+ room: this.room,
477
+ user: (_a = sender == null ? void 0 : sender.user) != null ? _a : null
478
+ }, message));
479
+ }
480
+ _emitQuitters(quitters) {
481
+ quitters.forEach((quitter) => {
482
+ this._broadcast({
483
+ message: {
484
+ type: "$EXIT",
485
+ data: { sessionId: quitter.id }
486
+ },
487
+ senderId: quitter.id
488
+ });
489
+ });
490
+ }
491
+ _emitRegistered(session) {
492
+ this._sendSelfMessage(
493
+ {
494
+ type: "$REGISTERED",
495
+ data: { sessionId: session.id }
496
+ },
497
+ session
498
+ );
499
+ }
500
+ _emitStorageReceived(session) {
501
+ this._platform.persistance.getStorageState(this.room).then((_state) => {
502
+ const state = _state != null ? _state : this._doc.encodeStateAsUpdate();
503
+ this._sendSelfMessage(
504
+ {
505
+ type: "$STORAGE_RECEIVED",
506
+ data: { state }
507
+ },
508
+ session
509
+ );
510
+ });
511
+ }
512
+ _emitUserJoined(session) {
513
+ const user = session.user;
514
+ if (!user)
515
+ return;
516
+ this._broadcast({
517
+ message: {
518
+ type: "$USER_JOINED",
519
+ data: {
520
+ connectionId: session.id,
521
+ user
522
+ }
523
+ },
524
+ senderId: session.id
525
+ });
526
+ }
527
+ _getAuthorizedUser(token) {
528
+ return __async(this, null, function* () {
529
+ var _a;
530
+ if (!this._authorize)
531
+ return null;
532
+ if (!token)
533
+ return null;
534
+ const payload = yield authorize({
535
+ platform: this._platform,
536
+ secret: this._authorize.secret
537
+ }).decode(token);
538
+ if (!payload) {
539
+ this._logDebug(import_chalk.default.blue("Could not decode token:"));
540
+ this._logDebug(token);
541
+ return null;
542
+ }
543
+ if (payload.room !== this.room) {
544
+ this._logDebug(
545
+ import_chalk.default.blue(`Token is not authorized for room ${this.room}:`)
546
+ );
547
+ this._logDebug(import_chalk.default.blue("Received:"), payload.room);
548
+ this._logDebug(token);
549
+ return null;
550
+ }
551
+ try {
552
+ return (_a = this._authorize.user.parse(payload.user)) != null ? _a : null;
553
+ } catch (e) {
554
+ this._logDebug(`${import_chalk.default.blue("Token fails validation:")} ${token}`);
555
+ return null;
556
+ }
557
+ });
558
+ }
559
+ _getSessionsSize() {
560
+ const currentTime = new Date().getTime();
561
+ return Array.from(this._sessions.values()).reduce((count, session) => {
562
+ if (session.quit)
563
+ return count;
564
+ return currentTime - session.timers.ping > PING_TIMEOUT_MS ? count : count + 1;
565
+ }, 0);
566
+ }
567
+ _getEventConfig(message) {
568
+ var _a;
569
+ return (_a = this._events[message.type]) != null ? _a : null;
570
+ }
571
+ _initialize() {
572
+ return __async(this, null, function* () {
573
+ this._logDebug(`${import_chalk.default.blue(`Initializing room ${this.room}:`)}`);
574
+ if (!!this._uninitialize) {
575
+ yield this._uninitialize();
576
+ this._uninitialize = null;
577
+ }
578
+ const pubSubId = yield this._platform.pubSub.subscribe(
579
+ this.room,
580
+ (_a) => {
581
+ var _b = _a, { options = {} } = _b, message = __objRest(_b, ["options"]);
582
+ const session = {
583
+ id: message.connectionId,
584
+ user: message.user
585
+ };
586
+ this._sendMessage(message, session, options);
587
+ }
588
+ );
589
+ this._uninitialize = () => __async(this, null, function* () {
590
+ this._platform.pubSub.unsubscribe(pubSubId);
591
+ this._doc.destroy();
592
+ this._doc = (0, import_crdt_yjs.doc)();
593
+ this._listeners.onDestroy();
594
+ });
595
+ });
596
+ }
597
+ _logDebug(...data) {
598
+ this._debug && console.log(...data);
599
+ }
600
+ _parseMessage(message) {
601
+ const parsed = this._platform.parseData(message.data);
602
+ try {
603
+ if (typeof parsed !== "object")
604
+ return null;
605
+ if (typeof parsed.type !== "string")
606
+ return null;
607
+ if (typeof parsed.data !== "object")
608
+ return null;
609
+ return parsed;
610
+ } catch (e) {
611
+ return null;
612
+ }
613
+ }
614
+ _resolveMessage(message, context) {
615
+ return __async(this, null, function* () {
616
+ const { session } = context;
617
+ const eventConfig = this._getEventConfig(message);
618
+ if (!eventConfig)
619
+ return null;
620
+ let input;
621
+ try {
622
+ input = eventConfig.input ? eventConfig.input.parse(message.data) : message.data;
623
+ } catch (error) {
624
+ session == null ? void 0 : session.webSocket.handleError({
625
+ error,
626
+ message: "Invalid input",
627
+ session
628
+ });
629
+ return null;
630
+ }
631
+ const output = yield eventConfig.resolver(input, context);
632
+ if (!output)
633
+ return null;
634
+ return output;
635
+ });
636
+ }
637
+ _sendMessage(message, sender, options = {}) {
638
+ switch (options.type) {
639
+ case "self": {
640
+ this._sendSelfMessage(message, sender);
641
+ return;
642
+ }
643
+ case "sync": {
644
+ this._sendSyncMessage(message, sender);
645
+ return;
646
+ }
647
+ case "broadcast":
648
+ default: {
649
+ const sessionIds = options.sessionIds;
650
+ this._sendBroadcastMessage(message, sender, sessionIds);
651
+ }
652
+ }
653
+ }
654
+ _sendBroadcastMessage(message, sender, sessionIds) {
655
+ var _a, _b;
656
+ const senderId = (_a = sender == null ? void 0 : sender.id) != null ? _a : null;
657
+ const sessions = (_b = sessionIds == null ? void 0 : sessionIds.reduce((dict, id) => {
658
+ const _session = this._sessions.get(id);
659
+ return _session ? dict.set(id, _session) : dict;
660
+ }, /* @__PURE__ */ new Map())) != null ? _b : this._sessions;
661
+ Array.from(sessions.values()).forEach((_session) => {
662
+ _session.webSocket.sendMessage(__spreadValues({
663
+ connectionId: senderId,
664
+ room: this.room,
665
+ user: _session.user
666
+ }, message));
667
+ });
668
+ }
669
+ _sendSelfMessage(message, sender) {
670
+ const senderId = sender == null ? void 0 : sender.id;
671
+ if (!senderId)
672
+ return;
673
+ const session = this._sessions.get(senderId);
674
+ if (!session)
675
+ return;
676
+ session.webSocket.sendMessage(__spreadValues({
677
+ connectionId: sender.id,
678
+ room: this.room,
679
+ user: sender.user
680
+ }, message));
681
+ }
682
+ _sendSyncMessage(message, sender) {
683
+ const senderId = sender == null ? void 0 : sender.id;
684
+ if (!senderId)
685
+ return;
686
+ this._resolveMessage(message, {
687
+ context: this._context,
688
+ doc: this._doc,
689
+ room: this.room,
690
+ session: null,
691
+ sessions: this._sessions
692
+ }).then((output) => {
693
+ if (!output)
694
+ return;
695
+ Object.keys(output).forEach((type) => {
696
+ var _a, _b;
697
+ const data = (_a = output[type]) != null ? _a : {};
698
+ this._platform.pubSub.publish(this.room, {
699
+ connectionId: senderId,
700
+ data,
701
+ options: { type: "self" },
702
+ room: this.room,
703
+ type,
704
+ user: (_b = sender.user) != null ? _b : null
705
+ });
706
+ });
707
+ });
708
+ }
709
+ };
710
+
711
+ // src/PluvIO.ts
712
+ var PluvIO = class {
713
+ constructor(options) {
714
+ this._authorize = null;
715
+ this._events = {
716
+ $PING: {
717
+ resolver: (data, { session }) => {
718
+ if (!session)
719
+ return {};
720
+ const currentTime = new Date().getTime();
721
+ session.timers.ping = currentTime;
722
+ return { $PONG: {} };
723
+ },
724
+ options: { type: "self" }
725
+ },
726
+ $GET_OTHERS: {
727
+ resolver: (data, { session, sessions }) => {
728
+ const others = Array.from(sessions.entries()).filter(([, wsSession]) => wsSession.id !== (session == null ? void 0 : session.id)).reduce(
729
+ (acc, [connectionId, { presence, user }]) => __spreadProps(__spreadValues({}, acc), {
730
+ [connectionId]: {
731
+ connectionId,
732
+ presence,
733
+ room: this.room,
734
+ user
735
+ }
736
+ }),
737
+ {}
738
+ );
739
+ return { $OTHERS_RECEIVED: { others } };
740
+ },
741
+ options: { type: "sync" }
742
+ },
743
+ $UPDATE_PRESENCE: {
744
+ resolver: ({ presence }, { session }) => {
745
+ if (!session)
746
+ return {};
747
+ session.presence = Object.assign(
748
+ /* @__PURE__ */ Object.create(null),
749
+ session.presence,
750
+ presence
751
+ );
752
+ return { $PRESENCE_UPDATED: { presence } };
753
+ }
754
+ },
755
+ $UPDATE_STORAGE: {
756
+ resolver: ({ origin, update }, { doc: doc2, room }) => {
757
+ if (origin === "$STORAGE_RECEIVED" && Object.keys(doc2.toJSON()).length) {
758
+ return;
759
+ }
760
+ const updated = doc2.applyUpdate(update);
761
+ const state = updated.encodeStateAsUpdate();
762
+ this._platform.persistance.setStorageState(room, state);
763
+ return { $STORAGE_UPDATED: { state } };
764
+ }
765
+ }
766
+ };
767
+ this.room = null;
768
+ this._context = {};
769
+ this._rooms = /* @__PURE__ */ new Map();
770
+ const {
771
+ authorize: authorize2,
772
+ context,
773
+ debug = false,
774
+ events,
775
+ platform,
776
+ room
777
+ } = options;
778
+ this._debug = debug;
779
+ this._platform = platform;
780
+ if (authorize2)
781
+ this._authorize = authorize2;
782
+ if (context)
783
+ this._context = context;
784
+ if (events)
785
+ this._events = events;
786
+ if (room)
787
+ this.room = room;
788
+ }
789
+ createToken(params) {
790
+ return __async(this, null, function* () {
791
+ if (!this._authorize) {
792
+ throw new Error(
793
+ "IO does not specify authorize during initialization."
794
+ );
795
+ }
796
+ return yield authorize({
797
+ platform: this._platform,
798
+ secret: this._authorize.secret
799
+ }).encode(params);
800
+ });
801
+ }
802
+ event(event, config) {
803
+ if (event.startsWith("$")) {
804
+ throw new Error('Event name must not start with "$".');
805
+ }
806
+ const newEvent = {
807
+ [event]: config
808
+ };
809
+ return PluvIO.merge(
810
+ this,
811
+ new PluvIO({
812
+ events: newEvent,
813
+ platform: this._platform
814
+ })
815
+ );
816
+ }
817
+ getRoom(room) {
818
+ var _a;
819
+ if (this.room) {
820
+ throw new Error("IO cannot getRoom from within a room.");
821
+ }
822
+ if (!/^[a-z0-9]+[a-z0-9\-_]+[a-z0-9]+$/i.test(room)) {
823
+ throw new Error("Unsupported room name");
824
+ }
825
+ const platform = this._platform;
826
+ const oldRoom = this._rooms.get(room);
827
+ if (oldRoom)
828
+ return oldRoom;
829
+ const newRoom = new IORoom(room, {
830
+ authorize: (_a = this._authorize) != null ? _a : void 0,
831
+ context: this._context,
832
+ debug: this._debug,
833
+ events: this._events,
834
+ onDestroy: () => {
835
+ this._logDebug(`${import_chalk2.default.blue("Deleting empty room:")} ${room}`);
836
+ this._rooms.delete(room);
837
+ if (this._debug) {
838
+ const rooms = Array.from(this._rooms.keys());
839
+ this._logDebug(`${import_chalk2.default.blue("Deleted room:")} ${room}`);
840
+ this._logDebug(
841
+ `${import_chalk2.default.blue("Rooms available:")} ${rooms.join(", ")}`
842
+ );
843
+ }
844
+ },
845
+ platform
846
+ });
847
+ this._rooms.set(room, newRoom);
848
+ if (this._debug) {
849
+ const rooms = Array.from(this._rooms.keys());
850
+ this._logDebug(`${import_chalk2.default.blue("Created room:")} ${room}`);
851
+ this._logDebug(
852
+ `${import_chalk2.default.blue("Rooms available:")} ${rooms.join(", ")}`
853
+ );
854
+ }
855
+ return newRoom;
856
+ }
857
+ static merge(first, second) {
858
+ var _a, _b;
859
+ const authorize2 = (_a = first._authorize) != null ? _a : void 0;
860
+ const debug = first._debug;
861
+ const platform = first._platform;
862
+ const room = (_b = first.room) != null ? _b : void 0;
863
+ const events1 = first._events;
864
+ const events2 = second._events;
865
+ const events = Object.assign(/* @__PURE__ */ Object.create(null), events1, events2);
866
+ return new PluvIO({
867
+ authorize: authorize2,
868
+ debug,
869
+ events,
870
+ platform,
871
+ room
872
+ });
873
+ }
874
+ _logDebug(...data) {
875
+ this._debug && console.log(...data);
876
+ }
877
+ };
878
+
879
+ // src/createIO.ts
880
+ var createIO = (params) => {
881
+ const { authorize: authorize2, context, debug, platform } = params;
882
+ return new PluvIO({ authorize: authorize2, context, debug, platform });
883
+ };
884
+ // Annotate the CommonJS export names for ESM import in node:
885
+ 0 && (module.exports = {
886
+ AbstractPersistance,
887
+ AbstractPlatform,
888
+ AbstractPubSub,
889
+ AbstractWebSocket,
890
+ createIO
891
+ });