@pluv/io 0.23.0 → 0.24.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/.turbo/turbo-build.log +9 -9
- package/CHANGELOG.md +128 -0
- package/dist/index.d.mts +11 -12
- package/dist/index.d.ts +11 -12
- package/dist/index.js +115 -94
- package/dist/index.mjs +115 -94
- package/package.json +5 -5
- package/src/IORoom.ts +149 -93
- package/src/PluvServer.ts +44 -65
- package/src/index.ts +1 -1
- package/src/types.ts +0 -2
- package/src/version.generated.ts +1 -1
package/src/IORoom.ts
CHANGED
|
@@ -40,7 +40,7 @@ interface BroadcastParams<TIO extends IORoom<any, any, any, any>> {
|
|
|
40
40
|
senderId?: string;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
interface IORoomListeners<
|
|
43
|
+
export interface IORoomListeners<
|
|
44
44
|
TPlatform extends AbstractPlatform<any>,
|
|
45
45
|
TAuthorize extends IOAuthorize<any, any, InferRoomContextType<TPlatform>>,
|
|
46
46
|
TContext extends JsonObject,
|
|
@@ -90,8 +90,8 @@ export class IORoom<
|
|
|
90
90
|
public readonly id: string;
|
|
91
91
|
public readonly _authorize: TAuthorize | null = null;
|
|
92
92
|
|
|
93
|
-
private _doc: AbstractCrdtDoc<any
|
|
94
|
-
private _uninitialize: (
|
|
93
|
+
private _doc: Promise<AbstractCrdtDoc<any>>;
|
|
94
|
+
private _uninitialize: Promise<() => Promise<void>> | null = null;
|
|
95
95
|
|
|
96
96
|
private readonly _context: TContext & InferRoomContextType<TPlatform>;
|
|
97
97
|
private readonly _debug: boolean;
|
|
@@ -122,18 +122,21 @@ export class IORoom<
|
|
|
122
122
|
}) as BroadcastProxy<this>;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
private get _initialized(): Promise<boolean> {
|
|
126
|
+
return Promise.resolve(!!this._uninitialize?.then((result) => !!result));
|
|
127
|
+
}
|
|
128
|
+
|
|
125
129
|
constructor(id: string, config: IORoomConfig<TPlatform, TAuthorize, TContext, TEvents>) {
|
|
126
130
|
const { authorize, context, crdt = noop, debug, onDestroy, onMessage, platform, router } = config;
|
|
127
131
|
|
|
132
|
+
this.id = id;
|
|
133
|
+
|
|
128
134
|
this._context = context;
|
|
129
135
|
this._debug = debug;
|
|
130
136
|
this._docFactory = crdt.doc(() => ({}));
|
|
131
|
-
this._doc = this._docFactory.getEmpty();
|
|
132
137
|
this._router = router;
|
|
133
138
|
this._platform = platform.initialize({ context });
|
|
134
139
|
|
|
135
|
-
this.id = id;
|
|
136
|
-
|
|
137
140
|
this._listeners = {
|
|
138
141
|
onDestroy: (event) => onDestroy?.(event),
|
|
139
142
|
onMessage: (event) => onMessage?.(event),
|
|
@@ -143,8 +146,6 @@ export class IORoom<
|
|
|
143
146
|
|
|
144
147
|
const webSockets = this._platform.getWebSockets() as readonly InferPlatformWebSocketSource<TPlatform>[];
|
|
145
148
|
|
|
146
|
-
if (!webSockets.length) return;
|
|
147
|
-
|
|
148
149
|
webSockets.forEach((webSocket) => {
|
|
149
150
|
const deserialized = this._platform.getSerializedState(webSocket);
|
|
150
151
|
const sessionId = this._platform.getSessionId(webSocket);
|
|
@@ -152,8 +153,15 @@ export class IORoom<
|
|
|
152
153
|
if (!deserialized) return;
|
|
153
154
|
if (typeof sessionId !== "string") return;
|
|
154
155
|
|
|
155
|
-
|
|
156
|
+
const pluvWs = this._platform.convertWebSocket(webSocket, { room: this.id });
|
|
157
|
+
|
|
158
|
+
this._sessions.set(sessionId, pluvWs);
|
|
156
159
|
});
|
|
160
|
+
|
|
161
|
+
const { doc, uninitialize } = this._initialize();
|
|
162
|
+
|
|
163
|
+
this._doc = doc;
|
|
164
|
+
this._uninitialize = uninitialize;
|
|
157
165
|
}
|
|
158
166
|
|
|
159
167
|
public getSize(): number {
|
|
@@ -174,32 +182,32 @@ export class IORoom<
|
|
|
174
182
|
}, 0);
|
|
175
183
|
}
|
|
176
184
|
|
|
177
|
-
public onClose(webSocket: WebSocketType<TPlatform>): (event: AbstractCloseEvent) => void {
|
|
185
|
+
public onClose(webSocket: WebSocketType<TPlatform>): (event: AbstractCloseEvent) => Promise<void> {
|
|
178
186
|
this._ensureDetached();
|
|
179
187
|
|
|
180
188
|
const wsSession = this._getAbstractWs(webSocket);
|
|
181
189
|
|
|
182
|
-
if (!wsSession) return ()
|
|
190
|
+
if (!wsSession) return async () => undefined;
|
|
183
191
|
|
|
184
192
|
return this._onClose(wsSession);
|
|
185
193
|
}
|
|
186
194
|
|
|
187
|
-
public onError(webSocket: WebSocketType<TPlatform>): (event: AbstractErrorEvent) => void {
|
|
195
|
+
public onError(webSocket: WebSocketType<TPlatform>): (event: AbstractErrorEvent) => Promise<void> {
|
|
188
196
|
this._ensureDetached();
|
|
189
197
|
|
|
190
198
|
const wsSession = this._getAbstractWs(webSocket);
|
|
191
199
|
|
|
192
|
-
if (!wsSession) return ()
|
|
200
|
+
if (!wsSession) return async () => undefined;
|
|
193
201
|
|
|
194
202
|
return this._onClose(wsSession);
|
|
195
203
|
}
|
|
196
204
|
|
|
197
|
-
public onMessage(webSocket: WebSocketType<TPlatform>): (event: AbstractMessageEvent) => void {
|
|
205
|
+
public onMessage(webSocket: WebSocketType<TPlatform>): (event: AbstractMessageEvent) => Promise<void> {
|
|
198
206
|
this._ensureDetached();
|
|
199
207
|
|
|
200
208
|
const wsSession = this._getAbstractWs(webSocket);
|
|
201
209
|
|
|
202
|
-
if (!wsSession) return ()
|
|
210
|
+
if (!wsSession) return async () => undefined;
|
|
203
211
|
|
|
204
212
|
return this._onMessage(wsSession);
|
|
205
213
|
}
|
|
@@ -210,7 +218,15 @@ export class IORoom<
|
|
|
210
218
|
): Promise<void> {
|
|
211
219
|
const { token } = options;
|
|
212
220
|
|
|
213
|
-
|
|
221
|
+
const sessionId = this._platform.getSessionId(webSocket);
|
|
222
|
+
const sessionExists = typeof sessionId === "string" && this._sessions.has(sessionId);
|
|
223
|
+
|
|
224
|
+
if (sessionExists) return;
|
|
225
|
+
if (!(await this._initialized)) {
|
|
226
|
+
this._initialize();
|
|
227
|
+
|
|
228
|
+
await this._initialized;
|
|
229
|
+
}
|
|
214
230
|
|
|
215
231
|
const user = await this._getAuthorizedUser(token);
|
|
216
232
|
const ioAuthorize = this._getIOAuthorize();
|
|
@@ -221,7 +237,7 @@ export class IORoom<
|
|
|
221
237
|
!!user &&
|
|
222
238
|
(await this._getSessions().then((sessions) => sessions.some((session) => session.user?.id === user.id)));
|
|
223
239
|
|
|
224
|
-
const pluvWs =
|
|
240
|
+
const pluvWs = this._platform.convertWebSocket(webSocket, { room: this.id });
|
|
225
241
|
|
|
226
242
|
this._logDebug(`${colors.blue(`Registering connection for room ${this.id}:`)} ${pluvWs.sessionId}`);
|
|
227
243
|
|
|
@@ -326,14 +342,6 @@ export class IORoom<
|
|
|
326
342
|
return sessions.find((pluvWs) => pluvWs.webSocket === webSocket) ?? null;
|
|
327
343
|
}
|
|
328
344
|
|
|
329
|
-
private async _getSession(webSocket: WebSocketType<TPlatform>): Promise<WebSocketSession<TAuthorize>> {
|
|
330
|
-
const pluvWs = this._getAbstractWs(webSocket);
|
|
331
|
-
|
|
332
|
-
if (!pluvWs) throw new Error("Session could not be found");
|
|
333
|
-
|
|
334
|
-
return await pluvWs.getSession<TAuthorize>();
|
|
335
|
-
}
|
|
336
|
-
|
|
337
345
|
private async _getAuthorizedUser(token: Maybe<string>): Promise<InferIOAuthorizeUser<InferIOAuthorize<this>>> {
|
|
338
346
|
const ioAuthorize = this._getIOAuthorize();
|
|
339
347
|
|
|
@@ -369,6 +377,15 @@ export class IORoom<
|
|
|
369
377
|
}
|
|
370
378
|
}
|
|
371
379
|
|
|
380
|
+
private async _getInitialDoc(): Promise<AbstractCrdtDoc<any>> {
|
|
381
|
+
const doc = this._docFactory.getEmpty();
|
|
382
|
+
const encodedState = await this._platform.persistance.getStorageState(this.id);
|
|
383
|
+
|
|
384
|
+
if (typeof encodedState === "string") doc.applyEncodedState({ update: encodedState });
|
|
385
|
+
|
|
386
|
+
return doc;
|
|
387
|
+
}
|
|
388
|
+
|
|
372
389
|
private _getIOAuthorize() {
|
|
373
390
|
if (typeof this._authorize === "function") {
|
|
374
391
|
return this._authorize(this._context);
|
|
@@ -395,77 +412,97 @@ export class IORoom<
|
|
|
395
412
|
return procedure.config.input ? procedure.config.input.parse(message.data) : message.data;
|
|
396
413
|
}
|
|
397
414
|
|
|
415
|
+
private async _getSession(webSocket: WebSocketType<TPlatform>): Promise<WebSocketSession<TAuthorize>> {
|
|
416
|
+
const pluvWs = this._getAbstractWs(webSocket);
|
|
417
|
+
|
|
418
|
+
if (!pluvWs) throw new Error("Session could not be found");
|
|
419
|
+
|
|
420
|
+
return await pluvWs.getSession<TAuthorize>();
|
|
421
|
+
}
|
|
422
|
+
|
|
398
423
|
private async _getSessions() {
|
|
399
424
|
const promises = Array.from(this._sessions.values()).map(async (pluvWs) => await pluvWs.getSession());
|
|
400
425
|
|
|
401
426
|
return await Promise.all(promises);
|
|
402
427
|
}
|
|
403
428
|
|
|
404
|
-
private
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
if (!!this._uninitialize) {
|
|
408
|
-
await this._uninitialize();
|
|
409
|
-
|
|
410
|
-
this._uninitialize = null;
|
|
411
|
-
}
|
|
429
|
+
private _initialize() {
|
|
430
|
+
const promise = (async () => {
|
|
431
|
+
this._logDebug(`${colors.blue(`Initializing room ${this.id}:`)}`);
|
|
412
432
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
const sender: SendMessageSender = {
|
|
417
|
-
sessionId: message.connectionId,
|
|
418
|
-
user: message.user,
|
|
419
|
-
};
|
|
433
|
+
if (!!this._uninitialize) {
|
|
434
|
+
const uninitialize = (await this._uninitialize).bind(this);
|
|
435
|
+
await uninitialize();
|
|
420
436
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
await this._sendSelfMessage(message, sender);
|
|
437
|
+
this._uninitialize = null;
|
|
438
|
+
}
|
|
424
439
|
|
|
425
|
-
|
|
440
|
+
const pubSubId = await this._platform.pubSub.subscribe(
|
|
441
|
+
this.id,
|
|
442
|
+
async ({ options = {}, ...message }): Promise<void> => {
|
|
443
|
+
const sender: SendMessageSender = {
|
|
444
|
+
sessionId: message.connectionId,
|
|
445
|
+
user: message.user,
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
switch (options.type) {
|
|
449
|
+
case "self": {
|
|
450
|
+
await this._sendSelfMessage(message, sender);
|
|
451
|
+
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
case "sync": {
|
|
455
|
+
await this._sendSyncMessage(message, sender);
|
|
456
|
+
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
case "broadcast":
|
|
460
|
+
default: {
|
|
461
|
+
const sessionIds = options.sessionIds;
|
|
462
|
+
|
|
463
|
+
await this._sendBroadcastMessage(message, sender, sessionIds);
|
|
464
|
+
}
|
|
426
465
|
}
|
|
427
|
-
|
|
428
|
-
|
|
466
|
+
},
|
|
467
|
+
);
|
|
429
468
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
default: {
|
|
434
|
-
const sessionIds = options.sessionIds;
|
|
469
|
+
const doc = await this._getInitialDoc();
|
|
470
|
+
const uninitialize = async () => {
|
|
471
|
+
this._platform.pubSub.unsubscribe(pubSubId);
|
|
435
472
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
}
|
|
439
|
-
},
|
|
440
|
-
);
|
|
473
|
+
const doc = await this._doc;
|
|
474
|
+
const encodedState = doc.getEncodedState();
|
|
441
475
|
|
|
442
|
-
|
|
443
|
-
|
|
476
|
+
doc.destroy();
|
|
477
|
+
this._doc = Promise.resolve(this._docFactory.getEmpty());
|
|
444
478
|
|
|
445
|
-
|
|
479
|
+
this._listeners.onDestroy({
|
|
480
|
+
context: this._context,
|
|
481
|
+
encodedState,
|
|
482
|
+
room: this.id,
|
|
483
|
+
});
|
|
446
484
|
|
|
447
|
-
|
|
448
|
-
|
|
485
|
+
this._uninitialize = null;
|
|
486
|
+
};
|
|
449
487
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
encodedState,
|
|
453
|
-
room: this.id,
|
|
454
|
-
});
|
|
488
|
+
return { doc, uninitialize };
|
|
489
|
+
})();
|
|
455
490
|
|
|
456
|
-
|
|
457
|
-
|
|
491
|
+
this._doc = promise.then((result) => result.doc);
|
|
492
|
+
this._uninitialize = promise.then((result) => result.uninitialize);
|
|
493
|
+
|
|
494
|
+
return { doc: this._doc, uninitialize: this._uninitialize };
|
|
458
495
|
}
|
|
459
496
|
|
|
460
497
|
private _logDebug(...data: any[]): void {
|
|
461
498
|
this._debug && console.log(...data);
|
|
462
499
|
}
|
|
463
500
|
|
|
464
|
-
private _onClose(webSocket: AbstractWebSocket): () => void {
|
|
465
|
-
return (): void => {
|
|
466
|
-
if (!this.
|
|
501
|
+
private _onClose(webSocket: AbstractWebSocket): () => Promise<void> {
|
|
502
|
+
return async (): Promise<void> => {
|
|
503
|
+
if (!(await this._initialized)) return;
|
|
467
504
|
|
|
468
|
-
webSocket.state.quit
|
|
505
|
+
webSocket.state = { ...webSocket.state, quit: true };
|
|
469
506
|
|
|
470
507
|
this._logDebug(`${colors.blue(`Unregistering connection for room ${this.id}:`)} ${webSocket.sessionId}`);
|
|
471
508
|
this._sessions.delete(webSocket.sessionId);
|
|
@@ -486,13 +523,18 @@ export class IORoom<
|
|
|
486
523
|
|
|
487
524
|
if (size) return;
|
|
488
525
|
|
|
489
|
-
this._uninitialize
|
|
526
|
+
if (this._uninitialize) {
|
|
527
|
+
const uninitialize = (await this._uninitialize).bind(this);
|
|
528
|
+
await uninitialize();
|
|
529
|
+
}
|
|
490
530
|
});
|
|
491
531
|
};
|
|
492
532
|
}
|
|
493
533
|
|
|
494
|
-
private _onMessage(webSocket: AbstractWebSocket): (event: AbstractMessageEvent) => void {
|
|
534
|
+
private _onMessage(webSocket: AbstractWebSocket): (event: AbstractMessageEvent) => Promise<void> {
|
|
495
535
|
return async (event: AbstractMessageEvent): Promise<void> => {
|
|
536
|
+
if (!(await this._initialized)) return;
|
|
537
|
+
|
|
496
538
|
const pluvWs = this._getAbstractWs(webSocket as WebSocketType<TPlatform>);
|
|
497
539
|
|
|
498
540
|
if (!pluvWs) throw new Error("Could not get session");
|
|
@@ -502,16 +544,15 @@ export class IORoom<
|
|
|
502
544
|
this._getSessions(),
|
|
503
545
|
]);
|
|
504
546
|
|
|
547
|
+
const doc = await this._doc;
|
|
505
548
|
const baseContext: EventResolverContext<TPlatform, TAuthorize, TContext> = {
|
|
506
549
|
context: this._context,
|
|
507
|
-
doc
|
|
550
|
+
doc,
|
|
508
551
|
room: this.id,
|
|
509
552
|
session,
|
|
510
553
|
sessions,
|
|
511
554
|
};
|
|
512
555
|
|
|
513
|
-
if (!this._uninitialize) return;
|
|
514
|
-
|
|
515
556
|
if (pluvWs.state.quit) {
|
|
516
557
|
pluvWs.close(1011, "WebSocket broken.");
|
|
517
558
|
|
|
@@ -547,7 +588,7 @@ export class IORoom<
|
|
|
547
588
|
TContext & InferRoomContextType<TPlatform>
|
|
548
589
|
> = { ...baseContext, context: { ...this._context } };
|
|
549
590
|
|
|
550
|
-
Promise.all([
|
|
591
|
+
await Promise.all([
|
|
551
592
|
procedure.config.broadcast?.(inputs, extendedContext),
|
|
552
593
|
procedure.config.self?.(inputs, extendedContext),
|
|
553
594
|
procedure.config.sync?.(inputs, baseContext),
|
|
@@ -555,25 +596,33 @@ export class IORoom<
|
|
|
555
596
|
const sessionId = session.id;
|
|
556
597
|
const user = session.user;
|
|
557
598
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
599
|
+
const handleBroadcast = async () => {
|
|
600
|
+
if (!broadcast) return;
|
|
601
|
+
|
|
602
|
+
await Promise.all(
|
|
603
|
+
Object.entries(broadcast).map(async ([type, data]: any) => {
|
|
604
|
+
await this._broadcast({
|
|
605
|
+
message: { data, type },
|
|
606
|
+
senderId: sessionId,
|
|
607
|
+
});
|
|
608
|
+
}),
|
|
609
|
+
);
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
const handleSelf = async () => {
|
|
613
|
+
if (!self) return;
|
|
566
614
|
|
|
567
|
-
if (self) {
|
|
568
615
|
await Promise.all(
|
|
569
616
|
Object.entries(self).map(async ([type, data]) => {
|
|
570
617
|
await this._sendSelfMessage({ data, type }, { sessionId, user });
|
|
571
618
|
}),
|
|
572
619
|
);
|
|
573
|
-
}
|
|
620
|
+
};
|
|
574
621
|
|
|
575
|
-
|
|
576
|
-
|
|
622
|
+
const handleSync = async () => {
|
|
623
|
+
if (!sync) return;
|
|
624
|
+
|
|
625
|
+
await this._platform.pubSub.publish(this.id, {
|
|
577
626
|
connectionId: session.id,
|
|
578
627
|
options: { type: "sync" },
|
|
579
628
|
room: this.id,
|
|
@@ -586,7 +635,9 @@ export class IORoom<
|
|
|
586
635
|
await this._sendSelfMessage({ data, type }, { sessionId, user });
|
|
587
636
|
}),
|
|
588
637
|
);
|
|
589
|
-
}
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
await Promise.all([handleBroadcast(), handleSelf(), handleSync()]);
|
|
590
641
|
});
|
|
591
642
|
};
|
|
592
643
|
}
|
|
@@ -608,9 +659,13 @@ export class IORoom<
|
|
|
608
659
|
private async _sendMessage(pluvWs: AbstractWebSocket<any>, message: IOEventMessage<any>): Promise<void> {
|
|
609
660
|
const { data, type } = message;
|
|
610
661
|
|
|
662
|
+
if (!(await this._initialized)) return;
|
|
663
|
+
|
|
664
|
+
const doc = await this._doc;
|
|
665
|
+
|
|
611
666
|
this._listeners.onMessage({
|
|
612
667
|
context: this._context,
|
|
613
|
-
encodedState:
|
|
668
|
+
encodedState: doc.getEncodedState(),
|
|
614
669
|
message: { data, type } as InferEventMessage<InferEventsOutput<TEvents>, keyof InferEventsOutput<TEvents>>,
|
|
615
670
|
room: this.id,
|
|
616
671
|
user: message.user,
|
|
@@ -675,9 +730,10 @@ export class IORoom<
|
|
|
675
730
|
): Promise<void> {
|
|
676
731
|
if (!sender) return;
|
|
677
732
|
|
|
733
|
+
const doc = await this._doc;
|
|
678
734
|
const context: EventResolverContext<TPlatform, TAuthorize, TContext> = {
|
|
679
735
|
context: this._context,
|
|
680
|
-
doc
|
|
736
|
+
doc,
|
|
681
737
|
room: this.id,
|
|
682
738
|
session: null,
|
|
683
739
|
sessions: await this._getSessions(),
|
package/src/PluvServer.ts
CHANGED
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
InferRoomContextType,
|
|
18
18
|
WebSocketRegistrationMode,
|
|
19
19
|
} from "./AbstractPlatform";
|
|
20
|
+
import type { IORoomListeners } from "./IORoom";
|
|
20
21
|
import { IORoom } from "./IORoom";
|
|
21
22
|
import { PluvProcedure } from "./PluvProcedure";
|
|
22
23
|
import type { PluvRouterEventConfig } from "./PluvRouter";
|
|
@@ -47,9 +48,23 @@ export type PluvServerConfig<
|
|
|
47
48
|
router?: PluvRouter<TPlatform, TAuthorize, TContext, TEvents>;
|
|
48
49
|
};
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
type BaseCreateRoomOptions<
|
|
52
|
+
TPlatform extends AbstractPlatform<any>,
|
|
53
|
+
TAuthorize extends IOAuthorize<any, any, InferPlatformContextType<TPlatform>>,
|
|
54
|
+
TContext extends JsonObject,
|
|
55
|
+
TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext>,
|
|
56
|
+
> = Partial<IORoomListeners<TPlatform, TAuthorize, TContext, TEvents>> & {
|
|
57
|
+
debug?: boolean;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type CreateRoomOptions<
|
|
61
|
+
TPlatform extends AbstractPlatform<any>,
|
|
62
|
+
TAuthorize extends IOAuthorize<any, any, InferPlatformContextType<TPlatform>>,
|
|
63
|
+
TContext extends JsonObject,
|
|
64
|
+
TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext>,
|
|
65
|
+
> = keyof InferRoomContextType<TPlatform> extends never
|
|
66
|
+
? [BaseCreateRoomOptions<TPlatform, TAuthorize, TContext, TEvents>] | []
|
|
67
|
+
: [Id<BaseCreateRoomOptions<TPlatform, TAuthorize, TContext, TEvents> & InferRoomContextType<TPlatform>>];
|
|
53
68
|
|
|
54
69
|
export class PluvServer<
|
|
55
70
|
TPlatform extends AbstractPlatform<any> = AbstractPlatform<any>,
|
|
@@ -117,12 +132,7 @@ export class PluvServer<
|
|
|
117
132
|
const update = (data as any)?.update as Maybe<string>;
|
|
118
133
|
|
|
119
134
|
const updates: readonly Maybe<string>[] = [
|
|
120
|
-
oldState
|
|
121
|
-
? null
|
|
122
|
-
: await this._getInitialStorage?.({
|
|
123
|
-
...context,
|
|
124
|
-
room,
|
|
125
|
-
}),
|
|
135
|
+
oldState ? null : await this._getInitialStorage?.({ ...context, room }),
|
|
126
136
|
update,
|
|
127
137
|
];
|
|
128
138
|
|
|
@@ -169,9 +179,7 @@ export class PluvServer<
|
|
|
169
179
|
const origin = (data as any)?.origin as Maybe<string>;
|
|
170
180
|
const update: string | null = (data as any)?.update ?? null;
|
|
171
181
|
|
|
172
|
-
if (origin === "$INITIALIZED" && Object.keys(doc.toJson()).length) {
|
|
173
|
-
return {};
|
|
174
|
-
}
|
|
182
|
+
if (origin === "$INITIALIZED" && Object.keys(doc.toJson()).length) return {};
|
|
175
183
|
|
|
176
184
|
const updated = update === null ? doc : doc.applyEncodedState({ update });
|
|
177
185
|
const encodedState = updated.getEncodedState();
|
|
@@ -192,7 +200,6 @@ export class PluvServer<
|
|
|
192
200
|
private readonly _debug: boolean;
|
|
193
201
|
private readonly _getInitialStorage: GetInitialStorageFn<TPlatform> | null = null;
|
|
194
202
|
private readonly _platform: TPlatform;
|
|
195
|
-
private readonly _rooms = new Map<string, IORoom<TPlatform, TAuthorize, TContext, TEvents>>();
|
|
196
203
|
private readonly _router: PluvRouter<TPlatform, TAuthorize, TContext, TEvents>;
|
|
197
204
|
|
|
198
205
|
public get _events() {
|
|
@@ -242,41 +249,16 @@ export class PluvServer<
|
|
|
242
249
|
};
|
|
243
250
|
}
|
|
244
251
|
|
|
245
|
-
public
|
|
246
|
-
params: JWTEncodeParams<InferIOAuthorizeUser<InferIOAuthorize<this>>, TPlatform>,
|
|
247
|
-
): Promise<string> {
|
|
248
|
-
if (!this._authorize) {
|
|
249
|
-
throw new Error("IO does not specify authorize during initialization.");
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const ioAuthorize =
|
|
253
|
-
typeof this._authorize === "function" ? this._authorize(params) : (this._authorize as { secret: string });
|
|
254
|
-
|
|
255
|
-
const secret = ioAuthorize.secret;
|
|
256
|
-
|
|
257
|
-
return await authorize({
|
|
258
|
-
platform: this._platform,
|
|
259
|
-
secret,
|
|
260
|
-
}).encode(params);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
public getRoom(
|
|
252
|
+
public createRoom(
|
|
264
253
|
room: string,
|
|
265
|
-
...options:
|
|
254
|
+
...options: CreateRoomOptions<TPlatform, TAuthorize, TContext, TEvents>
|
|
266
255
|
): IORoom<TPlatform, TAuthorize, TContext, TEvents> {
|
|
267
|
-
const { debug, ...platformRoomContext } = options[0] ?? {};
|
|
268
|
-
|
|
269
|
-
this._purgeEmptyRooms();
|
|
256
|
+
const { debug, onDestroy, onMessage, ...platformRoomContext } = options[0] ?? {};
|
|
270
257
|
|
|
271
258
|
if (!/^[a-z0-9]+[a-z0-9\-_]+[a-z0-9]+$/i.test(room)) {
|
|
272
259
|
throw new Error("Unsupported room name");
|
|
273
260
|
}
|
|
274
261
|
|
|
275
|
-
const platform = this._platform;
|
|
276
|
-
const oldRoom = this._rooms.get(room);
|
|
277
|
-
|
|
278
|
-
if (oldRoom) return oldRoom;
|
|
279
|
-
|
|
280
262
|
const roomContext = {
|
|
281
263
|
...this._context,
|
|
282
264
|
...platformRoomContext,
|
|
@@ -289,48 +271,45 @@ export class PluvServer<
|
|
|
289
271
|
debug: debug ?? this._debug,
|
|
290
272
|
onDestroy: async (event) => {
|
|
291
273
|
this._logDebug(`${colors.blue("Deleting empty room:")} ${room}`);
|
|
292
|
-
this._rooms.delete(room);
|
|
293
274
|
|
|
275
|
+
await Promise.resolve(onDestroy?.(event));
|
|
294
276
|
await Promise.resolve(this._listeners.onRoomDeleted(event));
|
|
295
277
|
await this._platform.persistance.deleteStorageState(room);
|
|
296
278
|
|
|
297
|
-
|
|
298
|
-
const rooms = Array.from(this._rooms.keys());
|
|
299
|
-
|
|
300
|
-
this._logDebug(`${colors.blue("Deleted room:")} ${room}`);
|
|
301
|
-
this._logDebug(`${colors.blue("Rooms available:")} ${rooms.join(", ")}`);
|
|
302
|
-
}
|
|
279
|
+
this._logDebug(`${colors.blue("Deleted room:")} ${room}`);
|
|
303
280
|
},
|
|
304
281
|
onMessage: async (event) => {
|
|
282
|
+
await Promise.resolve(onMessage?.(event));
|
|
305
283
|
await Promise.resolve(this._listeners.onRoomMessage(event));
|
|
306
284
|
},
|
|
307
|
-
platform,
|
|
285
|
+
platform: this._platform,
|
|
308
286
|
router: this._router,
|
|
309
287
|
});
|
|
310
288
|
|
|
311
|
-
this.
|
|
289
|
+
this._logDebug(`${colors.blue("Created room:")} ${room}`);
|
|
312
290
|
|
|
313
|
-
|
|
314
|
-
|
|
291
|
+
return newRoom;
|
|
292
|
+
}
|
|
315
293
|
|
|
316
|
-
|
|
317
|
-
|
|
294
|
+
public async createToken(
|
|
295
|
+
params: JWTEncodeParams<InferIOAuthorizeUser<InferIOAuthorize<this>>, TPlatform>,
|
|
296
|
+
): Promise<string> {
|
|
297
|
+
if (!this._authorize) {
|
|
298
|
+
throw new Error("IO does not specify authorize during initialization.");
|
|
318
299
|
}
|
|
319
300
|
|
|
320
|
-
|
|
301
|
+
const ioAuthorize =
|
|
302
|
+
typeof this._authorize === "function" ? this._authorize(params) : (this._authorize as { secret: string });
|
|
303
|
+
|
|
304
|
+
const secret = ioAuthorize.secret;
|
|
305
|
+
|
|
306
|
+
return await authorize({
|
|
307
|
+
platform: this._platform,
|
|
308
|
+
secret,
|
|
309
|
+
}).encode(params);
|
|
321
310
|
}
|
|
322
311
|
|
|
323
312
|
private _logDebug(...data: any[]): void {
|
|
324
313
|
this._debug && console.log(...data);
|
|
325
314
|
}
|
|
326
|
-
|
|
327
|
-
private _purgeEmptyRooms(): void {
|
|
328
|
-
const rooms = Array.from(this._rooms.entries());
|
|
329
|
-
|
|
330
|
-
rooms.forEach(([id, room]) => {
|
|
331
|
-
if (!!room?.getSize()) return;
|
|
332
|
-
|
|
333
|
-
this._rooms.delete(id);
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
315
|
}
|
package/src/index.ts
CHANGED
|
@@ -33,5 +33,5 @@ export type { PluvProcedureConfig } from "./PluvProcedure";
|
|
|
33
33
|
export { PluvRouter } from "./PluvRouter";
|
|
34
34
|
export type { MergedRouter, PluvRouterEventConfig } from "./PluvRouter";
|
|
35
35
|
export { PluvServer } from "./PluvServer";
|
|
36
|
-
export type {
|
|
36
|
+
export type { CreateRoomOptions, InferIORoom, PluvServerConfig } from "./PluvServer";
|
|
37
37
|
export type { WebSocketSerializedState, WebSocketSession } from "./types";
|
package/src/types.ts
CHANGED
package/src/version.generated.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by genversion.
|
|
2
|
-
export const version = '0.
|
|
2
|
+
export const version = '0.24.0'
|