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