@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.
@@ -1,17 +1,17 @@
1
1
 
2
- > @pluv/client@0.8.1 build /home/runner/work/pluv/pluv/packages/client
2
+ > @pluv/client@0.9.0 build /home/runner/work/pluv/pluv/packages/client
3
3
  > tsup src/index.ts --format esm,cjs --dts
4
4
 
5
5
  CLI Building entry: src/index.ts
6
6
  CLI Using tsconfig: tsconfig.json
7
- CLI tsup v6.7.0
7
+ CLI tsup v7.0.0
8
8
  CLI Target: es6
9
9
  ESM Build start
10
10
  CJS Build start
11
- ESM dist/index.mjs 35.08 KB
12
- ESM ⚡️ Build success in 90ms
13
- CJS dist/index.js 36.22 KB
14
- CJS ⚡️ Build success in 92ms
11
+ CJS dist/index.js 40.23 KB
12
+ CJS ⚡️ Build success in 98ms
13
+ ESM dist/index.mjs 39.02 KB
14
+ ESM ⚡️ Build success in 100ms
15
15
  DTS Build start
16
- DTS ⚡️ Build success in 3816ms
17
- DTS dist/index.d.ts 11.73 KB
16
+ DTS ⚡️ Build success in 4765ms
17
+ DTS dist/index.d.ts 12.92 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,55 @@
1
1
  # @pluv/client
2
2
 
3
+ ## 0.9.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 829d31b: Added support for defining persistant frontend storage for rooms via a new `addons` option on rooms.
8
+
9
+ This also introduces the first new addon `@pluv/addon-indexeddb`, which is more-or-less the equivalent to `y-indexeddb` which you can install like so:
10
+
11
+ ```
12
+ npm install @pluv/addon-indexeddb
13
+ ```
14
+
15
+ To use this new addon, simply pass it to options when creating a room:
16
+
17
+ ```ts
18
+ import { addonIndexedDB } from "@pluv/addon-indexeddb";
19
+ import { createClient } from "@pluv/client";
20
+
21
+ const client = createClient({
22
+ // ...
23
+ });
24
+
25
+ const room = client.createRoom("my-new-room", {
26
+ addons: [
27
+ // Define your addons in an array like so
28
+ addonIndexedDB(),
29
+ ],
30
+ });
31
+ ```
32
+
33
+ Or when using `@pluv/react`:
34
+
35
+ ```ts
36
+ const PluvRoom = createRoomBundle({
37
+ // ...
38
+ addons: [
39
+ // Define your addons in an array like so
40
+ addonIndexedDB(),
41
+ ],
42
+ });
43
+ ```
44
+
45
+ ### Patch Changes
46
+
47
+ - 8d11672: bumped dependencies to latest
48
+ - Updated dependencies [8d11672]
49
+ - Updated dependencies [829d31b]
50
+ - @pluv/crdt-yjs@0.4.0
51
+ - @pluv/types@0.2.1
52
+
3
53
  ## 0.8.1
4
54
 
5
55
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -64,6 +64,17 @@ declare abstract class AbstractRoom<TIO extends IOLike, TPresence extends JsonOb
64
64
  abstract updateMyPresence(presence: Partial<TPresence>): void;
65
65
  }
66
66
 
67
+ declare abstract class AbstractStorageStore {
68
+ room: string;
69
+ constructor(room: string);
70
+ abstract addUpdate(update: string): Promise<void>;
71
+ abstract destroy(): Promise<void>;
72
+ abstract flatten(encodedState: string): Promise<void>;
73
+ abstract getShouldFlatten(): Promise<boolean>;
74
+ abstract getUpdates(start?: number): Promise<readonly string[]>;
75
+ abstract initialize(): Promise<void>;
76
+ }
77
+
67
78
  interface CrdtManagerOptions<TStorage extends Record<string, AbstractType<any>> = {}> {
68
79
  encodedState?: string | Uint8Array | null;
69
80
  initialStorage?: () => TStorage;
@@ -94,11 +105,19 @@ type RoomEndpoints<TIO extends IOLike> = {
94
105
  } : {
95
106
  authEndpoint?: undefined;
96
107
  });
