@pluv/client 2.0.1 → 2.1.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 +104 -0
- package/dist/index.d.mts +27 -25
- package/dist/index.d.ts +27 -25
- package/dist/index.js +214 -68
- package/dist/index.mjs +214 -68
- package/package.json +6 -6
- package/src/CrdtNotifier.ts +2 -1
- package/src/MockedRoom.ts +83 -33
- package/src/PluvRoom.ts +160 -58
- package/src/StateNotifier.ts +11 -17
- package/src/UsersNotifier.ts +66 -0
- package/src/OtherNotifier.ts +0 -50
package/src/PluvRoom.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AbstractCrdtDocFactory,
|
|
3
|
-
CrdtType,
|
|
4
3
|
InferCrdtJson,
|
|
5
4
|
InferStorage,
|
|
6
5
|
NoopCrdtDocFactory,
|
|
@@ -8,9 +7,11 @@ import type {
|
|
|
8
7
|
import type {
|
|
9
8
|
BaseIOEventRecord,
|
|
10
9
|
BaseUser,
|
|
10
|
+
BroadcastProxy,
|
|
11
11
|
CrdtDocLike,
|
|
12
12
|
EventMessage,
|
|
13
13
|
EventNotifierSubscriptionCallback,
|
|
14
|
+
EventProxy,
|
|
14
15
|
IOEventMessage,
|
|
15
16
|
IOLike,
|
|
16
17
|
Id,
|
|
@@ -22,8 +23,15 @@ import type {
|
|
|
22
23
|
JsonObject,
|
|
23
24
|
MergeEvents,
|
|
24
25
|
OptionalProps,
|
|
25
|
-
|
|
26
|
+
OtherSubscriptionCallback,
|
|
27
|
+
OthersSubscriptionCallback,
|
|
26
28
|
RoomLike,
|
|
29
|
+
StateNotifierSubjects,
|
|
30
|
+
StorageProxy,
|
|
31
|
+
StorageRootSubscriptionCallback,
|
|
32
|
+
StorageSubscriptionCallback,
|
|
33
|
+
SubscribeProxy,
|
|
34
|
+
SubscriptionCallback,
|
|
27
35
|
UpdateMyPresenceAction,
|
|
28
36
|
UserInfo,
|
|
29
37
|
WebSocketState,
|
|
@@ -34,11 +42,9 @@ import type { CrdtManagerOptions } from "./CrdtManager";
|
|
|
34
42
|
import { CrdtManager } from "./CrdtManager";
|
|
35
43
|
import { CrdtNotifier } from "./CrdtNotifier";
|
|
36
44
|
import { EventNotifier } from "./EventNotifier";
|
|
37
|
-
import { OtherNotifier } from "./OtherNotifier";
|
|
38
45
|
import { PluvProcedure } from "./PluvProcedure";
|
|
39
46
|
import type { PluvRouterEventConfig } from "./PluvRouter";
|
|
40
47
|
import { PluvRouter } from "./PluvRouter";
|
|
41
|
-
import type { StateNotifierSubjects, SubscriptionCallback } from "./StateNotifier";
|
|
42
48
|
import { StateNotifier } from "./StateNotifier";
|
|
43
49
|
import { StorageStore } from "./StorageStore";
|
|
44
50
|
import type {
|
|
@@ -53,6 +59,7 @@ import type {
|
|
|
53
59
|
} from "./types";
|
|
54
60
|
import type { UsersManagerConfig } from "./UsersManager";
|
|
55
61
|
import { UsersManager } from "./UsersManager";
|
|
62
|
+
import { UsersNotifier } from "./UsersNotifier";
|
|
56
63
|
import { debounce } from "./utils";
|
|
57
64
|
|
|
58
65
|
const ADD_TO_STORAGE_STATE_DEBOUNCE_MS = 1_000;
|
|
@@ -214,7 +221,6 @@ export class PluvRoom<
|
|
|
214
221
|
};
|
|
215
222
|
private readonly _limits: PluvClientLimits;
|
|
216
223
|
private readonly _listeners: InternalListeners;
|
|
217
|
-
private readonly _otherNotifier = new OtherNotifier<TIO, TPresence>();
|
|
218
224
|
private readonly _publicKey: PublicKey<TMetadata> | null = null;
|
|
219
225
|
private readonly _reconnectTimeoutMs: ReconnectTimeoutMs;
|
|
220
226
|
private readonly _router: PluvRouter<TIO, TPresence, InferStorage<TCrdt>, TEvents>;
|
|
@@ -228,6 +234,7 @@ export class PluvRoom<
|
|
|
228
234
|
reconnect: null,
|
|
229
235
|
};
|
|
230
236
|
private readonly _usersManager: UsersManager<TIO, TPresence>;
|
|
237
|
+
private readonly _usersNotifier = new UsersNotifier<TIO, TPresence>();
|
|
231
238
|
|
|
232
239
|
private _lastMetadata: TMetadata | null = null;
|
|
233
240
|
private _state: WebSocketState<TIO> = {
|
|
@@ -364,14 +371,7 @@ export class PluvRoom<
|
|
|
364
371
|
};
|
|
365
372
|
},
|
|
366
373
|
},
|
|
367
|
-
) as
|
|
368
|
-
event: TEvent,
|
|
369
|
-
data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[TEvent]>,
|
|
370
|
-
) => Promise<void>) & {
|
|
371
|
-
[PEvent in keyof InferIOInput<MergeEvents<TEvents, TIO>>]: (
|
|
372
|
-
data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[PEvent]>,
|
|
373
|
-
) => Promise<void>;
|
|
374
|
-
};
|
|
374
|
+
) as BroadcastProxy<TIO, TEvents>;
|
|
375
375
|
|
|
376
376
|
public canRedo = (): boolean => {
|
|
377
377
|
return !!this._crdtManager.doc.canRedo();
|
|
@@ -470,6 +470,9 @@ export class PluvRoom<
|
|
|
470
470
|
this._stateNotifier.subjects["my-presence"].next(null);
|
|
471
471
|
}
|
|
472
472
|
|
|
473
|
+
/**
|
|
474
|
+
* @deprecated To be removed in v3. Use PluvRoom.subscribe.event instead
|
|
475
|
+
*/
|
|
473
476
|
public event = new Proxy(
|
|
474
477
|
<TEvent extends keyof InferIOOutput<MergeEvents<TEvents, TIO>>>(
|
|
475
478
|
event: TEvent,
|
|
@@ -482,14 +485,7 @@ export class PluvRoom<
|
|
|
482
485
|
): (() => void) => fn(prop as any, callback);
|
|
483
486
|
},
|
|
484
487
|
},
|
|
485
|
-
) as
|
|
486
|
-
event: TEvent,
|
|
487
|
-
callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, TEvent>,
|
|
488
|
-
) => () => void) & {
|
|
489
|
-
[PEvent in keyof InferIOOutput<MergeEvents<TEvents, TIO>>]: (
|
|
490
|
-
callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, PEvent>,
|
|
491
|
-
) => () => void;
|
|
492
|
-
};
|
|
488
|
+
) as EventProxy<TIO, TEvents>;
|
|
493
489
|
|
|
494
490
|
public getConnection = (): WebSocketConnection => {
|
|
495
491
|
// Create a read-only clone of the connection state
|
|
@@ -542,42 +538,104 @@ export class PluvRoom<
|
|
|
542
538
|
return this._crdtManager.doc.toJson(type);
|
|
543
539
|
}
|
|
544
540
|
|
|
541
|
+
/**
|
|
542
|
+
* @deprecated To be removed in v3. Use PluvRoom.subscribe.other instead
|
|
543
|
+
*/
|
|
545
544
|
public other = (
|
|
546
545
|
connectionId: string,
|
|
547
|
-
callback:
|
|
546
|
+
callback: OtherSubscriptionCallback<TIO, TPresence>,
|
|
548
547
|
): (() => void) => {
|
|
549
548
|
const clientId = this._usersManager.getClientId(connectionId);
|
|
550
549
|
|
|
551
550
|
if (!clientId) return () => undefined;
|
|
552
551
|
|
|
553
|
-
return this.
|
|
552
|
+
return this._usersNotifier.subscribeOther(clientId, callback);
|
|
554
553
|
};
|
|
555
554
|
|
|
556
555
|
public redo = (): void => {
|
|
557
556
|
this._crdtManager.doc.redo();
|
|
558
557
|
};
|
|
559
558
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
559
|
+
/**
|
|
560
|
+
* @deprecated To be removed in v3. To be removed in v3. Use PluvRoom.subscribe.storage instead
|
|
561
|
+
*/
|
|
562
|
+
public storage = new Proxy(
|
|
563
|
+
<TKey extends keyof InferStorage<TCrdt>>(
|
|
564
|
+
key: TKey,
|
|
565
|
+
callback: StorageSubscriptionCallback<InferStorage<TCrdt>, TKey>,
|
|
566
|
+
): (() => void) => this._crdtNotifier.subscribe(key, callback),
|
|
567
|
+
{
|
|
568
|
+
get: (fn, prop) => {
|
|
569
|
+
type _Storage = InferStorage<TCrdt>;
|
|
566
570
|
|
|
571
|
+
if (!!prop) {
|
|
572
|
+
return (callback: StorageRootSubscriptionCallback<_Storage>) => {
|
|
573
|
+
return this._crdtNotifier.subcribeRoot(callback);
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
return (callback: StorageSubscriptionCallback<_Storage, keyof _Storage>) => {
|
|
578
|
+
return fn(prop as keyof InferStorage<TCrdt>, callback);
|
|
579
|
+
};
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
) as StorageProxy<InferStorage<TCrdt>>;
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* @deprecated To be removed in v3. Use PluvRoom.subscribe.storage instead
|
|
586
|
+
* @description Subscribes to the root storage object (serialized)
|
|
587
|
+
*/
|
|
567
588
|
public storageRoot = (
|
|
568
|
-
|
|
589
|
+
callback: (value: {
|
|
569
590
|
[P in keyof InferStorage<TCrdt>]: InferCrdtJson<InferStorage<TCrdt>[P]>;
|
|
570
591
|
}) => void,
|
|
571
592
|
): (() => void) => {
|
|
572
|
-
return this._crdtNotifier.subcribeRoot(
|
|
593
|
+
return this._crdtNotifier.subcribeRoot(callback);
|
|
573
594
|
};
|
|
574
595
|
|
|
575
|
-
public subscribe =
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
596
|
+
public subscribe = new Proxy(
|
|
597
|
+
<TSubject extends keyof StateNotifierSubjects<TIO, TPresence>>(
|
|
598
|
+
name: TSubject,
|
|
599
|
+
callback: SubscriptionCallback<TIO, TPresence, TSubject>,
|
|
600
|
+
): (() => void) => this._stateNotifier.subscribe(name, callback),
|
|
601
|
+
{
|
|
602
|
+
get: (fn, prop) => {
|
|
603
|
+
if (prop === "connection") {
|
|
604
|
+
return (callback: SubscriptionCallback<TIO, TPresence, "connection">) => {
|
|
605
|
+
return fn("connection", callback);
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
if (prop === "myPresence") {
|
|
610
|
+
return (callback: SubscriptionCallback<TIO, TPresence, "my-presence">) => {
|
|
611
|
+
return fn("my-presence", callback);
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (prop === "myself") {
|
|
616
|
+
return (callback: SubscriptionCallback<TIO, TPresence, "myself">) => {
|
|
617
|
+
return fn("myself", callback);
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (prop === "others") {
|
|
622
|
+
return (callback: OthersSubscriptionCallback<TIO, TPresence>) => {
|
|
623
|
+
return this._usersNotifier.subscribeOthers(callback);
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
if (prop === "storageLoaded") {
|
|
628
|
+
return (callback: SubscriptionCallback<TIO, TPresence, "storage-loaded">) => {
|
|
629
|
+
return fn("storage-loaded", callback);
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
if (prop === "event") return this.event;
|
|
634
|
+
if (prop === "other") return this.other;
|
|
635
|
+
if (prop === "storage") return this.storage;
|
|
636
|
+
},
|
|
637
|
+
},
|
|
638
|
+
) as SubscribeProxy<TIO, TPresence, InferStorage<TCrdt>, TEvents>;
|
|
581
639
|
|
|
582
640
|
public transact = (fn: (storage: InferStorage<TCrdt>) => void, origin?: string): void => {
|
|
583
641
|
const _origin = origin ?? this._state.connection.id;
|
|
@@ -679,9 +737,10 @@ export class PluvRoom<
|
|
|
679
737
|
this._detachWindowListeners();
|
|
680
738
|
this._usersManager.removeMyself();
|
|
681
739
|
this._usersManager.clearConnections();
|
|
682
|
-
this.
|
|
740
|
+
this._usersNotifier.clear();
|
|
683
741
|
this._stateNotifier.subjects.myself.next(null);
|
|
684
742
|
this._stateNotifier.subjects.others.next([]);
|
|
743
|
+
this._usersNotifier.others.next({ others: [], event: { kind: "clear" } });
|
|
685
744
|
this._state.webSocket?.close();
|
|
686
745
|
|
|
687
746
|
this._detachWsListeners();
|
|
@@ -841,13 +900,26 @@ export class PluvRoom<
|
|
|
841
900
|
|
|
842
901
|
this._stateNotifier.subjects.others.next(others);
|
|
843
902
|
|
|
903
|
+
// Should not reach here
|
|
904
|
+
if (!deleted) {
|
|
905
|
+
console.warn("Could not identify exited connection");
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
const { data: user, remaining } = deleted;
|
|
910
|
+
|
|
911
|
+
this._usersNotifier.others.next({
|
|
912
|
+
others,
|
|
913
|
+
event: { kind: "leave", user },
|
|
914
|
+
});
|
|
915
|
+
|
|
844
916
|
/**
|
|
845
|
-
* @description A single user can have multiple connections. So we're checking that
|
|
846
|
-
* isn't a remaining connection before choosing to delete that user for other
|
|
847
|
-
* participants.
|
|
917
|
+
* @description A single user can have multiple connections. So we're checking that
|
|
918
|
+
* there isn't a remaining connection before choosing to delete that user for other
|
|
919
|
+
* connected participants.
|
|
848
920
|
* @date April 16, 2025
|
|
849
921
|
*/
|
|
850
|
-
if (!
|
|
922
|
+
if (!remaining && !!clientId) this._usersNotifier.delete(clientId);
|
|
851
923
|
}
|
|
852
924
|
|
|
853
925
|
private _handlePresenceUpdatedMessage(message: IOEventMessage<TIO>): void {
|
|
@@ -886,27 +958,41 @@ export class PluvRoom<
|
|
|
886
958
|
|
|
887
959
|
if (!!clientId) {
|
|
888
960
|
const other = this._usersManager.getOther(connectionId);
|
|
961
|
+
const others = this._usersManager.getOthers();
|
|
889
962
|
|
|
890
|
-
this.
|
|
891
|
-
|
|
892
|
-
/**
|
|
893
|
-
* !HACK
|
|
894
|
-
* @description User could not be found. Add the connection to keep others up-to-date
|
|
895
|
-
* @date April 19, 2025
|
|
896
|
-
*/
|
|
897
|
-
const added = this._usersManager.addConnection({
|
|
898
|
-
connectionId,
|
|
899
|
-
presence: data.presence as TPresence,
|
|
900
|
-
user: message.user as Id<InferIOAuthorizeUser<InferIOAuthorize<TIO>>>,
|
|
901
|
-
});
|
|
902
|
-
const other = this._usersManager.getOther(connectionId);
|
|
963
|
+
this._usersNotifier.other(clientId).next(other);
|
|
964
|
+
this._stateNotifier.subjects.others.next(others);
|
|
903
965
|
|
|
904
|
-
|
|
966
|
+
if (!!other) {
|
|
967
|
+
this._usersNotifier.others.next({
|
|
968
|
+
others,
|
|
969
|
+
event: { kind: "update", user: other },
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
return;
|
|
905
974
|
}
|
|
906
975
|
|
|
976
|
+
/**
|
|
977
|
+
* !HACK
|
|
978
|
+
* @description User could not be found. Add the connection to keep others up-to-date
|
|
979
|
+
* @date April 19, 2025
|
|
980
|
+
*/
|
|
981
|
+
const added = this._usersManager.addConnection({
|
|
982
|
+
connectionId,
|
|
983
|
+
presence: data.presence as TPresence,
|
|
984
|
+
user: message.user as Id<InferIOAuthorizeUser<InferIOAuthorize<TIO>>>,
|
|
985
|
+
});
|
|
986
|
+
const other = this._usersManager.getOther(connectionId);
|
|
907
987
|
const others = this._usersManager.getOthers();
|
|
908
988
|
|
|
989
|
+
this._usersNotifier.other(added.clientId).next(other);
|
|
909
990
|
this._stateNotifier.subjects.others.next(others);
|
|
991
|
+
|
|
992
|
+
this._usersNotifier.others.next({
|
|
993
|
+
others,
|
|
994
|
+
event: { kind: "update", user: added.data },
|
|
995
|
+
});
|
|
910
996
|
}
|
|
911
997
|
|
|
912
998
|
private _handleReceiveOthers(message: IOEventMessage<TIO>): void {
|
|
@@ -931,12 +1017,16 @@ export class PluvRoom<
|
|
|
931
1017
|
const clientId = result.clientId;
|
|
932
1018
|
const other = this._usersManager.getOther(connectionId);
|
|
933
1019
|
|
|
934
|
-
this.
|
|
1020
|
+
this._usersNotifier.other(clientId).next(other);
|
|
935
1021
|
});
|
|
936
1022
|
|
|
937
1023
|
const others = this._usersManager.getOthers();
|
|
938
1024
|
|
|
939
1025
|
this._stateNotifier.subjects.others.next(others);
|
|
1026
|
+
this._usersNotifier.others.next({
|
|
1027
|
+
others,
|
|
1028
|
+
event: { kind: "sync", users: others },
|
|
1029
|
+
});
|
|
940
1030
|
}
|
|
941
1031
|
|
|
942
1032
|
private async _handleRegisteredMessage(message: IOEventMessage<TIO>): Promise<void> {
|
|
@@ -1083,6 +1173,13 @@ export class PluvRoom<
|
|
|
1083
1173
|
|
|
1084
1174
|
quitters.forEach((quitter) => {
|
|
1085
1175
|
this._usersManager.deleteConnection(quitter.connectionId);
|
|
1176
|
+
|
|
1177
|
+
const remaining = this._usersManager.getOthers();
|
|
1178
|
+
|
|
1179
|
+
this._usersNotifier.others.next({
|
|
1180
|
+
others: remaining,
|
|
1181
|
+
event: { kind: "leave", user: quitter },
|
|
1182
|
+
});
|
|
1086
1183
|
});
|
|
1087
1184
|
|
|
1088
1185
|
const remaining = this._usersManager.getOthers();
|
|
@@ -1115,8 +1212,13 @@ export class PluvRoom<
|
|
|
1115
1212
|
const other = this._usersManager.getOther(connectionId);
|
|
1116
1213
|
const others = this._usersManager.getOthers();
|
|
1117
1214
|
|
|
1118
|
-
this.
|
|
1215
|
+
this._usersNotifier.other(added.clientId).next(other);
|
|
1119
1216
|
this._stateNotifier.subjects.others.next(others);
|
|
1217
|
+
|
|
1218
|
+
this._usersNotifier.others.next({
|
|
1219
|
+
others,
|
|
1220
|
+
event: { kind: "enter", user: added.data },
|
|
1221
|
+
});
|
|
1120
1222
|
}
|
|
1121
1223
|
|
|
1122
1224
|
private _heartbeat(): void {
|
package/src/StateNotifier.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
Id,
|
|
3
|
+
IOLike,
|
|
4
|
+
JsonObject,
|
|
5
|
+
StateNotifierSubjects,
|
|
6
|
+
SubscriptionCallback,
|
|
7
|
+
UserInfo,
|
|
8
|
+
WebSocketState,
|
|
9
|
+
} from "@pluv/types";
|
|
2
10
|
import type { Subject } from "wonka";
|
|
3
11
|
import { makeSubject, subscribe } from "wonka";
|
|
4
12
|
|
|
5
|
-
export interface StateNotifierSubjects<TIO extends IOLike, TPresence extends JsonObject> {
|
|
6
|
-
connection: Subject<Id<WebSocketState<TIO>>>;
|
|
7
|
-
"my-presence": Subject<TPresence | null>;
|
|
8
|
-
myself: Subject<Readonly<Id<UserInfo<TIO>>> | null>;
|
|
9
|
-
others: Subject<readonly Id<UserInfo<TIO>>[]>;
|
|
10
|
-
"storage-loaded": Subject<boolean>;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
13
|
type InferSubjectValue<
|
|
14
14
|
TIO extends IOLike,
|
|
15
15
|
TPresence extends JsonObject,
|
|
@@ -19,18 +19,12 @@ type InferSubjectValue<
|
|
|
19
19
|
? Id<IValue>
|
|
20
20
|
: never;
|
|
21
21
|
|
|
22
|
-
export type SubscriptionCallback<
|
|
23
|
-
TIO extends IOLike,
|
|
24
|
-
TPresence extends JsonObject,
|
|
25
|
-
TSubject extends keyof StateNotifierSubjects<TIO, TPresence>,
|
|
26
|
-
> = (value: InferSubjectValue<TIO, TPresence, TSubject>) => void;
|
|
27
|
-
|
|
28
22
|
export class StateNotifier<TIO extends IOLike, TPresence extends JsonObject = {}> {
|
|
29
23
|
public subjects: StateNotifierSubjects<TIO, TPresence> = {
|
|
30
24
|
connection: makeSubject<Id<WebSocketState<TIO>>>(),
|
|
31
25
|
"my-presence": makeSubject<TPresence>(),
|
|
32
|
-
myself: makeSubject<Readonly<Id<UserInfo<TIO>>> | null>(),
|
|
33
|
-
others: makeSubject<readonly Id<UserInfo<TIO>>[]>(),
|
|
26
|
+
myself: makeSubject<Readonly<Id<UserInfo<TIO, TPresence>>> | null>(),
|
|
27
|
+
others: makeSubject<readonly Id<UserInfo<TIO, TPresence>>[]>(),
|
|
34
28
|
"storage-loaded": makeSubject<true>(),
|
|
35
29
|
};
|
|
36
30
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Id,
|
|
3
|
+
IOLike,
|
|
4
|
+
JsonObject,
|
|
5
|
+
OtherSubscriptionCallback,
|
|
6
|
+
OthersSubscriptionCallback,
|
|
7
|
+
OthersSubscriptionEvent,
|
|
8
|
+
UserInfo,
|
|
9
|
+
} from "@pluv/types";
|
|
10
|
+
import type { Subject } from "wonka";
|
|
11
|
+
import { makeSubject, subscribe, TypeOfSource } from "wonka";
|
|
12
|
+
|
|
13
|
+
export class UsersNotifier<TIO extends IOLike, TPresence extends JsonObject = {}> {
|
|
14
|
+
public readonly others = makeSubject<{
|
|
15
|
+
others: readonly Id<UserInfo<TIO, TPresence>>[];
|
|
16
|
+
event: OthersSubscriptionEvent<TIO, TPresence>;
|
|
17
|
+
}>();
|
|
18
|
+
|
|
19
|
+
private _otherSubjects = new Map<
|
|
20
|
+
[clientId: string][0],
|
|
21
|
+
Subject<Id<UserInfo<TIO, TPresence>> | null>
|
|
22
|
+
>();
|
|
23
|
+
|
|
24
|
+
public clear(): void {
|
|
25
|
+
this._otherSubjects.forEach((subject) => {
|
|
26
|
+
subject.next(null);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
this._otherSubjects.clear();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public delete(clientId: string): void {
|
|
33
|
+
this.other(clientId).next(null);
|
|
34
|
+
this._otherSubjects.delete(clientId);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public other(clientId: string): Subject<Id<UserInfo<TIO, TPresence>> | null> {
|
|
38
|
+
const subject = this._otherSubjects.get(clientId);
|
|
39
|
+
|
|
40
|
+
if (subject) return subject;
|
|
41
|
+
|
|
42
|
+
const newSubject = makeSubject<Id<UserInfo<TIO, TPresence>>>();
|
|
43
|
+
|
|
44
|
+
this._otherSubjects.set(clientId, newSubject);
|
|
45
|
+
|
|
46
|
+
return newSubject;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public subscribeOther(
|
|
50
|
+
clientId: string,
|
|
51
|
+
callback: OtherSubscriptionCallback<TIO, TPresence>,
|
|
52
|
+
): () => void {
|
|
53
|
+
const source = this.other(clientId).source;
|
|
54
|
+
const subscription = subscribe(callback)(source);
|
|
55
|
+
|
|
56
|
+
return subscription.unsubscribe;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public subscribeOthers(callback: OthersSubscriptionCallback<TIO, TPresence>): () => void {
|
|
60
|
+
const subscription = subscribe<TypeOfSource<typeof this.others.source>>(
|
|
61
|
+
({ others, event }) => callback(others, event),
|
|
62
|
+
)(this.others.source);
|
|
63
|
+
|
|
64
|
+
return subscription.unsubscribe;
|
|
65
|
+
}
|
|
66
|
+
}
|
package/src/OtherNotifier.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
Id,
|
|
3
|
-
IOLike,
|
|
4
|
-
JsonObject,
|
|
5
|
-
OtherNotifierSubscriptionCallback,
|
|
6
|
-
UserInfo,
|
|
7
|
-
} from "@pluv/types";
|
|
8
|
-
import type { Subject } from "wonka";
|
|
9
|
-
import { makeSubject, subscribe } from "wonka";
|
|
10
|
-
|
|
11
|
-
export class OtherNotifier<TIO extends IOLike, TPresence extends JsonObject = {}> {
|
|
12
|
-
private _subjects = new Map<
|
|
13
|
-
[clientId: string][0],
|
|
14
|
-
Subject<Id<UserInfo<TIO, TPresence>> | null>
|
|
15
|
-
>();
|
|
16
|
-
|
|
17
|
-
public clear(): void {
|
|
18
|
-
this._subjects.forEach((subject) => {
|
|
19
|
-
subject.next(null);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
this._subjects.clear();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
public delete(clientId: string): void {
|
|
26
|
-
this.subject(clientId).next(null);
|
|
27
|
-
this._subjects.delete(clientId);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
public subject(clientId: string): Subject<Id<UserInfo<TIO, TPresence>> | null> {
|
|
31
|
-
const subject = this._subjects.get(clientId);
|
|
32
|
-
|
|
33
|
-
if (subject) return subject;
|
|
34
|
-
|
|
35
|
-
const newSubject = makeSubject<Id<UserInfo<TIO, TPresence>>>();
|
|
36
|
-
|
|
37
|
-
this._subjects.set(clientId, newSubject);
|
|
38
|
-
|
|
39
|
-
return newSubject;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
public subscribe(
|
|
43
|
-
clientId: string,
|
|
44
|
-
callback: OtherNotifierSubscriptionCallback<TIO, TPresence>,
|
|
45
|
-
): () => void {
|
|
46
|
-
const source = this.subject(clientId).source;
|
|
47
|
-
|
|
48
|
-
return subscribe(callback)(source).unsubscribe;
|
|
49
|
-
}
|
|
50
|
-
}
|