@pluv/io 3.2.2 → 4.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.mjs CHANGED
@@ -1,1532 +1,1446 @@
1
- var __defProp = Object.defineProperty;
2
- var __defProps = Object.defineProperties;
3
- var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
- var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
- var __spreadValues = (a, b) => {
9
- for (var prop in b || (b = {}))
10
- if (__hasOwnProp.call(b, prop))
11
- __defNormalProp(a, prop, b[prop]);
12
- if (__getOwnPropSymbols)
13
- for (var prop of __getOwnPropSymbols(b)) {
14
- if (__propIsEnum.call(b, prop))
15
- __defNormalProp(a, prop, b[prop]);
16
- }
17
- return a;
18
- };
19
- var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
- var __objRest = (source, exclude) => {
21
- var target = {};
22
- for (var prop in source)
23
- if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
24
- target[prop] = source[prop];
25
- if (source != null && __getOwnPropSymbols)
26
- for (var prop of __getOwnPropSymbols(source)) {
27
- if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
28
- target[prop] = source[prop];
29
- }
30
- return target;
31
- };
32
- var __async = (__this, __arguments, generator) => {
33
- return new Promise((resolve, reject) => {
34
- var fulfilled = (value) => {
35
- try {
36
- step(generator.next(value));
37
- } catch (e) {
38
- reject(e);
39
- }
40
- };
41
- var rejected = (value) => {
42
- try {
43
- step(generator.throw(value));
44
- } catch (e) {
45
- reject(e);
46
- }
47
- };
48
- var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
49
- step((generator = generator.apply(__this, __arguments)).next());
50
- });
51
- };
1
+ import { makeSubject, subscribe } from "wonka";
2
+ import hkdf from "@panva/hkdf";
3
+ import { EncryptJWT, jwtDecrypt } from "jose";
4
+ import { noop } from "@pluv/crdt";
5
+ import colors from "kleur";
52
6
 
53
- // src/AbstractPersistence.ts
54
- var AbstractPersistence = class {
55
- };
7
+ //#region src/AbstractPersistence.ts
8
+ var AbstractPersistence = class {};
56
9
 
57
- // src/Persistence.ts
10
+ //#endregion
11
+ //#region src/Persistence.ts
58
12
  var Persistence = class extends AbstractPersistence {
59
- constructor() {
60
- super(...arguments);
61
- this._storages = /* @__PURE__ */ new Map();
62
- this._users = /* @__PURE__ */ new Map();
63
- }
64
- addUser(room, connectionId, user) {
65
- var _a;
66
- const users = (_a = this._users.get(room)) != null ? _a : /* @__PURE__ */ new Map();
67
- this._users.set(room, users);
68
- users.set(connectionId, user);
69
- return Promise.resolve();
70
- }
71
- deleteStorageState(room) {
72
- this._storages.delete(room);
73
- return Promise.resolve();
74
- }
75
- deleteUser(room, connectionId) {
76
- return __async(this, null, function* () {
77
- const users = this._users.get(room);
78
- if (!users) return;
79
- users.delete(connectionId);
80
- if (!users.size) {
81
- yield this.deleteUsers(room);
82
- yield this.deleteStorageState(room);
83
- }
84
- return Promise.resolve();
85
- });
86
- }
87
- deleteUsers(room) {
88
- this._users.delete(room);
89
- return Promise.resolve();
90
- }
91
- getSize(room) {
92
- var _a, _b;
93
- return Promise.resolve((_b = (_a = this._users.get(room)) == null ? void 0 : _a.size) != null ? _b : 0);
94
- }
95
- getStorageState(room) {
96
- var _a;
97
- const storage = (_a = this._storages.get(room)) != null ? _a : null;
98
- return Promise.resolve(storage);
99
- }
100
- getUser(room, connectionId) {
101
- var _a;
102
- const user = (_a = this._users.get(room)) == null ? void 0 : _a.get(connectionId);
103
- return Promise.resolve(user != null ? user : null);
104
- }
105
- getUsers(room) {
106
- return __async(this, null, function* () {
107
- const users = this._users.get(room);
108
- if (!users) return /* @__PURE__ */ new Map();
109
- return users;
110
- });
111
- }
112
- getUsersSize(room) {
113
- return __async(this, null, function* () {
114
- return this._users.size;
115
- });
116
- }
117
- initialize(roomContext) {
118
- return this;
119
- }
120
- setStorageState(room, state) {
121
- this._storages.set(room, state);
122
- return Promise.resolve();
123
- }
13
+ _storages = /* @__PURE__ */ new Map();
14
+ _users = /* @__PURE__ */ new Map();
15
+ addUser(room, connectionId, user) {
16
+ const users = this._users.get(room) ?? /* @__PURE__ */ new Map();
17
+ this._users.set(room, users);
18
+ users.set(connectionId, user);
19
+ return Promise.resolve();
20
+ }
21
+ deleteStorageState(room) {
22
+ this._storages.delete(room);
23
+ return Promise.resolve();
24
+ }
25
+ async deleteUser(room, connectionId) {
26
+ const users = this._users.get(room);
27
+ if (!users) return;
28
+ users.delete(connectionId);
29
+ if (!users.size) {
30
+ await this.deleteUsers(room);
31
+ await this.deleteStorageState(room);
32
+ }
33
+ return Promise.resolve();
34
+ }
35
+ deleteUsers(room) {
36
+ this._users.delete(room);
37
+ return Promise.resolve();
38
+ }
39
+ getSize(room) {
40
+ return Promise.resolve(this._users.get(room)?.size ?? 0);
41
+ }
42
+ getStorageState(room) {
43
+ const storage = this._storages.get(room) ?? null;
44
+ return Promise.resolve(storage);
45
+ }
46
+ getUser(room, connectionId) {
47
+ const user = this._users.get(room)?.get(connectionId);
48
+ return Promise.resolve(user ?? null);
49
+ }
50
+ async getUsers(room) {
51
+ const users = this._users.get(room);
52
+ if (!users) return /* @__PURE__ */ new Map();
53
+ return users;
54
+ }
55
+ async getUsersSize(room) {
56
+ return this._users.size;
57
+ }
58
+ initialize(roomContext) {
59
+ return this;
60
+ }
61
+ setStorageState(room, state) {
62
+ this._storages.set(room, state);
63
+ return Promise.resolve();
64
+ }
124
65
  };
125
66
 
126
- // src/PubSub.ts
127
- import { makeSubject, subscribe } from "wonka";
128
-
129
- // src/AbstractPubSub.ts
130
- var AbstractPubSub = class {
131
- };
67
+ //#endregion
68
+ //#region src/AbstractPubSub.ts
69
+ var AbstractPubSub = class {};
132
70
 
133
- // src/PubSub.ts
71
+ //#endregion
72
+ //#region src/PubSub.ts
134
73
  var PubSub = class extends AbstractPubSub {
135
- constructor() {
136
- super(...arguments);
137
- this._currentSubscriptionId = 0;
138
- // Map of roomName to subjects
139
- this._subjects = /* @__PURE__ */ new Map();
140
- // Map of roomName to subscription ids
141
- this._subscriptionsRefs = /* @__PURE__ */ new Map();
142
- // Map of
143
- this._unsubscriptions = /* @__PURE__ */ new Map();
144
- }
145
- publish(roomName, payload) {
146
- return __async(this, null, function* () {
147
- const subject = this._subjects.get(roomName);
148
- if (!subject) return;
149
- Promise.resolve(subject.next(payload));
150
- });
151
- }
152
- subscribe(roomName, onMessage) {
153
- var _a;
154
- const id = this._currentSubscriptionId++;
155
- const refs = (_a = this._subscriptionsRefs.get(roomName)) != null ? _a : [];
156
- const subject = makeSubject();
157
- this._subjects.set(roomName, subject);
158
- this._subscriptionsRefs.set(roomName, [...refs, id]);
159
- const unsubscribe = subscribe(onMessage)(subject.source).unsubscribe;
160
- this._unsubscriptions.set(id, [roomName, unsubscribe]);
161
- return Promise.resolve(id);
162
- }
163
- unsubscribe(subscriptionId) {
164
- var _a;
165
- const [roomName, unsubscribe] = (_a = this._unsubscriptions.get(subscriptionId)) != null ? _a : [];
166
- if (!roomName || !unsubscribe) return;
167
- const refs = this._subscriptionsRefs.get(roomName);
168
- if (!refs) return;
169
- if (refs.length === 1) {
170
- this._subjects.delete(roomName);
171
- this._subscriptionsRefs.delete(roomName);
172
- this._unsubscriptions.delete(subscriptionId);
173
- return;
174
- }
175
- const index = refs.indexOf(subscriptionId);
176
- this._subscriptionsRefs.set(
177
- roomName,
178
- index === -1 ? refs : [...refs.slice(0, index), ...refs.slice(index + 1)]
179
- );
180
- unsubscribe();
181
- this._unsubscriptions.delete(subscriptionId);
182
- }
74
+ _currentSubscriptionId = 0;
75
+ _subjects = /* @__PURE__ */ new Map();
76
+ _subscriptionsRefs = /* @__PURE__ */ new Map();
77
+ _unsubscriptions = /* @__PURE__ */ new Map();
78
+ async publish(roomName, payload) {
79
+ const subject = this._subjects.get(roomName);
80
+ if (!subject) return;
81
+ Promise.resolve(subject.next(payload));
82
+ }
83
+ subscribe(roomName, onMessage) {
84
+ const id = this._currentSubscriptionId++;
85
+ const refs = this._subscriptionsRefs.get(roomName) ?? [];
86
+ const subject = makeSubject();
87
+ this._subjects.set(roomName, subject);
88
+ this._subscriptionsRefs.set(roomName, [...refs, id]);
89
+ const unsubscribe = subscribe(onMessage)(subject.source).unsubscribe;
90
+ this._unsubscriptions.set(id, [roomName, unsubscribe]);
91
+ return Promise.resolve(id);
92
+ }
93
+ unsubscribe(subscriptionId) {
94
+ const [roomName, unsubscribe] = this._unsubscriptions.get(subscriptionId) ?? [];
95
+ if (!roomName || !unsubscribe) return;
96
+ const refs = this._subscriptionsRefs.get(roomName);
97
+ if (!refs) return;
98
+ if (refs.length === 1) {
99
+ this._subjects.delete(roomName);
100
+ this._subscriptionsRefs.delete(roomName);
101
+ this._unsubscriptions.delete(subscriptionId);
102
+ return;
103
+ }
104
+ const index = refs.indexOf(subscriptionId);
105
+ this._subscriptionsRefs.set(roomName, index === -1 ? refs : [...refs.slice(0, index), ...refs.slice(index + 1)]);
106
+ unsubscribe();
107
+ this._unsubscriptions.delete(subscriptionId);
108
+ }
183
109
  };
184
110
 
185
- // src/AbstractPlatform.ts
111
+ //#endregion
112
+ //#region src/AbstractPlatform.ts
186
113
  var AbstractPlatform = class {
187
- constructor(config = {}) {
188
- this._initialized = false;
189
- const { roomContext, persistence, pubSub } = config;
190
- this._meta = config._meta;
191
- if (!!roomContext) this._roomContext = roomContext;
192
- this.persistence = persistence != null ? persistence : new Persistence();
193
- this.pubSub = pubSub != null ? pubSub : new PubSub();
194
- }
195
- validateConfig(config) {
196
- }
197
- _initialize() {
198
- if (this._initialized) throw new Error("Platform is already initialized");
199
- this._initialized = true;
200
- return this;
201
- }
114
+ _initContext;
115
+ _roomContext;
116
+ _initialized = false;
117
+ persistence;
118
+ pubSub;
119
+ _createToken;
120
+ _fetch;
121
+ constructor(config = {}) {
122
+ const { roomContext, persistence, pubSub } = config;
123
+ this._meta = config._meta;
124
+ if (!!roomContext) this._roomContext = roomContext;
125
+ this.persistence = persistence ?? new Persistence();
126
+ this.pubSub = pubSub ?? new PubSub();
127
+ }
128
+ validateConfig(config) {}
129
+ _initialize() {
130
+ if (this._initialized) throw new Error("Platform is already initialized");
131
+ this._initialized = true;
132
+ return this;
133
+ }
202
134
  };
203
135
 
204
- // src/AbstractWebSocket.ts
136
+ //#endregion
137
+ //#region src/AbstractWebSocket.ts
205
138
  var AbstractWebSocket = class {
206
- constructor(webSocket, config) {
207
- /** The connection is not yet open. */
208
- this.CONNECTING = 0;
209
- /** The connection is open and ready to communicate. */
210
- this.OPEN = 1;
211
- /** The connection is in the process of closing. */
212
- this.CLOSING = 2;
213
- /** The connection is closed. */
214
- this.CLOSED = 3;
215
- const { persistence, platform, room } = config;
216
- this.persistence = persistence;
217
- this._platform = platform;
218
- this.room = room;
219
- this.webSocket = webSocket;
220
- }
221
- handleError(params) {
222
- var _a, _b, _c;
223
- const { error, message: _message, room, session } = params;
224
- const message = error instanceof Error ? error.message : "Unexpected error";
225
- const stack = error instanceof Error ? (_a = error.stack) != null ? _a : null : null;
226
- const connectionId = (_b = session == null ? void 0 : session.id) != null ? _b : null;
227
- return this.sendMessage({
228
- connectionId,
229
- data: { message, stack },
230
- room,
231
- type: "$error",
232
- user: (_c = session == null ? void 0 : session.user) != null ? _c : null
233
- });
234
- }
235
- sendMessage(data) {
236
- return this.send(JSON.stringify(data));
237
- }
139
+ /** The connection is not yet open. */
140
+ CONNECTING = 0;
141
+ /** The connection is open and ready to communicate. */
142
+ OPEN = 1;
143
+ /** The connection is in the process of closing. */
144
+ CLOSING = 2;
145
+ /** The connection is closed. */
146
+ CLOSED = 3;
147
+ persistence;
148
+ room;
149
+ webSocket;
150
+ _platform;
151
+ constructor(webSocket, config) {
152
+ const { persistence, platform, room } = config;
153
+ this.persistence = persistence;
154
+ this._platform = platform;
155
+ this.room = room;
156
+ this.webSocket = webSocket;
157
+ }
158
+ handleError(params) {
159
+ const { error, message: _message, room, session } = params;
160
+ const message = error instanceof Error ? error.message : "Unexpected error";
161
+ const stack = error instanceof Error ? error.stack ?? null : null;
162
+ const connectionId = session?.id ?? null;
163
+ return this.sendMessage({
164
+ connectionId,
165
+ data: {
166
+ message,
167
+ stack
168
+ },
169
+ room,
170
+ type: "$error",
171
+ user: session?.user ?? null
172
+ });
173
+ }
174
+ sendMessage(data) {
175
+ return this.send(JSON.stringify(data));
176
+ }
238
177
  };