108
+ type PluvRoomAddon<TIO extends IOLike = IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, AbstractType<any>> = {}> = (input: PluvRoomAddonInput<TIO, TPresence, TStorage>) => Partial<PluvRoomAddonResult>;
109
+ interface PluvRoomAddonInput<TIO extends IOLike = IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, AbstractType<any>> = {}> {
110
+ room: PluvRoom<TIO, TPresence, TStorage>;
111
+ }
112
+ interface PluvRoomAddonResult {
113
+ storage: AbstractStorageStore;
114
+ }
97
115
  type PluvRoomDebug<TIO extends IOLike> = Id<{
98
116
  output: readonly (keyof InferIOOutput<TIO>)[];
99
117
  input: readonly (keyof InferIOInput<TIO>)[];
100
118
  }>;
101
119
  type PluvRoomOptions<TIO extends IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, AbstractType<any>> = {}> = {
120
+ addons?: readonly PluvRoomAddon<TIO, TPresence, TStorage>[];
102
121
  debug?: boolean | PluvRoomDebug<TIO>;
103
122
  onAuthorizationFail?: (error: Error) => void;
104
123
  } & Omit<CrdtManagerOptions<TStorage>, "encodedState"> & UsersManagerConfig<TPresence>;
@@ -109,12 +128,12 @@ declare class PluvRoom<TIO extends IOLike, TPresence extends JsonObject = {}, TS
109
128
  private _crdtNotifier;
110
129
  private _debug;
111
130
  private _eventNotifier;
112
- private _initialStorage;
113
131
  private _intervals;
114
132
  private _listeners;
115
133
  private _otherNotifier;
116
134
  private _state;
117
135
  private _stateNotifier;
136
+ private _storageStore;
118
137
  private _subscriptions;
119
138
  private _timeouts;
120
139
  private _windowListeners;
@@ -124,7 +143,7 @@ declare class PluvRoom<TIO extends IOLike, TPresence extends JsonObject = {}, TS
124
143
  get webSocket(): WebSocket | null;
125
144
  broadcast<TEvent extends keyof InferIOInput<TIO>>(event: TEvent, data: Id<InferIOInput<TIO>[TEvent]>): void;
126
145
  connect(): Promise<void>;
127
- disconnect(): void;
146
+ disconnect(): Promise<void>;
128
147
  event: <TEvent extends keyof InferIOOutput<TIO>>(event: TEvent, callback: EventNotifierSubscriptionCallback<TIO, TEvent>) => (() => void);
129
148
  getConnection(): WebSocketConnection;
130
149
  getMyPresence: () => TPresence;
@@ -136,6 +155,8 @@ declare class PluvRoom<TIO extends IOLike, TPresence extends JsonObject = {}, TS
136
155
  storage: <TKey extends keyof TStorage>(key: TKey, fn: (value: InferYjsSharedTypeJson<TStorage[TKey]>) => void) => (() => void);
137
156
  subscribe: <TSubject extends keyof StateNotifierSubjects<TIO, TPresence>>(name: TSubject, callback: SubscriptionCallback<TIO, TPresence, TSubject>) => (() => void);
138
157
  updateMyPresence: (presence: Partial<TPresence>) => void;
158
+ private _addToStorageStore;
159
+ private _applyStorageStore;
139
160
  private _attachWindowListeners;
140
161
  private _attachWsListeners;
141
162
  private _clearInterval;
@@ -143,8 +164,11 @@ declare class PluvRoom<TIO extends IOLike, TPresence extends JsonObject = {}, TS
143
164
  private _closeWs;
144
165
  private _detachWindowListeners;
145
166
  private _detachWsListeners;
146
- private _getAuthorization;
167
+ private _emitSharedTypes;
168
+ private _flattenStorageStore;
169
+ private _getAddon;
147
170
  private _getAuthFetchOptions;
