@pluv/client 0.17.3 → 0.18.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 +8 -8
- package/CHANGELOG.md +152 -0
- package/README.md +2 -5
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +59 -129
- package/dist/index.mjs +59 -129
- package/package.json +5 -5
- package/src/AbstractRoom.ts +8 -33
- package/src/CrdtManager.ts +9 -33
- package/src/CrdtNotifier.ts +5 -11
- package/src/EventNotifier.ts +5 -11
- package/src/MockedRoom.ts +14 -57
- package/src/PluvClient.ts +4 -17
- package/src/PluvRoom.ts +87 -203
- package/src/StateNotifier.ts +4 -17
- package/src/UsersManager.ts +4 -20
- package/src/createClient.ts +1 -3
- package/src/index.ts +2 -12
- package/src/types.ts +2 -11
package/src/PluvRoom.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AbstractCrdtDoc,
|
|
3
|
-
AbstractCrdtType,
|
|
4
|
-
InferCrdtStorageJson,
|
|
5
|
-
} from "@pluv/crdt";
|
|
1
|
+
import type { AbstractCrdtDoc, AbstractCrdtType, InferCrdtStorageJson } from "@pluv/crdt";
|
|
6
2
|
import type {
|
|
7
3
|
BaseIOEventRecord,
|
|
8
4
|
EventMessage,
|
|
@@ -17,7 +13,7 @@ import type {
|
|
|
17
13
|
JsonObject,
|
|
18
14
|
} from "@pluv/types";
|
|
19
15
|
import { AbstractRoom } from "./AbstractRoom";
|
|
20
|
-
import { AbstractStorageStore } from "./AbstractStorageStore";
|
|
16
|
+
import type { AbstractStorageStore } from "./AbstractStorageStore";
|
|
21
17
|
import type { CrdtManagerOptions } from "./CrdtManager";
|
|
22
18
|
import { CrdtManager } from "./CrdtManager";
|
|
23
19
|
import { CrdtNotifier } from "./CrdtNotifier";
|
|
@@ -25,21 +21,12 @@ import type { EventNotifierSubscriptionCallback } from "./EventNotifier";
|
|
|
25
21
|
import { EventNotifier } from "./EventNotifier";
|
|
26
22
|
import type { OtherNotifierSubscriptionCallback } from "./OtherNotifier";
|
|
27
23
|
import { OtherNotifier } from "./OtherNotifier";
|
|
28
|
-
import type {
|
|
29
|
-
StateNotifierSubjects,
|
|
30
|
-
SubscriptionCallback,
|
|
31
|
-
} from "./StateNotifier";
|
|
24
|
+
import type { StateNotifierSubjects, SubscriptionCallback } from "./StateNotifier";
|
|
32
25
|
import { StateNotifier } from "./StateNotifier";
|
|
33
26
|
import { StorageStore } from "./StorageStore";
|
|
34
27
|
import type { UsersManagerConfig } from "./UsersManager";
|
|
35
28
|
import { UsersManager } from "./UsersManager";
|
|
36
|
-
import type {
|
|
37
|
-
AuthorizationState,
|
|
38
|
-
InternalSubscriptions,
|
|
39
|
-
UserInfo,
|
|
40
|
-
WebSocketConnection,
|
|
41
|
-
WebSocketState,
|
|
42
|
-
} from "./types";
|
|
29
|
+
import type { AuthorizationState, InternalSubscriptions, UserInfo, WebSocketConnection, WebSocketState } from "./types";
|
|
43
30
|
import { ConnectionState } from "./types";
|
|
44
31
|
import { debounce } from "./utils";
|
|
45
32
|
|
|
@@ -89,10 +76,7 @@ interface TimeoutIds {
|
|
|
89
76
|
reconnect: number | null;
|
|
90
77
|
}
|
|
91
78
|
|
|
92
|
-
export type AuthEndpoint =
|
|
93
|
-
| string
|
|
94
|
-
| ((room: string) => string | FetchOptions)
|
|
95
|
-
| true;
|
|
79
|
+
export type AuthEndpoint = string | ((room: string) => string | FetchOptions) | true;
|
|
96
80
|
export type WsEndpoint = string | ((room: string) => string);
|
|
97
81
|
|
|
98
82
|
type FetchOptions = { url: string; options?: RequestInit };
|
|
@@ -111,9 +95,7 @@ export type PluvRoomAddon<
|
|
|
111
95
|
TIO extends IOLike = IOLike,
|
|
112
96
|
TPresence extends JsonObject = {},
|
|
113
97
|
TStorage extends Record<string, AbstractCrdtType<any, any>> = {},
|
|
114
|
-
> = (
|
|
115
|
-
input: PluvRoomAddonInput<TIO, TPresence, TStorage>,
|
|
116
|
-
) => Partial<PluvRoomAddonResult>;
|
|
98
|
+
> = (input: PluvRoomAddonInput<TIO, TPresence, TStorage>) => Partial<PluvRoomAddonResult>;
|
|
117
99
|
|
|
118
100
|
export interface PluvRoomAddonInput<
|
|
119
101
|
TIO extends IOLike = IOLike,
|
|
@@ -220,10 +202,7 @@ export class PluvRoom<
|
|
|
220
202
|
this._listeners = {
|
|
221
203
|
onAuthorizationFail: (error) => {
|
|
222
204
|
this._clearTimeout(this._timeouts.reconnect);
|
|
223
|
-
this._timeouts.reconnect = setTimeout(
|
|
224
|
-
this._reconnect.bind(this),
|
|
225
|
-
RECONNECT_TIMEOUT_MS,
|
|
226
|
-
);
|
|
205
|
+
this._timeouts.reconnect = setTimeout(this._reconnect.bind(this), RECONNECT_TIMEOUT_MS);
|
|
227
206
|
|
|
228
207
|
onAuthorizationFail?.(error);
|
|
229
208
|
},
|
|
@@ -247,17 +226,25 @@ export class PluvRoom<
|
|
|
247
226
|
return this._state.webSocket;
|
|
248
227
|
}
|
|
249
228
|
|
|
250
|
-
public broadcast
|
|
251
|
-
event: TEvent,
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (!this._state.webSocket) return;
|
|
255
|
-
if (this._state.connection.state !== ConnectionState.Open) return;
|
|
229
|
+
public broadcast = new Proxy(
|
|
230
|
+
<TEvent extends keyof InferIOInput<TIO>>(event: TEvent, data: Id<InferIOInput<TIO>[TEvent]>): void => {
|
|
231
|
+
if (!this._state.webSocket) return;
|
|
232
|
+
if (this._state.connection.state !== ConnectionState.Open) return;
|
|
256
233
|
|
|
257
|
-
|
|
234
|
+
const type = event.toString();
|
|
258
235
|
|
|
259
|
-
|
|
260
|
-
|
|
236
|
+
this._sendMessage({ data, type });
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
get(fn, prop) {
|
|
240
|
+
return (data: Id<InferIOInput<TIO>[any]>): void => {
|
|
241
|
+
return fn(prop, data);
|
|
242
|
+
};
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
) as (<TEvent extends keyof InferIOInput<TIO>>(event: TEvent, data: Id<InferIOInput<TIO>[TEvent]>) => void) & {
|
|
246
|
+
[event in keyof InferIOInput<TIO>]: (data: Id<InferIOInput<TIO>[event]>) => void;
|
|
247
|
+
};
|
|
261
248
|
|
|
262
249
|
public canRedo = (): boolean => {
|
|
263
250
|
return !!this._crdtManager.doc.canRedo();
|
|
@@ -272,10 +259,9 @@ export class PluvRoom<
|
|
|
272
259
|
throw new Error("Must provide an wsEndpoint.");
|
|
273
260
|
}
|
|
274
261
|
|
|
275
|
-
const canConnect = [
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
].some((state) => this._state.connection.state === state);
|
|
262
|
+
const canConnect = [ConnectionState.Closed, ConnectionState.Untouched].some(
|
|
263
|
+
(state) => this._state.connection.state === state,
|
|
264
|
+
);
|
|
279
265
|
|
|
280
266
|
if (!canConnect) return;
|
|
281
267
|
|
|
@@ -298,8 +284,7 @@ export class PluvRoom<
|
|
|
298
284
|
try {
|
|
299
285
|
authToken = await this._getAuthorization(this.id);
|
|
300
286
|
|
|
301
|
-
authToken &&
|
|
302
|
-
url.searchParams.set("token", encodeURIComponent(authToken));
|
|
287
|
+
authToken && url.searchParams.set("token", encodeURIComponent(authToken));
|
|
303
288
|
|
|
304
289
|
webSocket = new WebSocket(url.toString());
|
|
305
290
|
} catch (err) {
|
|
@@ -328,10 +313,9 @@ export class PluvRoom<
|
|
|
328
313
|
|
|
329
314
|
if (!this._state.webSocket) return;
|
|
330
315
|
|
|
331
|
-
const canDisconnect = [
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
].some((state) => this._state.connection.state === state);
|
|
316
|
+
const canDisconnect = [ConnectionState.Connecting, ConnectionState.Open].some(
|
|
317
|
+
(state) => this._state.connection.state === state,
|
|
318
|
+
);
|
|
335
319
|
|
|
336
320
|
if (!canDisconnect) return;
|
|
337
321
|
|
|
@@ -346,18 +330,26 @@ export class PluvRoom<
|
|
|
346
330
|
this._stateNotifier.subjects["my-presence"].next(null);
|
|
347
331
|
}
|
|
348
332
|
|
|
349
|
-
public event =
|
|
333
|
+
public event = new Proxy(
|
|
334
|
+
<TEvent extends keyof InferIOOutput<TIO>>(
|
|
335
|
+
event: TEvent,
|
|
336
|
+
callback: EventNotifierSubscriptionCallback<TIO, TEvent>,
|
|
337
|
+
): (() => void) => this._eventNotifier.subscribe(event, callback),
|
|
338
|
+
{
|
|
339
|
+
get(fn, prop) {
|
|
340
|
+
return (callback: EventNotifierSubscriptionCallback<TIO, any>): (() => void) => fn(prop, callback);
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
) as (<TEvent extends keyof InferIOOutput<TIO>>(
|
|
350
344
|
event: TEvent,
|
|
351
345
|
callback: EventNotifierSubscriptionCallback<TIO, TEvent>,
|
|
352
|
-
)
|
|
353
|
-
|
|
346
|
+
) => () => void) & {
|
|
347
|
+
[event in keyof InferIOOutput<TIO>]: (callback: EventNotifierSubscriptionCallback<TIO, event>) => () => void;
|
|
354
348
|
};
|
|
355
349
|
|
|
356
350
|
public getConnection(): WebSocketConnection {
|
|
357
351
|
// Create a read-only clone of the connection state
|
|
358
|
-
return Object.freeze(
|
|
359
|
-
JSON.parse(JSON.stringify(this._state.connection)),
|
|
360
|
-
);
|
|
352
|
+
return Object.freeze(JSON.parse(JSON.stringify(this._state.connection)));
|
|
361
353
|
}
|
|
362
354
|
|
|
363
355
|
public getDoc(): AbstractCrdtDoc<TStorage> {
|
|
@@ -372,9 +364,7 @@ export class PluvRoom<
|
|
|
372
364
|
return this._usersManager.myself;
|
|
373
365
|
};
|
|
374
366
|
|
|
375
|
-
public getOther = (
|
|
376
|
-
connectionId: string,
|
|
377
|
-
): Id<UserInfo<TIO, TPresence>> | null => {
|
|
367
|
+
public getOther = (connectionId: string): Id<UserInfo<TIO, TPresence>> | null => {
|
|
378
368
|
return this._usersManager.getOther(connectionId);
|
|
379
369
|
};
|
|
380
370
|
|
|
@@ -382,9 +372,7 @@ export class PluvRoom<
|
|
|
382
372
|
return this._usersManager.getOthers();
|
|
383
373
|
};
|
|
384
374
|
|
|
385
|
-
public getStorage = <TKey extends keyof TStorage>(
|
|
386
|
-
key: TKey,
|
|
387
|
-
): TStorage[TKey] | null => {
|
|
375
|
+
public getStorage = <TKey extends keyof TStorage>(key: TKey): TStorage[TKey] | null => {
|
|
388
376
|
const sharedType = this._crdtManager.get(key);
|
|
389
377
|
|
|
390
378
|
if (typeof sharedType === "undefined") return null;
|
|
@@ -392,10 +380,7 @@ export class PluvRoom<
|
|
|
392
380
|
return sharedType;
|
|
393
381
|
};
|
|
394
382
|
|
|
395
|
-
public other = (
|
|
396
|
-
connectionId: string,
|
|
397
|
-
callback: OtherNotifierSubscriptionCallback<TIO>,
|
|
398
|
-
): (() => void) => {
|
|
383
|
+
public other = (connectionId: string, callback: OtherNotifierSubscriptionCallback<TIO>): (() => void) => {
|
|
399
384
|
return this._otherNotifier.subscribe(connectionId, callback);
|
|
400
385
|
};
|
|
401
386
|
|
|
@@ -418,19 +403,14 @@ export class PluvRoom<
|
|
|
418
403
|
return this._crdtNotifier.subcribeRoot(fn);
|
|
419
404
|
};
|
|
420
405
|
|
|
421
|
-
public subscribe = <
|
|
422
|
-
TSubject extends keyof StateNotifierSubjects<TIO, TPresence>,
|
|
423
|
-
>(
|
|
406
|
+
public subscribe = <TSubject extends keyof StateNotifierSubjects<TIO, TPresence>>(
|
|
424
407
|
name: TSubject,
|
|
425
408
|
callback: SubscriptionCallback<TIO, TPresence, TSubject>,
|
|
426
409
|
): (() => void) => {
|
|
427
410
|
return this._stateNotifier.subscribe(name, callback);
|
|
428
411
|
};
|
|
429
412
|
|
|
430
|
-
public transact = (
|
|
431
|
-
fn: (storage: TStorage) => void,
|
|
432
|
-
origin?: string,
|
|
433
|
-
): void => {
|
|
413
|
+
public transact = (fn: (storage: TStorage) => void, origin?: string): void => {
|
|
434
414
|
const _origin = origin ?? this._state.connection.id;
|
|
435
415
|
|
|
436
416
|
/**
|
|
@@ -462,10 +442,7 @@ export class PluvRoom<
|
|
|
462
442
|
this._stateNotifier.subjects["my-presence"].next(myPresence);
|
|
463
443
|
!!myself && this._stateNotifier.subjects["myself"].next(myself);
|
|
464
444
|
|
|
465
|
-
this.broadcast(
|
|
466
|
-
"$UPDATE_PRESENCE" as keyof InferIOInput<TIO>,
|
|
467
|
-
{ presence } as any,
|
|
468
|
-
);
|
|
445
|
+
this.broadcast("$UPDATE_PRESENCE" as keyof InferIOInput<TIO>, { presence } as any);
|
|
469
446
|
};
|
|
470
447
|
|
|
471
448
|
private async _addToStorageStore(update: string): Promise<void> {
|
|
@@ -499,14 +476,8 @@ export class PluvRoom<
|
|
|
499
476
|
onVisibilityChange: this._onVisibilityChange.bind(this),
|
|
500
477
|
};
|
|
501
478
|
|
|
502
|
-
window.addEventListener(
|
|
503
|
-
|
|
504
|
-
this._windowListeners.onNavigatorOnline,
|
|
505
|
-
);
|
|
506
|
-
document.addEventListener(
|
|
507
|
-
"visibilitychange",
|
|
508
|
-
this._windowListeners.onVisibilityChange,
|
|
509
|
-
);
|
|
479
|
+
window.addEventListener("online", this._windowListeners.onNavigatorOnline);
|
|
480
|
+
document.addEventListener("visibilitychange", this._windowListeners.onVisibilityChange);
|
|
510
481
|
}
|
|
511
482
|
|
|
512
483
|
private _attachWsListeners(): void {
|
|
@@ -519,22 +490,10 @@ export class PluvRoom<
|
|
|
519
490
|
onOpen: this._onOpen.bind(this),
|
|
520
491
|
};
|
|
521
492
|
|
|
522
|
-
this._state.webSocket.addEventListener(
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
);
|
|
526
|
-
this._state.webSocket.addEventListener(
|
|
527
|
-
"error",
|
|
528
|
-
this._wsListeners.onError,
|
|
529
|
-
);
|
|
530
|
-
this._state.webSocket.addEventListener(
|
|
531
|
-
"message",
|
|
532
|
-
this._wsListeners.onMessage,
|
|
533
|
-
);
|
|
534
|
-
this._state.webSocket.addEventListener(
|
|
535
|
-
"open",
|
|
536
|
-
this._wsListeners.onOpen,
|
|
537
|
-
);
|
|
493
|
+
this._state.webSocket.addEventListener("close", this._wsListeners.onClose);
|
|
494
|
+
this._state.webSocket.addEventListener("error", this._wsListeners.onError);
|
|
495
|
+
this._state.webSocket.addEventListener("message", this._wsListeners.onMessage);
|
|
496
|
+
this._state.webSocket.addEventListener("open", this._wsListeners.onOpen);
|
|
538
497
|
}
|
|
539
498
|
|
|
540
499
|
private _clearInterval(interval: number | null) {
|
|
@@ -574,14 +533,8 @@ export class PluvRoom<
|
|
|
574
533
|
if (typeof window === "undefined") return;
|
|
575
534
|
if (typeof document === "undefined") return;
|
|
576
535
|
|
|
577
|
-
window.removeEventListener(
|
|
578
|
-
|
|
579
|
-
this._windowListeners.onNavigatorOnline,
|
|
580
|
-
);
|
|
581
|
-
document.removeEventListener(
|
|
582
|
-
"visibilitychange",
|
|
583
|
-
this._windowListeners.onVisibilityChange,
|
|
584
|
-
);
|
|
536
|
+
window.removeEventListener("online", this._windowListeners.onNavigatorOnline);
|
|
537
|
+
document.removeEventListener("visibilitychange", this._windowListeners.onVisibilityChange);
|
|
585
538
|
|
|
586
539
|
this._windowListeners = null;
|
|
587
540
|
}
|
|
@@ -589,22 +542,10 @@ export class PluvRoom<
|
|
|
589
542
|
private _detachWsListeners(): void {
|
|
590
543
|
if (!this._state.webSocket || !this._wsListeners) return;
|
|
591
544
|
|
|
592
|
-
this._state.webSocket.removeEventListener(
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
);
|
|
596
|
-
this._state.webSocket.removeEventListener(
|
|
597
|
-
"error",
|
|
598
|
-
this._wsListeners.onError,
|
|
599
|
-
);
|
|
600
|
-
this._state.webSocket.removeEventListener(
|
|
601
|
-
"message",
|
|
602
|
-
this._wsListeners.onMessage,
|
|
603
|
-
);
|
|
604
|
-
this._state.webSocket.removeEventListener(
|
|
605
|
-
"open",
|
|
606
|
-
this._wsListeners.onOpen,
|
|
607
|
-
);
|
|
545
|
+
this._state.webSocket.removeEventListener("close", this._wsListeners.onClose);
|
|
546
|
+
this._state.webSocket.removeEventListener("error", this._wsListeners.onError);
|
|
547
|
+
this._state.webSocket.removeEventListener("message", this._wsListeners.onMessage);
|
|
548
|
+
this._state.webSocket.removeEventListener("open", this._wsListeners.onOpen);
|
|
608
549
|
|
|
609
550
|
this._wsListeners = null;
|
|
610
551
|
}
|
|
@@ -676,9 +617,7 @@ export class PluvRoom<
|
|
|
676
617
|
|
|
677
618
|
const result = this._endpoints.authEndpoint(room);
|
|
678
619
|
|
|
679
|
-
return typeof result === "string"
|
|
680
|
-
? { url: result, options: {} }
|
|
681
|
-
: result;
|
|
620
|
+
return typeof result === "string" ? { url: result, options: {} } : result;
|
|
682
621
|
}
|
|
683
622
|
|
|
684
623
|
private async _getAuthorization(room: string): Promise<string | null> {
|
|
@@ -697,9 +636,7 @@ export class PluvRoom<
|
|
|
697
636
|
try {
|
|
698
637
|
return await res.text().then((text) => text.trim());
|
|
699
638
|
} catch (err) {
|
|
700
|
-
throw new Error(
|
|
701
|
-
err instanceof Error ? err.message : "Room is unauthorized",
|
|
702
|
-
);
|
|
639
|
+
throw new Error(err instanceof Error ? err.message : "Room is unauthorized");
|
|
703
640
|
}
|
|
704
641
|
}
|
|
705
642
|
|
|
@@ -737,14 +674,9 @@ export class PluvRoom<
|
|
|
737
674
|
// Should not reach here
|
|
738
675
|
if (!this._state.webSocket) throw new Error("Could not find WebSocket");
|
|
739
676
|
|
|
740
|
-
const data = message.data as BaseIOEventRecord<
|
|
741
|
-
InferIOAuthorize<TIO>
|
|
742
|
-
>["$PRESENCE_UPDATED"];
|
|
677
|
+
const data = message.data as BaseIOEventRecord<InferIOAuthorize<TIO>>["$PRESENCE_UPDATED"];
|
|
743
678
|
|
|
744
|
-
this._usersManager.patchPresence(
|
|
745
|
-
connectionId,
|
|
746
|
-
data.presence as TPresence,
|
|
747
|
-
);
|
|
679
|
+
this._usersManager.patchPresence(connectionId, data.presence as TPresence);
|
|
748
680
|
|
|
749
681
|
const myPresence = this._usersManager.myPresence;
|
|
750
682
|
const myself = this._usersManager.myself ?? null;
|
|
@@ -768,9 +700,7 @@ export class PluvRoom<
|
|
|
768
700
|
// Should not reach here
|
|
769
701
|
if (!this._state.webSocket) throw new Error("Could not find WebSocket");
|
|
770
702
|
|
|
771
|
-
const data = message.data as BaseIOEventRecord<
|
|
772
|
-
InferIOAuthorize<TIO>
|
|
773
|
-
>["$OTHERS_RECEIVED"];
|
|
703
|
+
const data = message.data as BaseIOEventRecord<InferIOAuthorize<TIO>>["$OTHERS_RECEIVED"];
|
|
774
704
|
|
|
775
705
|
Object.keys(data.others).forEach((connectionId) => {
|
|
776
706
|
const { presence, user } = data.others[connectionId];
|
|
@@ -781,11 +711,7 @@ export class PluvRoom<
|
|
|
781
711
|
|
|
782
712
|
const other = this._usersManager.getOther(connectionId);
|
|
783
713
|
|
|
784
|
-
!!presence &&
|
|
785
|
-
this._usersManager.patchPresence(
|
|
786
|
-
connectionId,
|
|
787
|
-
presence as TPresence,
|
|
788
|
-
);
|
|
714
|
+
!!presence && this._usersManager.patchPresence(connectionId, presence as TPresence);
|
|
789
715
|
|
|
790
716
|
this._otherNotifier.subject(connectionId).next(other);
|
|
791
717
|
});
|
|
@@ -810,10 +736,7 @@ export class PluvRoom<
|
|
|
810
736
|
return oldState;
|
|
811
737
|
});
|
|
812
738
|
|
|
813
|
-
this._usersManager.setMyself(
|
|
814
|
-
connectionId,
|
|
815
|
-
user as Id<InferIOAuthorizeUser<InferIOAuthorize<TIO>>>,
|
|
816
|
-
);
|
|
739
|
+
this._usersManager.setMyself(connectionId, user as Id<InferIOAuthorizeUser<InferIOAuthorize<TIO>>>);
|
|
817
740
|
|
|
818
741
|
const presence = this._usersManager.myPresence;
|
|
819
742
|
const myself = this._usersManager.myself ?? null;
|
|
@@ -821,10 +744,7 @@ export class PluvRoom<
|
|
|
821
744
|
this._stateNotifier.subjects["my-presence"].next(presence);
|
|
822
745
|
this._stateNotifier.subjects["myself"].next(myself);
|
|
823
746
|
|
|
824
|
-
const update =
|
|
825
|
-
this._state.connection.count > 1
|
|
826
|
-
? this._crdtManager.doc.getEncodedState() ?? null
|
|
827
|
-
: null;
|
|
747
|
+
const update = this._state.connection.count > 1 ? this._crdtManager.doc.getEncodedState() ?? null : null;
|
|
828
748
|
|
|
829
749
|
this._sendMessage({
|
|
830
750
|
type: "$INITIALIZE_SESSION",
|
|
@@ -832,18 +752,14 @@ export class PluvRoom<
|
|
|
832
752
|
});
|
|
833
753
|
}
|
|
834
754
|
|
|
835
|
-
private async _handleStorageReceivedMessage(
|
|
836
|
-
message: IOEventMessage<TIO>,
|
|
837
|
-
): Promise<void> {
|
|
755
|
+
private async _handleStorageReceivedMessage(message: IOEventMessage<TIO>): Promise<void> {
|
|
838
756
|
const { connectionId } = message;
|
|
839
757
|
|
|
840
758
|
if (!connectionId) return;
|
|
841
759
|
// Should not reach here
|
|
842
760
|
if (!this._state.webSocket) throw new Error("Could not find WebSocket");
|
|
843
761
|
|
|
844
|
-
const data = message.data as BaseIOEventRecord<
|
|
845
|
-
InferIOAuthorize<TIO>
|
|
846
|
-
>["$STORAGE_RECEIVED"];
|
|
762
|
+
const data = message.data as BaseIOEventRecord<InferIOAuthorize<TIO>>["$STORAGE_RECEIVED"];
|
|
847
763
|
|
|
848
764
|
this._crdtManager.initialize({
|
|
849
765
|
onInitialized: async (update) => {
|
|
@@ -878,9 +794,7 @@ export class PluvRoom<
|
|
|
878
794
|
// Should not reach here
|
|
879
795
|
if (!this._state.webSocket) throw new Error("Could not find WebSocket");
|
|
880
796
|
|
|
881
|
-
const data = message.data as BaseIOEventRecord<
|
|
882
|
-
InferIOAuthorize<TIO>
|
|
883
|
-
>[typeof ORIGIN_STORAGE_UPDATED];
|
|
797
|
+
const data = message.data as BaseIOEventRecord<InferIOAuthorize<TIO>>[typeof ORIGIN_STORAGE_UPDATED];
|
|
884
798
|
|
|
885
799
|
if (!this._crdtManager) return;
|
|
886
800
|
|
|
@@ -917,9 +831,7 @@ export class PluvRoom<
|
|
|
917
831
|
if (!this._state.webSocket) throw new Error("Could not find WebSocket");
|
|
918
832
|
if (!this._usersManager.myself) return;
|
|
919
833
|
|
|
920
|
-
const data = message.data as BaseIOEventRecord<
|
|
921
|
-
InferIOAuthorize<TIO>
|
|
922
|
-
>["$USER_JOINED"];
|
|
834
|
+
const data = message.data as BaseIOEventRecord<InferIOAuthorize<TIO>>["$USER_JOINED"];
|
|
923
835
|
|
|
924
836
|
const myself = this._usersManager.myself;
|
|
925
837
|
|
|
@@ -927,11 +839,7 @@ export class PluvRoom<
|
|
|
927
839
|
this._sendMessage({ type: "$GET_OTHERS", data: {} });
|
|
928
840
|
}
|
|
929
841
|
|
|
930
|
-
this._usersManager.setUser(
|
|
931
|
-
connectionId,
|
|
932
|
-
data.user,
|
|
933
|
-
data.presence as TPresence,
|
|
934
|
-
);
|
|
842
|
+
this._usersManager.setUser(connectionId, data.user, data.presence as TPresence);
|
|
935
843
|
|
|
936
844
|
const other = this._usersManager.getOther(connectionId);
|
|
937
845
|
const others = this._usersManager.getOthers();
|
|
@@ -942,10 +850,7 @@ export class PluvRoom<
|
|
|
942
850
|
|
|
943
851
|
private _heartbeat(): void {
|
|
944
852
|
this._clearTimeout(this._timeouts.pong);
|
|
945
|
-
this._timeouts.pong = setTimeout(
|
|
946
|
-
this._reconnect.bind(this),
|
|
947
|
-
PONG_TIMEOUT_MS,
|
|
948
|
-
);
|
|
853
|
+
this._timeouts.pong = setTimeout(this._reconnect.bind(this), PONG_TIMEOUT_MS);
|
|
949
854
|
|
|
950
855
|
this._sendMessage({ type: "$PING", data: {} });
|
|
951
856
|
}
|
|
@@ -968,10 +873,7 @@ export class PluvRoom<
|
|
|
968
873
|
this._addToStorageStore(event.update);
|
|
969
874
|
this._emitSharedTypes();
|
|
970
875
|
|
|
971
|
-
if (
|
|
972
|
-
origin === ORIGIN_INITIALIZED ||
|
|
973
|
-
origin === ORIGIN_STORAGE_UPDATED
|
|
974
|
-
) {
|
|
876
|
+
if (origin === ORIGIN_INITIALIZED || origin === ORIGIN_STORAGE_UPDATED) {
|
|
975
877
|
return;
|
|
976
878
|
}
|
|
977
879
|
|
|
@@ -989,8 +891,7 @@ export class PluvRoom<
|
|
|
989
891
|
}
|
|
990
892
|
|
|
991
893
|
private _onAuthorizationFail(error: unknown): void {
|
|
992
|
-
const authError =
|
|
993
|
-
error instanceof Error ? error : new Error("Authorization failed");
|
|
894
|
+
const authError = error instanceof Error ? error : new Error("Authorization failed");
|
|
994
895
|
|
|
995
896
|
this._listeners.onAuthorizationFail(authError);
|
|
996
897
|
|
|
@@ -1040,17 +941,13 @@ export class PluvRoom<
|
|
|
1040
941
|
if (!message) return;
|
|
1041
942
|
|
|
1042
943
|
const shouldLog =
|
|
1043
|
-
typeof this._debug === "boolean"
|
|
1044
|
-
? this._debug
|
|
1045
|
-
: this._debug.output.find((value) => value === message.type);
|
|
944
|
+
typeof this._debug === "boolean" ? this._debug : this._debug.output.find((value) => value === message.type);
|
|
1046
945
|
|
|
1047
946
|
if (shouldLog) {
|
|
1048
947
|
this._logDebug("WebSocket event received: ", message.type, message);
|
|
1049
948
|
}
|
|
1050
949
|
|
|
1051
|
-
this._eventNotifier
|
|
1052
|
-
.subject(message.type as keyof InferIOOutput<TIO>)
|
|
1053
|
-
.next(message as any);
|
|
950
|
+
this._eventNotifier.subject(message.type as keyof InferIOOutput<TIO>).next(message as any);
|
|
1054
951
|
|
|
1055
952
|
const { connectionId } = message;
|
|
1056
953
|
|
|
@@ -1105,10 +1002,7 @@ export class PluvRoom<
|
|
|
1105
1002
|
});
|
|
1106
1003
|
|
|
1107
1004
|
this._clearInterval(this._intervals.heartbeat);
|
|
1108
|
-
this._intervals.heartbeat = setInterval(
|
|
1109
|
-
this._heartbeat.bind(this),
|
|
1110
|
-
HEARTBEAT_INTERVAL_MS,
|
|
1111
|
-
);
|
|
1005
|
+
this._intervals.heartbeat = setInterval(this._heartbeat.bind(this), HEARTBEAT_INTERVAL_MS);
|
|
1112
1006
|
}
|
|
1113
1007
|
|
|
1114
1008
|
private async _onNavigatorOnline(): Promise<void> {
|
|
@@ -1137,9 +1031,7 @@ export class PluvRoom<
|
|
|
1137
1031
|
}
|
|
1138
1032
|
}
|
|
1139
1033
|
|
|
1140
|
-
private _parseMessage(message: {
|
|
1141
|
-
data: string | ArrayBuffer;
|
|
1142
|
-
}): IOEventMessage<TIO> | null {
|
|
1034
|
+
private _parseMessage(message: { data: string | ArrayBuffer }): IOEventMessage<TIO> | null {
|
|
1143
1035
|
/**
|
|
1144
1036
|
* !HACK
|
|
1145
1037
|
* @description We'll only handle stringified JSONs for now
|
|
@@ -1181,17 +1073,13 @@ export class PluvRoom<
|
|
|
1181
1073
|
await this.connect();
|
|
1182
1074
|
}
|
|
1183
1075
|
|
|
1184
|
-
private _sendMessage<
|
|
1185
|
-
TMessage extends EventMessage<string, any> = EventMessage<string, any>,
|
|
1186
|
-
>(data: TMessage): void {
|
|
1076
|
+
private _sendMessage<TMessage extends EventMessage<string, any> = EventMessage<string, any>>(data: TMessage): void {
|
|
1187
1077
|
const webSocket = this._state.webSocket;
|
|
1188
1078
|
|
|
1189
1079
|
if (webSocket?.readyState !== WebSocket.OPEN) return;
|
|
1190
1080
|
|
|
1191
1081
|
const shouldLog =
|
|
1192
|
-
typeof this._debug === "boolean"
|
|
1193
|
-
? this._debug
|
|
1194
|
-
: this._debug.input.find((value) => value === data.type);
|
|
1082
|
+
typeof this._debug === "boolean" ? this._debug : this._debug.input.find((value) => value === data.type);
|
|
1195
1083
|
|
|
1196
1084
|
if (shouldLog) {
|
|
1197
1085
|
this._logDebug("WebSocket event sent: ", data.type, data);
|
|
@@ -1200,15 +1088,11 @@ export class PluvRoom<
|
|
|
1200
1088
|
webSocket.send(JSON.stringify(data));
|
|
1201
1089
|
}
|
|
1202
1090
|
|
|
1203
|
-
private _updateState(
|
|
1204
|
-
updater: (oldState: WebSocketState<TIO>) => WebSocketState<TIO>,
|
|
1205
|
-
): WebSocketState<TIO> {
|
|
1091
|
+
private _updateState(updater: (oldState: WebSocketState<TIO>) => WebSocketState<TIO>): WebSocketState<TIO> {
|
|
1206
1092
|
let authorization: AuthorizationState<TIO>;
|
|
1207
1093
|
|
|
1208
1094
|
try {
|
|
1209
|
-
authorization = JSON.parse(
|
|
1210
|
-
JSON.stringify(this._state.authorization),
|
|
1211
|
-
);
|
|
1095
|
+
authorization = JSON.parse(JSON.stringify(this._state.authorization));
|
|
1212
1096
|
} catch {
|
|
1213
1097
|
throw new Error("User is not JSON serializable");
|
|
1214
1098
|
}
|
package/src/StateNotifier.ts
CHANGED
|
@@ -3,10 +3,7 @@ import type { Subject } from "wonka";
|
|
|
3
3
|
import { makeSubject, subscribe } from "wonka";
|
|
4
4
|
import type { UserInfo, WebSocketState } from "./types";
|
|
5
5
|
|
|
6
|
-
export interface StateNotifierSubjects<
|
|
7
|
-
TIO extends IOLike,
|
|
8
|
-
TPresence extends JsonObject,
|
|
9
|
-
> {
|
|
6
|
+
export interface StateNotifierSubjects<TIO extends IOLike, TPresence extends JsonObject> {
|
|
10
7
|
connection: Subject<Id<WebSocketState<TIO>>>;
|
|
11
8
|
"my-presence": Subject<TPresence | null>;
|
|
12
9
|
myself: Subject<Readonly<Id<UserInfo<TIO>>> | null>;
|
|
@@ -18,12 +15,7 @@ type InferSubjectValue<
|
|
|
18
15
|
TIO extends IOLike,
|
|
19
16
|
TPresence extends JsonObject,
|
|
20
17
|
TSubject extends keyof StateNotifierSubjects<TIO, TPresence>,
|
|
21
|
-
> =
|
|
22
|
-
StateNotifierSubjects<TIO, TPresence>[TSubject] extends Subject<
|
|
23
|
-
infer IValue
|
|
24
|
-
>
|
|
25
|
-
? Id<IValue>
|
|
26
|
-
: never;
|
|
18
|
+
> = StateNotifierSubjects<TIO, TPresence>[TSubject] extends Subject<infer IValue> ? Id<IValue> : never;
|
|
27
19
|
|
|
28
20
|
export type SubscriptionCallback<
|
|
29
21
|
TIO extends IOLike,
|
|
@@ -31,10 +23,7 @@ export type SubscriptionCallback<
|
|
|
31
23
|
TSubject extends keyof StateNotifierSubjects<TIO, TPresence>,
|
|
32
24
|
> = (value: InferSubjectValue<TIO, TPresence, TSubject>) => void;
|
|
33
25
|
|
|
34
|
-
export class StateNotifier<
|
|
35
|
-
TIO extends IOLike,
|
|
36
|
-
TPresence extends JsonObject = {},
|
|
37
|
-
> {
|
|
26
|
+
export class StateNotifier<TIO extends IOLike, TPresence extends JsonObject = {}> {
|
|
38
27
|
public subjects: StateNotifierSubjects<TIO, TPresence> = {
|
|
39
28
|
connection: makeSubject<Id<WebSocketState<TIO>>>(),
|
|
40
29
|
"my-presence": makeSubject<TPresence>(),
|
|
@@ -43,9 +32,7 @@ export class StateNotifier<
|
|
|
43
32
|
"storage-loaded": makeSubject<true>(),
|
|
44
33
|
};
|
|
45
34
|
|
|
46
|
-
public subscribe<
|
|
47
|
-
TSubject extends keyof StateNotifierSubjects<TIO, TPresence>,
|
|
48
|
-
>(
|
|
35
|
+
public subscribe<TSubject extends keyof StateNotifierSubjects<TIO, TPresence>>(
|
|
49
36
|
name: TSubject,
|
|
50
37
|
callback: SubscriptionCallback<TIO, TPresence, TSubject>,
|
|
51
38
|
): () => void {
|
package/src/UsersManager.ts
CHANGED
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
Id,
|
|
3
|
-
InferIOAuthorize,
|
|
4
|
-
InferIOAuthorizeUser,
|
|
5
|
-
InputZodLike,
|
|
6
|
-
IOLike,
|
|
7
|
-
JsonObject,
|
|
8
|
-
} from "@pluv/types";
|
|
1
|
+
import type { Id, InferIOAuthorize, InferIOAuthorizeUser, InputZodLike, IOLike, JsonObject } from "@pluv/types";
|
|
9
2
|
import type { UserInfo } from "./types";
|
|
10
3
|
|
|
11
4
|
export type Presence = Record<string, unknown>;
|
|
@@ -15,10 +8,7 @@ export type UsersManagerConfig<TPresence extends JsonObject = {}> = {
|
|
|
15
8
|
presence?: InputZodLike<TPresence>;
|
|
16
9
|
};
|
|
17
10
|
|
|
18
|
-
export class UsersManager<
|
|
19
|
-
TIO extends IOLike,
|
|
20
|
-
TPresence extends JsonObject = {},
|
|
21
|
-
> {
|
|
11
|
+
export class UsersManager<TIO extends IOLike, TPresence extends JsonObject = {}> {
|
|
22
12
|
private _initialPresence: TPresence;
|
|
23
13
|
/**
|
|
24
14
|
* @description This presence can be updated while the user is not
|
|
@@ -60,10 +50,7 @@ export class UsersManager<
|
|
|
60
50
|
return Array.from(this._others.values());
|
|
61
51
|
}
|
|
62
52
|
|
|
63
|
-
public patchPresence(
|
|
64
|
-
connectionId: string,
|
|
65
|
-
patch: Partial<TPresence>,
|
|
66
|
-
): void {
|
|
53
|
+
public patchPresence(connectionId: string, patch: Partial<TPresence>): void {
|
|
67
54
|
if (this._myself?.connectionId === connectionId) {
|
|
68
55
|
const presence = {
|
|
69
56
|
...this._myPresence,
|
|
@@ -98,10 +85,7 @@ export class UsersManager<
|
|
|
98
85
|
this._others.delete(connectionId);
|
|
99
86
|
}
|
|
100
87
|
|
|
101
|
-
public setMyself(
|
|
102
|
-
connectionId: string,
|
|
103
|
-
user: Id<InferIOAuthorizeUser<InferIOAuthorize<TIO>>>,
|
|
104
|
-
): void {
|
|
88
|
+
public setMyself(connectionId: string, user: Id<InferIOAuthorizeUser<InferIOAuthorize<TIO>>>): void {
|
|
105
89
|
this._myself = {
|
|
106
90
|
connectionId,
|
|
107
91
|
presence: this._initialPresence,
|