239
178
 
240
- // src/authorize.ts
241
- import hkdf from "@panva/hkdf";
242
- import { EncryptJWT, jwtDecrypt } from "jose";
243
- var DEFAULT_MAX_AGE_MS = 6e4;
244
- var getEncryptionKey = (secret) => __async(null, null, function* () {
245
- return yield hkdf("sha256", secret, "", "Pluv.io Generated Encryption Key", 32);
246
- });
247
- var now = () => Date.now() / 1e3 | 0;
248
- var authorize = (params) => {
249
- const { platform, secret } = params;
250
- const encode = (encodeParams) => __async(null, null, function* () {
251
- const { maxAge = DEFAULT_MAX_AGE_MS, room, user } = encodeParams;
252
- const encryptionSecret = yield getEncryptionKey(secret);
253
- const token = yield new EncryptJWT({
254
- room,
255
- sub: room ? `${room}|${user.id}` : user.id,
256
- user
257
- }).setProtectedHeader({ alg: "dir", enc: "A256GCM" }).setIssuedAt().setExpirationTime(now() + maxAge).setJti(platform.randomUUID()).encrypt(encryptionSecret);
258
- return token.toString();
259
- });
260
- const decode = (jwt) => __async(null, null, function* () {
261
- const encryptionSecret = yield getEncryptionKey(secret);
262
- const { payload } = yield jwtDecrypt(jwt, encryptionSecret, {
263
- clockTolerance: 15
264
- });
265
- return payload != null ? payload : null;
266
- });
267
- return { decode, encode };
179
+ //#endregion
180
+ //#region src/authorize.ts
181
+ const DEFAULT_MAX_AGE_MS = 6e4;
182
+ const getEncryptionKey = async (secret) => {
183
+ return await hkdf("sha256", secret, "", "Pluv.io Generated Encryption Key", 32);
268
184
  };
269
-
270
- // src/PluvIO.ts
271
- import { noop as noop3 } from "@pluv/crdt";
272
-
273
- // src/PluvProcedure.ts
274
- var PluvProcedure = class _PluvProcedure {
275
- constructor(config = {}) {
276
- this._broadcast = null;
277
- this._input = null;
278
- this._self = null;
279
- this._sync = null;
280
- const { broadcast, input, self, sync } = config;
281
- this._broadcast = broadcast != null ? broadcast : null;
282
- this._input = input != null ? input : null;
283
- this._self = self != null ? self : null;
284
- this._sync = sync != null ? sync : null;
285
- }
286
- get config() {
287
- var _a, _b, _c, _d, _e, _f, _g;
288
- return {
289
- broadcast: (_b = (_a = this._broadcast) == null ? void 0 : _a.bind(this)) != null ? _b : null,
290
- input: (_c = this._input) != null ? _c : null,
291
- resolver: this._resolver(),
292
- self: (_e = (_d = this._self) == null ? void 0 : _d.bind(this)) != null ? _e : null,
293
- sync: (_g = (_f = this._sync) == null ? void 0 : _f.bind(this)) != null ? _g : null
294
- };
295
- }
296
- broadcast(resolver) {
297
- const { broadcast } = this.config;
298
- if (!!broadcast) throw new Error("Broadcast was already defined for this procedure");
299
- return new _PluvProcedure(__spreadProps(__spreadValues({}, this.config), {
300
- broadcast: resolver
301
- }));
302
- }
303
- input(input) {
304
- return new _PluvProcedure({
305
- input
306
- });
307
- }
308
- self(resolver) {
309
- const { self } = this.config;
310
- if (!!self) throw new Error("Self was already defined for this procedure");
311
- return new _PluvProcedure(__spreadProps(__spreadValues({}, this.config), {
312
- self: resolver
313
- }));
314
- }
315
- sync(resolver) {
316
- const { sync } = this.config;
317
- if (!!sync) throw new Error("Sync was already defined for this procedure");
318
- return new _PluvProcedure(__spreadProps(__spreadValues({}, this.config), {
319
- sync: resolver
320
- }));
321
- }
322
- _resolver() {
323
- return (data, context) => {
324
- var _a, _b, _c;
325
- return __spreadValues(__spreadValues(__spreadValues({}, (_a = this._broadcast) == null ? void 0 : _a.call(this, data, context)), (_b = this._self) == null ? void 0 : _b.call(this, data, context)), (_c = this._sync) == null ? void 0 : _c.call(this, data, context));
326
- };
327
- }
185
+ const now = () => Date.now() / 1e3 | 0;
186
+ const authorize = (params) => {
187
+ const { platform, secret } = params;
188
+ const encode = async (encodeParams) => {
189
+ const { maxAge = DEFAULT_MAX_AGE_MS, room, user } = encodeParams;
190
+ const encryptionSecret = await getEncryptionKey(secret);
191
+ return (await new EncryptJWT({
192
+ room,
193
+ sub: room ? `${room}|${user.id}` : user.id,
194
+ user
195
+ }).setProtectedHeader({
196
+ alg: "dir",
197
+ enc: "A256GCM"
198
+ }).setIssuedAt().setExpirationTime(now() + maxAge).setJti(platform.randomUUID()).encrypt(encryptionSecret)).toString();
199
+ };
200
+ const decode = async (jwt) => {
201
+ const { payload } = await jwtDecrypt(jwt, await getEncryptionKey(secret), { clockTolerance: 15 });
202
+ return payload ?? null;
203
+ };
204
+ return {
205
+ decode,
206
+ encode
207
+ };
328
208
  };
329
209
 
330
- // src/PluvRouter.ts
331
- var PluvRouter = class _PluvRouter {
332
- constructor(events) {
333
- this._defs = { events: {} };
334
- const invalidName = Object.keys(events).find((name) => !this._isValidEventName(name));
335
- if (typeof invalidName === "string") {
336
- throw new Error(
337
- `Invalid event name. Event names must be formatted as valid JavaScript variable names: "${invalidName}"`
338
- );
339
- }
340
- this._defs = { events };
341
- }
342
- static merge(...routers) {
343
- const events = Object.assign(
344
- /* @__PURE__ */ Object.create(null),
345
- ...routers.map((router) => router._defs.events)
346
- );
347
- return new _PluvRouter(events);
348
- }
349
- _isValidEventName(name) {
350
- return /^[a-z_$][a-z0-9_$]*$/gi.test(name);
351
- }
210
+ //#endregion
211
+ //#region src/PluvProcedure.ts
212
+ var PluvProcedure = class PluvProcedure {
213
+ _broadcast = null;
214
+ _input = null;
215
+ _self = null;
216
+ _sync = null;
217
+ get config() {
218
+ return {
219
+ broadcast: this._broadcast?.bind(this) ?? null,
220
+ input: this._input ?? null,
221
+ resolver: this._resolver(),
222
+ self: this._self?.bind(this) ?? null,
223
+ sync: this._sync?.bind(this) ?? null
224
+ };
225
+ }
226
+ constructor(config = {}) {
227
+ const { broadcast, input, self, sync } = config;
228
+ this._broadcast = broadcast ?? null;
229
+ this._input = input ?? null;
230
+ this._self = self ?? null;
231
+ this._sync = sync ?? null;
232
+ }
233
+ broadcast(resolver) {
234
+ const { broadcast } = this.config;
235
+ if (!!broadcast) throw new Error("Broadcast was already defined for this procedure");
236
+ return new PluvProcedure({
237
+ ...this.config,
238
+ broadcast: resolver
239
+ });
240
+ }
241
+ input(input) {
242
+ return new PluvProcedure({ input });
243
+ }
244
+ self(resolver) {
245
+ const { self } = this.config;
246
+ if (!!self) throw new Error("Self was already defined for this procedure");
247
+ return new PluvProcedure({
248
+ ...this.config,
249
+ self: resolver
250
+ });
251
+ }
252
+ sync(resolver) {
253
+ const { sync } = this.config;
254
+ if (!!sync) throw new Error("Sync was already defined for this procedure");
255
+ return new PluvProcedure({
256
+ ...this.config,
257
+ sync: resolver
258
+ });
259
+ }
260
+ _resolver() {
261
+ return (data, context) => {
262
+ return {
263
+ ...this._broadcast?.(data, context),
264
+ ...this._self?.(data, context),
265
+ ...this._sync?.(data, context)
266
+ };
267
+ };
268
+ }
352
269
  };
353
270
 
354
- // src/PluvServer.ts
355
- import { noop as noop2 } from "@pluv/crdt";
356
- import colors2 from "kleur";
357
-
358
- // src/IORoom.ts
359
- import { noop } from "@pluv/crdt";
360
- import colors from "kleur";
271
+ //#endregion
272
+ //#region src/PluvRouter.ts
273
+ var PluvRouter = class PluvRouter {
274
+ _defs = { events: {} };
275
+ constructor(events) {
276
+ const invalidName = Object.keys(events).find((name) => !this._isValidEventName(name));
277
+ if (typeof invalidName === "string") throw new Error(`Invalid event name. Event names must be formatted as valid JavaScript variable names: "${invalidName}"`);
278
+ this._defs = { events };
279
+ }
280
+ static merge(...routers) {
281
+ return new PluvRouter(Object.assign(Object.create(null), ...routers.map((router) => router._defs.events)));
282
+ }
283
+ _isValidEventName(name) {
284
+ return /^[a-z_$][a-z0-9_$]*$/gi.test(name);
285
+ }
286
+ };
361
287
 
362
- // src/constants.ts
363
- var GARBAGE_COLLECT_INTERVAL_MS = 6e4;
364
- var MAX_PRESENCE_SIZE_BYTES = 512;
365
- var MAX_STORAGE_SIZE_BYTES = 31457280;
366
- var MAX_USER_ID_LENGTH = 128;
367
- var MAX_USER_SIZE_BYTES = 512;
368
- var PING_TIMEOUT_MS = 3e4;
288
+ //#endregion
289
+ //#region src/constants.ts
290
+ const GARBAGE_COLLECT_INTERVAL_MS = 6e4;
291
+ const MAX_PRESENCE_SIZE_BYTES = 512;
292
+ const MAX_STORAGE_SIZE_BYTES = 31457280;
293
+ const MAX_USER_ID_LENGTH = 128;
294
+ const MAX_USER_SIZE_BYTES = 512;
295
+ const PING_TIMEOUT_MS = 3e4;
369
296
 
370
- // src/utils/oneLine.ts
371
- var oneLine = (strings, ...values) => {
372
- return strings.map((str) => str.replace(/\s+/g, " ")).reduce((acc, str, i) => acc + str + (i < values.length ? values[i] : ""), "").replace(/\s+/g, " ").trim();
297
+ //#endregion
298
+ //#region src/utils/oneLine.ts
299
+ const oneLine = (strings, ...values) => {
300
+ return strings.map((str) => str.replace(/\s+/g, " ")).reduce((acc, str, i) => acc + str + (i < values.length ? values[i] : ""), "").replace(/\s+/g, " ").trim();
373
301
  };
374
302
 
375
- // src/utils/pickBy.ts
376
- var pickBy = (obj, by) => {
377
- const result = {};
378
- const keys = Object.keys(obj);
379
- for (let i = 0; i < keys.length; ++i) {
380
- const key = keys[i];
381
- const value = obj[key];
382
- if (by(value)) result[key] = value;
383
- }
384
- return result;
303
+ //#endregion
304
+ //#region src/utils/pickBy.ts
305
+ const pickBy = (obj, by) => {
306
+ const result = {};
307
+ const keys = Object.keys(obj);
308
+ for (let i = 0; i < keys.length; ++i) {
309
+ const key = keys[i];
310
+ const value = obj[key];
311
+ if (by(value)) result[key] = value;
312
+ }
313
+ return result;
385
314
  };
386
315
 
387
- // src/IORoom.ts
316
+ //#endregion
317
+ //#region src/IORoom.ts
388
318
  var IORoom = class {
389
- constructor(id, config) {
390
- this._lastGarbageCollectMs = -1 * (GARBAGE_COLLECT_INTERVAL_MS + 1);
391
- this._uninitialize = null;
392
- this._authorize = null;
393
- this._sessions = /* @__PURE__ */ new Map();
394
- this._userSessionss = /* @__PURE__ */ new Map();
395
- const {
396
- _meta,
397
- authorize: authorize2,
398
- context,
399
- crdt = noop,
400
- debug,
401
- getInitialStorage,
402
- onDestroy,
403
- onMessage,
404
- onUserConnected,
405
- onUserDisconnected,
406
- platform,
407
- roomContext,
408
- router
409
- } = config;
410
- this.id = id;
411
- this._context = context;
412
- this._crdt = crdt;
413
- this._debug = debug;
414
- this._docFactory = crdt.doc(() => ({}));
415
- this._getInitialStorage = getInitialStorage;
416
- this._roomContext = roomContext;
417
- this._router = router;
418
- this._platform = platform.initialize(__spreadProps(__spreadValues({}, !!_meta ? { _meta } : {}), { roomContext }));
419
- this._listeners = {
420
- onDestroy: (event) => onDestroy == null ? void 0 : onDestroy(event),
421
- onMessage: (event) => onMessage == null ? void 0 : onMessage(event),
422
- onUserConnected: (event) => onUserConnected == null ? void 0 : onUserConnected(event),
423
- onUserDisconnected: (event) => onUserDisconnected == null ? void 0 : onUserDisconnected(event)
424
- };
425
- if (authorize2) this._authorize = authorize2;
426
- const webSockets = this._platform.getWebSockets();
427
- webSockets.forEach((webSocket) => {
428
- var _a;
429
- const deserialized = this._platform.getSerializedState(webSocket);
430
- const sessionId = this._platform.getSessionId(webSocket);
431
- if (!deserialized) return;
432
- if (typeof sessionId !== "string") return;
433
- const pluvWs = this._platform.convertWebSocket(webSocket, { room: this.id });
434
- this._sessions.set(sessionId, pluvWs);
435
- const userId = (_a = pluvWs.session.user) == null ? void 0 : _a.id;
436
- if (!!userId) this._addUserSession(userId, sessionId);
437
- });
438
- const { doc, uninitialize } = this._initialize();
439
- this._doc = doc;
440
- this._uninitialize = uninitialize;
441
- }
442
- /**
443
- * @ignore
444
- * @readonly
445
- * @deprecated Internal use only. Changes to this will never be marked as breaking.
446
- */
447
- get _defs() {
448
- return {
449
- authorize: this._authorize,
450
- context: this._context,
451
- crdt: this._crdt,
452
- events: this._router._defs.events,
453
- platform: this._platform
454
- };
455
- }
456
- get broadcast() {
457
- const _broadcast = (event, data) => {
458
- const message = { type: event, data };
459
- return Promise.resolve(this._broadcast({ message }));
460
- };
461
- return new Proxy(_broadcast, {
462
- get(fn, prop) {
463
- return (data) => fn(prop, data);
464
- }
465
- });
466
- }
467
- get _initialized() {
468
- var _a;
469
- return Promise.resolve(!!((_a = this._uninitialize) == null ? void 0 : _a.then((result) => !!result)));
470
- }
471
- /**
472
- * @description Closes a connection with the specified sessionId
473
- * @param sessionId The session id of the connection to close
474
- */
475
- evict(sessionId) {
476
- return __async(this, null, function* () {
477
- const session = this._sessions.get(sessionId);
478
- if (session) session.state.quit = true;
479
- yield this._emitQuitters();
480
- });
481
- }
482
- /**
483
- * @description Closes all connections to this room, effectively destroying
484
- * the room
485
- */
486
- evictAll() {
487
- return __async(this, null, function* () {
488
- const sessions = Array.from(this._sessions.values());
489
- sessions.forEach((session) => {
490
- session.state.quit = true;
491
- });
492
- yield this._emitQuitters();
493
- });
494
- }
495
- /**
496
- * @description The IORoom will garbage collect occasionally as connections ping/pong the
497
- * server
498
- */
499
- garbageCollect() {
500
- return __async(this, null, function* () {
501
- this._lastGarbageCollectMs = Date.now();
502
- this._emitQuitters();
503
- this._emitSyncState();
504
- });
505
- }
506
- getSize() {
507
- const currentTime = (/* @__PURE__ */ new Date()).getTime();
508
- return Array.from(this._sessions.values()).reduce((count, pluvWs) => {
509
- var _a;
510
- if (pluvWs.state.quit) return count;
511
- const pingTime = (_a = this._platform.getLastPing(pluvWs)) != null ? _a : pluvWs.state.timers.ping;
512
- return currentTime - pingTime > PING_TIMEOUT_MS ? count : count + 1;
513
- }, 0);
514
- }
515
- onClose(webSocket) {
516
- this._ensureDetached();
517
- const wsSession = this._getAbstractWs(webSocket);
518
- if (!wsSession) return () => __async(this, null, function* () {
519
- return void 0;
520
- });
521
- return this._onClose(wsSession);
522
- }
523
- onError(webSocket) {
524
- this._ensureDetached();
525
- const wsSession = this._getAbstractWs(webSocket);
526
- if (!wsSession) return () => __async(this, null, function* () {
527
- return void 0;
528
- });
529
- return this._onClose(wsSession);
530
- }
531
- onMessage(webSocket) {
532
- this._ensureDetached();
533
- const wsSession = this._getAbstractWs(webSocket);
534
- if (!wsSession) return () => __async(this, null, function* () {
535
- return void 0;
536
- });
537
- return this._onMessage(wsSession);
538
- }
539
- register(webSocket, ...options) {
540
- return __async(this, null, function* () {
541
- var _a, _b;
542
- const _options = (_a = options[0]) != null ? _a : {};
543
- const token = (_b = _options.token) != null ? _b : null;
544
- const sessionId = this._platform.getSessionId(webSocket);
545
- const sessionExists = typeof sessionId === "string" && this._sessions.has(sessionId);
546
- if (sessionExists) return;
547
- if (!(yield this._initialized)) {
548
- this._initialize();
549
- yield this._initialized;
550
- }
551
- const user = yield this._getAuthorizedUser(token, _options);
552
- const ioAuthorize = this._getIOAuthorize(_options);
553
- const isUnauthorized = !!ioAuthorize && !user;
554
- const pluvWs = this._platform.convertWebSocket(webSocket, { room: this.id });
555
- if (isUnauthorized) {
556
- this._logDebug(`${colors.blue("Authorization failed for connection")}`);
557
- pluvWs.handleError({ error: new Error("Not authorized"), room: this.id });
558
- pluvWs.close(3e3, "WebSocket unauthorized.");
559
- return;
560
- }
561
- if (!!user) {
562
- const latest = this._getLatestPresence(user.id);
563
- const prevState = pluvWs.state;
564
- pluvWs.user = user;
565
- this._platform.setSerializedState(pluvWs, __spreadProps(__spreadValues({}, prevState), {
566
- presence: latest.presence,
567
- timers: __spreadProps(__spreadValues({}, prevState.timers), { presence: latest.timer })
568
- }));
569
- this._addUserSession(user.id, pluvWs.sessionId);
570
- }
571
- this._logDebug(
572
- `${colors.blue(`Registering connection for room ${this.id}:`)} ${pluvWs.sessionId}`
573
- );
574
- yield this._platform.acceptWebSocket(pluvWs);
575
- this._sessions.set(pluvWs.sessionId, pluvWs);
576
- yield this._platform.persistence.addUser(this.id, pluvWs.sessionId, user != null ? user : {});
577
- if (this._platform._config.registrationMode === "attached") {
578
- const onClose = this._onClose(pluvWs).bind(this);
579
- const onMessage = this._onMessage(pluvWs).bind(this);
580
- pluvWs.addEventListener("close", onClose);
581
- pluvWs.addEventListener("error", onClose);
582
- pluvWs.addEventListener("message", onMessage);
583
- }
584
- yield this._emitRegistered(pluvWs);
585
- const size = this.getSize();
586
- this._logDebug(oneLine`
319
+ id;
320
+ _doc;
321
+ _lastGarbageCollectMs = -1 * (GARBAGE_COLLECT_INTERVAL_MS + 1);
322
+ _uninitialize = null;
323
+ _authorize = null;
324
+ _context;
325
+ _crdt;
326
+ _debug;
327
+ _docFactory;
328
+ _getInitialStorage;
329
+ _listeners;
330
+ _platform;
331
+ _roomContext;
332
+ _router;
333
+ _sessions = /* @__PURE__ */ new Map();
334
+ _userSessionss = /* @__PURE__ */ new Map();
335
+ _wasDocEmptyOnInit = true;
336
+ _storageInitializedViaSession = false;
337
+ /**
338
+ * @ignore
339
+ * @readonly
340
+ * @deprecated Internal use only. Changes to this will never be marked as breaking.
341
+ */
342
+ get _defs() {
343
+ return {
344
+ authorize: this._authorize,
345
+ context: this._context,
346
+ crdt: this._crdt,
347
+ events: this._router._defs.events,
348
+ platform: this._platform
349
+ };
350
+ }
351
+ get broadcast() {
352
+ const _broadcast = (event, data) => {
353
+ const message = {
354
+ type: event,
355
+ data
356
+ };
357
+ return Promise.resolve(this._broadcast({ message }));
358
+ };
359
+ return new Proxy(_broadcast, { get(fn, prop) {
360
+ return (data) => fn(prop, data);
361
+ } });
362
+ }
363
+ get _initialized() {
364
+ return Promise.resolve(!!this._uninitialize?.then((result) => !!result));
365
+ }
366
+ constructor(id, config) {
367
+ const { _meta, authorize: authorize$1, context, crdt = noop, debug, getInitialStorage, onRoomDestroyed, onStorageDestroyed, onMessage, onUserConnected, onUserDisconnected, platform, roomContext, router } = config;
368
+ this.id = id;
369
+ this._context = context;
370
+ this._crdt = crdt;
371
+ this._debug = debug;
372
+ this._docFactory = crdt.doc(() => ({}));
373
+ this._getInitialStorage = getInitialStorage;
374
+ this._roomContext = roomContext;
375
+ this._router = router;
376
+ this._platform = platform.initialize({
377
+ ...!!_meta ? { _meta } : {},
378
+ roomContext
379
+ });
380
+ this._listeners = {
381
+ onRoomDestroyed: (event) => onRoomDestroyed?.(event),
382
+ onStorageDestroyed: (event) => onStorageDestroyed?.(event),
383
+ onMessage: (event) => onMessage?.(event),
384
+ onUserConnected: (event) => onUserConnected?.(event),
385
+ onUserDisconnected: (event) => onUserDisconnected?.(event)
386
+ };
387
+ if (authorize$1) this._authorize = authorize$1;
388
+ this._platform.getWebSockets().forEach((webSocket) => {
389
+ const deserialized = this._platform.getSerializedState(webSocket);
390
+ const sessionId = this._platform.getSessionId(webSocket);
391
+ if (!deserialized) return;
392
+ if (typeof sessionId !== "string") return;
393
+ const pluvWs = this._platform.convertWebSocket(webSocket, { room: this.id });
394
+ this._sessions.set(sessionId, pluvWs);
395
+ const userId = pluvWs.session.user?.id;
396
+ if (!!userId) this._addUserSession(userId, sessionId);
397
+ });
398
+ const { doc, uninitialize } = this._initialize();
399
+ this._doc = doc;
400
+ this._uninitialize = uninitialize;
401
+ }
402
+ /**
403
+ * @description Closes a connection with the specified sessionId
404
+ * @param sessionId The session id of the connection to close
405
+ */
406
+ async evict(sessionId) {
407
+ const session = this._sessions.get(sessionId);
408
+ if (session) session.state.quit = true;
409
+ await this._emitQuitters();
410
+ }
411
+ /**
412
+ * @description Closes all connections to this room, effectively destroying
413
+ * the room
414
+ */
415
+ async evictAll() {
416
+ Array.from(this._sessions.values()).forEach((session) => {
417
+ session.state.quit = true;
418
+ });
419
+ await this._emitQuitters();
420
+ }
421
+ /**
422
+ * @description The IORoom will garbage collect occasionally as connections ping/pong the
423
+ * server
424
+ */
425
+ async garbageCollect() {
426
+ this._lastGarbageCollectMs = Date.now();
427
+ this._emitQuitters();
428
+ this._emitSyncState();
429
+ }
430
+ getSize() {
431
+ const currentTime = (/* @__PURE__ */ new Date()).getTime();
432
+ /**
433
+ * @description Doing this instead of .size because some sessions
434
+ * in the map can be considered as "omitted".
435
+ * @date December 21, 2022
436
+ */
437
+ return Array.from(this._sessions.values()).reduce((count, pluvWs) => {
438
+ if (pluvWs.state.quit) return count;
439
+ return currentTime - (this._platform.getLastPing(pluvWs) ?? pluvWs.state.timers.ping) > PING_TIMEOUT_MS ? count : count + 1;
440
+ }, 0);
441
+ }
442
+ onClose(webSocket) {
443
+ this._ensureDetached();
444
+ const wsSession = this._getAbstractWs(webSocket);
445
+ if (!wsSession) return async () => void 0;
446
+ return this._onClose(wsSession);
447
+ }
448
+ onError(webSocket) {
449
+ this._ensureDetached();
450
+ const wsSession = this._getAbstractWs(webSocket);
451
+ if (!wsSession) return async () => void 0;
452
+ return this._onClose(wsSession);
453
+ }
454
+ onMessage(webSocket) {
455
+ this._ensureDetached();
456
+ const wsSession = this._getAbstractWs(webSocket);
457
+ if (!wsSession) return async () => void 0;
458
+ return this._onMessage(wsSession);
459
+ }
460
+ async register(webSocket, ...options) {
461
+ const _options = options[0] ?? {};
462
+ const token = _options.token ?? null;
463
+ /**
464
+ * TODO
465
+ * @description Update the sessionId strategy so that the userId can be used to retrieve
466
+ * the session in O(1) time if authorized.
467
+ * @date April 15, 2025
468
+ */
469
+ const sessionId = this._platform.getSessionId(webSocket);
470
+ if (typeof sessionId === "string" && this._sessions.has(sessionId)) return;
471
+ if (!await this._initialized) {
472
+ this._initialize();
473
+ await this._initialized;
474
+ }
475
+ const user = await this._getAuthorizedUser(token, _options);
476
+ const isUnauthorized = !!this._getIOAuthorize(_options) && !user;
477
+ const pluvWs = this._platform.convertWebSocket(webSocket, { room: this.id });
478
+ if (isUnauthorized) {
479
+ this._logDebug(`${colors.blue("Authorization failed for connection")}`);
480
+ pluvWs.handleError({
481
+ error: /* @__PURE__ */ new Error("Not authorized"),
482
+ room: this.id
483
+ });
484
+ pluvWs.close(3e3, "WebSocket unauthorized.");
485
+ return;
486
+ }
487
+ if (!!user) {
488
+ const latest = this._getLatestPresence(user.id);
489
+ const prevState = pluvWs.state;
490
+ pluvWs.user = user;
491
+ this._platform.setSerializedState(pluvWs, {
492
+ ...prevState,
493
+ presence: latest.presence,
494
+ timers: {
495
+ ...prevState.timers,
496
+ presence: latest.timer
497
+ }
498
+ });
499
+ this._addUserSession(user.id, pluvWs.sessionId);
500
+ }
501
+ this._logDebug(`${colors.blue(`Registering connection for room ${this.id}:`)} ${pluvWs.sessionId}`);
502
+ await this._platform.acceptWebSocket(pluvWs);
503
+ this._sessions.set(pluvWs.sessionId, pluvWs);
504
+ await this._platform.persistence.addUser(this.id, pluvWs.sessionId, user ?? {});
505
+ if (this._platform._config.registrationMode === "attached") {
506
+ const onClose = this._onClose(pluvWs).bind(this);
507
+ const onMessage = this._onMessage(pluvWs).bind(this);
508
+ pluvWs.addEventListener("close", onClose);
509
+ pluvWs.addEventListener("error", onClose);
510
+ pluvWs.addEventListener("message", onMessage);
511
+ }
512
+ await this._emitRegistered(pluvWs);
513
+ const size = this.getSize();
514
+ this._logDebug(oneLine`
587
515
  ${colors.blue(`Registered connection for room ${this.id}:`)}
588
516
  ${pluvWs.sessionId}
589
517
  `);
590
- this._logDebug(`${colors.blue(`Room ${this.id} size:`)} ${size}`);
591
- });
592
- }
593
- _addUserSession(userId, sessionId) {
594
- var _a;
595
- const set = (_a = this._userSessionss.get(userId)) != null ? _a : /* @__PURE__ */ new Set();
596
- const updated = set.add(sessionId);
597
- this._userSessionss.set(userId, updated);
598
- return updated;
599
- }
600
- _broadcast(params) {
601
- return __async(this, null, function* () {
602
- var _a, _b, _c;
603
- const { message, senderId } = params;
604
- const sender = senderId ? (_a = this._sessions.get(senderId)) != null ? _a : null : null;
605
- const session = (_b = sender == null ? void 0 : sender.session) != null ? _b : null;
606
- const user = (_c = session == null ? void 0 : session.user) != null ? _c : null;
607
- if (typeof senderId !== "string") return;
608
- this._platform.pubSub.publish(this.id, __spreadValues({
609
- connectionId: senderId != null ? senderId : null,
610
- room: this.id,
611
- user
612
- }, message));
613
- });
614
- }
615
- _closeWebSockets(webSockets) {
616
- return __async(this, null, function* () {
617
- const closeWebSocket = (webSocket) => __async(this, null, function* () {
618
- webSocket.state = __spreadProps(__spreadValues({}, webSocket.state), { quit: true });
619
- const sessionId = webSocket.sessionId;
620
- this._logDebug(
621
- `${colors.blue(`Unregistering connection for room ${this.id}:`)} ${sessionId}`
622
- );
623
- this._sessions.delete(sessionId);
624
- yield this._platform.persistence.deleteUser(this.id, sessionId).catch(() => null);
625
- yield this._broadcast({
626
- message: { type: "$exit", data: { sessionId } },
627
- senderId: sessionId
628
- });
629
- const session = webSocket.session;
630
- const user = session.user;
631
- if (!!user) this._removeUserSession(user.id, sessionId);
632
- try {
633
- const [doc, context] = yield Promise.all([this._doc, this._getContext()]);
634
- const encodedState = doc.getEncodedState();
635
- yield Promise.resolve(
636
- this._listeners.onUserDisconnected({
637
- context,
638
- encodedState,
639
- platform: this._platform,
640
- room: this.id,
641
- user
642
- })
643
- );
644
- } catch (error) {
645
- console.error(error);
646
- }
647
- this._logDebug(
648
- `${colors.blue(`Unregistered connection for room ${this.id}:`)} ${webSocket.sessionId}`
649
- );
650
- });
651
- const promises = webSockets.map((webSocket) => __async(this, null, function* () {
652
- yield closeWebSocket(webSocket).catch(() => null);
653
- }));
654
- yield Promise.all(promises).catch(() => null);
655
- const size = this.getSize();
656
- this._logDebug(`${colors.blue(`Room ${this.id} size:`)} ${size}`);
657
- if (!!size) return;
658
- if (!this._uninitialize) return;
659
- const uninitialize = (yield this._uninitialize).bind(this);
660
- yield uninitialize();
661
- });
662
- }
663
- _emitQuitters() {
664
- return __async(this, null, function* () {
665
- const currentTime = (/* @__PURE__ */ new Date()).getTime();
666
- const quitters = Array.from(this._sessions.values()).filter((pluvWs) => {
667
- return pluvWs.state.quit || currentTime - pluvWs.state.timers.ping > PING_TIMEOUT_MS;
668
- });
669
- this._closeWebSockets(quitters);
670
- });
671
- }
672
- _emitRegistered(pluvWs) {
673
- return __async(this, null, function* () {
674
- const session = pluvWs.session;
675
- const sessionId = session.id;
676
- const presence = session.presence;
677
- const user = session.user;
678
- const [doc, context] = yield Promise.all([this._doc, this._getContext()]);
679
- const encodedState = doc.isEmpty() ? null : doc.getEncodedState();
680
- yield this._sendSelfMessage(
681
- {
682
- type: "$registered",
683
- data: {
684
- presence,
685
- sessionId,
686
- state: encodedState,
687
- timers: { presence: session.timers.presence }
688
- }
689
- },
690
- { sessionId, user }
691
- );
692
- try {
693
- yield Promise.resolve(
694
- this._listeners.onUserConnected({
695
- context,
696
- encodedState,
697
- platform: this._platform,
698
- room: this.id,
699
- user,
700
- webSocket: pluvWs.webSocket
701
- })
702
- );
703
- } catch (err) {
704
- console.error(err);
705
- }
706
- });
707
- }
708
- _emitSyncState() {
709
- return __async(this, null, function* () {
710
- const connectionIds = yield this._platform.persistence.getUsers(this.id).then((map) => Object.keys(map));
711
- yield this._broadcast({
712
- message: {
713
- type: "$syncStateReceived",
714
- data: { connectionIds }
715
- }
716
- });
717
- });
718
- }
719
- _ensureDetached() {
720
- if (this._platform._config.registrationMode === "detached") return;
721
- throw new Error("Platform must use detached mode");
722
- }
723
- _getAbstractWs(webSocket) {
724
- var _a, _b;
725
- if (webSocket instanceof AbstractWebSocket) return webSocket;
726
- const sessionId = this._platform.getSessionId(webSocket);
727
- if (typeof sessionId === "string") {
728
- const session = (_a = this._sessions.get(sessionId)) != null ? _a : null;
729
- if (session) return session;
730
- }
731
- const sessions = Array.from(this._sessions.values());
732
- return (_b = sessions.find((pluvWs) => pluvWs.webSocket === webSocket)) != null ? _b : null;
733
- }
734
- /**
735
- * TODO
736
- * @description Revisit any cast types
737
- * @date July 17, 2025
738
- */
739
- _getAuthorizedUser(token, options) {
740
- return __async(this, null, function* () {
741
- var _a;
742
- const ioAuthorize = this._getIOAuthorize(options);
743
- if (!ioAuthorize) return null;
744
- if (!token) return null;
745
- if (!ioAuthorize.secret)
746
- throw new Error("`authorize` was specified without a valid secret");
747
- const payload = yield authorize({
748
- platform: this._platform,
749
- secret: ioAuthorize.secret
750
- }).decode(token);
751
- if (!payload) {
752
- this._logDebug(colors.blue("Could not decode token:"));
753
- this._logDebug(token);
754
- return null;
755
- }
756
- if (payload.room !== this.id) {
757
- this._logDebug(colors.blue(`Token is not authorized for room ${this.id}:`));
758
- this._logDebug(colors.blue("Received:"), payload.room);
759
- this._logDebug(token);
760
- return null;
761
- }
762
- try {
763
- return (_a = ioAuthorize.user.parse(payload.user)) != null ? _a : null;
764
- } catch (e) {
765
- this._logDebug(`${colors.blue("Token fails validation:")} ${token}`);
766
- return null;
767
- }
768
- });
769
- }
770
- _getContext() {
771
- return __async(this, null, function* () {
772
- return typeof this._context === "function" ? yield Promise.resolve(this._context(this._roomContext)) : yield Promise.resolve(this._context);
773
- });
774
- }
775
- _getInitialDoc() {
776
- return __async(this, null, function* () {
777
- const doc = this._docFactory.getEmpty();
778
- const [encodedState, context] = yield Promise.all([
779
- this._platform.persistence.getStorageState(this.id),
780
- this._getContext()
781
- ]);
782
- if (!encodedState) {
783
- const loadedState = yield this._getInitialStorage({
784
- context,
785
- room: this.id
786
- });
787
- if (!!loadedState && !this._docFactory.isEmpty(loadedState)) {
788
- doc.applyEncodedState({ update: loadedState });
789
- }
790
- }
791
- if (typeof encodedState === "string") doc.applyEncodedState({ update: encodedState });
792
- return doc;
793
- });
794
- }
795
- _getIOAuthorize(options) {
796
- if (typeof this._authorize === "function") return this._authorize(options);
797
- return this._authorize;
798
- }
799
- _getLatestPresence(userId) {
800
- var _a, _b;
801
- if (!this._authorize) return { timer: null, presence: null };
802
- const sessionIds = Array.from((_b = (_a = this._userSessionss.get(userId)) == null ? void 0 : _a.values()) != null ? _b : []);
803
- if (!sessionIds.length) return { timer: null, presence: null };
804
- return sessionIds.reduce(
805
- (state, sessionId) => {
806
- var _a2, _b2;
807
- const pluvWs = (_a2 = this._sessions.get(sessionId)) != null ? _a2 : null;
808
- if (!pluvWs) return state;
809
- const session = pluvWs.session;
810
- const presence = session.presence;
811
- const timer = session.timers.presence;
812
- if (((_b2 = session.user) == null ? void 0 : _b2.id) !== userId) return state;
813
- if (typeof state.timer !== "number") return { presence, timer };
814
- if (typeof timer !== "number") return state;
815
- return timer > state.timer ? { presence, timer } : state;
816
- },
817
- { presence: null, timer: null }
818
- );
819
- }
820
- _getProcedure(message) {
821
- var _a;
822
- return (_a = this._router._defs.events[message.type]) != null ? _a : null;
823
- }
824
- _getProcedureInputs(message) {
825
- const procedure = this._getProcedure(message);
826
- if (!procedure) return message.data;
827
- return procedure.config.input ? procedure.config.input.parse(message.data) : message.data;
828
- }
829
- _getSession(webSocket) {
830
- const pluvWs = this._getAbstractWs(webSocket);
831
- if (!pluvWs) throw new Error("Session could not be found");
832
- return pluvWs.session;
833
- }
834
- _getSessions() {
835
- const sessions = Array.from(this._sessions.values()).map((pluvWs) => pluvWs.session);
836
- return sessions;
837
- }
838
- _initialize() {
839
- const promise = (() => __async(this, null, function* () {
840
- this._logDebug(`${colors.blue(`Initializing room: ${this.id}`)}`);
841
- if (!!this._uninitialize) {
842
- const uninitialize2 = (yield this._uninitialize).bind(this);
843
- yield uninitialize2();
844
- this._uninitialize = null;
845
- }
846
- const pubSubId = yield this._platform.pubSub.subscribe(
847
- this.id,
848
- (_a) => __async(this, null, function* () {
849
- var _b = _a, { options = {} } = _b, message = __objRest(_b, ["options"]);
850
- yield promise;
851
- const sender = {
852
- sessionId: message.connectionId,
853
- user: message.user
854
- };
855
- switch (options.type) {
856
- case "self": {
857
- yield this._sendSelfMessage(message, sender);
858
- return;
859
- }
860
- case "sync": {
861
- yield this._sendSyncMessage(message, sender);
862
- return;
863
- }
864
- case "broadcast":
865
- default: {
866
- const sessionIds = options.sessionIds;
867
- yield this._sendBroadcastMessage(message, sender, sessionIds);
868
- }
869
- }
870
- })
871
- );
872
- const doc = yield this._getInitialDoc();
873
- const uninitialize = () => __async(this, null, function* () {
874
- this._platform.pubSub.unsubscribe(pubSubId);
875
- const [doc2, context] = yield Promise.all([this._doc, this._getContext()]);
876
- const encodedState = doc2.getEncodedState();
877
- doc2.destroy();
878
- this._doc = Promise.resolve(this._docFactory.getEmpty());
879
- yield Promise.resolve(
880
- this._listeners.onDestroy(__spreadProps(__spreadValues({}, "_meta" in this._platform && !!this._platform._meta ? { _meta: this._platform._meta } : {}), {
881
- context,
882
- encodedState,
883
- platform: this._platform,
884
- room: this.id
885
- }))
886
- );
887
- this._uninitialize = null;
888
- });
889
- return { doc, uninitialize };
890
- }))();
891
- this._doc = promise.then((result) => result.doc);
892
- this._uninitialize = promise.then((result) => result.uninitialize);
893
- return { doc: this._doc, uninitialize: this._uninitialize };
894
- }
895
- _logDebug(...data) {
896
- if (this._debug) console.log(...data);
897
- }
898
- _onClose(webSocket) {
899
- return () => __async(this, null, function* () {
900
- if (!(yield this._initialized)) return;
901
- yield this._closeWebSockets([webSocket]);
902
- });
903
- }
904
- _onMessage(webSocket) {
905
- return (event) => __async(this, null, function* () {
906
- var _a, _b, _c, _d, _e, _f;
907
- if (!(yield this._initialized)) return;
908
- const pluvWs = this._getAbstractWs(webSocket);
909
- if (!pluvWs) throw new Error("Could not get session");
910
- const session = this._getSession(pluvWs);
911
- const sessions = this._getSessions();
912
- const setPresence = (params) => {
913
- this._setPresence.bind(this)(params);
914
- };
915
- const [doc, context] = yield Promise.all([this._doc, this._getContext()]);
916
- const eventContext = {
917
- context,
918
- doc,
919
- garbageCollect: () => __async(this, null, function* () {
920
- yield this.garbageCollect();
921
- }),
922
- platform: this._platform,
923
- get presence() {
924
- return session.presence;
925
- },
926
- set presence(presence) {
927
- var _a2;
928
- const sessionId2 = (_a2 = this.session) == null ? void 0 : _a2.id;
929
- if (!sessionId2) return;
930
- setPresence({ presence, sessionId: sessionId2, timer: this.time });
931
- },
932
- room: this.id,
933
- session,
934
- sessions,
935
- time: (/* @__PURE__ */ new Date()).getTime()
936
- };
937
- if (pluvWs.state.quit) {
938
- yield this._closeWebSockets([pluvWs]).catch(() => null);
939
- pluvWs.close(1011, "WebSocket broken.");
940
- return;
941
- }
942
- const message = this._parseMessage(event);
943
- if (!message) return;
944
- const procedure = this._getProcedure(message);
945
- this._listeners.onMessage(__spreadProps(__spreadValues({}, "_meta" in this._platform && !!this._platform._meta ? { _meta: this._platform._meta } : {}), {
946
- context,
947
- encodedState: doc.getEncodedState(),
948
- message,
949
- platform: this._platform,
950
- room: this.id,
951
- user: session.user,
952
- webSocket: session.webSocket.webSocket
953
- }));
954
- const sessionId = session.id;
955
- const user = session.user;
956
- if (!procedure) {
957
- yield this._broadcast({
958
- message,
959
- senderId: sessionId
960
- });
961
- return;
962
- }
963
- let inputs;
964
- try {
965
- inputs = this._getProcedureInputs(message);
966
- } catch (error) {
967
- pluvWs.handleError({
968
- error,
969
- message: "Invalid input",
970
- room: this.id,
971
- session
972
- });
973
- return;
974
- }
975
- yield Promise.all([
976
- (_b = (_a = procedure.config).broadcast) == null ? void 0 : _b.call(_a, inputs, eventContext),
977
- (_d = (_c = procedure.config).self) == null ? void 0 : _d.call(_c, inputs, eventContext),
978
- (_f = (_e = procedure.config).sync) == null ? void 0 : _f.call(_e, inputs, eventContext)
979
- ]).then((_0) => __async(this, [_0], function* ([broadcast, self, sync]) {
980
- const handleBroadcast = () => __async(this, null, function* () {
981
- if (!broadcast) return;
982
- const messages = Object.entries(broadcast).map((_02) => __async(this, [_02], function* ([type, data]) {
983
- yield this._broadcast({
984
- message: { data, type },
985
- senderId: sessionId
986
- });
987
- }));
988
- yield Promise.all(messages);
989
- });
990
- const handleSelf = () => __async(this, null, function* () {
991
- if (!self) return;
992
- const messages = Object.entries(self).map((_02) => __async(this, [_02], function* ([type, data]) {
993
- yield this._sendSelfMessage({ data, type }, { sessionId, user });
994
- }));
995
- yield Promise.all(messages);
996
- });
997
- const handleSync = () => __async(this, null, function* () {
998
- if (!sync) return;
999
- yield this._platform.pubSub.publish(this.id, __spreadValues({
1000
- connectionId: session.id,
1001
- options: { type: "sync" },
1002
- room: this.id,
1003
- user
1004
- }, message));
1005
- const messages = Object.entries(sync).map((_02) => __async(this, [_02], function* ([type, data]) {
1006
- yield this._sendSelfMessage({ data, type }, { sessionId, user });
1007
- }));
1008
- yield Promise.all(messages);
1009
- });
1010
- yield Promise.all([handleBroadcast(), handleSelf(), handleSync()]);
1011
- }));
1012
- });
1013
- }
1014
- _parseMessage(message) {
1015
- try {
1016
- const parsed = this._platform.parseData(message.data);
1017
- if (typeof parsed !== "object") return null;
1018
- if (typeof parsed.type !== "string") return null;
1019
- if (typeof parsed.data !== "object") return null;
1020
- return parsed;
1021
- } catch (e) {
1022
- return null;
1023
- }
1024
- }
1025
- _setPresence(params) {
1026
- var _a, _b;
1027
- const { presence, sessionId, timer: _timer } = params;
1028
- const timer = _timer != null ? _timer : (/* @__PURE__ */ new Date()).getTime();
1029
- const pluvWs = (_a = this._sessions.get(sessionId)) != null ? _a : null;
1030
- if (!pluvWs) return;
1031
- const wsSession = pluvWs.session;
1032
- const user = wsSession.user;
1033
- const sessionIds = user ? /* @__PURE__ */ new Set([...(_b = this._userSessionss.get(user.id)) != null ? _b : [], sessionId]) : /* @__PURE__ */ new Set([sessionId]);
1034
- sessionIds.forEach((sId) => {
1035
- const pWs = this._sessions.get(sId);
1036
- const session = pWs == null ? void 0 : pWs.session;
1037
- if (!session) return;
1038
- const prevState = session.webSocket.state;
1039
- this._platform.setSerializedState(session.webSocket, __spreadProps(__spreadValues({}, prevState), {
1040
- presence,
1041
- timers: __spreadProps(__spreadValues({}, prevState.timers), {
1042
- presence: timer
1043
- })
1044
- }));
1045
- });
1046
- }
1047
- _removeUserSession(userId, sessionId) {
1048
- const set = this._userSessionss.get(userId);
1049
- if (!set) return null;
1050
- set.delete(sessionId);
1051
- if (!!set.size) return set;
1052
- this._userSessionss.delete(userId);
1053
- return set;
1054
- }
1055
- _sendMessage(pluvWs, message) {
1056
- return __async(this, null, function* () {
1057
- if (!(yield this._initialized)) return;
1058
- yield Promise.resolve(pluvWs.sendMessage(message));
1059
- });
1060
- }
1061
- _sendBroadcastMessage(message, sender, sessionIds) {
1062
- return __async(this, null, function* () {
1063
- var _a, _b;
1064
- const { data, type } = message;
1065
- const connectionId = (_a = sender == null ? void 0 : sender.sessionId) != null ? _a : null;
1066
- const room = this.id;
1067
- if (typeof connectionId !== "string") return;
1068
- const webSockets = (_b = sessionIds == null ? void 0 : sessionIds.reduce((dict, id) => {
1069
- const pluvWs = this._sessions.get(id);
1070
- return pluvWs ? dict.set(id, pluvWs) : dict;
1071
- }, /* @__PURE__ */ new Map())) != null ? _b : this._sessions;
1072
- const promises = Array.from(webSockets.values()).map((pluvWs) => __async(this, null, function* () {
1073
- var _a2;
1074
- const session = pluvWs.session;
1075
- const user = (_a2 = session.user) != null ? _a2 : null;
1076
- this._sendMessage(pluvWs, { connectionId, data, room, type, user });
1077
- }));
1078
- yield Promise.all(promises);
1079
- });
1080
- }
1081
- _sendSelfMessage(message, sender) {
1082
- return __async(this, null, function* () {
1083
- var _a, _b;
1084
- const senderId = (_a = sender == null ? void 0 : sender.sessionId) != null ? _a : null;
1085
- if (typeof senderId !== "string") return;
1086
- const pluvWs = this._sessions.get(senderId);
1087
- if (!pluvWs) return;
1088
- if (message.type === "$pong") {
1089
- const elapsedMs = Date.now() - this._lastGarbageCollectMs;
1090
- if (elapsedMs > GARBAGE_COLLECT_INTERVAL_MS) {
1091
- yield this.garbageCollect();
1092
- }
1093
- }
1094
- const session = pluvWs.session;
1095
- const user = (_b = session.user) != null ? _b : null;
1096
- this._sendMessage(pluvWs, __spreadValues({
1097
- connectionId: senderId,
1098
- room: this.id,
1099
- user
1100
- }, message));
1101
- });
1102
- }
1103
- _sendSyncMessage(message, sender) {
1104
- return __async(this, null, function* () {
1105
- var _a;
1106
- if (!sender) return;
1107
- const connectionId = sender.sessionId;
1108
- if (typeof connectionId !== "string") return;
1109
- const [doc, context] = yield Promise.all([this._doc, this._getContext()]);
1110
- const resolverCtx = {
1111
- context,
1112
- doc,
1113
- garbageCollect: () => __async(this, null, function* () {
1114
- yield this.garbageCollect();
1115
- }),
1116
- platform: this._platform,
1117
- get presence() {
1118
- return null;
1119
- },
1120
- set presence(presence) {
1121
- return;
1122
- },
1123
- room: this.id,
1124
- session: null,
1125
- sessions: this._getSessions(),
1126
- time: (/* @__PURE__ */ new Date()).getTime()
1127
- };
1128
- const resolver = (_a = this._getProcedure(message)) == null ? void 0 : _a.config.sync;
1129
- if (!resolver) return;
1130
- let inputs;
1131
- try {
1132
- inputs = this._getProcedureInputs(message);
1133
- } catch (e) {
1134
- return;
1135
- }
1136
- Promise.resolve(resolver(inputs, resolverCtx)).then((output) => {
1137
- if (!output) return;
1138
- Object.keys(output).forEach((type) => {
1139
- var _a2, _b;
1140
- const data = (_a2 = output[type]) != null ? _a2 : {};
1141
- this._platform.pubSub.publish(this.id, {
1142
- connectionId,
1143
- data,
1144
- options: { type: "self" },
1145
- room: this.id,
1146
- type,
1147
- user: (_b = sender.user) != null ? _b : null
1148
- });
1149
- });
1150
- });
1151
- });
1152
- }
518
+ this._logDebug(`${colors.blue(`Room ${this.id} size:`)} ${size}`);
519
+ }
520
+ _addUserSession(userId, sessionId) {
521
+ const updated = (this._userSessionss.get(userId) ?? /* @__PURE__ */ new Set()).add(sessionId);
522
+ this._userSessionss.set(userId, updated);
523
+ return updated;
524
+ }
525
+ async _broadcast(params) {
526
+ const { message, senderId } = params;
527
+ const user = ((senderId ? this._sessions.get(senderId) ?? null : null)?.session ?? null)?.user ?? null;
528
+ if (typeof senderId !== "string") return;
529
+ this._platform.pubSub.publish(this.id, {
530
+ connectionId: senderId ?? null,
531
+ room: this.id,
532
+ user,
533
+ ...message
534
+ });
535
+ }
536
+ async _closeWebSockets(webSockets) {
537
+ const closeWebSocket = async (webSocket) => {
538
+ webSocket.state = {
539
+ ...webSocket.state,
540
+ quit: true
541
+ };
542
+ const sessionId = webSocket.sessionId;
543
+ this._logDebug(`${colors.blue(`Unregistering connection for room ${this.id}:`)} ${sessionId}`);
544
+ this._sessions.delete(sessionId);
545
+ await this._platform.persistence.deleteUser(this.id, sessionId).catch(() => null);
546
+ await this._broadcast({
547
+ message: {
548
+ type: "$exit",
549
+ data: { sessionId }
550
+ },
551
+ senderId: sessionId
552
+ });
553
+ const user = webSocket.session.user;
554
+ if (!!user) this._removeUserSession(user.id, sessionId);
555
+ try {
556
+ const [doc, context] = await Promise.all([this._doc, this._getContext()]);
557
+ const encodedState = doc.getEncodedState();
558
+ await Promise.resolve(this._listeners.onUserDisconnected({
559
+ context,
560
+ encodedState,
561
+ platform: this._platform,
562
+ room: this.id,
563
+ user
564
+ }));
565
+ } catch (error) {
566
+ console.error(error);
567
+ }
568
+ this._logDebug(`${colors.blue(`Unregistered connection for room ${this.id}:`)} ${webSocket.sessionId}`);
569
+ };
570
+ const promises = webSockets.map(async (webSocket) => {
571
+ await closeWebSocket(webSocket).catch(() => null);
572
+ });
573
+ await Promise.all(promises).catch(() => null);
574
+ const size = this.getSize();
575
+ this._logDebug(`${colors.blue(`Room ${this.id} size:`)} ${size}`);
576
+ if (!!size) return;
577
+ if (!this._uninitialize) return;
578
+ await (await this._uninitialize).bind(this)();
579
+ }
580
+ async _emitQuitters() {
581
+ const currentTime = (/* @__PURE__ */ new Date()).getTime();
582
+ const quitters = Array.from(this._sessions.values()).filter((pluvWs) => {
583
+ return pluvWs.state.quit || currentTime - pluvWs.state.timers.ping > PING_TIMEOUT_MS;
584
+ });
585
+ this._closeWebSockets(quitters);
586
+ }
587
+ async _emitRegistered(pluvWs) {
588
+ const session = pluvWs.session;
589
+ const sessionId = session.id;
590
+ const presence = session.presence;
591
+ const user = session.user;
592
+ const [doc, context] = await Promise.all([this._doc, this._getContext()]);
593
+ const encodedState = doc.isEmpty() ? null : doc.getEncodedState();
594
+ await this._sendSelfMessage({
595
+ type: "$registered",
596
+ data: {
597
+ presence,
598
+ sessionId,
599
+ state: encodedState,
600
+ timers: { presence: session.timers.presence }
601
+ }
602
+ }, {
603
+ sessionId,
604
+ user
605
+ });
606
+ try {
607
+ await Promise.resolve(this._listeners.onUserConnected({
608
+ context,
609
+ encodedState,
610
+ platform: this._platform,
611
+ room: this.id,
612
+ user,
613
+ webSocket: pluvWs.webSocket
614
+ }));
615
+ } catch (err) {
616
+ console.error(err);
617
+ }
618
+ }
619
+ async _emitSyncState() {
620
+ const connectionIds = await this._platform.persistence.getUsers(this.id).then((map) => Object.keys(map));
621
+ await this._broadcast({ message: {
622
+ type: "$syncStateReceived",
623
+ data: { connectionIds }
624
+ } });
625
+ }
626
+ _ensureDetached() {
627
+ if (this._platform._config.registrationMode === "detached") return;
628
+ throw new Error("Platform must use detached mode");
629
+ }
630
+ _getAbstractWs(webSocket) {
631
+ if (webSocket instanceof AbstractWebSocket) return webSocket;
632
+ const sessionId = this._platform.getSessionId(webSocket);
633
+ if (typeof sessionId === "string") {
634
+ const session = this._sessions.get(sessionId) ?? null;
635
+ if (session) return session;
636
+ }
637
+ return Array.from(this._sessions.values()).find((pluvWs) => pluvWs.webSocket === webSocket) ?? null;
638
+ }
639
+ /**
640
+ * TODO
641
+ * @description Revisit any cast types
642
+ * @date July 17, 2025
643
+ */
644
+ async _getAuthorizedUser(token, options) {
645
+ const ioAuthorize = this._getIOAuthorize(options);
646
+ if (!ioAuthorize) return null;
647
+ if (!token) return null;
648
+ if (!ioAuthorize.secret) throw new Error("`authorize` was specified without a valid secret");
649
+ const payload = await authorize({
650
+ platform: this._platform,
651
+ secret: ioAuthorize.secret
652
+ }).decode(token);
653
+ if (!payload) {
654
+ this._logDebug(colors.blue("Could not decode token:"));
655
+ this._logDebug(token);
656
+ return null;
657
+ }
658
+ if (payload.room !== this.id) {
659
+ this._logDebug(colors.blue(`Token is not authorized for room ${this.id}:`));
660
+ this._logDebug(colors.blue("Received:"), payload.room);
661
+ this._logDebug(token);
662
+ return null;
663
+ }
664
+ try {
665
+ return ioAuthorize.user.parse(payload.user) ?? null;
666
+ } catch {
667
+ this._logDebug(`${colors.blue("Token fails validation:")} ${token}`);
668
+ return null;
669
+ }
670
+ }
671
+ async _getContext() {
672
+ return typeof this._context === "function" ? await Promise.resolve(this._context(this._roomContext)) : await Promise.resolve(this._context);
673
+ }
674
+ async _getInitialDoc() {
675
+ const doc = this._docFactory.getEmpty();
676
+ const [encodedState, context] = await Promise.all([this._platform.persistence.getStorageState(this.id), this._getContext()]);
677
+ if (!encodedState) {
678
+ const loadedState = await this._getInitialStorage({
679
+ context,
680
+ room: this.id
681
+ });
682
+ if (!!loadedState && !this._docFactory.isEmpty(loadedState)) doc.applyEncodedState({ update: loadedState });
683
+ }
684
+ if (typeof encodedState === "string") {
685
+ doc.applyEncodedState({ update: encodedState });
686
+ if (!doc.isEmpty()) this._storageInitializedViaSession = true;
687
+ }
688
+ return doc;
689
+ }
690
+ _getIOAuthorize(options) {
691
+ if (typeof this._authorize === "function") return this._authorize(options);
692
+ return this._authorize;
693
+ }
694
+ _getLatestPresence(userId) {
695
+ if (!this._authorize) return {
696
+ timer: null,
697
+ presence: null
698
+ };
699
+ const sessionIds = Array.from(this._userSessionss.get(userId)?.values() ?? []);
700
+ if (!sessionIds.length) return {
701
+ timer: null,
702
+ presence: null
703
+ };
704
+ return sessionIds.reduce((state, sessionId) => {
705
+ const pluvWs = this._sessions.get(sessionId) ?? null;
706
+ if (!pluvWs) return state;
707
+ const session = pluvWs.session;
708
+ const presence = session.presence;
709
+ const timer = session.timers.presence;
710
+ if (session.user?.id !== userId) return state;
711
+ if (typeof state.timer !== "number") return {
712
+ presence,
713
+ timer
714
+ };
715
+ if (typeof timer !== "number") return state;
716
+ return timer > state.timer ? {
717
+ presence,
718
+ timer
719
+ } : state;
720
+ }, {
721
+ presence: null,
722
+ timer: null
723
+ });
724
+ }
725
+ _getProcedure(message) {
726
+ return this._router._defs.events[message.type] ?? null;
727
+ }
728
+ _getProcedureInputs(message) {
729
+ const procedure = this._getProcedure(message);
730
+ if (!procedure) return message.data;
731
+ return procedure.config.input ? procedure.config.input.parse(message.data) : message.data;
732
+ }
733
+ _getSession(webSocket) {
734
+ const pluvWs = this._getAbstractWs(webSocket);
735
+ if (!pluvWs) throw new Error("Session could not be found");
736
+ return pluvWs.session;
737
+ }
738
+ _getSessions() {
739
+ return Array.from(this._sessions.values()).map((pluvWs) => pluvWs.session);
740
+ }
741
+ _initialize() {
742
+ const promise = (async () => {
743
+ this._logDebug(`${colors.blue(`Initializing room: ${this.id}`)}`);
744
+ if (!!this._uninitialize) {
745
+ await (await this._uninitialize).bind(this)();
746
+ this._uninitialize = null;
747
+ }
748
+ const pubSubId = await this._platform.pubSub.subscribe(this.id, async ({ options = {}, ...message }) => {
749
+ await promise;
750
+ const sender = {
751
+ sessionId: message.connectionId,
752
+ user: message.user
753
+ };
754
+ switch (options.type) {
755
+ case "self":
756
+ await this._sendSelfMessage(message, sender);
757
+ return;
758
+ case "sync":
759
+ await this._sendSyncMessage(message, sender);
760
+ return;
761
+ case "broadcast":
762
+ default: {
763
+ const sessionIds = options.sessionIds;
764
+ await this._sendBroadcastMessage(message, sender, sessionIds);
765
+ }
766
+ }
767
+ });
768
+ const doc = await this._getInitialDoc();
769
+ this._wasDocEmptyOnInit = doc.isEmpty();
770
+ const uninitialize = async () => {
771
+ this._platform.pubSub.unsubscribe(pubSubId);
772
+ const [doc$1, context] = await Promise.all([this._doc, this._getContext()]);
773
+ const encodedState = doc$1.getEncodedState();
774
+ doc$1.destroy();
775
+ this._doc = Promise.resolve(this._docFactory.getEmpty());
776
+ await Promise.resolve(this._listeners.onRoomDestroyed({
777
+ ..."_meta" in this._platform && !!this._platform._meta ? { _meta: this._platform._meta } : {},
778
+ context,
779
+ platform: this._platform,
780
+ room: this.id
781
+ }));
782
+ if (this._storageInitializedViaSession) {
783
+ await Promise.resolve(this._listeners.onStorageDestroyed({
784
+ ..."_meta" in this._platform && !!this._platform._meta ? { _meta: this._platform._meta } : {},
785
+ context,
786
+ encodedState,
787
+ platform: this._platform,
788
+ room: this.id
789
+ }));
790
+ this._storageInitializedViaSession = false;
791
+ }
792
+ this._uninitialize = null;
793
+ };
794
+ return {
795
+ doc,
796
+ uninitialize
797
+ };
798
+ })();
799
+ this._doc = promise.then((result) => result.doc);
800
+ this._uninitialize = promise.then((result) => result.uninitialize);
801
+ return {
802
+ doc: this._doc,
803
+ uninitialize: this._uninitialize
804
+ };
805
+ }
806
+ _logDebug(...data) {
807
+ if (this._debug) console.log(...data);
808
+ }
809
+ _onClose(webSocket) {
810
+ return async () => {
811
+ if (!await this._initialized) return;
812
+ await this._closeWebSockets([webSocket]);
813
+ };
814
+ }
815
+ _onMessage(webSocket) {
816
+ return async (event) => {
817
+ if (!await this._initialized) return;
818
+ const pluvWs = this._getAbstractWs(webSocket);
819
+ if (!pluvWs) throw new Error("Could not get session");
820
+ const session = this._getSession(pluvWs);
821
+ const sessions = this._getSessions();
822
+ const setPresence = (params) => {
823
+ this._setPresence.bind(this)(params);
824
+ };
825
+ const [doc, context] = await Promise.all([this._doc, this._getContext()]);
826
+ const eventContext = {
827
+ context,
828
+ doc,
829
+ garbageCollect: async () => {
830
+ await this.garbageCollect();
831
+ },
832
+ platform: this._platform,
833
+ get presence() {
834
+ return session.presence;
835
+ },
836
+ set presence(presence) {
837
+ const sessionId$1 = this.session?.id;
838
+ if (!sessionId$1) return;
839
+ setPresence({
840
+ presence,
841
+ sessionId: sessionId$1,
842
+ timer: this.time
843
+ });
844
+ },
845
+ room: this.id,
846
+ session,
847
+ sessions,
848
+ time: (/* @__PURE__ */ new Date()).getTime()
849
+ };
850
+ if (pluvWs.state.quit) {
851
+ await this._closeWebSockets([pluvWs]).catch(() => null);
852
+ pluvWs.close(1011, "WebSocket broken.");
853
+ return;
854
+ }
855
+ const message = this._parseMessage(event);
856
+ if (!message) return;
857
+ const procedure = this._getProcedure(message);
858
+ this._listeners.onMessage({
859
+ ..."_meta" in this._platform && !!this._platform._meta ? { _meta: this._platform._meta } : {},
860
+ context,
861
+ encodedState: doc.getEncodedState(),
862
+ message,
863
+ platform: this._platform,
864
+ room: this.id,
865
+ user: session.user,
866
+ webSocket: session.webSocket.webSocket
867
+ });
868
+ const sessionId = session.id;
869
+ const user = session.user;
870
+ if (!procedure) {
871
+ await this._broadcast({
872
+ message,
873
+ senderId: sessionId
874
+ });
875
+ return;
876
+ }
877
+ let inputs;
878
+ try {
879
+ inputs = this._getProcedureInputs(message);
880
+ } catch (error) {
881
+ pluvWs.handleError({
882
+ error,
883
+ message: "Invalid input",
884
+ room: this.id,
885
+ session
886
+ });
887
+ return;
888
+ }
889
+ await Promise.all([
890
+ procedure.config.broadcast?.(inputs, eventContext),
891
+ procedure.config.self?.(inputs, eventContext),
892
+ procedure.config.sync?.(inputs, eventContext)
893
+ ]).then(async ([broadcast, self, sync]) => {
894
+ const handleBroadcast = async () => {
895
+ if (!broadcast) return;
896
+ const messages = Object.entries(broadcast).map(async ([type, data]) => {
897
+ await this._broadcast({
898
+ message: {
899
+ data,
900
+ type
901
+ },
902
+ senderId: sessionId
903
+ });
904
+ });
905
+ await Promise.all(messages);
906
+ };
907
+ const handleSelf = async () => {
908
+ if (!self) return;
909
+ const messages = Object.entries(self).map(async ([type, data]) => {
910
+ await this._sendSelfMessage({
911
+ data,
912
+ type
913
+ }, {
914
+ sessionId,
915
+ user
916
+ });
917
+ });
918
+ await Promise.all(messages);
919
+ };
920
+ const handleSync = async () => {
921
+ if (!sync) return;
922
+ await this._platform.pubSub.publish(this.id, {
923
+ connectionId: session.id,
924
+ options: { type: "sync" },
925
+ room: this.id,
926
+ user,
927
+ ...message
928
+ });
929
+ const messages = Object.entries(sync).map(async ([type, data]) => {
930
+ await this._sendSelfMessage({
931
+ data,
932
+ type
933
+ }, {
934
+ sessionId,
935
+ user
936
+ });
937
+ });
938
+ await Promise.all(messages);
939
+ };
940
+ await Promise.all([
941
+ handleBroadcast(),
942
+ handleSelf(),
943
+ handleSync()
944
+ ]);
945
+ });
946
+ await this._checkStorageInitializedViaSession();
947
+ };
948
+ }
949
+ _parseMessage(message) {
950
+ try {
951
+ const parsed = this._platform.parseData(message.data);
952
+ if (typeof parsed !== "object") return null;
953
+ if (typeof parsed.type !== "string") return null;
954
+ if (typeof parsed.data !== "object") return null;
955
+ return parsed;
956
+ } catch {
957
+ return null;
958
+ }
959
+ }
960
+ _setPresence(params) {
961
+ const { presence, sessionId, timer: _timer } = params;
962
+ const timer = _timer ?? (/* @__PURE__ */ new Date()).getTime();
963
+ const pluvWs = this._sessions.get(sessionId) ?? null;
964
+ if (!pluvWs) return;
965
+ const user = pluvWs.session.user;
966
+ (user ? new Set([...this._userSessionss.get(user.id) ?? [], sessionId]) : new Set([sessionId])).forEach((sId) => {
967
+ const session = this._sessions.get(sId)?.session;
968
+ if (!session) return;
969
+ const prevState = session.webSocket.state;
970
+ this._platform.setSerializedState(session.webSocket, {
971
+ ...prevState,
972
+ presence,
973
+ timers: {
974
+ ...prevState.timers,
975
+ presence: timer
976
+ }
977
+ });
978
+ });
979
+ }
980
+ _removeUserSession(userId, sessionId) {
981
+ const set = this._userSessionss.get(userId);
982
+ if (!set) return null;
983
+ set.delete(sessionId);
984
+ if (!!set.size) return set;
985
+ this._userSessionss.delete(userId);
986
+ return set;
987
+ }
988
+ async _checkStorageInitializedViaSession() {
989
+ if (this._storageInitializedViaSession) return;
990
+ const doc = await this._doc;
991
+ if (this._wasDocEmptyOnInit && !doc.isEmpty() && this._sessions.size > 0) this._storageInitializedViaSession = true;
992
+ }
993
+ async _sendMessage(pluvWs, message) {
994
+ if (!await this._initialized) return;
995
+ await Promise.resolve(pluvWs.sendMessage(message));
996
+ }
997
+ async _sendBroadcastMessage(message, sender, sessionIds) {
998
+ const { data, type } = message;
999
+ const connectionId = sender?.sessionId ?? null;
1000
+ const room = this.id;
1001
+ if (typeof connectionId !== "string") return;
1002
+ const webSockets = sessionIds?.reduce((dict, id) => {
1003
+ const pluvWs = this._sessions.get(id);
1004
+ return pluvWs ? dict.set(id, pluvWs) : dict;
1005
+ }, /* @__PURE__ */ new Map()) ?? this._sessions;
1006
+ const promises = Array.from(webSockets.values()).map(async (pluvWs) => {
1007
+ const user = pluvWs.session.user ?? null;
1008
+ this._sendMessage(pluvWs, {
1009
+ connectionId,
1010
+ data,
1011
+ room,
1012
+ type,
1013
+ user
1014
+ });
1015
+ });
1016
+ await Promise.all(promises);
1017
+ }
1018
+ async _sendSelfMessage(message, sender) {
1019
+ const senderId = sender?.sessionId ?? null;
1020
+ if (typeof senderId !== "string") return;
1021
+ const pluvWs = this._sessions.get(senderId);
1022
+ if (!pluvWs) return;
1023
+ /**
1024
+ * @description Note that this will not fire for Cloudflare's hibernatable websockets
1025
+ * because the server will auto-respond with the $pong event without calling any
1026
+ * functions.
1027
+ * @date April 18, 2025
1028
+ */
1029
+ if (message.type === "$pong") {
1030
+ if (Date.now() - this._lastGarbageCollectMs > GARBAGE_COLLECT_INTERVAL_MS) await this.garbageCollect();
1031
+ }
1032
+ const user = pluvWs.session.user ?? null;
1033
+ this._sendMessage(pluvWs, {
1034
+ connectionId: senderId,
1035
+ room: this.id,
1036
+ user,
1037
+ ...message
1038
+ });
1039
+ }
1040
+ async _sendSyncMessage(message, sender) {
1041
+ if (!sender) return;
1042
+ const connectionId = sender.sessionId;
1043
+ if (typeof connectionId !== "string") return;
1044
+ const [doc, context] = await Promise.all([this._doc, this._getContext()]);
1045
+ const resolverCtx = {
1046
+ context,
1047
+ doc,
1048
+ garbageCollect: async () => {
1049
+ await this.garbageCollect();
1050
+ },
1051
+ platform: this._platform,
1052
+ get presence() {
1053
+ return null;
1054
+ },
1055
+ set presence(presence) {},
1056
+ room: this.id,
1057
+ session: null,
1058
+ sessions: this._getSessions(),
1059
+ time: (/* @__PURE__ */ new Date()).getTime()
1060
+ };
1061
+ const resolver = this._getProcedure(message)?.config.sync;
1062
+ if (!resolver) return;
1063
+ let inputs;
1064
+ try {
1065
+ inputs = this._getProcedureInputs(message);
1066
+ } catch {
1067
+ return;
1068
+ }
1069
+ Promise.resolve(resolver(inputs, resolverCtx)).then((output) => {
1070
+ if (!output) return;
1071
+ Object.keys(output).forEach((type) => {
1072
+ const data = output[type] ?? {};
1073
+ this._platform.pubSub.publish(this.id, {
1074
+ connectionId,
1075
+ data,
1076
+ options: { type: "self" },
1077
+ room: this.id,
1078
+ type,
1079
+ user: sender.user ?? null
1080
+ });
1081
+ });
1082
+ });
1083
+ }
1153
1084
  };
1154
1085
 
1155
- // src/version.generated.ts
1156
- var version = "3.2.2";
1086
+ //#endregion
1087
+ //#region src/version.generated.ts
1088
+ const version = "4.0.0";
1157
1089
 
1158
- // src/PluvServer.ts
1090
+ //#endregion
1091
+ //#region src/PluvServer.ts
1159
1092
  var PluvServer = class {
1160
- constructor(options) {
1161
- this.version = version;
1162
- this._getInitialStorage = (...args) => {
1163
- var _a;
1164
- const getInitialStorage = typeof this._config.getInitialStorage !== "string" ? (_a = this._config.getInitialStorage) != null ? _a : () => null : () => null;
1165
- return getInitialStorage(...args);
1166
- };
1167
- this._config = __spreadValues({
1168
- crdt: noop2,
1169
- debug: false,
1170
- router: new PluvRouter({})
1171
- }, options);
1172
- const {
1173
- onRoomDeleted,
1174
- onRoomMessage,
1175
- onStorageUpdated,
1176
- onUserConnected,
1177
- onUserDisconnected
1178
- } = options;
1179
- this._docFactory = this._config.crdt.doc(() => ({}));
1180
- this._listeners = {
1181
- onRoomDeleted: (event) => onRoomDeleted == null ? void 0 : onRoomDeleted(event),
1182
- onRoomMessage: (event) => onRoomMessage == null ? void 0 : onRoomMessage(event),
1183
- onStorageUpdated: (event) => onStorageUpdated == null ? void 0 : onStorageUpdated(event),
1184
- onUserConnected: (event) => onUserConnected == null ? void 0 : onUserConnected(event),
1185
- onUserDisconnected: (event) => onUserDisconnected == null ? void 0 : onUserDisconnected(event)
1186
- };
1187
- }
1188
- get fetch() {
1189
- return (...args) => {
1190
- const platform = this._config.platform();
1191
- platform.validateConfig(this._config);
1192
- if (!platform._fetch) {
1193
- throw new Error(`\`${platform._name}\` does not support \`fetch\``);
1194
- }
1195
- return platform._fetch(...args);
1196
- };
1197
- }
1198
- /**
1199
- * @ignore
1200
- * @readonly
1201
- * @deprecated Internal use only. Changes to this will never be marked as breaking.
1202
- */
1203
- get _defs() {
1204
- return {
1205
- authorize: this._config.authorize,
1206
- context: this._config.context,
1207
- crdt: this._config.crdt,
1208
- events: this._router._defs.events,
1209
- platform: this._config.platform()
1210
- };
1211
- }
1212
- get _baseRouter() {
1213
- const listeners = this._getListeners();
1214
- return new PluvRouter({
1215
- $getOthers: this._procedure.sync((data, { room, session, sessions }) => {
1216
- const currentTime = Date.now();
1217
- const others = sessions.filter((wsSession) => {
1218
- if (wsSession.id === (session == null ? void 0 : session.id)) return false;
1219
- if (wsSession.quit) return false;
1220
- if (currentTime - wsSession.timers.ping > PING_TIMEOUT_MS) return false;
1221
- return true;
1222
- }).reduce(
1223
- (acc, { id, presence, timers, user }) => __spreadProps(__spreadValues({}, acc), {
1224
- [id]: {
1225
- connectionId: id,
1226
- presence,
1227
- room,
1228
- timers: { presence: timers.presence },
1229
- user
1230
- }
1231
- }),
1232
- {}
1233
- );
1234
- return { $othersReceived: { others } };
1235
- }),
1236
- $initializeSession: this._procedure.broadcast((data, { session }) => {
1237
- const presence = data == null ? void 0 : data.presence;
1238
- const timers = session.timers;
1239
- if (!session) return {};
1240
- session.presence = presence;
1241
- return {
1242
- $userJoined: {
1243
- connectionId: session.id,
1244
- user: session.user,
1245
- presence,
1246
- timers: { presence: timers.presence }
1247
- }
1248
- };
1249
- }).self((_0, _1) => __async(this, [_0, _1], function* (data, { context, doc, platform, room, session }) {
1250
- const oldState = yield platform.persistence.getStorageState(room);
1251
- const update = data == null ? void 0 : data.update;
1252
- if (!oldState) {
1253
- const loadedState = yield this._getInitialStorage({ context, room });
1254
- if (!!loadedState && !this._docFactory.isEmpty(loadedState)) {
1255
- doc.applyEncodedState({ update: loadedState });
1256
- }
1257
- }
1258
- if (!doc.isEmpty()) {
1259
- const encodedState2 = doc.getEncodedState();
1260
- return {
1261
- $storageReceived: { changeKind: "unchanged", state: encodedState2 }
1262
- };
1263
- }
1264
- if (!!update) {
1265
- const encodedState2 = doc.applyEncodedState({ update }).getEncodedState();
1266
- const storageSize = new TextEncoder().encode(encodedState2).length;
1267
- if (!!this._config.limits.storageMaxSize && storageSize > this._config.limits.storageMaxSize) {
1268
- throw new Error("Storage has exceeded the size limit");
1269
- }
1270
- yield platform.persistence.setStorageState(room, encodedState2).catch((error) => {
1271
- this._logDebug(error);
1272
- });
1273
- listeners.onStorageUpdated({
1274
- context,
1275
- encodedState: encodedState2,
1276
- platform,
1277
- room,
1278
- user: session == null ? void 0 : session.user,
1279
- webSocket: session == null ? void 0 : session.webSocket.webSocket
1280
- });
1281
- return {
1282
- $storageReceived: { changeKind: "initialized", state: encodedState2 }
1283
- };
1284
- }
1285
- const encodedState = doc.getEncodedState();
1286
- return { $storageReceived: { changeKind: "empty", state: encodedState } };
1287
- })),
1288
- $ping: this._procedure.self((data, { platform, session }) => {
1289
- if (!session) return {};
1290
- const currentTime = (/* @__PURE__ */ new Date()).getTime();
1291
- const prevState = session.webSocket.state;
1292
- platform.setSerializedState(session.webSocket, __spreadProps(__spreadValues({}, prevState), {
1293
- timers: __spreadProps(__spreadValues({}, prevState.timers), {
1294
- ping: currentTime
1295
- })
1296
- }));
1297
- return { $pong: {} };
1298
- }),
1299
- $updatePresence: this._procedure.broadcast((data, context) => {
1300
- const presence = data == null ? void 0 : data.presence;
1301
- const { session } = context;
1302
- if (!session) return {};
1303
- const cleanedPatch = pickBy(
1304
- presence != null ? presence : {},
1305
- (value) => typeof value !== "undefined"
1306
- );
1307
- const updated = Object.assign(/* @__PURE__ */ Object.create(null), session.presence, cleanedPatch);
1308
- const bytes = new TextEncoder().encode(JSON.stringify(updated)).length;
1309
- if (!!this._config.limits.presenceMaxSize && bytes > this._config.limits.presenceMaxSize) {
1310
- throw new Error(oneLine`
1093
+ version = version;
1094
+ _config;
1095
+ _docFactory;
1096
+ get fetch() {
1097
+ return (...args) => {
1098
+ const platform = this._config.platform();
1099
+ platform.validateConfig(this._config);
1100
+ if (!platform._fetch) throw new Error(`\`${platform._name}\` does not support \`fetch\``);
1101
+ return platform._fetch(...args);
1102
+ };
1103
+ }
1104
+ /**
1105
+ * @ignore
1106
+ * @readonly
1107
+ * @deprecated Internal use only. Changes to this will never be marked as breaking.
1108
+ */
1109
+ get _defs() {
1110
+ return {
1111
+ authorize: this._config.authorize,
1112
+ context: this._config.context,
1113
+ crdt: this._config.crdt,
1114
+ events: this._router._defs.events,
1115
+ platform: this._config.platform()
1116
+ };
1117
+ }
1118
+ get _baseRouter() {
1119
+ const listeners = this._getListeners();
1120
+ return new PluvRouter({
1121
+ $getOthers: this._procedure.sync((data, { room, session, sessions }) => {
1122
+ const currentTime = Date.now();
1123
+ return { $othersReceived: { others: sessions.filter((wsSession) => {
1124
+ if (wsSession.id === session?.id) return false;
1125
+ if (wsSession.quit) return false;
1126
+ if (currentTime - wsSession.timers.ping > PING_TIMEOUT_MS) return false;
1127
+ return true;
1128
+ }).reduce((acc, { id, presence, timers, user }) => ({
1129
+ ...acc,
1130
+ [id]: {
1131
+ connectionId: id,
1132
+ presence,
1133
+ room,
1134
+ timers: { presence: timers.presence },
1135
+ user
1136
+ }
1137
+ }), {}) } };
1138
+ }),
1139
+ $initializeSession: this._procedure.broadcast((data, { session }) => {
1140
+ const presence = data?.presence;
1141
+ const timers = session.timers;
1142
+ if (!session) return {};
1143
+ session.presence = presence;
1144
+ return { $userJoined: {
1145
+ connectionId: session.id,
1146
+ user: session.user,
1147
+ presence,
1148
+ timers: { presence: timers.presence }
1149
+ } };
1150
+ }).self(async (data, { context, doc, platform, room, session }) => {
1151
+ const oldState = await platform.persistence.getStorageState(room);
1152
+ /**
1153
+ * !HACK
1154
+ * @description This is the frontend's initialStorage. We only want to
1155
+ * apply this if the server's storage is empty (i.e. no initial storage
1156
+ * has been applied)
1157
+ */
1158
+ const update = data?.update;
1159
+ if (!oldState) {
1160
+ const loadedState = await this._getInitialStorage({
1161
+ context,
1162
+ room
1163
+ });
1164
+ if (!!loadedState && !this._docFactory.isEmpty(loadedState)) doc.applyEncodedState({ update: loadedState });
1165
+ }
1166
+ /**
1167
+ * @description Storage was already initialized. Don't overwrite the current
1168
+ * storage state with the incoming initial storage. Return what the current state
1169
+ * is without changes.
1170
+ * @date May 7, 2025
1171
+ */
1172
+ if (!doc.isEmpty()) return { $storageReceived: {
1173
+ changeKind: "unchanged",
1174
+ state: doc.getEncodedState()
1175
+ } };
1176
+ /**
1177
+ * @description Storage was never initialized, and there is an incoming
1178
+ * initialStorage from the client. Apply the initialStorage and persist the state
1179
+ * as an update.
1180
+ * @date May 7, 2025
1181
+ */
1182
+ if (!!update) {
1183
+ const encodedState = doc.applyEncodedState({ update }).getEncodedState();
1184
+ const storageSize = new TextEncoder().encode(encodedState).length;
1185
+ if (!!this._config.limits.storageMaxSize && storageSize > this._config.limits.storageMaxSize) throw new Error("Storage has exceeded the size limit");
1186
+ await platform.persistence.setStorageState(room, encodedState).catch((error) => {
1187
+ this._logDebug(error);
1188
+ });
1189
+ listeners.onStorageUpdated({
1190
+ context,
1191
+ encodedState,
1192
+ platform,
1193
+ room,
1194
+ user: session?.user,
1195
+ webSocket: session?.webSocket.webSocket
1196
+ });
1197
+ return { $storageReceived: {
1198
+ changeKind: "initialized",
1199
+ state: encodedState
1200
+ } };
1201
+ }
1202
+ return { $storageReceived: {
1203
+ changeKind: "empty",
1204
+ state: doc.getEncodedState()
1205
+ } };
1206
+ }),
1207
+ $ping: this._procedure.self((data, { platform, session }) => {
1208
+ if (!session) return {};
1209
+ const currentTime = (/* @__PURE__ */ new Date()).getTime();
1210
+ const prevState = session.webSocket.state;
1211
+ platform.setSerializedState(session.webSocket, {
1212
+ ...prevState,
1213
+ timers: {
1214
+ ...prevState.timers,
1215
+ ping: currentTime
1216
+ }
1217
+ });
1218
+ return { $pong: {} };
1219
+ }),
1220
+ $updatePresence: this._procedure.broadcast((data, context) => {
1221
+ const presence = data?.presence;
1222
+ const { session } = context;
1223
+ if (!session) return {};
1224
+ const cleanedPatch = pickBy(presence ?? {}, (value) => typeof value !== "undefined");
1225
+ const updated = Object.assign(Object.create(null), session.presence, cleanedPatch);
1226
+ const bytes = new TextEncoder().encode(JSON.stringify(updated)).length;
1227
+ if (!!this._config.limits.presenceMaxSize && bytes > this._config.limits.presenceMaxSize) throw new Error(oneLine`
1311
1228
  Large presence. Presence must be at most
1312
1229
  ${this._config.limits.presenceMaxSize.toLocaleString()} bytes.
1313
1230
  Current size: ${bytes.toLocaleString()} bytes
1314
1231
  `);
1315
- }
1316
- context.presence = updated;
1317
- return {
1318
- $presenceUpdated: {
1319
- presence: updated,
1320
- timers: { presence: session.timers.presence }
1321
- }
1322
- };
1323
- }),
1324
- $updateStorage: this._procedure.broadcast((data, { context, doc, platform, room }) => {
1325
- var _a;
1326
- const origin = data == null ? void 0 : data.origin;
1327
- const update = (_a = data == null ? void 0 : data.update) != null ? _a : null;
1328
- if (origin === "$initialized" && Object.keys(doc.toJson()).length) return {};
1329
- const updated = update === null ? doc : doc.applyEncodedState({ update });
1330
- const encodedState = updated.getEncodedState();
1331
- const storageSize = new TextEncoder().encode(encodedState).length;
1332
- if (!!this._config.limits.storageMaxSize && storageSize > this._config.limits.storageMaxSize) {
1333
- throw new Error(oneLine`
1232
+ context.presence = updated;
1233
+ return { $presenceUpdated: {
1234
+ presence: updated,
1235
+ timers: { presence: session.timers.presence }
1236
+ } };
1237
+ }),
1238
+ $updateStorage: this._procedure.broadcast((data, { context, doc, platform, room }) => {
1239
+ const origin = data?.origin;
1240
+ const update = data?.update ?? null;
1241
+ if (origin === "$initialized" && Object.keys(doc.toJson()).length) return {};
1242
+ const encodedState = (update === null ? doc : doc.applyEncodedState({ update })).getEncodedState();
1243
+ const storageSize = new TextEncoder().encode(encodedState).length;
1244
+ if (!!this._config.limits.storageMaxSize && storageSize > this._config.limits.storageMaxSize) throw new Error(oneLine`
1334
1245
  Large Storage. Storage must be at most
1335
1246
  ${this._config.limits.storageMaxSize.toLocaleString()} bytes.
1336
1247
  Current size: ${storageSize.toLocaleString()} bytes
1337
1248
  `);
1338
- }
1339
- platform.persistence.setStorageState(room, encodedState).then(() => {
1340
- listeners.onStorageUpdated({
1341
- context,
1342
- encodedState,
1343
- platform,
1344
- room
1345
- });
1346
- });
1347
- return { $storageUpdated: { state: encodedState } };
1348
- })
1349
- });
1350
- }
1351
- get _procedure() {
1352
- return new PluvProcedure();
1353
- }
1354
- get _router() {
1355
- return this._config.router ? PluvRouter.merge(this._baseRouter, this._config.router) : this._baseRouter;
1356
- }
1357
- createRoom(room, ...options) {
1358
- var _a, _c;
1359
- const _b = (_a = options[0]) != null ? _a : {}, { _meta, debug, onDestroy, onMessage } = _b, platformRoomContext = __objRest(_b, ["_meta", "debug", "onDestroy", "onMessage"]);
1360
- const platform = this._config.platform();
1361
- platform.validateConfig(this._config);
1362
- if (platform._config.handleMode !== "io") {
1363
- throw new Error(`\`createRoom\` is unsupported for \`${platform._name}\``);
1364
- }
1365
- if (!/^[a-z0-9](?:[a-z0-9-_]*[a-z0-9])?$/i.test(room))
1366
- throw new Error("Unsupported room name");
1367
- const roomContext = platformRoomContext;
1368
- const listeners = this._getListeners();
1369
- const logDebug = this._logDebug.bind(this);
1370
- const newRoom = new IORoom(room, __spreadProps(__spreadValues({}, !!_meta ? { _meta } : {}), {
1371
- authorize: (_c = this._config.authorize) != null ? _c : void 0,
1372
- context: this._config.context,
1373
- crdt: this._config.crdt,
1374
- debug: debug != null ? debug : this._config.debug,
1375
- getInitialStorage: this._getInitialStorage,
1376
- onDestroy(event) {
1377
- return __async(this, null, function* () {
1378
- logDebug(`${colors2.blue("Deleting empty room:")} ${room}`);
1379
- yield Promise.resolve(onDestroy == null ? void 0 : onDestroy(event));
1380
- yield Promise.resolve(listeners.onRoomDeleted(event));
1381
- yield event.platform.persistence.deleteStorageState(room);
1382
- logDebug(`${colors2.blue("Deleted room:")} ${room}`);
1383
- });
1384
- },
1385
- onMessage(event) {
1386
- return __async(this, null, function* () {
1387
- yield Promise.resolve(onMessage == null ? void 0 : onMessage(event));
1388
- yield Promise.resolve(listeners.onRoomMessage(event));
1389
- });
1390
- },
1391
- onUserConnected(event) {
1392
- return __async(this, null, function* () {
1393
- yield Promise.resolve(listeners.onUserConnected(event));
1394
- });
1395
- },
1396
- onUserDisconnected(event) {
1397
- return __async(this, null, function* () {
1398
- yield Promise.resolve(listeners.onUserDisconnected(event));
1399
- });
1400
- },
1401
- platform,
1402
- roomContext,
1403
- router: this._router
1404
- }));
1405
- this._logDebug(`${colors2.blue("Created room:")} ${room}`);
1406
- return newRoom;
1407
- }
1408
- createToken(params) {
1409
- return __async(this, null, function* () {
1410
- return yield this._config.io.createToken(params);
1411
- });
1412
- }
1413
- _getListeners() {
1414
- return this._listeners;
1415
- }
1416
- _logDebug(...data) {
1417
- if (this._config.debug) console.log(...data);
1418
- }
1249
+ platform.persistence.setStorageState(room, encodedState).then(() => {
1250
+ listeners.onStorageUpdated({
1251
+ context,
1252
+ encodedState,
1253
+ platform,
1254
+ room
1255
+ });
1256
+ });
1257
+ return { $storageUpdated: { state: encodedState } };
1258
+ })
1259
+ });
1260
+ }
1261
+ get _procedure() {
1262
+ return new PluvProcedure();
1263
+ }
1264
+ get _router() {
1265
+ return this._config.router ? PluvRouter.merge(this._baseRouter, this._config.router) : this._baseRouter;
1266
+ }
1267
+ constructor(options) {
1268
+ this._config = {
1269
+ crdt: noop,
1270
+ debug: false,
1271
+ router: new PluvRouter({}),
1272
+ ...options
1273
+ };
1274
+ const { onRoomDestroyed, onRoomMessage, onStorageDestroyed, onStorageUpdated, onUserConnected, onUserDisconnected } = options;
1275
+ this._docFactory = this._config.crdt.doc(() => ({}));
1276
+ this._listeners = {
1277
+ onRoomDestroyed: (event) => onRoomDestroyed?.(event),
1278
+ onRoomMessage: (event) => onRoomMessage?.(event),
1279
+ onStorageDestroyed: (event) => onStorageDestroyed?.(event),
1280
+ onStorageUpdated: (event) => onStorageUpdated?.(event),
1281
+ onUserConnected: (event) => onUserConnected?.(event),
1282
+ onUserDisconnected: (event) => onUserDisconnected?.(event)
1283
+ };
1284
+ }
1285
+ createRoom(room, ...options) {
1286
+ const { _meta, debug, ...platformRoomContext } = options[0] ?? {};
1287
+ const platform = this._config.platform();
1288
+ platform.validateConfig(this._config);
1289
+ if (platform._config.handleMode !== "io") throw new Error(`\`createRoom\` is unsupported for \`${platform._name}\``);
1290
+ if (!/^[a-z0-9](?:[a-z0-9-_]*[a-z0-9])?$/i.test(room)) throw new Error("Unsupported room name");
1291
+ const roomContext = platformRoomContext;
1292
+ const listeners = this._getListeners();
1293
+ const logDebug = this._logDebug.bind(this);
1294
+ const newRoom = new IORoom(room, {
1295
+ ...!!_meta ? { _meta } : {},
1296
+ authorize: this._config.authorize ?? void 0,
1297
+ context: this._config.context,
1298
+ crdt: this._config.crdt,
1299
+ debug: debug ?? this._config.debug,
1300
+ getInitialStorage: this._getInitialStorage,
1301
+ async onRoomDestroyed(event) {
1302
+ logDebug(`${colors.blue("Deleting empty room:")} ${room}`);
1303
+ await Promise.resolve(listeners.onRoomDestroyed(event));
1304
+ logDebug(`${colors.blue("Deleted room:")} ${room}`);
1305
+ },
1306
+ async onStorageDestroyed(event) {
1307
+ logDebug(`${colors.blue("Destroying storage for room:")} ${room}`);
1308
+ await Promise.resolve(listeners.onStorageDestroyed(event));
1309
+ await event.platform.persistence.deleteStorageState(room);
1310
+ logDebug(`${colors.blue("Destroyed storage for room:")} ${room}`);
1311
+ },
1312
+ async onMessage(event) {
1313
+ await Promise.resolve(listeners.onRoomMessage(event));
1314
+ },
1315
+ async onUserConnected(event) {
1316
+ await Promise.resolve(listeners.onUserConnected(event));
1317
+ },
1318
+ async onUserDisconnected(event) {
1319
+ await Promise.resolve(listeners.onUserDisconnected(event));
1320
+ },
1321
+ platform,
1322
+ roomContext,
1323
+ router: this._router
1324
+ });
1325
+ this._logDebug(`${colors.blue("Created room:")} ${room}`);
1326
+ return newRoom;
1327
+ }
1328
+ async createToken(params) {
1329
+ return await this._config.io.createToken(params);
1330
+ }
1331
+ _getInitialStorage = (...args) => {
1332
+ return (typeof this._config.getInitialStorage !== "string" ? this._config.getInitialStorage ?? (() => null) : (() => null))(...args);
1333
+ };
1334
+ _getListeners() {
1335
+ return this._listeners;
1336
+ }
1337
+ _logDebug(...data) {
1338
+ if (this._config.debug) console.log(...data);
1339
+ }
1419
1340
  };
1420
1341
 
1421
- // src/PluvIO.ts
1342
+ //#endregion
1343
+ //#region src/PluvIO.ts
1422
1344
  var PluvIO = class {
1423
- constructor(options) {
1424
- this.version = version;
1425
- this._authorize = null;
1426
- this._context = {};
1427
- const { authorize: authorize2, context, crdt = noop3, debug = false, limits, platform } = options;
1428
- this._crdt = crdt;
1429
- this._debug = debug;
1430
- this._limits = __spreadValues({
1431
- presenceMaxSize: MAX_PRESENCE_SIZE_BYTES,
1432
- storageMaxSize: MAX_STORAGE_SIZE_BYTES,
1433
- userIdMaxLength: MAX_USER_ID_LENGTH,
1434
- userMaxSize: MAX_USER_SIZE_BYTES
1435
- }, limits);
1436
- this._platform = platform;
1437
- if (authorize2) this._authorize = authorize2;
1438
- if (context) this._context = context;
1439
- }
1440
- get procedure() {
1441
- return new PluvProcedure();
1442
- }
1443
- createToken(params) {
1444
- return __async(this, null, function* () {
1445
- var _a;
1446
- if (!this._authorize) {
1447
- throw new Error("IO does not specify authorize during initialization.");
1448
- }
1449
- const ioAuthorize = this._getIOAuthorize(params);
1450
- const user = params.user;
1451
- const parsed = !!ioAuthorize ? ioAuthorize.user.parse(user) : user;
1452
- if (!!this._limits.userIdMaxLength && user.id.length > this._limits.userIdMaxLength) {
1453
- throw new Error(oneLine`
1345
+ version = version;
1346
+ _authorize = null;
1347
+ _context = {};
1348
+ _crdt;
1349
+ _debug;
1350
+ _limits;
1351
+ _platform;
1352
+ get procedure() {
1353
+ return new PluvProcedure();
1354
+ }
1355
+ constructor(options) {
1356
+ const { authorize: authorize$1, context, crdt = noop, debug = false, limits, platform } = options;
1357
+ this._crdt = crdt;
1358
+ this._debug = debug;
1359
+ this._limits = {
1360
+ presenceMaxSize: MAX_PRESENCE_SIZE_BYTES,
1361
+ storageMaxSize: MAX_STORAGE_SIZE_BYTES,
1362
+ userIdMaxLength: MAX_USER_ID_LENGTH,
1363
+ userMaxSize: MAX_USER_SIZE_BYTES,
1364
+ ...limits
1365
+ };
1366
+ this._platform = platform;
1367
+ if (authorize$1) this._authorize = authorize$1;
1368
+ if (context) this._context = context;
1369
+ }
1370
+ async createToken(params) {
1371
+ if (!this._authorize) throw new Error("IO does not specify authorize during initialization.");
1372
+ const ioAuthorize = this._getIOAuthorize(params);
1373
+ const user = params.user;
1374
+ const parsed = !!ioAuthorize ? ioAuthorize.user.parse(user) : user;
1375
+ if (!!this._limits.userIdMaxLength && user.id.length > this._limits.userIdMaxLength) throw new Error(oneLine`
1454
1376
  createToken was called with a long user id. User ID must be at
1455
1377
  most 128 characters. Current length: ${user.id.length.toLocaleString()}
1456
1378
  `);
1457
- }
1458
- const bytes = new TextEncoder().encode(JSON.stringify(parsed)).length;
1459
- if (!!this._limits.userMaxSize && bytes > this._limits.userMaxSize) {
1460
- throw new Error(oneLine`
1379
+ const bytes = new TextEncoder().encode(JSON.stringify(parsed)).length;
1380
+ if (!!this._limits.userMaxSize && bytes > this._limits.userMaxSize) throw new Error(oneLine`
1461
1381
  createToken called with large payload. User must be at most 512
1462
1382
  bytes. Current size: ${bytes.toLocaleString()}
1463
1383
  `);
1464
- }
1465
- const platform = this._platform();
1466
- if (platform._createToken) {
1467
- return yield platform._createToken(__spreadProps(__spreadValues({}, params), { authorize: ioAuthorize }));
1468
- }
1469
- const secret = (_a = ioAuthorize == null ? void 0 : ioAuthorize.secret) != null ? _a : null;
1470
- if (!secret) throw new Error("`authorize` was specified without a valid secret");
1471
- return yield authorize({ platform, secret }).encode(
1472
- params
1473
- );
1474
- });
1475
- }
1476
- mergeRouters(...routers) {
1477
- return PluvRouter.merge(...routers);
1478
- }
1479
- router(events) {
1480
- const invalidName = Object.keys(events).find((name) => name.includes("$"));
1481
- if (typeof invalidName === "string") {
1482
- throw new Error(oneLine`
1384
+ const platform = this._platform();
1385
+ /**
1386
+ * !HACK
1387
+ * @description Allow the platform to overwrite this behavior as needed. This is introduced
1388
+ * to support platformPluv
1389
+ * @date December 15, 2024
1390
+ */
1391
+ if (platform._createToken) return await platform._createToken({
1392
+ ...params,
1393
+ authorize: ioAuthorize
1394
+ });
1395
+ const secret = ioAuthorize?.secret ?? null;
1396
+ if (!secret) throw new Error("`authorize` was specified without a valid secret");
1397
+ return await authorize({
1398
+ platform,
1399
+ secret
1400
+ }).encode(params);
1401
+ }
1402
+ mergeRouters(...routers) {
1403
+ return PluvRouter.merge(...routers);
1404
+ }
1405
+ router(events) {
1406
+ const invalidName = Object.keys(events).find((name) => name.includes("$"));
1407
+ if (typeof invalidName === "string") throw new Error(oneLine`
1483
1408
  Invalid event name. Event names must not contain $: "${invalidName}"
1484
1409
  `);
1485
- }
1486
- return new PluvRouter(events);
1487
- }
1488
- server(...config) {
1489
- var _a, _b;
1490
- const serverConfig = (_a = config[0]) != null ? _a : {};
1491
- return new PluvServer(__spreadProps(__spreadValues({}, serverConfig), {
1492
- authorize: (_b = this._authorize) != null ? _b : void 0,
1493
- context: this._context,
1494
- crdt: this._crdt,
1495
- debug: this._debug,
1496
- io: this,
1497
- limits: this._limits,
1498
- platform: this._platform
1499
- }));
1500
- }
1501
- _getIOAuthorize(options) {
1502
- if (typeof this._authorize === "function") {
1503
- return this._authorize(options);
1504
- }
1505
- return this._authorize;
1506
- }
1410
+ return new PluvRouter(events);
1411
+ }
1412
+ server(...config) {
1413
+ return new PluvServer({
1414
+ ...config[0] ?? {},
1415
+ authorize: this._authorize ?? void 0,
1416
+ context: this._context,
1417
+ crdt: this._crdt,
1418
+ debug: this._debug,
1419
+ io: this,
1420
+ limits: this._limits,
1421
+ platform: this._platform
1422
+ });
1423
+ }
1424
+ _getIOAuthorize(options) {
1425
+ if (typeof this._authorize === "function") return this._authorize(options);
1426
+ return this._authorize;
1427
+ }
1507
1428
  };
1508
1429
 
1509
- // src/createIO.ts
1510
- var createIO = (params) => {
1511
- const { authorize: authorize2, context, crdt, debug, limits, platform } = params;
1512
- return new PluvIO({
1513
- authorize: authorize2,
1514
- context,
1515
- crdt,
1516
- debug,
1517
- limits,
1518
- platform
1519
- });
1520
- };
1521
- export {
1522
- AbstractPersistence,
1523
- AbstractPlatform,
1524
- AbstractPubSub,
1525
- AbstractWebSocket,
1526
- PluvIO,
1527
- PluvProcedure,
1528
- PluvRouter,
1529
- PluvServer,
1530
- authorize,
1531
- createIO
1430
+ //#endregion
1431
+ //#region src/createIO.ts
1432
+ const createIO = (params) => {
1433
+ const { authorize: authorize$1, context, crdt, debug, limits, platform } = params;
1434
+ return new PluvIO({
1435
+ authorize: authorize$1,
1436
+ context,
1437
+ crdt,
1438
+ debug,
1439
+ limits,
1440
+ platform
1441
+ });
1532
1442
  };
1443
+
1444
+ //#endregion
1445
+ export { AbstractPersistence, AbstractPlatform, AbstractPubSub, AbstractWebSocket, PluvIO, PluvProcedure, PluvRouter, PluvServer, authorize, createIO };
1446
+ //# sourceMappingURL=index.mjs.map