171
+ private _getAuthorization;
148
172
  private _getWsEndpoint;
149
173
  private _handleExit;
150
174
  private _handlePresenceUpdatedMessage;
@@ -195,7 +219,7 @@ declare class PluvClient<TIO extends IOLike = IOLike> {
195
219
  enter: (room: string | PluvRoom<TIO, any, any>) => Promise<PluvRoom<TIO, JsonObject, any>>;
196
220
  getRoom: <TPresence extends JsonObject = {}, TStorage extends Record<string, AbstractType<any>> = {}>(room: string) => PluvRoom<TIO, TPresence, TStorage> | null;
197
221
  getRooms: () => readonly PluvRoom<TIO, JsonObject, any>[];
198
- leave: (room: string | PluvRoom<TIO, any, any>) => void;
222
+ leave: (room: string | PluvRoom<TIO, any, any>) => Promise<void>;
199
223
  private _logDebug;
200
224
  }
201
225
 
@@ -246,4 +270,4 @@ declare namespace y {
246
270
  };
247
271
  }
248
272
 
249
- export { AbstractRoom, ConnectionState, MockedRoom, MockedRoomConfig, MockedRoomEvents, PluvClient, PluvClientOptions, PluvRoom, PluvRoomDebug, UserInfo, WebSocketConnection, WebSocketState, createClient, y };
273
+ export { AbstractRoom, AbstractStorageStore, ConnectionState, MockedRoom, MockedRoomConfig, MockedRoomEvents, PluvClient, PluvClientOptions, PluvRoom, PluvRoomAddon, PluvRoomAddonInput, PluvRoomAddonResult, PluvRoomDebug, UserInfo, WebSocketConnection, WebSocketState, createClient, y };
package/dist/index.js CHANGED
@@ -58,6 +58,7 @@ var __async = (__this, __arguments, generator) => {
58
58
  var src_exports = {};
59
59
  __export(src_exports, {
60
60
  AbstractRoom: () => AbstractRoom,
61
+ AbstractStorageStore: () => AbstractStorageStore,
61
62
  ConnectionState: () => ConnectionState,
62
63
  MockedRoom: () => MockedRoom,
63
64
  PluvClient: () => PluvClient,
@@ -74,10 +75,18 @@ var AbstractRoom = class {
74
75
  }
75
76
  };
76
77
 
78
+ // src/AbstractStorageStore.ts
79
+ var AbstractStorageStore = class {
80
+ constructor(room) {
81
+ this.room = room;
82
+ }
83
+ };
84
+
77
85
  // src/CrdtManager.ts
78
86
  var import_crdt_yjs = require("@pluv/crdt-yjs");
79
87
  var CrdtManager = class {
80
88
  constructor(options = {}) {
89
+ this.initialized = false;
81
90
  const { encodedState, initialStorage } = options;
82
91
  const _doc = encodedState ? (0, import_crdt_yjs.doc)().applyUpdate(encodedState) : null;
83
92
  if (_doc && !!Object.keys(_doc.toJSON()).length) {
@@ -87,12 +96,50 @@ var CrdtManager = class {
87
96
  _doc == null ? void 0 : _doc.destroy();
88
97
  this.doc = (0, import_crdt_yjs.doc)(initialStorage == null ? void 0 : initialStorage());
89
98
  }
99
+ /**
100
+ * !HACK
101
+ * @description If this is called before the doc is initialized. Omit the
102
+ * update. This unfortunately means updates can be dropped entirely
103
+ * @date June 13, 2023
104
+ */
105
+ applyUpdate(update, origin) {
106
+ if (!this.initialized)
107
+ return this;
108
+ const updates = typeof update === "string" ? [update] : update;
109
+ this._applyDocUpdates(this.doc, updates, origin);
110
+ return this;
111
+ }
90
112
  destroy() {
91
113
  this.doc.destroy();
92
114
  }
93
115
  get(key) {
94
116
  return this.doc.get(key);
95
117
  }
118
+ initialize(params) {
119
+ const { onInitialized, origin, update } = params;
120
+ const updates = typeof update === "string" ? [update] : update;
121
+ if (!updates.length)
122
+ return this;
123
+ if (this.initialized) {
124
+ this._applyDocUpdates(this.doc, updates, origin);
125
+ return this;
126
+ }
127
+ const _doc = this._applyDocUpdates((0, import_crdt_yjs.doc)(), updates, origin);
128
+ this.initialized = true;
129
+ if (!!Object.keys(_doc.toJSON()).length) {
130
+ this.doc = _doc;
131
+ } else {
132
+ _doc.destroy();
133
+ }
134
+ onInitialized == null ? void 0 : onInitialized();
135
+ return this;
136
+ }
137
+ _applyDocUpdates(yjsDoc, updates, origin) {
138
+ yjsDoc.transact(() => {
139
+ updates.reduce((acc, _update) => acc.applyUpdate(_update), yjsDoc);
140
+ }, origin);
141
+ return yjsDoc;
142
+ }
96
143
  };
97
144
 
98
145
  // src/CrdtNotifier.ts
@@ -176,6 +223,31 @@ var StateNotifier = class {
176
223
  }
177
224
  };
178
225
 
226
+ // src/StorageStore.ts
227
+ var StorageStore = class extends AbstractStorageStore {
228
+ addUpdate() {
229
+ return Promise.resolve();
230
+ }
231
+ applyUpdates() {
232
+ return Promise.resolve();
233
+ }
234
+ destroy() {
235
+ return Promise.resolve();
236
+ }
237
+ flatten() {
238
+ return Promise.resolve();
239
+ }
240
+ getShouldFlatten() {
241
+ return Promise.resolve(false);
242
+ }
243
+ getUpdates() {
244
+ return Promise.resolve([]);
245
+ }
246
+ initialize() {
247
+ return Promise.resolve();
248
+ }
249
+ };
250
+
179
251
  // src/types.ts
180
252
  var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
181
253
  ConnectionState2["Closed"] = "Closed";
@@ -281,13 +353,40 @@ var UsersManager = class {
281
353
  }
282
354
  };
283
355
 
356
+ // src/utils/debounce.ts
357
+ var debounce = (fn, options) => {
358
+ const { wait, immediate = false } = options != null ? options : {};
359
+ let prevResult;
360
+ let timeout = null;
361
+ return (...args) => {
362
+ if (typeof timeout === "number")
363
+ clearTimeout(timeout);
364
+ timeout = setTimeout(() => {
365
+ timeout = null;
366
+ if (!immediate)
367
+ prevResult = fn(args);
368
+ }, wait);
369
+ if (immediate && !timeout) {
370
+ fn(args);
371
+ return;
372
+ }
373
+ return prevResult;
374
+ };
375
+ };
376
+
284
377
  // src/PluvRoom.ts
378
+ var ADD_TO_STORAGE_STATE_DEBOUNCE_MS = 1e3;
285
379
  var HEARTBEAT_INTERVAL_MS = 1e4;
286
380
  var PONG_TIMEOUT_MS = 2e3;
287
381
  var RECONNECT_TIMEOUT_MS = 3e4;
382
+ var Y_ORIGIN_INITIALIZED = "$INITIALIZED";
383
+ var DEFAULT_PLUV_CLIENT_ADDON = (input) => ({
384
+ storage: new StorageStore(input.room.id)
385
+ });
288
386
  var PluvRoom = class extends AbstractRoom {
289
387
  constructor(room, options) {
290
388
  const {
389
+ addons = [],
291
390
  authEndpoint,
292
391
  debug = false,
293
392
  initialPresence,
@@ -297,10 +396,8 @@ var PluvRoom = class extends AbstractRoom {
297
396
  wsEndpoint
298
397
  } = options;
299
398
  super(room);
300
- this._crdtManager = null;
301
399
  this._crdtNotifier = new CrdtNotifier();
302
400
  this._eventNotifier = new EventNotifier();
303
- this._initialStorage = null;
304
401
  this._intervals = {
305
402
  heartbeat: null
306
403
  };
@@ -367,6 +464,27 @@ var PluvRoom = class extends AbstractRoom {
367
464
  { presence }
368
465
  );
369
466
  };
467
+ this._flattenStorageStore = debounce(
468
+ () => __async(this, null, function* () {
469
+ const shouldFlatten = yield this._storageStore.getShouldFlatten();
470
+ if (!shouldFlatten)
471
+ return;
472
+ const updates = yield this._storageStore.getUpdates();
473
+ this._crdtManager.applyUpdate(updates, this);
474
+ const encodedState = this._crdtManager.doc.encodeStateAsUpdate();
475
+ yield this._storageStore.flatten(encodedState);
476
+ }),
477
+ { wait: ADD_TO_STORAGE_STATE_DEBOUNCE_MS }
478
+ );
479
+ this._getAddon = (addons) => {
480
+ return addons.reduce(
481
+ (acc, addon) => () => __spreadValues(__spreadValues({}, acc({ room: this })), addon({ room: this })),
482
+ DEFAULT_PLUV_CLIENT_ADDON
483
+ );
484
+ };
485
+ const addon = this._getAddon(addons);
486
+ const { storage } = __spreadValues(__spreadValues({}, DEFAULT_PLUV_CLIENT_ADDON({ room: this })), addon({ room: this }));
487
+ this._storageStore = storage;
370
488
  this._debug = debug;
371
489
  this._endpoints = { authEndpoint, wsEndpoint };
372
490
  this._listeners = {
@@ -383,8 +501,7 @@ var PluvRoom = class extends AbstractRoom {
383
501
  initialPresence,
384
502
  presence
385
503
  });
386
- if (initialStorage)
387
- this._initialStorage = initialStorage;
504
+ this._crdtManager = new CrdtManager({ initialStorage });
388
505
  }
389
506
  get webSocket() {
390
507
  return this._state.webSocket;
@@ -413,19 +530,17 @@ var PluvRoom = class extends AbstractRoom {
413
530
  oldState.connection.state = "Connecting" /* Connecting */;
414
531
  return oldState;
415
532
  });
533
+ yield this._storageStore.initialize();
534
+ yield this._applyStorageStore();
416
535
  const url = new URL(this._getWsEndpoint(this.id));
417
536
  let authToken = null;
418
- try {
419
- authToken = yield this._getAuthorization(this.id);
420
- } catch (err) {
421
- this._onAuthorizationFail(err);
422
- return;
423
- }
424
- authToken && url.searchParams.set("token", encodeURIComponent(authToken));
425
537
  let webSocket;
426
538
  try {
539
+ authToken = yield this._getAuthorization(this.id);
540
+ authToken && url.searchParams.set("token", encodeURIComponent(authToken));
427
541
  webSocket = new WebSocket(url.toString());
428
542
  } catch (err) {
543
+ yield this._storageStore.destroy();
429
544
  this._onAuthorizationFail(err);
430
545
  return;
431
546
  }
@@ -439,27 +554,24 @@ var PluvRoom = class extends AbstractRoom {
439
554
  });
440
555
  }
441
556
  disconnect() {
442
- var _a;
443
- if (!this._endpoints.wsEndpoint) {
444
- throw new Error("Must provide an wsEndpoint.");
445
- }
446
- if (!this._state.webSocket)
447
- return;
448
- const canDisconnect = [
449
- "Connecting" /* Connecting */,
450
- "Open" /* Open */
451
- ].some((state) => this._state.connection.state === state);
452
- if (!canDisconnect)
453
- return;
454
- this._clearInterval(this._intervals.heartbeat);
455
- this._closeWs();
456
- (_a = this._crdtManager) == null ? void 0 : _a.destroy();
457
- this._stateNotifier.subjects["my-presence"].next(null);
458
- this._updateState((oldState) => {
459
- oldState.authorization.token = null;
460
- oldState.connection.state = "Closed" /* Closed */;
461
- oldState.webSocket = null;
462
- return oldState;
557
+ return __async(this, null, function* () {
558
+ var _a;
559
+ if (!this._endpoints.wsEndpoint) {
560
+ throw new Error("Must provide an wsEndpoint.");
561
+ }
562
+ if (!this._state.webSocket)
563
+ return;
564
+ const canDisconnect = [
565
+ "Connecting" /* Connecting */,
566
+ "Open" /* Open */
567
+ ].some((state) => this._state.connection.state === state);
568
+ if (!canDisconnect)
569
+ return;
570
+ this._clearInterval(this._intervals.heartbeat);
571
+ this._closeWs();
572
+ yield this._storageStore.destroy();
573
+ (_a = this._crdtManager) == null ? void 0 : _a.destroy();
574
+ this._stateNotifier.subjects["my-presence"].next(null);
463
575
  });
464
576
  }
465
577
  getConnection() {
@@ -467,6 +579,25 @@ var PluvRoom = class extends AbstractRoom {
467
579
  JSON.parse(JSON.stringify(this._state.connection))
468
580
  );
469
581
  }
582
+ _addToStorageStore(update) {
583
+ return __async(this, null, function* () {
584
+ yield this._storageStore.addUpdate(update);
585
+ this._flattenStorageStore();
586
+ });
587
+ }
588
+ _applyStorageStore() {
589
+ return __async(this, null, function* () {
590
+ const updates = yield this._storageStore.getUpdates();
591
+ this._crdtManager.initialize({
592
+ onInitialized: () => {
593
+ this._observeCrdt();
594
+ },
595
+ origin: Y_ORIGIN_INITIALIZED,
596
+ update: updates
597
+ });
598
+ this._emitSharedTypes();
599
+ });
600
+ }
470
601
  _attachWindowListeners() {
471
602
  if (typeof window === "undefined")
472
603
  return;
@@ -539,7 +670,6 @@ var PluvRoom = class extends AbstractRoom {
539
670
  if (canClose)
540
671
  this._state.webSocket.close();
541
672
  this._detachWsListeners();
542
- this._stateNotifier.subjects["myself"].next(null);
543
673
  }
544
674
  _detachWindowListeners() {
545
675
  if (!this._windowListeners)
@@ -579,23 +709,11 @@ var PluvRoom = class extends AbstractRoom {
579
709
  );
580
710
  this._wsListeners = null;
581
711
  }
582
- _getAuthorization(room) {
583
- return __async(this, null, function* () {
584
- const fetchOptions = this._getAuthFetchOptions(room);
585
- if (!fetchOptions)
586
- return null;
587
- const { url, options } = fetchOptions;
588
- const res = yield fetch(url, options);
589
- if (!res.ok || res.status !== 200) {
590
- throw new Error("Room is unauthorized");
591
- }
592
- try {
593
- return yield res.text().then((text2) => text2.trim());
594
- } catch (err) {
595
- throw new Error(
596
- err instanceof Error ? err.message : "Room is unauthorized"
597
- );
598
- }
712
+ _emitSharedTypes() {
713
+ const sharedTypes = this._crdtManager.doc.getSharedTypes();
714
+ Object.entries(sharedTypes).forEach(([prop, sharedType]) => {
715
+ const serialized = sharedType.toJSON();
716
+ this._crdtNotifier.subject(prop).next(serialized);
599
717
  });
600
718
  }
601
719
  _getAuthFetchOptions(room) {
@@ -616,6 +734,25 @@ var PluvRoom = class extends AbstractRoom {
616
734
  const result = this._endpoints.authEndpoint(room);
617
735
  return typeof result === "string" ? { url: result, options: {} } : result;
618
736
  }
737
+ _getAuthorization(room) {
738
+ return __async(this, null, function* () {
739
+ const fetchOptions = this._getAuthFetchOptions(room);
740
+ if (!fetchOptions)
741
+ return null;
742
+ const { url, options } = fetchOptions;
743
+ const res = yield fetch(url, options);
744
+ if (!res.ok || res.status !== 200) {
745
+ throw new Error("Room is unauthorized");
746
+ }
747
+ try {
748
+ return yield res.text().then((text2) => text2.trim());
749
+ } catch (err) {
750
+ throw new Error(
751
+ err instanceof Error ? err.message : "Room is unauthorized"
752
+ );
753
+ }
754
+ });
755
+ }
619
756
  _getWsEndpoint(room) {
620
757
  switch (typeof this._endpoints.wsEndpoint) {
621
758
  case "undefined":
@@ -711,28 +848,28 @@ var PluvRoom = class extends AbstractRoom {
711
848
  });
712
849
  }
713
850
  _handleStorageReceivedMessage(message) {
714
- var _a;
715
- const { connectionId } = message;
716
- if (!connectionId)
717
- return;
718
- if (!this._state.webSocket)
719
- throw new Error("Could not find WebSocket");
720
- const data = message.data;
721
- this._crdtManager = new CrdtManager({
722
- encodedState: data.state,
723
- initialStorage: (_a = this._initialStorage) != null ? _a : void 0
724
- });
725
- this._observeCrdt();
726
- const sharedTypes = this._crdtManager.doc.getSharedTypes();
727
- Object.keys(sharedTypes).forEach((key) => {
728
- const sharedType = sharedTypes[key];
729
- const serialized = sharedType.toJSON();
730
- this._crdtNotifier.subject(key).next(serialized);
731
- });
732
- const update = this._crdtManager.doc.encodeStateAsUpdate();
733
- this._sendMessage({
734
- type: "$UPDATE_STORAGE",
735
- data: { origin: "$STORAGE_RECEIVED", update }
851
+ return __async(this, null, function* () {
852
+ const { connectionId } = message;
853
+ if (!connectionId)
854
+ return;
855
+ if (!this._state.webSocket)
856
+ throw new Error("Could not find WebSocket");
857
+ const data = message.data;
858
+ this._crdtManager.initialize({
859
+ onInitialized: () => __async(this, null, function* () {
860
+ this._observeCrdt();
861
+ }),
862
+ origin: Y_ORIGIN_INITIALIZED,
863
+ update: data.state
864
+ });
865
+ const encodedState = this._crdtManager.doc.encodeStateAsUpdate();
866
+ yield this._storageStore.addUpdate(encodedState);
867
+ this._emitSharedTypes();
868
+ const update = this._crdtManager.doc.encodeStateAsUpdate();
869
+ this._sendMessage({
870
+ type: "$UPDATE_STORAGE",
871
+ data: { origin: Y_ORIGIN_INITIALIZED, update }
872
+ });
736
873
  });
737
874
  }
738
875
  _handleStorageUpdatedMessage(message) {
@@ -746,14 +883,11 @@ var PluvRoom = class extends AbstractRoom {
746
883
  return;
747
884
  this._crdtManager.doc.applyUpdate(data.state, "$STORAGE_UPDATED");
748
885
  const sharedTypes = this._crdtManager.doc.getSharedTypes();
749
- Object.keys(sharedTypes).forEach((key) => {
886
+ Object.entries(sharedTypes).forEach(([prop, sharedType]) => {
750
887
  if (!this._crdtManager)
751
888
  return;
752
- const sharedType = sharedTypes[key];
753
- if (!sharedType)
754
- return;
755
889
  const serialized = sharedType.toJSON();
756
- this._crdtNotifier.subject(key).next(serialized);
890
+ this._crdtNotifier.subject(prop).next(serialized);
757
891
  });
758
892
  }
759
893
  _handleUserJoinedMessage(message) {
@@ -801,7 +935,9 @@ var PluvRoom = class extends AbstractRoom {
801
935
  const unsubscribe = this._crdtManager.doc.subscribe(
802
936
  (update, _origin) => {
803
937
  const origin = _origin != null ? _origin : null;
804
- if (!this._crdtManager)
938
+ this._addToStorageStore(update);
939
+ this._emitSharedTypes();
940
+ if (origin === Y_ORIGIN_INITIALIZED || origin === this)
805
941
  return;
806
942
  if (!this._state.webSocket)
807
943
  return;
@@ -811,13 +947,6 @@ var PluvRoom = class extends AbstractRoom {
811
947
  return;
812
948
  if (origin === "$STORAGE_UPDATED")
813
949
  return;
814
- const sharedTypes = this._crdtManager.doc.getSharedTypes();
815
- Object.entries(sharedTypes).forEach(([prop, sharedType]) => {
816
- if (!this._crdtManager)
817
- return;
818
- const serialized = sharedType.toJSON();
819
- this._crdtNotifier.subject(prop).next(serialized);
820
- });
821
950
  this._sendMessage({
822
951
  type: "$UPDATE_STORAGE",
823
952
  data: { origin, update }
@@ -842,14 +971,18 @@ var PluvRoom = class extends AbstractRoom {
842
971
  * @date August 19, 2022
843
972
  */
844
973
  _onClose(event) {
845
- this._logDebug("WebSocket closed");
846
- !!event.reason && this._logDebug(event.reason);
847
- this._clearInterval(this._intervals.heartbeat);
848
- this._clearTimeout(this._timeouts.pong);
849
- this._updateState((oldState) => {
850
- oldState.connection.state = "Closed" /* Closed */;
851
- oldState.webSocket = null;
852
- return oldState;
974
+ return __async(this, null, function* () {
975
+ this._logDebug("WebSocket closed");
976
+ !!event.reason && this._logDebug(event.reason);
977
+ this._clearInterval(this._intervals.heartbeat);
978
+ this._clearTimeout(this._timeouts.pong);
979
+ this._stateNotifier.subjects["myself"].next(null);
980
+ this._updateState((oldState) => {
981
+ oldState.authorization.token = null;
982
+ oldState.connection.state = "Closed" /* Closed */;
983
+ oldState.webSocket = null;
984
+ return oldState;
985
+ });
853
986
  });
854
987
  }
855
988
  /**
@@ -1022,6 +1155,7 @@ var PluvClient = class {
1022
1155
  if (oldRoom)
1023
1156
  return oldRoom;
1024
1157
  const newRoom = new PluvRoom(room, {
1158
+ addons: options.addons,
1025
1159
  authEndpoint: this._authEndpoint,
1026
1160
  debug: options.debug,
1027
1161
  initialPresence: options.initialPresence,
@@ -1050,14 +1184,14 @@ var PluvClient = class {
1050
1184
  this.getRooms = () => {
1051
1185
  return Array.from(this._rooms.values());
1052
1186
  };
1053
- this.leave = (room) => {
1187
+ this.leave = (room) => __async(this, null, function* () {
1054
1188
  const toLeave = typeof room === "string" ? this.getRoom(room) : room;
1055
1189
  if (!toLeave)
1056
1190
  return;
1057
- toLeave.disconnect();
1191
+ yield toLeave.disconnect();
1058
1192
  this._rooms.delete(toLeave.id);
1059
1193
  this._logDebug(`Left and deleted room: ${room}`);
1060
- };
1194
+ });
1061
1195
  const { authEndpoint, debug = false, wsEndpoint } = options;
1062
1196
  this._authEndpoint = authEndpoint;
1063
1197
  this._debug = debug;
@@ -1204,6 +1338,7 @@ var import_crdt_yjs2 = require("@pluv/crdt-yjs");
1204
1338
  // Annotate the CommonJS export names for ESM import in node:
1205
1339
  0 && (module.exports = {
1206
1340
  AbstractRoom,
1341
+ AbstractStorageStore,
1207
1342
  ConnectionState,
1208
1343
  MockedRoom,
1209
1344
  PluvClient,