@pluv/client 0.8.1 → 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 +50 -0
- package/dist/index.d.ts +29 -5
- package/dist/index.js +231 -96
- package/dist/index.mjs +230 -96
- package/package.json +10 -8
- package/src/AbstractStorageStore.ts +21 -0
- package/src/CrdtManager.ts +64 -0
- package/src/PluvClient.ts +9 -6
- package/src/PluvRoom.ts +160 -71
- 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/dist/index.mjs
CHANGED
|
@@ -49,10 +49,18 @@ var AbstractRoom = class {
|
|
|
49
49
|
}
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
+
// src/AbstractStorageStore.ts
|
|
53
|
+
var AbstractStorageStore = class {
|
|
54
|
+
constructor(room) {
|
|
55
|
+
this.room = room;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
52
59
|
// src/CrdtManager.ts
|
|
53
60
|
import { doc } from "@pluv/crdt-yjs";
|
|
54
61
|
var CrdtManager = class {
|
|
55
62
|
constructor(options = {}) {
|
|
63
|
+
this.initialized = false;
|
|
56
64
|
const { encodedState, initialStorage } = options;
|
|
57
65
|
const _doc = encodedState ? doc().applyUpdate(encodedState) : null;
|
|
58
66
|
if (_doc && !!Object.keys(_doc.toJSON()).length) {
|
|
@@ -62,12 +70,50 @@ var CrdtManager = class {
|
|
|
62
70
|
_doc == null ? void 0 : _doc.destroy();
|
|
63
71
|
this.doc = doc(initialStorage == null ? void 0 : initialStorage());
|
|
64
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* !HACK
|
|
75
|
+
* @description If this is called before the doc is initialized. Omit the
|
|
76
|
+
* update. This unfortunately means updates can be dropped entirely
|
|
77
|
+
* @date June 13, 2023
|
|
78
|
+
*/
|
|
79
|
+
applyUpdate(update, origin) {
|
|
80
|
+
if (!this.initialized)
|
|
81
|
+
return this;
|
|
82
|
+
const updates = typeof update === "string" ? [update] : update;
|
|
83
|
+
this._applyDocUpdates(this.doc, updates, origin);
|
|
84
|
+
return this;
|
|
85
|
+
}
|
|
65
86
|
destroy() {
|
|
66
87
|
this.doc.destroy();
|
|
67
88
|
}
|
|
68
89
|
get(key) {
|
|
69
90
|
return this.doc.get(key);
|
|
70
91
|
}
|
|
92
|
+
initialize(params) {
|
|
93
|
+
const { onInitialized, origin, update } = params;
|
|
94
|
+
const updates = typeof update === "string" ? [update] : update;
|
|
95
|
+
if (!updates.length)
|
|
96
|
+
return this;
|
|
97
|
+
if (this.initialized) {
|
|
98
|
+
this._applyDocUpdates(this.doc, updates, origin);
|
|
99
|
+
return this;
|
|
100
|
+
}
|
|
101
|
+
const _doc = this._applyDocUpdates(doc(), updates, origin);
|
|
102
|
+
this.initialized = true;
|
|
103
|
+
if (!!Object.keys(_doc.toJSON()).length) {
|
|
104
|
+
this.doc = _doc;
|
|
105
|
+
} else {
|
|
106
|
+
_doc.destroy();
|
|
107
|
+
}
|
|
108
|
+
onInitialized == null ? void 0 : onInitialized();
|
|
109
|
+
return this;
|
|
110
|
+
}
|
|
111
|
+
_applyDocUpdates(yjsDoc, updates, origin) {
|
|
112
|
+
yjsDoc.transact(() => {
|
|
113
|
+
updates.reduce((acc, _update) => acc.applyUpdate(_update), yjsDoc);
|
|
114
|
+
}, origin);
|
|
115
|
+
return yjsDoc;
|
|
116
|
+
}
|
|
71
117
|
};
|
|
72
118
|
|
|
73
119
|
// src/CrdtNotifier.ts
|
|
@@ -151,6 +197,31 @@ var StateNotifier = class {
|
|
|
151
197
|
}
|
|
152
198
|
};
|
|
153
199
|
|
|
200
|
+
// src/StorageStore.ts
|
|
201
|
+
var StorageStore = class extends AbstractStorageStore {
|
|
202
|
+
addUpdate() {
|
|
203
|
+
return Promise.resolve();
|
|
204
|
+
}
|
|
205
|
+
applyUpdates() {
|
|
206
|
+
return Promise.resolve();
|
|
207
|
+
}
|
|
208
|
+
destroy() {
|
|
209
|
+
return Promise.resolve();
|
|
210
|
+
}
|
|
211
|
+
flatten() {
|
|
212
|
+
return Promise.resolve();
|
|
213
|
+
}
|
|
214
|
+
getShouldFlatten() {
|
|
215
|
+
return Promise.resolve(false);
|
|
216
|
+
}
|
|
217
|
+
getUpdates() {
|
|
218
|
+
return Promise.resolve([]);
|
|
219
|
+
}
|
|
220
|
+
initialize() {
|
|
221
|
+
return Promise.resolve();
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
154
225
|
// src/types.ts
|
|
155
226
|
var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
|
|
156
227
|
ConnectionState2["Closed"] = "Closed";
|
|
@@ -256,13 +327,40 @@ var UsersManager = class {
|
|
|
256
327
|
}
|
|
257
328
|
};
|
|
258
329
|
|
|
330
|
+
// src/utils/debounce.ts
|
|
331
|
+
var debounce = (fn, options) => {
|
|
332
|
+
const { wait, immediate = false } = options != null ? options : {};
|
|
333
|
+
let prevResult;
|
|
334
|
+
let timeout = null;
|
|
335
|
+
return (...args) => {
|
|
336
|
+
if (typeof timeout === "number")
|
|
337
|
+
clearTimeout(timeout);
|
|
338
|
+
timeout = setTimeout(() => {
|
|
339
|
+
timeout = null;
|
|
340
|
+
if (!immediate)
|
|
341
|
+
prevResult = fn(args);
|
|
342
|
+
}, wait);
|
|
343
|
+
if (immediate && !timeout) {
|
|
344
|
+
fn(args);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
return prevResult;
|
|
348
|
+
};
|
|
349
|
+
};
|
|
350
|
+
|
|
259
351
|
// src/PluvRoom.ts
|
|
352
|
+
var ADD_TO_STORAGE_STATE_DEBOUNCE_MS = 1e3;
|
|
260
353
|
var HEARTBEAT_INTERVAL_MS = 1e4;
|
|
261
354
|
var PONG_TIMEOUT_MS = 2e3;
|
|
262
355
|
var RECONNECT_TIMEOUT_MS = 3e4;
|
|
356
|
+
var Y_ORIGIN_INITIALIZED = "$INITIALIZED";
|
|
357
|
+
var DEFAULT_PLUV_CLIENT_ADDON = (input) => ({
|
|
358
|
+
storage: new StorageStore(input.room.id)
|
|
359
|
+
});
|
|
263
360
|
var PluvRoom = class extends AbstractRoom {
|
|
264
361
|
constructor(room, options) {
|
|
265
362
|
const {
|
|
363
|
+
addons = [],
|
|
266
364
|
authEndpoint,
|
|
267
365
|
debug = false,
|
|
268
366
|
initialPresence,
|
|
@@ -272,10 +370,8 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
272
370
|
wsEndpoint
|
|
273
371
|
} = options;
|
|
274
372
|
super(room);
|
|
275
|
-
this._crdtManager = null;
|
|
276
373
|
this._crdtNotifier = new CrdtNotifier();
|
|
277
374
|
this._eventNotifier = new EventNotifier();
|
|
278
|
-
this._initialStorage = null;
|
|
279
375
|
this._intervals = {
|
|
280
376
|
heartbeat: null
|
|
281
377
|
};
|
|
@@ -342,6 +438,27 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
342
438
|
{ presence }
|
|
343
439
|
);
|
|
344
440
|
};
|
|
441
|
+
this._flattenStorageStore = debounce(
|
|
442
|
+
() => __async(this, null, function* () {
|
|
443
|
+
const shouldFlatten = yield this._storageStore.getShouldFlatten();
|
|
444
|
+
if (!shouldFlatten)
|
|
445
|
+
return;
|
|
446
|
+
const updates = yield this._storageStore.getUpdates();
|
|
447
|
+
this._crdtManager.applyUpdate(updates, this);
|
|
448
|
+
const encodedState = this._crdtManager.doc.encodeStateAsUpdate();
|
|
449
|
+
yield this._storageStore.flatten(encodedState);
|
|
450
|
+
}),
|
|
451
|
+
{ wait: ADD_TO_STORAGE_STATE_DEBOUNCE_MS }
|
|
452
|
+
);
|
|
453
|
+
this._getAddon = (addons) => {
|
|
454
|
+
return addons.reduce(
|
|
455
|
+
(acc, addon) => () => __spreadValues(__spreadValues({}, acc({ room: this })), addon({ room: this })),
|
|
456
|
+
DEFAULT_PLUV_CLIENT_ADDON
|
|
457
|
+
);
|
|
458
|
+
};
|
|
459
|
+
const addon = this._getAddon(addons);
|
|
460
|
+
const { storage } = __spreadValues(__spreadValues({}, DEFAULT_PLUV_CLIENT_ADDON({ room: this })), addon({ room: this }));
|
|
461
|
+
this._storageStore = storage;
|
|
345
462
|
this._debug = debug;
|
|
346
463
|
this._endpoints = { authEndpoint, wsEndpoint };
|
|
347
464
|
this._listeners = {
|
|
@@ -358,8 +475,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
358
475
|
initialPresence,
|
|
359
476
|
presence
|
|
360
477
|
});
|
|
361
|
-
|
|
362
|
-
this._initialStorage = initialStorage;
|
|
478
|
+
this._crdtManager = new CrdtManager({ initialStorage });
|
|
363
479
|
}
|
|
364
480
|
get webSocket() {
|
|
365
481
|
return this._state.webSocket;
|
|
@@ -388,19 +504,17 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
388
504
|
oldState.connection.state = "Connecting" /* Connecting */;
|
|
389
505
|
return oldState;
|
|
390
506
|
});
|
|
507
|
+
yield this._storageStore.initialize();
|
|
508
|
+
yield this._applyStorageStore();
|
|
391
509
|
const url = new URL(this._getWsEndpoint(this.id));
|
|
392
510
|
let authToken = null;
|
|
393
|
-
try {
|
|
394
|
-
authToken = yield this._getAuthorization(this.id);
|
|
395
|
-
} catch (err) {
|
|
396
|
-
this._onAuthorizationFail(err);
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
authToken && url.searchParams.set("token", encodeURIComponent(authToken));
|
|
400
511
|
let webSocket;
|
|
401
512
|
try {
|
|
513
|
+
authToken = yield this._getAuthorization(this.id);
|
|
514
|
+
authToken && url.searchParams.set("token", encodeURIComponent(authToken));
|
|
402
515
|
webSocket = new WebSocket(url.toString());
|
|
403
516
|
} catch (err) {
|
|
517
|
+
yield this._storageStore.destroy();
|
|
404
518
|
this._onAuthorizationFail(err);
|
|
405
519
|
return;
|
|
406
520
|
}
|
|
@@ -414,27 +528,24 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
414
528
|
});
|
|
415
529
|
}
|
|
416
530
|
disconnect() {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
oldState.connection.state = "Closed" /* Closed */;
|
|
436
|
-
oldState.webSocket = null;
|
|
437
|
-
return oldState;
|
|
531
|
+
return __async(this, null, function* () {
|
|
532
|
+
var _a;
|
|
533
|
+
if (!this._endpoints.wsEndpoint) {
|
|
534
|
+
throw new Error("Must provide an wsEndpoint.");
|
|
535
|
+
}
|
|
536
|
+
if (!this._state.webSocket)
|
|
537
|
+
return;
|
|
538
|
+
const canDisconnect = [
|
|
539
|
+
"Connecting" /* Connecting */,
|
|
540
|
+
"Open" /* Open */
|
|
541
|
+
].some((state) => this._state.connection.state === state);
|
|
542
|
+
if (!canDisconnect)
|
|
543
|
+
return;
|
|
544
|
+
this._clearInterval(this._intervals.heartbeat);
|
|
545
|
+
this._closeWs();
|
|
546
|
+
yield this._storageStore.destroy();
|
|
547
|
+
(_a = this._crdtManager) == null ? void 0 : _a.destroy();
|
|
548
|
+
this._stateNotifier.subjects["my-presence"].next(null);
|
|
438
549
|
});
|
|
439
550
|
}
|
|
440
551
|
getConnection() {
|
|
@@ -442,6 +553,25 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
442
553
|
JSON.parse(JSON.stringify(this._state.connection))
|
|
443
554
|
);
|
|
444
555
|
}
|
|
556
|
+
_addToStorageStore(update) {
|
|
557
|
+
return __async(this, null, function* () {
|
|
558
|
+
yield this._storageStore.addUpdate(update);
|
|
559
|
+
this._flattenStorageStore();
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
_applyStorageStore() {
|
|
563
|
+
return __async(this, null, function* () {
|
|
564
|
+
const updates = yield this._storageStore.getUpdates();
|
|
565
|
+
this._crdtManager.initialize({
|
|
566
|
+
onInitialized: () => {
|
|
567
|
+
this._observeCrdt();
|
|
568
|
+
},
|
|
569
|
+
origin: Y_ORIGIN_INITIALIZED,
|
|
570
|
+
update: updates
|
|
571
|
+
});
|
|
572
|
+
this._emitSharedTypes();
|
|
573
|
+
});
|
|
574
|
+
}
|
|
445
575
|
_attachWindowListeners() {
|
|
446
576
|
if (typeof window === "undefined")
|
|
447
577
|
return;
|
|
@@ -514,7 +644,6 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
514
644
|
if (canClose)
|
|
515
645
|
this._state.webSocket.close();
|
|
516
646
|
this._detachWsListeners();
|
|
517
|
-
this._stateNotifier.subjects["myself"].next(null);
|
|
518
647
|
}
|
|
519
648
|
_detachWindowListeners() {
|
|
520
649
|
if (!this._windowListeners)
|
|
@@ -554,23 +683,11 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
554
683
|
);
|
|
555
684
|
this._wsListeners = null;
|
|
556
685
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
const { url, options } = fetchOptions;
|
|
563
|
-
const res = yield fetch(url, options);
|
|
564
|
-
if (!res.ok || res.status !== 200) {
|
|
565
|
-
throw new Error("Room is unauthorized");
|
|
566
|
-
}
|
|
567
|
-
try {
|
|
568
|
-
return yield res.text().then((text2) => text2.trim());
|
|
569
|
-
} catch (err) {
|
|
570
|
-
throw new Error(
|
|
571
|
-
err instanceof Error ? err.message : "Room is unauthorized"
|
|
572
|
-
);
|
|
573
|
-
}
|
|
686
|
+
_emitSharedTypes() {
|
|
687
|
+
const sharedTypes = this._crdtManager.doc.getSharedTypes();
|
|
688
|
+
Object.entries(sharedTypes).forEach(([prop, sharedType]) => {
|
|
689
|
+
const serialized = sharedType.toJSON();
|
|
690
|
+
this._crdtNotifier.subject(prop).next(serialized);
|
|
574
691
|
});
|
|
575
692
|
}
|
|
576
693
|
_getAuthFetchOptions(room) {
|
|
@@ -591,6 +708,25 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
591
708
|
const result = this._endpoints.authEndpoint(room);
|
|
592
709
|
return typeof result === "string" ? { url: result, options: {} } : result;
|
|
593
710
|
}
|
|
711
|
+
_getAuthorization(room) {
|
|
712
|
+
return __async(this, null, function* () {
|
|
713
|
+
const fetchOptions = this._getAuthFetchOptions(room);
|
|
714
|
+
if (!fetchOptions)
|
|
715
|
+
return null;
|
|
716
|
+
const { url, options } = fetchOptions;
|
|
717
|
+
const res = yield fetch(url, options);
|
|
718
|
+
if (!res.ok || res.status !== 200) {
|
|
719
|
+
throw new Error("Room is unauthorized");
|
|
720
|
+
}
|
|
721
|
+
try {
|
|
722
|
+
return yield res.text().then((text2) => text2.trim());
|
|
723
|
+
} catch (err) {
|
|
724
|
+
throw new Error(
|
|
725
|
+
err instanceof Error ? err.message : "Room is unauthorized"
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
}
|
|
594
730
|
_getWsEndpoint(room) {
|
|
595
731
|
switch (typeof this._endpoints.wsEndpoint) {
|
|
596
732
|
case "undefined":
|
|
@@ -686,28 +822,28 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
686
822
|
});
|
|
687
823
|
}
|
|
688
824
|
_handleStorageReceivedMessage(message) {
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
const
|
|
704
|
-
|
|
705
|
-
this.
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
825
|
+
return __async(this, null, function* () {
|
|
826
|
+
const { connectionId } = message;
|
|
827
|
+
if (!connectionId)
|
|
828
|
+
return;
|
|
829
|
+
if (!this._state.webSocket)
|
|
830
|
+
throw new Error("Could not find WebSocket");
|
|
831
|
+
const data = message.data;
|
|
832
|
+
this._crdtManager.initialize({
|
|
833
|
+
onInitialized: () => __async(this, null, function* () {
|
|
834
|
+
this._observeCrdt();
|
|
835
|
+
}),
|
|
836
|
+
origin: Y_ORIGIN_INITIALIZED,
|
|
837
|
+
update: data.state
|
|
838
|
+
});
|
|
839
|
+
const encodedState = this._crdtManager.doc.encodeStateAsUpdate();
|
|
840
|
+
yield this._storageStore.addUpdate(encodedState);
|
|
841
|
+
this._emitSharedTypes();
|
|
842
|
+
const update = this._crdtManager.doc.encodeStateAsUpdate();
|
|
843
|
+
this._sendMessage({
|
|
844
|
+
type: "$UPDATE_STORAGE",
|
|
845
|
+
data: { origin: Y_ORIGIN_INITIALIZED, update }
|
|
846
|
+
});
|
|
711
847
|
});
|
|
712
848
|
}
|
|
713
849
|
_handleStorageUpdatedMessage(message) {
|
|
@@ -721,14 +857,11 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
721
857
|
return;
|
|
722
858
|
this._crdtManager.doc.applyUpdate(data.state, "$STORAGE_UPDATED");
|
|
723
859
|
const sharedTypes = this._crdtManager.doc.getSharedTypes();
|
|
724
|
-
Object.
|
|
860
|
+
Object.entries(sharedTypes).forEach(([prop, sharedType]) => {
|
|
725
861
|
if (!this._crdtManager)
|
|
726
862
|
return;
|
|
727
|
-
const sharedType = sharedTypes[key];
|
|
728
|
-
if (!sharedType)
|
|
729
|
-
return;
|
|
730
863
|
const serialized = sharedType.toJSON();
|
|
731
|
-
this._crdtNotifier.subject(
|
|
864
|
+
this._crdtNotifier.subject(prop).next(serialized);
|
|
732
865
|
});
|
|
733
866
|
}
|
|
734
867
|
_handleUserJoinedMessage(message) {
|
|
@@ -776,7 +909,9 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
776
909
|
const unsubscribe = this._crdtManager.doc.subscribe(
|
|
777
910
|
(update, _origin) => {
|
|
778
911
|
const origin = _origin != null ? _origin : null;
|
|
779
|
-
|
|
912
|
+
this._addToStorageStore(update);
|
|
913
|
+
this._emitSharedTypes();
|
|
914
|
+
if (origin === Y_ORIGIN_INITIALIZED || origin === this)
|
|
780
915
|
return;
|
|
781
916
|
if (!this._state.webSocket)
|
|
782
917
|
return;
|
|
@@ -786,13 +921,6 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
786
921
|
return;
|
|
787
922
|
if (origin === "$STORAGE_UPDATED")
|
|
788
923
|
return;
|
|
789
|
-
const sharedTypes = this._crdtManager.doc.getSharedTypes();
|
|
790
|
-
Object.entries(sharedTypes).forEach(([prop, sharedType]) => {
|
|
791
|
-
if (!this._crdtManager)
|
|
792
|
-
return;
|
|
793
|
-
const serialized = sharedType.toJSON();
|
|
794
|
-
this._crdtNotifier.subject(prop).next(serialized);
|
|
795
|
-
});
|
|
796
924
|
this._sendMessage({
|
|
797
925
|
type: "$UPDATE_STORAGE",
|
|
798
926
|
data: { origin, update }
|
|
@@ -817,14 +945,18 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
817
945
|
* @date August 19, 2022
|
|
818
946
|
*/
|
|
819
947
|
_onClose(event) {
|
|
820
|
-
this
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
oldState
|
|
827
|
-
|
|
948
|
+
return __async(this, null, function* () {
|
|
949
|
+
this._logDebug("WebSocket closed");
|
|
950
|
+
!!event.reason && this._logDebug(event.reason);
|
|
951
|
+
this._clearInterval(this._intervals.heartbeat);
|
|
952
|
+
this._clearTimeout(this._timeouts.pong);
|
|
953
|
+
this._stateNotifier.subjects["myself"].next(null);
|
|
954
|
+
this._updateState((oldState) => {
|
|
955
|
+
oldState.authorization.token = null;
|
|
956
|
+
oldState.connection.state = "Closed" /* Closed */;
|
|
957
|
+
oldState.webSocket = null;
|
|
958
|
+
return oldState;
|
|
959
|
+
});
|
|
828
960
|
});
|
|
829
961
|
}
|
|
830
962
|
/**
|
|
@@ -997,6 +1129,7 @@ var PluvClient = class {
|
|
|
997
1129
|
if (oldRoom)
|
|
998
1130
|
return oldRoom;
|
|
999
1131
|
const newRoom = new PluvRoom(room, {
|
|
1132
|
+
addons: options.addons,
|
|
1000
1133
|
authEndpoint: this._authEndpoint,
|
|
1001
1134
|
debug: options.debug,
|
|
1002
1135
|
initialPresence: options.initialPresence,
|
|
@@ -1025,14 +1158,14 @@ var PluvClient = class {
|
|
|
1025
1158
|
this.getRooms = () => {
|
|
1026
1159
|
return Array.from(this._rooms.values());
|
|
1027
1160
|
};
|
|
1028
|
-
this.leave = (room) => {
|
|
1161
|
+
this.leave = (room) => __async(this, null, function* () {
|
|
1029
1162
|
const toLeave = typeof room === "string" ? this.getRoom(room) : room;
|
|
1030
1163
|
if (!toLeave)
|
|
1031
1164
|
return;
|
|
1032
|
-
toLeave.disconnect();
|
|
1165
|
+
yield toLeave.disconnect();
|
|
1033
1166
|
this._rooms.delete(toLeave.id);
|
|
1034
1167
|
this._logDebug(`Left and deleted room: ${room}`);
|
|
1035
|
-
};
|
|
1168
|
+
});
|
|
1036
1169
|
const { authEndpoint, debug = false, wsEndpoint } = options;
|
|
1037
1170
|
this._authEndpoint = authEndpoint;
|
|
1038
1171
|
this._debug = debug;
|
|
@@ -1178,6 +1311,7 @@ __export(y_exports, {
|
|
|
1178
1311
|
import { array, map, text, object } from "@pluv/crdt-yjs";
|
|
1179
1312
|
export {
|
|
1180
1313
|
AbstractRoom,
|
|
1314
|
+
AbstractStorageStore,
|
|
1181
1315
|
ConnectionState,
|
|
1182
1316
|
MockedRoom,
|
|
1183
1317
|
PluvClient,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pluv/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "@pluv/io frontend client",
|
|
5
5
|
"author": "leedavidcs",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,19 +17,21 @@
|
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
+
"@playwright/test": "^1.35.1",
|
|
21
|
+
"@types/react": "^18.2.13",
|
|
20
22
|
"wonka": "^6.3.2",
|
|
21
|
-
"@pluv/crdt-yjs": "^0.
|
|
22
|
-
"@pluv/types": "^0.2.
|
|
23
|
+
"@pluv/crdt-yjs": "^0.4.0",
|
|
24
|
+
"@pluv/types": "^0.2.1"
|
|
23
25
|
},
|
|
24
26
|
"peerDependencies": {
|
|
25
27
|
"yjs": "^13.0.0"
|
|
26
28
|
},
|
|
27
29
|
"devDependencies": {
|
|
28
|
-
"tsup": "^
|
|
29
|
-
"typescript": "^5.
|
|
30
|
-
"yjs": "^13.6.
|
|
31
|
-
"@pluv/tsconfig": "^0.1.
|
|
32
|
-
"eslint-config-pluv": "^0.1.
|
|
30
|
+
"tsup": "^7.0.0",
|
|
31
|
+
"typescript": "^5.1.3",
|
|
32
|
+
"yjs": "^13.6.4",
|
|
33
|
+
"@pluv/tsconfig": "^0.1.7",
|
|
34
|
+
"eslint-config-pluv": "^0.1.13"
|
|
33
35
|
},
|
|
34
36
|
"scripts": {
|
|
35
37
|
"build": "tsup src/index.ts --format esm,cjs --dts",
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { MaybePromise } from "@pluv/types";
|
|
2
|
+
|
|
3
|
+
export abstract class AbstractStorageStore {
|
|
4
|
+
public room: string;
|
|
5
|
+
|
|
6
|
+
constructor(room: string) {
|
|
7
|
+
this.room = room;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public abstract addUpdate(update: string): Promise<void>;
|
|
11
|
+
|
|
12
|
+
public abstract destroy(): Promise<void>;
|
|
13
|
+
|
|
14
|
+
public abstract flatten(encodedState: string): Promise<void>;
|
|
15
|
+
|
|
16
|
+
public abstract getShouldFlatten(): Promise<boolean>;
|
|
17
|
+
|
|
18
|
+
public abstract getUpdates(start?: number): Promise<readonly string[]>;
|
|
19
|
+
|
|
20
|
+
public abstract initialize(): Promise<void>;
|
|
21
|
+
}
|
package/src/CrdtManager.ts
CHANGED
|
@@ -9,8 +9,15 @@ export interface CrdtManagerOptions<
|
|
|
9
9
|
initialStorage?: () => TStorage;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
interface CrdtManagerInitializeParams {
|
|
13
|
+
onInitialized?: () => void;
|
|
14
|
+
origin?: any;
|
|
15
|
+
update: string | readonly string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
12
18
|
export class CrdtManager<TStorage extends Record<string, AbstractType<any>>> {
|
|
13
19
|
public doc: YjsDoc<TStorage>;
|
|
20
|
+
public initialized: boolean = false;
|
|
14
21
|
|
|
15
22
|
constructor(options: CrdtManagerOptions<TStorage> = {}) {
|
|
16
23
|
const { encodedState, initialStorage } = options;
|
|
@@ -30,6 +37,23 @@ export class CrdtManager<TStorage extends Record<string, AbstractType<any>>> {
|
|
|
30
37
|
this.doc = doc<TStorage>(initialStorage?.());
|
|
31
38
|
}
|
|
32
39
|
|
|
40
|
+
/**
|
|
41
|
+
* !HACK
|
|
42
|
+
* @description If this is called before the doc is initialized. Omit the
|
|
43
|
+
* update. This unfortunately means updates can be dropped entirely
|
|
44
|
+
* @date June 13, 2023
|
|
45
|
+
*/
|
|
46
|
+
public applyUpdate(update: string | readonly string[], origin?: any): this {
|
|
47
|
+
if (!this.initialized) return this;
|
|
48
|
+
|
|
49
|
+
const updates: readonly string[] =
|
|
50
|
+
typeof update === "string" ? [update] : update;
|
|
51
|
+
|
|
52
|
+
this._applyDocUpdates(this.doc, updates, origin);
|
|
53
|
+
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
|
|
33
57
|
public destroy(): void {
|
|
34
58
|
this.doc.destroy();
|
|
35
59
|
}
|
|
@@ -37,4 +61,44 @@ export class CrdtManager<TStorage extends Record<string, AbstractType<any>>> {
|
|
|
37
61
|
public get<TKey extends keyof TStorage>(key: TKey): TStorage[TKey] {
|
|
38
62
|
return this.doc.get(key);
|
|
39
63
|
}
|
|
64
|
+
|
|
65
|
+
public initialize(params: CrdtManagerInitializeParams): this {
|
|
66
|
+
const { onInitialized, origin, update } = params;
|
|
67
|
+
|
|
68
|
+
const updates = typeof update === "string" ? [update] : update;
|
|
69
|
+
|
|
70
|
+
if (!updates.length) return this;
|
|
71
|
+
|
|
72
|
+
if (this.initialized) {
|
|
73
|
+
this._applyDocUpdates(this.doc, updates, origin);
|
|
74
|
+
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const _doc = this._applyDocUpdates(doc<TStorage>(), updates, origin);
|
|
79
|
+
|
|
80
|
+
this.initialized = true;
|
|
81
|
+
|
|
82
|
+
if (!!Object.keys(_doc.toJSON()).length) {
|
|
83
|
+
this.doc = _doc;
|
|
84
|
+
} else {
|
|
85
|
+
_doc.destroy();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
onInitialized?.();
|
|
89
|
+
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private _applyDocUpdates(
|
|
94
|
+
yjsDoc: YjsDoc<TStorage>,
|
|
95
|
+
updates: readonly string[],
|
|
96
|
+
origin?: string
|
|
97
|
+
): YjsDoc<TStorage> {
|
|
98
|
+
yjsDoc.transact(() => {
|
|
99
|
+
updates.reduce((acc, _update) => acc.applyUpdate(_update), yjsDoc);
|
|
100
|
+
}, origin);
|
|
101
|
+
|
|
102
|
+
return yjsDoc;
|
|
103
|
+
}
|
|
40
104
|
}
|
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
|
|