@pluv/client 0.8.0 → 0.9.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 +56 -0
- package/dist/index.d.ts +32 -8
- package/dist/index.js +233 -100
- package/dist/index.mjs +232 -100
- package/package.json +10 -8
- package/src/AbstractRoom.ts +1 -1
- package/src/AbstractStorageStore.ts +21 -0
- package/src/CrdtManager.ts +64 -0
- package/src/MockedRoom.ts +2 -2
- package/src/PluvClient.ts +9 -6
- package/src/PluvRoom.ts +162 -73
- package/src/StorageStore.ts +31 -0
- package/src/index.ts +7 -1
- package/src/utils/debounce.ts +27 -0
- package/src/utils/index.ts +1 -0
package/src/PluvClient.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { IOLike, JsonObject } from "@pluv/types";
|
|
2
2
|
import { AbstractType } from "yjs";
|
|
3
|
-
import
|
|
3
|
+
import {
|
|
4
4
|
AuthEndpoint,
|
|
5
|
-
|
|
5
|
+
PluvRoom,
|
|
6
6
|
PluvRoomOptions,
|
|
7
|
-
WsEndpoint,
|
|
8
7
|
RoomConfig,
|
|
8
|
+
RoomEndpoints,
|
|
9
|
+
WsEndpoint,
|
|
9
10
|
} from "./PluvRoom";
|
|
10
|
-
import { PluvRoom } from "./PluvRoom";
|
|
11
11
|
|
|
12
12
|
export type PluvClientOptions<TIO extends IOLike> = RoomEndpoints<TIO> & {
|
|
13
13
|
debug?: boolean;
|
|
@@ -40,6 +40,7 @@ export class PluvClient<TIO extends IOLike = IOLike> {
|
|
|
40
40
|
if (oldRoom) return oldRoom;
|
|
41
41
|
|
|
42
42
|
const newRoom = new PluvRoom<TIO, TPresence, TStorage>(room, {
|
|
43
|
+
addons: options.addons,
|
|
43
44
|
authEndpoint: this._authEndpoint,
|
|
44
45
|
debug: options.debug,
|
|
45
46
|
initialPresence: options.initialPresence,
|
|
@@ -90,12 +91,14 @@ export class PluvClient<TIO extends IOLike = IOLike> {
|
|
|
90
91
|
return Array.from(this._rooms.values());
|
|
91
92
|
};
|
|
92
93
|
|
|
93
|
-
public leave = (
|
|
94
|
+
public leave = async (
|
|
95
|
+
room: string | PluvRoom<TIO, any, any>
|
|
96
|
+
): Promise<void> => {
|
|
94
97
|
const toLeave = typeof room === "string" ? this.getRoom(room) : room;
|
|
95
98
|
|
|
96
99
|
if (!toLeave) return;
|
|
97
100
|
|
|
98
|
-
toLeave.disconnect();
|
|
101
|
+
await toLeave.disconnect();
|
|
99
102
|
|
|
100
103
|
this._rooms.delete(toLeave.id);
|
|
101
104
|
|
package/src/PluvRoom.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
} from "@pluv/types";
|
|
15
15
|
import type { AbstractType } from "yjs";
|
|
16
16
|
import { AbstractRoom } from "./AbstractRoom";
|
|
17
|
+
import { AbstractStorageStore } from "./AbstractStorageStore";
|
|
17
18
|
import type { CrdtManagerOptions } from "./CrdtManager";
|
|
18
19
|
import { CrdtManager } from "./CrdtManager";
|
|
19
20
|
import { CrdtNotifier } from "./CrdtNotifier";
|
|
@@ -26,6 +27,7 @@ import type {
|
|
|
26
27
|
SubscriptionCallback,
|
|
27
28
|
} from "./StateNotifier";
|
|
28
29
|
import { StateNotifier } from "./StateNotifier";
|
|
30
|
+
import { StorageStore } from "./StorageStore";
|
|
29
31
|
import type {
|
|
30
32
|
AuthorizationState,
|
|
31
33
|
InternalSubscriptions,
|
|
@@ -36,10 +38,23 @@ import type {
|
|
|
36
38
|
import { ConnectionState } from "./types";
|
|
37
39
|
import type { UsersManagerConfig } from "./UsersManager";
|
|
38
40
|
import { UsersManager } from "./UsersManager";
|
|
41
|
+
import { debounce } from "./utils";
|
|
39
42
|
|
|
43
|
+
const ADD_TO_STORAGE_STATE_DEBOUNCE_MS = 1_000;
|
|
40
44
|
const HEARTBEAT_INTERVAL_MS = 10_000;
|
|
41
45
|
const PONG_TIMEOUT_MS = 2_000;
|
|
42
46
|
const RECONNECT_TIMEOUT_MS = 30_000;
|
|
47
|
+
const Y_ORIGIN_INITIALIZED = "$INITIALIZED";
|
|
48
|
+
|
|
49
|
+
export const DEFAULT_PLUV_CLIENT_ADDON = <
|
|
50
|
+
TIO extends IOLike = IOLike,
|
|
51
|
+
TPresence extends JsonObject = {},
|
|
52
|
+
TStorage extends Record<string, AbstractType<any>> = {}
|
|
53
|
+
>(
|
|
54
|
+
input: PluvRoomAddonInput<TIO, TPresence, TStorage>
|
|
55
|
+
): PluvRoomAddonResult => ({
|
|
56
|
+
storage: new StorageStore(input.room.id),
|
|
57
|
+
});
|
|
43
58
|
|
|
44
59
|
declare global {
|
|
45
60
|
var process: {
|
|
@@ -88,6 +103,26 @@ interface InternalListeners {
|
|
|
88
103
|
onAuthorizationFail: (error: Error) => void;
|
|
89
104
|
}
|
|
90
105
|
|
|
106
|
+
export type PluvRoomAddon<
|
|
107
|
+
TIO extends IOLike = IOLike,
|
|
108
|
+
TPresence extends JsonObject = {},
|
|
109
|
+
TStorage extends Record<string, AbstractType<any>> = {}
|
|
110
|
+
> = (
|
|
111
|
+
input: PluvRoomAddonInput<TIO, TPresence, TStorage>
|
|
112
|
+
) => Partial<PluvRoomAddonResult>;
|
|
113
|
+
|
|
114
|
+
export interface PluvRoomAddonInput<
|
|
115
|
+
TIO extends IOLike = IOLike,
|
|
116
|
+
TPresence extends JsonObject = {},
|
|
117
|
+
TStorage extends Record<string, AbstractType<any>> = {}
|
|
118
|
+
> {
|
|
119
|
+
room: PluvRoom<TIO, TPresence, TStorage>;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface PluvRoomAddonResult {
|
|
123
|
+
storage: AbstractStorageStore;
|
|
124
|
+
}
|
|
125
|
+
|
|
91
126
|
export type PluvRoomDebug<TIO extends IOLike> = Id<{
|
|
92
127
|
output: readonly (keyof InferIOOutput<TIO>)[];
|
|
93
128
|
input: readonly (keyof InferIOInput<TIO>)[];
|
|
@@ -98,6 +133,7 @@ export type PluvRoomOptions<
|
|
|
98
133
|
TPresence extends JsonObject = {},
|
|
99
134
|
TStorage extends Record<string, AbstractType<any>> = {}
|
|
100
135
|
> = {
|
|
136
|
+
addons?: readonly PluvRoomAddon<TIO, TPresence, TStorage>[];
|
|
101
137
|
debug?: boolean | PluvRoomDebug<TIO>;
|
|
102
138
|
onAuthorizationFail?: (error: Error) => void;
|
|
103
139
|
} & Omit<CrdtManagerOptions<TStorage>, "encodedState"> &
|
|
@@ -116,11 +152,10 @@ export class PluvRoom<
|
|
|
116
152
|
> extends AbstractRoom<TIO, TPresence, TStorage> {
|
|
117
153
|
readonly _endpoints: RoomEndpoints<TIO>;
|
|
118
154
|
|
|
119
|
-
private _crdtManager: CrdtManager<TStorage
|
|
155
|
+
private _crdtManager: CrdtManager<TStorage>;
|
|
120
156
|
private _crdtNotifier = new CrdtNotifier<TStorage>();
|
|
121
157
|
private _debug: boolean | PluvRoomDebug<TIO>;
|
|
122
158
|
private _eventNotifier = new EventNotifier<TIO>();
|
|
123
|
-
private _initialStorage: (() => TStorage) | null = null;
|
|
124
159
|
private _intervals: IntervalIds = {
|
|
125
160
|
heartbeat: null,
|
|
126
161
|
};
|
|
@@ -139,6 +174,7 @@ export class PluvRoom<
|
|
|
139
174
|
webSocket: null,
|
|
140
175
|
};
|
|
141
176
|
private _stateNotifier = new StateNotifier<TIO, TPresence>();
|
|
177
|
+
private _storageStore: AbstractStorageStore;
|
|
142
178
|
private _subscriptions: InternalSubscriptions = {
|
|
143
179
|
observeCrdt: null,
|
|
144
180
|
};
|
|
@@ -152,6 +188,7 @@ export class PluvRoom<
|
|
|
152
188
|
|
|
153
189
|
constructor(room: string, options: RoomConfig<TIO, TPresence, TStorage>) {
|
|
154
190
|
const {
|
|
191
|
+
addons = [],
|
|
155
192
|
authEndpoint,
|
|
156
193
|
debug = false,
|
|
157
194
|
initialPresence,
|
|
@@ -163,6 +200,15 @@ export class PluvRoom<
|
|
|
163
200
|
|
|
164
201
|
super(room);
|
|
165
202
|
|
|
203
|
+
const addon = this._getAddon(addons);
|
|
204
|
+
|
|
205
|
+
const { storage } = {
|
|
206
|
+
...DEFAULT_PLUV_CLIENT_ADDON({ room: this }),
|
|
207
|
+
...addon({ room: this }),
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
this._storageStore = storage;
|
|
211
|
+
|
|
166
212
|
this._debug = debug;
|
|
167
213
|
|
|
168
214
|
this._endpoints = { authEndpoint, wsEndpoint } as RoomEndpoints<TIO>;
|
|
@@ -184,7 +230,7 @@ export class PluvRoom<
|
|
|
184
230
|
presence,
|
|
185
231
|
});
|
|
186
232
|
|
|
187
|
-
|
|
233
|
+
this._crdtManager = new CrdtManager<TStorage>({ initialStorage });
|
|
188
234
|
}
|
|
189
235
|
|
|
190
236
|
public get webSocket(): WebSocket | null {
|
|
@@ -223,26 +269,24 @@ export class PluvRoom<
|
|
|
223
269
|
return oldState;
|
|
224
270
|
});
|
|
225
271
|
|
|
272
|
+
await this._storageStore.initialize();
|
|
273
|
+
await this._applyStorageStore();
|
|
274
|
+
|
|
226
275
|
const url = new URL(this._getWsEndpoint(this.id));
|
|
227
276
|
|
|
228
277
|
let authToken: string | null = null;
|
|
278
|
+
let webSocket: WebSocket;
|
|
229
279
|
|
|
230
280
|
try {
|
|
231
281
|
authToken = await this._getAuthorization(this.id);
|
|
232
|
-
} catch (err) {
|
|
233
|
-
this._onAuthorizationFail(err);
|
|
234
|
-
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
authToken &&
|
|
239
|
-
url.searchParams.set("token", encodeURIComponent(authToken));
|
|
240
282
|
|
|
241
|
-
|
|
283
|
+
authToken &&
|
|
284
|
+
url.searchParams.set("token", encodeURIComponent(authToken));
|
|
242
285
|
|
|
243
|
-
try {
|
|
244
286
|
webSocket = new WebSocket(url.toString());
|
|
245
287
|
} catch (err) {
|
|
288
|
+
await this._storageStore.destroy();
|
|
289
|
+
|
|
246
290
|
this._onAuthorizationFail(err);
|
|
247
291
|
|
|
248
292
|
return;
|
|
@@ -259,7 +303,7 @@ export class PluvRoom<
|
|
|
259
303
|
this._attachWsListeners();
|
|
260
304
|
}
|
|
261
305
|
|
|
262
|
-
public disconnect(): void {
|
|
306
|
+
public async disconnect(): Promise<void> {
|
|
263
307
|
if (!this._endpoints.wsEndpoint) {
|
|
264
308
|
throw new Error("Must provide an wsEndpoint.");
|
|
265
309
|
}
|
|
@@ -276,17 +320,12 @@ export class PluvRoom<
|
|
|
276
320
|
this._clearInterval(this._intervals.heartbeat);
|
|
277
321
|
|
|
278
322
|
this._closeWs();
|
|
279
|
-
this._crdtManager?.destroy();
|
|
280
323
|
|
|
281
|
-
this.
|
|
324
|
+
await this._storageStore.destroy();
|
|
282
325
|
|
|
283
|
-
this.
|
|
284
|
-
oldState.authorization.token = null;
|
|
285
|
-
oldState.connection.state = ConnectionState.Closed;
|
|
286
|
-
oldState.webSocket = null;
|
|
326
|
+
this._crdtManager?.destroy();
|
|
287
327
|
|
|
288
|
-
|
|
289
|
-
});
|
|
328
|
+
this._stateNotifier.subjects["my-presence"].next(null);
|
|
290
329
|
}
|
|
291
330
|
|
|
292
331
|
public event = <TEvent extends keyof InferIOOutput<TIO>>(
|
|
@@ -303,8 +342,8 @@ export class PluvRoom<
|
|
|
303
342
|
);
|
|
304
343
|
}
|
|
305
344
|
|
|
306
|
-
public getMyPresence = (): TPresence
|
|
307
|
-
return this._usersManager.
|
|
345
|
+
public getMyPresence = (): TPresence => {
|
|
346
|
+
return this._usersManager.myPresence;
|
|
308
347
|
};
|
|
309
348
|
|
|
310
349
|
public getMyself = (): Id<UserInfo<TIO, TPresence>> | null => {
|
|
@@ -365,6 +404,26 @@ export class PluvRoom<
|
|
|
365
404
|
);
|
|
366
405
|
};
|
|
367
406
|
|
|
407
|
+
private async _addToStorageStore(update: string): Promise<void> {
|
|
408
|
+
await this._storageStore.addUpdate(update);
|
|
409
|
+
|
|
410
|
+
this._flattenStorageStore();
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
private async _applyStorageStore(): Promise<void> {
|
|
414
|
+
const updates = await this._storageStore.getUpdates();
|
|
415
|
+
|
|
416
|
+
this._crdtManager.initialize({
|
|
417
|
+
onInitialized: () => {
|
|
418
|
+
this._observeCrdt();
|
|
419
|
+
},
|
|
420
|
+
origin: Y_ORIGIN_INITIALIZED,
|
|
421
|
+
update: updates,
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
this._emitSharedTypes();
|
|
425
|
+
}
|
|
426
|
+
|
|
368
427
|
private _attachWindowListeners(): void {
|
|
369
428
|
if (typeof window === "undefined") return;
|
|
370
429
|
if (typeof document === "undefined") return;
|
|
@@ -442,8 +501,6 @@ export class PluvRoom<
|
|
|
442
501
|
if (canClose) this._state.webSocket.close();
|
|
443
502
|
|
|
444
503
|
this._detachWsListeners();
|
|
445
|
-
|
|
446
|
-
this._stateNotifier.subjects["myself"].next(null);
|
|
447
504
|
}
|
|
448
505
|
|
|
449
506
|
private _detachWindowListeners(): void {
|
|
@@ -486,27 +543,44 @@ export class PluvRoom<
|
|
|
486
543
|
this._wsListeners = null;
|
|
487
544
|
}
|
|
488
545
|
|
|
489
|
-
private
|
|
490
|
-
const
|
|
546
|
+
private _emitSharedTypes(): void {
|
|
547
|
+
const sharedTypes = this._crdtManager.doc.getSharedTypes();
|
|
491
548
|
|
|
492
|
-
|
|
549
|
+
Object.entries(sharedTypes).forEach(([prop, sharedType]) => {
|
|
550
|
+
const serialized = sharedType.toJSON();
|
|
493
551
|
|
|
494
|
-
|
|
552
|
+
this._crdtNotifier.subject(prop).next(serialized);
|
|
553
|
+
});
|
|
554
|
+
}
|
|
495
555
|
|
|
496
|
-
|
|
556
|
+
private _flattenStorageStore = debounce(
|
|
557
|
+
async (): Promise<void> => {
|
|
558
|
+
const shouldFlatten = await this._storageStore.getShouldFlatten();
|
|
497
559
|
|
|
498
|
-
|
|
499
|
-
throw new Error("Room is unauthorized");
|
|
500
|
-
}
|
|
560
|
+
if (!shouldFlatten) return;
|
|
501
561
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
562
|
+
const updates = await this._storageStore.getUpdates();
|
|
563
|
+
|
|
564
|
+
this._crdtManager.applyUpdate(updates, this);
|
|
565
|
+
|
|
566
|
+
const encodedState = this._crdtManager.doc.encodeStateAsUpdate();
|
|
567
|
+
|
|
568
|
+
await this._storageStore.flatten(encodedState);
|
|
569
|
+
},
|
|
570
|
+
{ wait: ADD_TO_STORAGE_STATE_DEBOUNCE_MS }
|
|
571
|
+
);
|
|
572
|
+
|
|
573
|
+
private _getAddon = (
|
|
574
|
+
addons: readonly PluvRoomAddon<TIO, TPresence, TStorage>[]
|
|
575
|
+
): PluvRoomAddon<TIO, TPresence, TStorage> => {
|
|
576
|
+
return addons.reduce<PluvRoomAddon<TIO, TPresence, TStorage>>(
|
|
577
|
+
(acc, addon) => () => ({
|
|
578
|
+
...acc({ room: this }),
|
|
579
|
+
...addon({ room: this }),
|
|
580
|
+
}),
|
|
581
|
+
DEFAULT_PLUV_CLIENT_ADDON
|
|
582
|
+
);
|
|
583
|
+
};
|
|
510
584
|
|
|
511
585
|
private _getAuthFetchOptions(room: string): FetchOptions | null {
|
|
512
586
|
if (typeof this._endpoints.authEndpoint === "undefined") return null;
|
|
@@ -532,6 +606,28 @@ export class PluvRoom<
|
|
|
532
606
|
: result;
|
|
533
607
|
}
|
|
534
608
|
|
|
609
|
+
private async _getAuthorization(room: string): Promise<string | null> {
|
|
610
|
+
const fetchOptions = this._getAuthFetchOptions(room);
|
|
611
|
+
|
|
612
|
+
if (!fetchOptions) return null;
|
|
613
|
+
|
|
614
|
+
const { url, options } = fetchOptions;
|
|
615
|
+
|
|
616
|
+
const res = await fetch(url, options);
|
|
617
|
+
|
|
618
|
+
if (!res.ok || res.status !== 200) {
|
|
619
|
+
throw new Error("Room is unauthorized");
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
try {
|
|
623
|
+
return await res.text().then((text) => text.trim());
|
|
624
|
+
} catch (err) {
|
|
625
|
+
throw new Error(
|
|
626
|
+
err instanceof Error ? err.message : "Room is unauthorized"
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
535
631
|
private _getWsEndpoint(room: string): string {
|
|
536
632
|
switch (typeof this._endpoints.wsEndpoint) {
|
|
537
633
|
case "undefined":
|
|
@@ -661,7 +757,9 @@ export class PluvRoom<
|
|
|
661
757
|
});
|
|
662
758
|
}
|
|
663
759
|
|
|
664
|
-
private _handleStorageReceivedMessage(
|
|
760
|
+
private async _handleStorageReceivedMessage(
|
|
761
|
+
message: IOEventMessage<TIO>
|
|
762
|
+
): Promise<void> {
|
|
665
763
|
const { connectionId } = message;
|
|
666
764
|
|
|
667
765
|
if (!connectionId) return;
|
|
@@ -672,27 +770,25 @@ export class PluvRoom<
|
|
|
672
770
|
InferIOAuthorize<TIO>
|
|
673
771
|
>["$STORAGE_RECEIVED"];
|
|
674
772
|
|
|
675
|
-
this._crdtManager
|
|
676
|
-
|
|
677
|
-
|
|
773
|
+
this._crdtManager.initialize({
|
|
774
|
+
onInitialized: async () => {
|
|
775
|
+
this._observeCrdt();
|
|
776
|
+
},
|
|
777
|
+
origin: Y_ORIGIN_INITIALIZED,
|
|
778
|
+
update: data.state,
|
|
678
779
|
});
|
|
679
780
|
|
|
680
|
-
this.
|
|
681
|
-
|
|
682
|
-
const sharedTypes = this._crdtManager.doc.getSharedTypes();
|
|
781
|
+
const encodedState = this._crdtManager.doc.encodeStateAsUpdate();
|
|
683
782
|
|
|
684
|
-
|
|
685
|
-
const sharedType = sharedTypes[key];
|
|
686
|
-
const serialized = sharedType.toJSON();
|
|
783
|
+
await this._storageStore.addUpdate(encodedState);
|
|
687
784
|
|
|
688
|
-
|
|
689
|
-
});
|
|
785
|
+
this._emitSharedTypes();
|
|
690
786
|
|
|
691
787
|
const update = this._crdtManager.doc.encodeStateAsUpdate();
|
|
692
788
|
|
|
693
789
|
this._sendMessage({
|
|
694
790
|
type: "$UPDATE_STORAGE",
|
|
695
|
-
data: { origin:
|
|
791
|
+
data: { origin: Y_ORIGIN_INITIALIZED, update },
|
|
696
792
|
});
|
|
697
793
|
}
|
|
698
794
|
|
|
@@ -713,16 +809,12 @@ export class PluvRoom<
|
|
|
713
809
|
|
|
714
810
|
const sharedTypes = this._crdtManager.doc.getSharedTypes();
|
|
715
811
|
|
|
716
|
-
Object.
|
|
812
|
+
Object.entries(sharedTypes).forEach(([prop, sharedType]) => {
|
|
717
813
|
if (!this._crdtManager) return;
|
|
718
814
|
|
|
719
|
-
const sharedType = sharedTypes[key];
|
|
720
|
-
|
|
721
|
-
if (!sharedType) return;
|
|
722
|
-
|
|
723
815
|
const serialized = sharedType.toJSON();
|
|
724
816
|
|
|
725
|
-
this._crdtNotifier.subject(
|
|
817
|
+
this._crdtNotifier.subject(prop).next(serialized);
|
|
726
818
|
});
|
|
727
819
|
}
|
|
728
820
|
|
|
@@ -783,23 +875,17 @@ export class PluvRoom<
|
|
|
783
875
|
(update, _origin) => {
|
|
784
876
|
const origin = _origin ?? null;
|
|
785
877
|
|
|
786
|
-
|
|
878
|
+
this._addToStorageStore(update);
|
|
879
|
+
this._emitSharedTypes();
|
|
880
|
+
|
|
881
|
+
if (origin === Y_ORIGIN_INITIALIZED || origin === this) return;
|
|
882
|
+
|
|
787
883
|
if (!this._state.webSocket) return;
|
|
788
884
|
if (!this._state.connection.id) return;
|
|
789
885
|
if (this._state.webSocket.readyState !== WebSocket.OPEN) return;
|
|
790
886
|
|
|
791
887
|
if (origin === "$STORAGE_UPDATED") return;
|
|
792
888
|
|
|
793
|
-
const sharedTypes = this._crdtManager.doc.getSharedTypes();
|
|
794
|
-
|
|
795
|
-
Object.entries(sharedTypes).forEach(([prop, sharedType]) => {
|
|
796
|
-
if (!this._crdtManager) return;
|
|
797
|
-
|
|
798
|
-
const serialized = sharedType.toJSON();
|
|
799
|
-
|
|
800
|
-
this._crdtNotifier.subject(prop).next(serialized);
|
|
801
|
-
});
|
|
802
|
-
|
|
803
889
|
this._sendMessage({
|
|
804
890
|
type: "$UPDATE_STORAGE",
|
|
805
891
|
data: { origin, update },
|
|
@@ -830,14 +916,17 @@ export class PluvRoom<
|
|
|
830
916
|
* @author leedavidcs
|
|
831
917
|
* @date August 19, 2022
|
|
832
918
|
*/
|
|
833
|
-
private _onClose(event: CloseEvent): void {
|
|
919
|
+
private async _onClose(event: CloseEvent): Promise<void> {
|
|
834
920
|
this._logDebug("WebSocket closed");
|
|
835
921
|
!!event.reason && this._logDebug(event.reason);
|
|
836
922
|
|
|
837
923
|
this._clearInterval(this._intervals.heartbeat);
|
|
838
924
|
this._clearTimeout(this._timeouts.pong);
|
|
839
925
|
|
|
926
|
+
this._stateNotifier.subjects["myself"].next(null);
|
|
927
|
+
|
|
840
928
|
this._updateState((oldState) => {
|
|
929
|
+
oldState.authorization.token = null;
|
|
841
930
|
oldState.connection.state = ConnectionState.Closed;
|
|
842
931
|
oldState.webSocket = null;
|
|
843
932
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { AbstractStorageStore } from "./AbstractStorageStore";
|
|
2
|
+
|
|
3
|
+
export class StorageStore extends AbstractStorageStore {
|
|
4
|
+
public addUpdate() {
|
|
5
|
+
return Promise.resolve();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
public applyUpdates() {
|
|
9
|
+
return Promise.resolve();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
public destroy() {
|
|
13
|
+
return Promise.resolve();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public flatten() {
|
|
17
|
+
return Promise.resolve();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public getShouldFlatten() {
|
|
21
|
+
return Promise.resolve(false);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public getUpdates() {
|
|
25
|
+
return Promise.resolve([]);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public initialize() {
|
|
29
|
+
return Promise.resolve();
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -7,12 +7,18 @@ export type {
|
|
|
7
7
|
} from "@pluv/types";
|
|
8
8
|
export type { AbstractType } from "yjs";
|
|
9
9
|
export { AbstractRoom } from "./AbstractRoom";
|
|
10
|
+
export { AbstractStorageStore } from "./AbstractStorageStore";
|
|
10
11
|
export { createClient } from "./createClient";
|
|
11
12
|
export type { MockedRoomConfig, MockedRoomEvents } from "./MockedRoom";
|
|
12
13
|
export { MockedRoom } from "./MockedRoom";
|
|
13
14
|
export type { PluvClientOptions } from "./PluvClient";
|
|
14
15
|
export { PluvClient } from "./PluvClient";
|
|
15
|
-
export type {
|
|
16
|
+
export type {
|
|
17
|
+
PluvRoomAddon,
|
|
18
|
+
PluvRoomAddonInput,
|
|
19
|
+
PluvRoomAddonResult,
|
|
20
|
+
PluvRoomDebug,
|
|
21
|
+
} from "./PluvRoom";
|
|
16
22
|
export { PluvRoom } from "./PluvRoom";
|
|
17
23
|
export type { WebSocketConnection, UserInfo, WebSocketState } from "./types";
|
|
18
24
|
export { ConnectionState } from "./types";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const debounce = <TFunc extends (...args: any[]) => any>(
|
|
2
|
+
fn: TFunc,
|
|
3
|
+
options?: { wait: number; immediate?: boolean }
|
|
4
|
+
): ((...args: Parameters<TFunc>) => void) => {
|
|
5
|
+
const { wait, immediate = false } = options ?? {};
|
|
6
|
+
|
|
7
|
+
let prevResult: ReturnType<TFunc> | undefined;
|
|
8
|
+
let timeout: number | null = null;
|
|
9
|
+
|
|
10
|
+
return (...args: Parameters<TFunc>): void => {
|
|
11
|
+
if (typeof timeout === "number") clearTimeout(timeout);
|
|
12
|
+
|
|
13
|
+
timeout = setTimeout(() => {
|
|
14
|
+
timeout = null;
|
|
15
|
+
|
|
16
|
+
if (!immediate) prevResult = fn(args);
|
|
17
|
+
}, wait) as number;
|
|
18
|
+
|
|
19
|
+
if (immediate && !timeout) {
|
|
20
|
+
fn(args);
|
|
21
|
+
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return prevResult;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { debounce } from "./debounce";
|