@pluv/client 1.0.0 → 1.0.2
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 +16 -0
- package/dist/index.d.mts +0 -2
- package/dist/index.d.ts +0 -2
- package/dist/index.js +127 -95
- package/dist/index.mjs +127 -95
- package/package.json +5 -5
- package/src/AbstractRoom.ts +3 -1
- package/src/CrdtManager.ts +53 -31
- package/src/CrdtNotifier.ts +4 -1
- package/src/EventNotifier.ts +11 -5
- package/src/MockedRoom.ts +21 -11
- package/src/OtherNotifier.ts +8 -2
- package/src/PluvClient.ts +3 -1
- package/src/PluvProcedure.ts +6 -2
- package/src/PluvRoom.ts +116 -72
- package/src/PluvRouter.ts +4 -1
- package/src/StateNotifier.ts +4 -1
- package/src/UsersManager.ts +23 -5
- package/src/index.ts +8 -1
- package/src/infer.ts +3 -1
- package/src/register.ts +2 -1
- package/src/types.ts +7 -2
- package/src/utils/pickBy.ts +4 -1
package/src/MockedRoom.ts
CHANGED
|
@@ -86,8 +86,13 @@ export class MockedRoom<
|
|
|
86
86
|
presenceMaxSize: MAX_PRESENCE_SIZE_BYTES,
|
|
87
87
|
...limits,
|
|
88
88
|
};
|
|
89
|
-
this._router =
|
|
90
|
-
|
|
89
|
+
this._router =
|
|
90
|
+
router ?? (new PluvRouter({}) as PluvRouter<TIO, TPresence, TStorage, TEvents>);
|
|
91
|
+
this._usersManager = new UsersManager<TIO, TPresence>({
|
|
92
|
+
initialPresence,
|
|
93
|
+
limits: this._limits,
|
|
94
|
+
presence,
|
|
95
|
+
});
|
|
91
96
|
|
|
92
97
|
this._crdtManager = new CrdtManager<TStorage>({
|
|
93
98
|
initialStorage,
|
|
@@ -136,10 +141,9 @@ export class MockedRoom<
|
|
|
136
141
|
user: myself,
|
|
137
142
|
};
|
|
138
143
|
|
|
139
|
-
const output = await (
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
);
|
|
144
|
+
const output = await (
|
|
145
|
+
procedure.config.broadcast as EventResolver<TIO, any, any, TPresence, TStorage>
|
|
146
|
+
)(parsed, context);
|
|
143
147
|
|
|
144
148
|
Object.entries(output).forEach(([_type, _data]) => {
|
|
145
149
|
this._simulateEvent(_type as TEvent, _data as any);
|
|
@@ -147,7 +151,9 @@ export class MockedRoom<
|
|
|
147
151
|
},
|
|
148
152
|
{
|
|
149
153
|
get(fn, prop) {
|
|
150
|
-
return async (
|
|
154
|
+
return async (
|
|
155
|
+
data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[any]>,
|
|
156
|
+
): Promise<void> => {
|
|
151
157
|
return await fn(prop, data);
|
|
152
158
|
};
|
|
153
159
|
},
|
|
@@ -174,8 +180,9 @@ export class MockedRoom<
|
|
|
174
180
|
): (() => void) => this._eventNotifier.subscribe(event, callback),
|
|
175
181
|
{
|
|
176
182
|
get(fn, prop) {
|
|
177
|
-
return (
|
|
178
|
-
|
|
183
|
+
return (
|
|
184
|
+
callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, any>,
|
|
185
|
+
): (() => void) => fn(prop as any, callback);
|
|
179
186
|
},
|
|
180
187
|
},
|
|
181
188
|
) as (<TEvent extends keyof InferIOOutput<MergeEvents<TEvents, TIO>>>(
|
|
@@ -221,7 +228,9 @@ export class MockedRoom<
|
|
|
221
228
|
};
|
|
222
229
|
|
|
223
230
|
public getStorageJson(): InferCrdtJson<TStorage> | null;
|
|
224
|
-
public getStorageJson<TKey extends keyof TStorage>(
|
|
231
|
+
public getStorageJson<TKey extends keyof TStorage>(
|
|
232
|
+
type: TKey,
|
|
233
|
+
): InferCrdtJson<TStorage[TKey]> | null;
|
|
225
234
|
public getStorageJson<TKey extends keyof TStorage>(type?: TKey) {
|
|
226
235
|
if (this._state.connection.id === null) return null;
|
|
227
236
|
|
|
@@ -293,7 +302,8 @@ export class MockedRoom<
|
|
|
293
302
|
};
|
|
294
303
|
|
|
295
304
|
public updateMyPresence = (presence: UpdateMyPresenceAction<TPresence>): void => {
|
|
296
|
-
const newPresence =
|
|
305
|
+
const newPresence =
|
|
306
|
+
typeof presence === "function" ? presence(this.getMyPresence()) : presence;
|
|
297
307
|
|
|
298
308
|
this._usersManager.updateMyPresence(newPresence);
|
|
299
309
|
|
package/src/OtherNotifier.ts
CHANGED
|
@@ -8,7 +8,10 @@ export type OtherNotifierSubscriptionCallback<TIO extends IOLike, TPresence exte
|
|
|
8
8
|
) => void;
|
|
9
9
|
|
|
10
10
|
export class OtherNotifier<TIO extends IOLike, TPresence extends JsonObject = {}> {
|
|
11
|
-
private _subjects = new Map<
|
|
11
|
+
private _subjects = new Map<
|
|
12
|
+
[clientId: string][0],
|
|
13
|
+
Subject<Id<UserInfo<TIO, TPresence>> | null>
|
|
14
|
+
>();
|
|
12
15
|
|
|
13
16
|
public clear(): void {
|
|
14
17
|
this._subjects.forEach((subject) => {
|
|
@@ -35,7 +38,10 @@ export class OtherNotifier<TIO extends IOLike, TPresence extends JsonObject = {}
|
|
|
35
38
|
return newSubject;
|
|
36
39
|
}
|
|
37
40
|
|
|
38
|
-
public subscribe(
|
|
41
|
+
public subscribe(
|
|
42
|
+
clientId: string,
|
|
43
|
+
callback: OtherNotifierSubscriptionCallback<TIO, TPresence>,
|
|
44
|
+
): () => void {
|
|
39
45
|
const source = this.subject(clientId).source;
|
|
40
46
|
|
|
41
47
|
return subscribe(callback)(source).unsubscribe;
|
package/src/PluvClient.ts
CHANGED
|
@@ -158,7 +158,9 @@ export class PluvClient<
|
|
|
158
158
|
};
|
|
159
159
|
|
|
160
160
|
public getRoom = (room: string): PluvRoom<TIO, TMetadata, TPresence, TStorage, any> | null => {
|
|
161
|
-
const found = this._rooms.get(room) as
|
|
161
|
+
const found = this._rooms.get(room) as
|
|
162
|
+
| PluvRoom<TIO, TMetadata, TPresence, TStorage, any>
|
|
163
|
+
| undefined;
|
|
162
164
|
|
|
163
165
|
return found ?? null;
|
|
164
166
|
};
|
package/src/PluvProcedure.ts
CHANGED
|
@@ -21,7 +21,8 @@ export class PluvProcedure<
|
|
|
21
21
|
TStorage extends Record<string, CrdtType<any, any>> = {},
|
|
22
22
|
TFilled extends "input" | "broadcast" | "" = "",
|
|
23
23
|
> {
|
|
24
|
-
private _broadcast: EventResolver<TIO, TInput, Partial<TOutput>, TPresence, TStorage> | null =
|
|
24
|
+
private _broadcast: EventResolver<TIO, TInput, Partial<TOutput>, TPresence, TStorage> | null =
|
|
25
|
+
null;
|
|
25
26
|
private _input: InputZodLike<TInput> | null = null;
|
|
26
27
|
|
|
27
28
|
public get config(): ProcedureLike<TInput, TOutput>["config"] {
|
|
@@ -71,7 +72,10 @@ export class PluvProcedure<
|
|
|
71
72
|
|
|
72
73
|
public input<TData extends JsonObject>(
|
|
73
74
|
input: InputZodLike<TData>,
|
|
74
|
-
): Omit<
|
|
75
|
+
): Omit<
|
|
76
|
+
PluvProcedure<TIO, TData, {}, TPresence, TStorage, TFilled | "input">,
|
|
77
|
+
TFilled | "input"
|
|
78
|
+
> {
|
|
75
79
|
return new PluvProcedure<TIO, TData, {}, TPresence, TStorage, TFilled | "input">({ input });
|
|
76
80
|
}
|
|
77
81
|
|
package/src/PluvRoom.ts
CHANGED
|
@@ -109,7 +109,9 @@ export type AuthEndpoint<TMetadata extends JsonObject> =
|
|
|
109
109
|
| string
|
|
110
110
|
| ((params: EndpointParams<TMetadata>) => string | FetchOptions)
|
|
111
111
|
| true;
|
|
112
|
-
export type WsEndpoint<TMetadata extends JsonObject> =
|
|
112
|
+
export type WsEndpoint<TMetadata extends JsonObject> =
|
|
113
|
+
| string
|
|
114
|
+
| ((params: EndpointParams<TMetadata>) => string);
|
|
113
115
|
|
|
114
116
|
type FetchOptions = { url: string; options?: RequestInit };
|
|
115
117
|
|
|
@@ -128,7 +130,9 @@ export type PluvRoomAddon<
|
|
|
128
130
|
TMetadata extends JsonObject = {},
|
|
129
131
|
TPresence extends JsonObject = {},
|
|
130
132
|
TStorage extends Record<string, CrdtType<any, any>> = {},
|
|
131
|
-
> = (
|
|
133
|
+
> = (
|
|
134
|
+
input: PluvRoomAddonInput<TIO, TMetadata, TPresence, TStorage>,
|
|
135
|
+
) => Partial<PluvRoomAddonResult>;
|
|
132
136
|
|
|
133
137
|
export interface PluvRoomAddonInput<
|
|
134
138
|
TIO extends IOLike = IOLike,
|
|
@@ -276,8 +280,13 @@ export class PluvRoom<
|
|
|
276
280
|
},
|
|
277
281
|
};
|
|
278
282
|
|
|
279
|
-
this._router =
|
|
280
|
-
|
|
283
|
+
this._router =
|
|
284
|
+
router ?? (new PluvRouter({}) as PluvRouter<TIO, TPresence, TStorage, TEvents>);
|
|
285
|
+
this._usersManager = new UsersManager<TIO, TPresence>({
|
|
286
|
+
initialPresence,
|
|
287
|
+
limits: this._limits,
|
|
288
|
+
presence,
|
|
289
|
+
});
|
|
281
290
|
this._crdtManager = new CrdtManager<TStorage>({ initialStorage });
|
|
282
291
|
}
|
|
283
292
|
|
|
@@ -325,10 +334,9 @@ export class PluvRoom<
|
|
|
325
334
|
user: myself,
|
|
326
335
|
};
|
|
327
336
|
|
|
328
|
-
const output = await (
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
);
|
|
337
|
+
const output = await (
|
|
338
|
+
procedure.config.broadcast as EventResolver<TIO, any, any, TPresence, TStorage>
|
|
339
|
+
)(parsed, context);
|
|
332
340
|
|
|
333
341
|
Object.entries(output).forEach(([_type, _data]) => {
|
|
334
342
|
this._sendMessage({ data: _data, type: _type });
|
|
@@ -336,7 +344,9 @@ export class PluvRoom<
|
|
|
336
344
|
},
|
|
337
345
|
{
|
|
338
346
|
get(fn, prop) {
|
|
339
|
-
return async (
|
|
347
|
+
return async (
|
|
348
|
+
data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[any]>,
|
|
349
|
+
): Promise<void> => {
|
|
340
350
|
return await fn(prop, data);
|
|
341
351
|
};
|
|
342
352
|
},
|
|
@@ -364,9 +374,11 @@ export class PluvRoom<
|
|
|
364
374
|
|
|
365
375
|
this._setMetadata(metadata);
|
|
366
376
|
|
|
367
|
-
const canConnect = [
|
|
368
|
-
|
|
369
|
-
|
|
377
|
+
const canConnect = [
|
|
378
|
+
ConnectionState.Closed,
|
|
379
|
+
ConnectionState.Unavailable,
|
|
380
|
+
ConnectionState.Untouched,
|
|
381
|
+
].some((state) => this._state.connection.state === state);
|
|
370
382
|
|
|
371
383
|
if (!canConnect) return;
|
|
372
384
|
|
|
@@ -378,7 +390,6 @@ export class PluvRoom<
|
|
|
378
390
|
});
|
|
379
391
|
|
|
380
392
|
await this._storageStore.initialize();
|
|
381
|
-
await this._applyStorageStore();
|
|
382
393
|
|
|
383
394
|
const wsEndpoint = this._getWsEndpoint(this.id, params);
|
|
384
395
|
const url = new URL(wsEndpoint);
|
|
@@ -453,8 +464,9 @@ export class PluvRoom<
|
|
|
453
464
|
): (() => void) => this._eventNotifier.subscribe(event, callback),
|
|
454
465
|
{
|
|
455
466
|
get(fn, prop) {
|
|
456
|
-
return (
|
|
457
|
-
|
|
467
|
+
return (
|
|
468
|
+
callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, any>,
|
|
469
|
+
): (() => void) => fn(prop as any, callback);
|
|
458
470
|
},
|
|
459
471
|
},
|
|
460
472
|
) as (<TEvent extends keyof InferIOOutput<MergeEvents<TEvents, TIO>>>(
|
|
@@ -500,7 +512,9 @@ export class PluvRoom<
|
|
|
500
512
|
};
|
|
501
513
|
|
|
502
514
|
public getStorageJson(): InferCrdtJson<TStorage> | null;
|
|
503
|
-
public getStorageJson<TKey extends keyof TStorage>(
|
|
515
|
+
public getStorageJson<TKey extends keyof TStorage>(
|
|
516
|
+
type: TKey,
|
|
517
|
+
): InferCrdtJson<TStorage[TKey]> | null;
|
|
504
518
|
public getStorageJson<TKey extends keyof TStorage>(type?: TKey) {
|
|
505
519
|
if (this._state.connection.id === null) return null;
|
|
506
520
|
|
|
@@ -574,7 +588,8 @@ export class PluvRoom<
|
|
|
574
588
|
};
|
|
575
589
|
|
|
576
590
|
public updateMyPresence = (presence: UpdateMyPresenceAction<TPresence>): void => {
|
|
577
|
-
const newPresence =
|
|
591
|
+
const newPresence =
|
|
592
|
+
typeof presence === "function" ? presence(this.getMyPresence()) : presence;
|
|
578
593
|
|
|
579
594
|
this._usersManager.updateMyPresence(newPresence);
|
|
580
595
|
|
|
@@ -596,22 +611,6 @@ export class PluvRoom<
|
|
|
596
611
|
this._flattenStorageStore();
|
|
597
612
|
}
|
|
598
613
|
|
|
599
|
-
private async _applyStorageStore(): Promise<void> {
|
|
600
|
-
const updates = await this._storageStore.getUpdates();
|
|
601
|
-
|
|
602
|
-
this._crdtManager.initialize({
|
|
603
|
-
onInitialized: () => {
|
|
604
|
-
this._stateNotifier.subjects["storage-loaded"].next(true);
|
|
605
|
-
|
|
606
|
-
this._observeCrdt();
|
|
607
|
-
},
|
|
608
|
-
origin: ORIGIN_INITIALIZED,
|
|
609
|
-
update: updates,
|
|
610
|
-
});
|
|
611
|
-
|
|
612
|
-
this._emitSharedTypes();
|
|
613
|
-
}
|
|
614
|
-
|
|
615
614
|
private _attachWindowListeners(): void {
|
|
616
615
|
if (typeof window === "undefined") return;
|
|
617
616
|
if (typeof document === "undefined") return;
|
|
@@ -722,7 +721,7 @@ export class PluvRoom<
|
|
|
722
721
|
|
|
723
722
|
const updates = await this._storageStore.getUpdates();
|
|
724
723
|
|
|
725
|
-
this._crdtManager.applyUpdate(updates, this);
|
|
724
|
+
this._crdtManager.applyUpdate({ update: updates, origin: this });
|
|
726
725
|
|
|
727
726
|
const encodedState = this._crdtManager.doc.getEncodedState();
|
|
728
727
|
|
|
@@ -743,7 +742,10 @@ export class PluvRoom<
|
|
|
743
742
|
);
|
|
744
743
|
};
|
|
745
744
|
|
|
746
|
-
private _getAuthFetchOptions(
|
|
745
|
+
private _getAuthFetchOptions(
|
|
746
|
+
room: string,
|
|
747
|
+
params: GetAuthEndpointParams<TMetadata>,
|
|
748
|
+
): FetchOptions | null {
|
|
747
749
|
const metadata = params.metadata as TMetadata;
|
|
748
750
|
|
|
749
751
|
if (typeof this._endpoints.authEndpoint === "undefined") return null;
|
|
@@ -767,7 +769,10 @@ export class PluvRoom<
|
|
|
767
769
|
return typeof result === "string" ? { url: result, options: {} } : result;
|
|
768
770
|
}
|
|
769
771
|
|
|
770
|
-
private async _getAuthorization(
|
|
772
|
+
private async _getAuthorization(
|
|
773
|
+
room: string,
|
|
774
|
+
params: GetAuthEndpointParams<TMetadata>,
|
|
775
|
+
): Promise<string | null> {
|
|
771
776
|
const fetchOptions = this._getAuthFetchOptions(room, params);
|
|
772
777
|
|
|
773
778
|
if (!fetchOptions) return null;
|
|
@@ -798,7 +803,9 @@ export class PluvRoom<
|
|
|
798
803
|
|
|
799
804
|
switch (typeof this._endpoints.wsEndpoint) {
|
|
800
805
|
case "undefined":
|
|
801
|
-
return !!this._getPublicKey(params)
|
|
806
|
+
return !!this._getPublicKey(params)
|
|
807
|
+
? `wss://rooms.pluv.io/api/room/${room}`
|
|
808
|
+
: `/api/pluv/room/${room}`;
|
|
802
809
|
case "string":
|
|
803
810
|
return this._endpoints.wsEndpoint;
|
|
804
811
|
default:
|
|
@@ -917,7 +924,7 @@ export class PluvRoom<
|
|
|
917
924
|
this._stateNotifier.subjects.others.next(others);
|
|
918
925
|
}
|
|
919
926
|
|
|
920
|
-
private _handleRegisteredMessage(message: IOEventMessage<TIO>): void {
|
|
927
|
+
private async _handleRegisteredMessage(message: IOEventMessage<TIO>): Promise<void> {
|
|
921
928
|
const { connectionId } = message;
|
|
922
929
|
const user = message.user as Id<InferIOAuthorizeUser<InferIOAuthorize<TIO>>>;
|
|
923
930
|
|
|
@@ -926,6 +933,7 @@ export class PluvRoom<
|
|
|
926
933
|
if (!this._state.webSocket) throw new Error("Could not find WebSocket");
|
|
927
934
|
|
|
928
935
|
const data = message.data as BaseIOEventRecord<InferIOAuthorize<TIO>>["$registered"];
|
|
936
|
+
const state = data.state;
|
|
929
937
|
|
|
930
938
|
this._updateState((oldState) => {
|
|
931
939
|
oldState.connection.count += 1;
|
|
@@ -948,7 +956,10 @@ export class PluvRoom<
|
|
|
948
956
|
this._stateNotifier.subjects["my-presence"].next(presence);
|
|
949
957
|
this._stateNotifier.subjects.myself.next(myself);
|
|
950
958
|
|
|
951
|
-
const update =
|
|
959
|
+
const update = !state
|
|
960
|
+
? (this._crdtManager.resolveEncodedState(await this._storageStore.getUpdates()) ??
|
|
961
|
+
this._crdtManager.getInitialState())
|
|
962
|
+
: this._crdtManager.getInitialState();
|
|
952
963
|
|
|
953
964
|
this._sendMessage({
|
|
954
965
|
type: "$initializeSession",
|
|
@@ -964,30 +975,45 @@ export class PluvRoom<
|
|
|
964
975
|
if (!this._state.webSocket) throw new Error("Could not find WebSocket");
|
|
965
976
|
|
|
966
977
|
const data = message.data as BaseIOEventRecord<InferIOAuthorize<TIO>>["$storageReceived"];
|
|
978
|
+
const changeKind = data.changeKind;
|
|
979
|
+
const state = data.state;
|
|
967
980
|
|
|
968
|
-
this.
|
|
969
|
-
|
|
970
|
-
this._addToStorageStore(update);
|
|
971
|
-
this._emitSharedTypes();
|
|
981
|
+
const updates = await this._storageStore.getUpdates();
|
|
982
|
+
const origin = ORIGIN_INITIALIZED;
|
|
972
983
|
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
});
|
|
984
|
+
const updateStorage = (update: string | readonly string[]) => {
|
|
985
|
+
return this._crdtManager.initialized
|
|
986
|
+
? this._crdtManager.applyUpdate({ update, origin })
|
|
987
|
+
: this._crdtManager.initialize({ update, origin });
|
|
988
|
+
};
|
|
979
989
|
|
|
980
|
-
|
|
990
|
+
if (changeKind === "empty") {
|
|
991
|
+
this._crdtManager.destroy();
|
|
992
|
+
this._crdtManager.initialize({ origin, update: updates });
|
|
993
|
+
} else if (changeKind === "initialized") {
|
|
994
|
+
if (!!updates.length) updateStorage(updates);
|
|
995
|
+
else {
|
|
996
|
+
const encodedState = updateStorage(state).doc.getEncodedState();
|
|
981
997
|
|
|
982
|
-
|
|
998
|
+
this._addToStorageStore(encodedState);
|
|
999
|
+
}
|
|
1000
|
+
} else {
|
|
1001
|
+
const encodedState = updateStorage(state).doc.getEncodedState();
|
|
983
1002
|
|
|
984
|
-
|
|
1003
|
+
if (!!updates.length) this._crdtManager.applyUpdate({ update: updates, origin });
|
|
985
1004
|
|
|
986
|
-
|
|
1005
|
+
await this._addToStorageStore(encodedState);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
const encodedState = this._crdtManager.doc.getEncodedState();
|
|
1009
|
+
|
|
1010
|
+
this._emitSharedTypes();
|
|
1011
|
+
this._observeCrdt();
|
|
1012
|
+
this._stateNotifier.subjects["storage-loaded"].next(true);
|
|
987
1013
|
|
|
988
1014
|
this._sendMessage({
|
|
989
1015
|
type: "$updateStorage",
|
|
990
|
-
data: { origin
|
|
1016
|
+
data: { origin, update: encodedState },
|
|
991
1017
|
});
|
|
992
1018
|
}
|
|
993
1019
|
|
|
@@ -998,9 +1024,9 @@ export class PluvRoom<
|
|
|
998
1024
|
// Should not reach here
|
|
999
1025
|
if (!this._state.webSocket) throw new Error("Could not find WebSocket");
|
|
1000
1026
|
|
|
1001
|
-
const data = message.data as BaseIOEventRecord<
|
|
1002
|
-
|
|
1003
|
-
|
|
1027
|
+
const data = message.data as BaseIOEventRecord<
|
|
1028
|
+
InferIOAuthorize<TIO>
|
|
1029
|
+
>[typeof ORIGIN_STORAGE_UPDATED];
|
|
1004
1030
|
|
|
1005
1031
|
this._crdtManager.doc.applyEncodedState({
|
|
1006
1032
|
origin: ORIGIN_STORAGE_UPDATED,
|
|
@@ -1011,8 +1037,6 @@ export class PluvRoom<
|
|
|
1011
1037
|
|
|
1012
1038
|
const storageRoot = Object.keys(sharedTypes).reduce(
|
|
1013
1039
|
(acc, prop) => {
|
|
1014
|
-
if (!this._crdtManager) return acc;
|
|
1015
|
-
|
|
1016
1040
|
const serialized = this._crdtManager.doc.toJson(prop);
|
|
1017
1041
|
|
|
1018
1042
|
this._crdtNotifier.subject(prop).next(serialized);
|
|
@@ -1038,7 +1062,9 @@ export class PluvRoom<
|
|
|
1038
1062
|
const data = message.data as BaseIOEventRecord<InferIOAuthorize<TIO>>["$syncStateReceived"];
|
|
1039
1063
|
const active = new Set(data?.connectionIds ?? []);
|
|
1040
1064
|
|
|
1041
|
-
const quitters = this._usersManager
|
|
1065
|
+
const quitters = this._usersManager
|
|
1066
|
+
.getOthers()
|
|
1067
|
+
.filter((other) => !active.has(other.connectionId));
|
|
1042
1068
|
|
|
1043
1069
|
quitters.forEach((quitter) => {
|
|
1044
1070
|
this._usersManager.deleteConnection(quitter.connectionId);
|
|
@@ -1080,7 +1106,10 @@ export class PluvRoom<
|
|
|
1080
1106
|
|
|
1081
1107
|
private _heartbeat(): void {
|
|
1082
1108
|
this._clearTimeout(this._timeouts.pong);
|
|
1083
|
-
this._timeouts.pong = setTimeout(
|
|
1109
|
+
this._timeouts.pong = setTimeout(
|
|
1110
|
+
this._reconnect.bind(this),
|
|
1111
|
+
PONG_TIMEOUT_MS,
|
|
1112
|
+
) as unknown as number;
|
|
1084
1113
|
|
|
1085
1114
|
/**
|
|
1086
1115
|
* !HACK
|
|
@@ -1099,18 +1128,17 @@ export class PluvRoom<
|
|
|
1099
1128
|
private _observeCrdt(): void {
|
|
1100
1129
|
this._subscriptions.observeCrdt?.();
|
|
1101
1130
|
|
|
1102
|
-
if (!this._crdtManager) return;
|
|
1103
|
-
|
|
1104
1131
|
const unsubscribe = this._crdtManager.doc.subscribe((event) => {
|
|
1105
1132
|
const origin = event.origin ?? null;
|
|
1106
1133
|
|
|
1107
|
-
this._addToStorageStore(event.update);
|
|
1108
1134
|
this._emitSharedTypes();
|
|
1109
1135
|
|
|
1110
1136
|
if (origin === ORIGIN_INITIALIZED || origin === ORIGIN_STORAGE_UPDATED) {
|
|
1111
1137
|
return;
|
|
1112
1138
|
}
|
|
1113
1139
|
|
|
1140
|
+
this._addToStorageStore(event.update);
|
|
1141
|
+
|
|
1114
1142
|
if (!this._state.webSocket) return;
|
|
1115
1143
|
if (!this._state.connection.id) return;
|
|
1116
1144
|
if (this._state.webSocket.readyState !== WebSocket.OPEN) return;
|
|
@@ -1193,7 +1221,9 @@ export class PluvRoom<
|
|
|
1193
1221
|
if (!message) return;
|
|
1194
1222
|
|
|
1195
1223
|
const shouldLog =
|
|
1196
|
-
typeof this._debug === "boolean"
|
|
1224
|
+
typeof this._debug === "boolean"
|
|
1225
|
+
? this._debug
|
|
1226
|
+
: this._debug.output.find((value) => value === message.type);
|
|
1197
1227
|
|
|
1198
1228
|
if (shouldLog) {
|
|
1199
1229
|
this._logDebug("WebSocket event received: ", message.type, message);
|
|
@@ -1246,7 +1276,6 @@ export class PluvRoom<
|
|
|
1246
1276
|
if (this._state.connection.state !== ConnectionState.Connecting) return;
|
|
1247
1277
|
|
|
1248
1278
|
this._logDebug("WebSocket connected");
|
|
1249
|
-
|
|
1250
1279
|
this._updateState((oldState) => {
|
|
1251
1280
|
oldState.connection.state = ConnectionState.Open;
|
|
1252
1281
|
|
|
@@ -1254,7 +1283,10 @@ export class PluvRoom<
|
|
|
1254
1283
|
});
|
|
1255
1284
|
|
|
1256
1285
|
this._clearInterval(this._intervals.heartbeat);
|
|
1257
|
-
this._intervals.heartbeat = setInterval(
|
|
1286
|
+
this._intervals.heartbeat = setInterval(
|
|
1287
|
+
this._heartbeat.bind(this),
|
|
1288
|
+
HEARTBEAT_INTERVAL_MS,
|
|
1289
|
+
) as unknown as number;
|
|
1258
1290
|
}
|
|
1259
1291
|
|
|
1260
1292
|
private async _onNavigatorOffline(): Promise<void> {
|
|
@@ -1325,9 +1357,15 @@ export class PluvRoom<
|
|
|
1325
1357
|
typeof this._reconnectTimeoutMs === "number"
|
|
1326
1358
|
? this._reconnectTimeoutMs
|
|
1327
1359
|
: this._reconnectTimeoutMs({ attempts: this._state.connection.attempts });
|
|
1328
|
-
const clampedMs = Math.max(
|
|
1360
|
+
const clampedMs = Math.max(
|
|
1361
|
+
Math.min(MIN_RECONNECT_TIMEOUT_MS, timeoutMs),
|
|
1362
|
+
MAX_RECONNECT_TIMEOUT_MS,
|
|
1363
|
+
);
|
|
1329
1364
|
|
|
1330
|
-
this._timeouts.reconnect = setTimeout(
|
|
1365
|
+
this._timeouts.reconnect = setTimeout(
|
|
1366
|
+
this._reconnect.bind(this),
|
|
1367
|
+
clampedMs,
|
|
1368
|
+
) as unknown as number;
|
|
1331
1369
|
}
|
|
1332
1370
|
|
|
1333
1371
|
private async _reconnect(): Promise<void> {
|
|
@@ -1342,13 +1380,17 @@ export class PluvRoom<
|
|
|
1342
1380
|
if (this._state.connection.state === ConnectionState.Closed) return;
|
|
1343
1381
|
|
|
1344
1382
|
const metadata = this._lastMetadata;
|
|
1345
|
-
const params = (
|
|
1383
|
+
const params = (
|
|
1384
|
+
typeof metadata === "undefined" ? [] : [{ metadata }]
|
|
1385
|
+
) as RoomConnectParams<TMetadata>;
|
|
1346
1386
|
|
|
1347
1387
|
await this.connect(...params);
|
|
1348
1388
|
}
|
|
1349
1389
|
|
|
1350
1390
|
private _sendMessage(data: string): void;
|
|
1351
|
-
private _sendMessage<TMessage extends EventMessage<string, any> = EventMessage<string, any>>(
|
|
1391
|
+
private _sendMessage<TMessage extends EventMessage<string, any> = EventMessage<string, any>>(
|
|
1392
|
+
data: TMessage,
|
|
1393
|
+
): void;
|
|
1352
1394
|
private _sendMessage(data: any): void {
|
|
1353
1395
|
const webSocket = this._state.webSocket;
|
|
1354
1396
|
|
|
@@ -1387,7 +1429,9 @@ export class PluvRoom<
|
|
|
1387
1429
|
return parsed;
|
|
1388
1430
|
}
|
|
1389
1431
|
|
|
1390
|
-
private _updateState(
|
|
1432
|
+
private _updateState(
|
|
1433
|
+
updater: (oldState: WebSocketState<TIO>) => WebSocketState<TIO>,
|
|
1434
|
+
): WebSocketState<TIO> {
|
|
1391
1435
|
let authorization: AuthorizationState<TIO>;
|
|
1392
1436
|
|
|
1393
1437
|
try {
|
package/src/PluvRouter.ts
CHANGED
|
@@ -67,7 +67,10 @@ export class PluvRouter<
|
|
|
67
67
|
public static merge<TRouters extends PluvRouter<any, any, any, any>[]>(
|
|
68
68
|
...routers: TRouters
|
|
69
69
|
): MergedRouter<TRouters> {
|
|
70
|
-
const events = Object.assign(
|
|
70
|
+
const events = Object.assign(
|
|
71
|
+
Object.create(null),
|
|
72
|
+
...routers.map((router) => router._defs.events),
|
|
73
|
+
);
|
|
71
74
|
|
|
72
75
|
if (Object.keys(events).some((key) => key.startsWith("$"))) {
|
|
73
76
|
throw new Error('Procedures may not start with "$"');
|
package/src/StateNotifier.ts
CHANGED
|
@@ -15,7 +15,10 @@ type InferSubjectValue<
|
|
|
15
15
|
TIO extends IOLike,
|
|
16
16
|
TPresence extends JsonObject,
|
|
17
17
|
TSubject extends keyof StateNotifierSubjects<TIO, TPresence>,
|
|
18
|
-
> =
|
|
18
|
+
> =
|
|
19
|
+
StateNotifierSubjects<TIO, TPresence>[TSubject] extends Subject<infer IValue>
|
|
20
|
+
? Id<IValue>
|
|
21
|
+
: never;
|
|
19
22
|
|
|
20
23
|
export type SubscriptionCallback<
|
|
21
24
|
TIO extends IOLike,
|
package/src/UsersManager.ts
CHANGED
|
@@ -129,7 +129,11 @@ export class UsersManager<TIO extends IOLike, TPresence extends JsonObject = {}>
|
|
|
129
129
|
if (!userInfo) return null;
|
|
130
130
|
|
|
131
131
|
const remaining = this._getConnectionIds(clientId)?.size ?? 0;
|
|
132
|
-
const result: DeleteConnectionResult<TIO, TPresence> = {
|
|
132
|
+
const result: DeleteConnectionResult<TIO, TPresence> = {
|
|
133
|
+
clientId,
|
|
134
|
+
remaining,
|
|
135
|
+
data: userInfo,
|
|
136
|
+
};
|
|
133
137
|
|
|
134
138
|
/**
|
|
135
139
|
* @description A user may have multiple websocket connections open. If one connection is
|
|
@@ -195,8 +199,15 @@ export class UsersManager<TIO extends IOLike, TPresence extends JsonObject = {}>
|
|
|
195
199
|
if (!other) return null;
|
|
196
200
|
|
|
197
201
|
const cleanedPatch = pickBy(patch ?? {}, (value) => typeof value !== "undefined");
|
|
198
|
-
const cleanedPresence = pickBy(
|
|
199
|
-
|
|
202
|
+
const cleanedPresence = pickBy(
|
|
203
|
+
other.presence ?? {},
|
|
204
|
+
(value) => typeof value !== "undefined",
|
|
205
|
+
);
|
|
206
|
+
const presence = {
|
|
207
|
+
...this.initialPresence,
|
|
208
|
+
...cleanedPresence,
|
|
209
|
+
...cleanedPatch,
|
|
210
|
+
} as TPresence;
|
|
200
211
|
const validated = this._presence ? this._presence.parse(presence) : presence;
|
|
201
212
|
|
|
202
213
|
this._others.set(clientId, { ...other, presence: validated });
|
|
@@ -255,7 +266,11 @@ export class UsersManager<TIO extends IOLike, TPresence extends JsonObject = {}>
|
|
|
255
266
|
public updateMyPresence(patch: Partial<TPresence>): TPresence {
|
|
256
267
|
const cleanedPatch = pickBy(patch, (value) => typeof value !== "undefined");
|
|
257
268
|
const cleanedPresence = pickBy(this._myPresence, (value) => typeof value !== "undefined");
|
|
258
|
-
const updated = {
|
|
269
|
+
const updated = {
|
|
270
|
+
...this.initialPresence,
|
|
271
|
+
...cleanedPresence,
|
|
272
|
+
...cleanedPatch,
|
|
273
|
+
} as TPresence;
|
|
259
274
|
const bytes = new TextEncoder().encode(JSON.stringify(updated)).length;
|
|
260
275
|
|
|
261
276
|
if (!!this._limits.presenceMaxSize && bytes > this._limits.presenceMaxSize) {
|
|
@@ -297,7 +312,10 @@ export class UsersManager<TIO extends IOLike, TPresence extends JsonObject = {}>
|
|
|
297
312
|
return set ?? null;
|
|
298
313
|
}
|
|
299
314
|
|
|
300
|
-
private _setConnectionId(
|
|
315
|
+
private _setConnectionId(
|
|
316
|
+
connectionId: string,
|
|
317
|
+
clientId: string,
|
|
318
|
+
): Set<[connectionId: string][0]> {
|
|
301
319
|
const set = this._idMap.fromClientId.get(clientId) ?? new Set<string>();
|
|
302
320
|
const updated = set.add(connectionId);
|
|
303
321
|
|
package/src/index.ts
CHANGED
|
@@ -23,4 +23,11 @@ export type {
|
|
|
23
23
|
export type { MergeEvents, PluvRouter, PluvRouterEventConfig } from "./PluvRouter";
|
|
24
24
|
export { register } from "./register";
|
|
25
25
|
export type { RegisterParams } from "./register";
|
|
26
|
-
export type {
|
|
26
|
+
export type {
|
|
27
|
+
InferMetadata,
|
|
28
|
+
PublicKey,
|
|
29
|
+
PublicKeyParams,
|
|
30
|
+
UserInfo,
|
|
31
|
+
WebSocketConnection,
|
|
32
|
+
WebSocketState,
|
|
33
|
+
} from "./types";
|
package/src/infer.ts
CHANGED
|
@@ -5,4 +5,6 @@ export type InferCallback<TIO extends IOLike<any, any>> = (i: typeof identity) =
|
|
|
5
5
|
io: (io: TIO) => TIO;
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
-
export const infer = <TIO extends IOLike<any, any>>(
|
|
8
|
+
export const infer = <TIO extends IOLike<any, any>>(
|
|
9
|
+
callback: InferCallback<TIO>,
|
|
10
|
+
): InferCallback<TIO> => callback;
|
package/src/register.ts
CHANGED
|
@@ -9,7 +9,8 @@ export interface RegisterParams<TMetadata extends JsonObject> {
|
|
|
9
9
|
export const register = <TMetadata extends JsonObject>(params: RegisterParams<TMetadata>) => {
|
|
10
10
|
const { authEndpoint, publicKey } = params;
|
|
11
11
|
|
|
12
|
-
const wsEndpoint: WsEndpoint<TMetadata> = ({ room }) =>
|
|
12
|
+
const wsEndpoint: WsEndpoint<TMetadata> = ({ room }) =>
|
|
13
|
+
`wss://connect.pluv.io/api/room?room=${room}`;
|
|
13
14
|
|
|
14
15
|
return {
|
|
15
16
|
authEndpoint,
|