@pluv/client 0.6.4 → 0.7.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,5 +1,5 @@
1
1
 
2
- > @pluv/client@0.6.4 build /home/runner/work/pluv/pluv/packages/client
2
+ > @pluv/client@0.7.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
@@ -8,10 +8,10 @@
8
8
  CLI Target: es6
9
9
  ESM Build start
10
10
  CJS Build start
11
- CJS dist/index.js 34.11 KB
12
- CJS ⚡️ Build success in 106ms
13
- ESM dist/index.mjs 32.97 KB
14
- ESM ⚡️ Build success in 107ms
11
+ ESM dist/index.mjs 34.72 KB
12
+ ESM ⚡️ Build success in 138ms
13
+ CJS dist/index.js 35.86 KB
14
+ CJS ⚡️ Build success in 138ms
15
15
  DTS Build start
16
- DTS ⚡️ Build success in 3699ms
17
- DTS dist/index.d.ts 11.50 KB
16
+ DTS ⚡️ Build success in 5111ms
17
+ DTS dist/index.d.ts 11.71 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # @pluv/client
2
2
 
3
+ ## 0.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - ae679a8: updated offline presence to be set when reconnecting to a room
8
+
9
+ ### Patch Changes
10
+
11
+ - 0dd847e: updated storage to be synced when reconnected to the room
12
+ - bb2886b: fixed not reconnecting during heartbeat
13
+ - 3518a83: bumped dependencies
14
+ - Updated dependencies [0dd847e]
15
+ - Updated dependencies [bb2886b]
16
+ - Updated dependencies [ae679a8]
17
+ - Updated dependencies [3518a83]
18
+ - @pluv/crdt-yjs@0.3.7
19
+ - @pluv/types@0.2.0
20
+
21
+ ## 0.6.5
22
+
23
+ ### Patch Changes
24
+
25
+ - 7ad4967: fixed user's presence not updating locally while offline
26
+ - 4535687: fix local yjs doc not updating while disconnected
27
+ - @pluv/crdt-yjs@0.3.6
28
+ - @pluv/types@0.1.6
29
+
3
30
  ## 0.6.4
4
31
 
5
32
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -21,6 +21,7 @@ declare enum ConnectionState {
21
21
  Untouched = "Untouched"
22
22
  }
23
23
  interface WebSocketConnection {
24
+ count: number;
24
25
  id: string | null;
25
26
  state: ConnectionState;
26
27
  }
@@ -60,6 +61,7 @@ declare abstract class AbstractRoom<TIO extends IOLike, TPresence extends JsonOb
60
61
  abstract other(connectionId: string, callback: OtherNotifierSubscriptionCallback<TIO>): () => void;
61
62
  abstract storage<TKey extends keyof TStorage>(key: TKey, fn: (value: InferYjsSharedTypeJson<TStorage[TKey]>) => void): () => void;
62
63
  abstract subscribe<TSubject extends keyof StateNotifierSubjects<TIO, TPresence>>(name: TSubject, callback: SubscriptionCallback<TIO, TPresence, TSubject>): () => void;
64
+ abstract updateMyPresence(presence: Partial<TPresence>): void;
63
65
  }
64
66
 
65
67
  interface CrdtManagerOptions<TStorage extends Record<string, AbstractType<any>> = {}> {
@@ -133,6 +135,7 @@ declare class PluvRoom<TIO extends IOLike, TPresence extends JsonObject = {}, TS
133
135
  other: (connectionId: string, callback: OtherNotifierSubscriptionCallback<TIO>) => (() => void);
134
136
  storage: <TKey extends keyof TStorage>(key: TKey, fn: (value: InferYjsSharedTypeJson<TStorage[TKey]>) => void) => (() => void);
135
137
  subscribe: <TSubject extends keyof StateNotifierSubjects<TIO, TPresence>>(name: TSubject, callback: SubscriptionCallback<TIO, TPresence, TSubject>) => (() => void);
138
+ updateMyPresence: (presence: Partial<TPresence>) => void;
136
139
  private _attachWindowListeners;
137
140
  private _attachWsListeners;
138
141
  private _clearInterval;
@@ -188,14 +191,14 @@ declare class PluvClient<TIO extends IOLike = IOLike> {
188
191
  private _rooms;
189
192
  constructor(options: PluvClientOptions<TIO>);
190
193
  createRoom: <TPresence extends JsonObject = {}, TStorage extends Record<string, AbstractType<any>> = {}>(room: string, options?: PluvRoomOptions<TIO, TPresence, TStorage>) => PluvRoom<TIO, TPresence, TStorage>;
191
- enter: (room: string | PluvRoom<TIO, JsonObject, any>) => Promise<PluvRoom<TIO, JsonObject, any>>;
194
+ enter: (room: string | PluvRoom<TIO, any, any>) => Promise<PluvRoom<TIO, JsonObject, any>>;
192
195
  getRoom: <TPresence extends JsonObject = {}, TStorage extends Record<string, AbstractType<any>> = {}>(room: string) => PluvRoom<TIO, TPresence, TStorage> | null;
193
196
  getRooms: () => readonly PluvRoom<TIO, JsonObject, any>[];
194
- leave: (room: string | PluvRoom<TIO, JsonObject, any>) => void;
197
+ leave: (room: string | PluvRoom<TIO, any, any>) => void;
195
198
  private _logDebug;
196
199
  }
197
200
 
198
- declare const createClient: <TIO extends IOLike<_pluv_types.IOAuthorize<any, any>, {}, {}> = IOLike<_pluv_types.IOAuthorize<any, any>, {}, {}>>(options: PluvClientOptions<TIO>) => PluvClient<TIO>;
201
+ declare const createClient: <TIO extends IOLike<_pluv_types.IOAuthorize<any, any>, {}, {}, {}, {}> = IOLike<_pluv_types.IOAuthorize<any, any>, {}, {}, {}, {}>>(options: PluvClientOptions<TIO>) => PluvClient<TIO>;
199
202
 
200
203
  type MockedRoomEvents<TIO extends IOLike> = Partial<{
201
204
  [P in keyof InferIOInput<TIO>]: (data: Id<InferIOInput<TIO>[P]>) => Partial<InferIOOutput<TIO>>;
@@ -225,6 +228,7 @@ declare class MockedRoom<TIO extends IOLike, TPresence extends JsonObject = {},
225
228
  other: (connectionId: string, callback: OtherNotifierSubscriptionCallback<TIO>) => (() => void);
226
229
  storage: <TKey extends keyof TStorage>(key: TKey, fn: (value: InferYjsSharedTypeJson<TStorage[TKey]>) => void) => (() => void);
227
230
  subscribe: <TSubject extends keyof StateNotifierSubjects<TIO, TPresence>>(name: TSubject, callback: SubscriptionCallback<TIO, TPresence, TSubject>) => (() => void);
231
+ updateMyPresence: (presence: Partial<TPresence>) => void;
228
232
  private _observeCrdt;
229
233
  }
230
234
 
package/dist/index.js CHANGED
@@ -79,12 +79,12 @@ var import_crdt_yjs = require("@pluv/crdt-yjs");
79
79
  var CrdtManager = class {
80
80
  constructor(options = {}) {
81
81
  const { encodedState, initialStorage } = options;
82
- const doc1 = encodedState ? (0, import_crdt_yjs.doc)().applyUpdate(encodedState) : null;
83
- if (doc1 && !!Object.keys(doc1.toJSON()).length) {
84
- this.doc = doc1;
82
+ const _doc = encodedState ? (0, import_crdt_yjs.doc)().applyUpdate(encodedState) : null;
83
+ if (_doc && !!Object.keys(_doc.toJSON()).length) {
84
+ this.doc = _doc;
85
85
  return;
86
86
  }
87
- doc1 == null ? void 0 : doc1.destroy();
87
+ _doc == null ? void 0 : _doc.destroy();
88
88
  this.doc = (0, import_crdt_yjs.doc)(initialStorage == null ? void 0 : initialStorage());
89
89
  }
90
90
  destroy() {
@@ -189,13 +189,20 @@ var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
189
189
  // src/UsersManager.ts
190
190
  var UsersManager = class {
191
191
  constructor(config) {
192
+ /**
193
+ * @description This is only set when the user is connected.
194
+ */
192
195
  this._myself = null;
193
196
  this._others = /* @__PURE__ */ new Map();
194
197
  this._presence = null;
195
198
  const { initialPresence, presence = null } = config;
196
199
  this._initialPresence = initialPresence;
200
+ this._myPresence = initialPresence;
197
201
  this._presence = presence;
198
202
  }
203
+ get myPresence() {
204
+ return this._myPresence;
205
+ }
199
206
  get myself() {
200
207
  return this._myself;
201
208
  }
@@ -212,21 +219,15 @@ var UsersManager = class {
212
219
  patchPresence(connectionId, patch) {
213
220
  var _a, _b;
214
221
  if (((_a = this._myself) == null ? void 0 : _a.connectionId) === connectionId) {
215
- this._myself.presence = Object.assign(
216
- /* @__PURE__ */ Object.create(null),
217
- this._myself.presence,
218
- patch
219
- );
222
+ const presence2 = __spreadValues(__spreadValues({}, this._myPresence), patch);
223
+ this._myself.presence = presence2;
224
+ this._myPresence = presence2;
220
225
  return;
221
226
  }
222
227
  const other = this._others.get(connectionId);
223
228
  if (!other)
224
229
  return;
225
- const presence = Object.assign(
226
- /* @__PURE__ */ Object.create(null),
227
- other.presence,
228
- patch
229
- );
230
+ const presence = __spreadValues(__spreadValues({}, other.presence), patch);
230
231
  (_b = this._presence) == null ? void 0 : _b.parse(presence);
231
232
  this._others.set(connectionId, __spreadProps(__spreadValues({}, other), { presence }));
232
233
  }
@@ -242,11 +243,13 @@ var UsersManager = class {
242
243
  presence: this._initialPresence,
243
244
  user
244
245
  };
246
+ this._myPresence = this._initialPresence;
245
247
  }
246
248
  setPresence(connectionId, presence) {
247
249
  var _a;
248
250
  if (((_a = this._myself) == null ? void 0 : _a.connectionId) === connectionId) {
249
251
  this._myself.presence = presence;
252
+ this._myPresence = presence;
250
253
  return;
251
254
  }
252
255
  const other = this._others.get(connectionId);
@@ -254,7 +257,7 @@ var UsersManager = class {
254
257
  return;
255
258
  this._others.set(connectionId, __spreadProps(__spreadValues({}, other), { presence }));
256
259
  }
257
- setUser(connectionId, user) {
260
+ setUser(connectionId, user, presence) {
258
261
  var _a;
259
262
  if (((_a = this._myself) == null ? void 0 : _a.connectionId) === connectionId)
260
263
  return;
@@ -263,10 +266,19 @@ var UsersManager = class {
263
266
  return;
264
267
  this._others.set(connectionId, {
265
268
  connectionId,
266
- presence: this._initialPresence,
269
+ presence: presence != null ? presence : this._initialPresence,
267
270
  user
268
271
  });
269
272
  }
273
+ /**
274
+ * @description This method need not care about being connected.
275
+ */
276
+ updateMyPresence(presence) {
277
+ this._myPresence = __spreadValues(__spreadValues({}, this._myPresence), presence);
278
+ if (!this._myself)
279
+ return;
280
+ this._myself.presence = this._myPresence;
281
+ }
270
282
  };
271
283
 
272
284
  // src/PluvRoom.ts
@@ -299,6 +311,7 @@ var PluvRoom = class extends AbstractRoom {
299
311
  user: null
300
312
  },
301
313
  connection: {
314
+ count: 0,
302
315
  id: null,
303
316
  state: "Untouched" /* Untouched */
304
317
  },
@@ -343,6 +356,18 @@ var PluvRoom = class extends AbstractRoom {
343
356
  this.subscribe = (name, callback) => {
344
357
  return this._stateNotifier.subscribe(name, callback);
345
358
  };
359
+ this.updateMyPresence = (presence) => {
360
+ var _a;
361
+ this._usersManager.updateMyPresence(presence);
362
+ const myPresence = this._usersManager.myPresence;
363
+ const myself = (_a = this._usersManager.myself) != null ? _a : null;
364
+ this._stateNotifier.subjects["my-presence"].next(myPresence);
365
+ !!myself && this._stateNotifier.subjects["myself"].next(myself);
366
+ this.broadcast(
367
+ "$UPDATE_PRESENCE",
368
+ { presence }
369
+ );
370
+ };
346
371
  this._debug = debug;
347
372
  this._endpoints = {
348
373
  authEndpoint,
@@ -430,8 +455,10 @@ var PluvRoom = class extends AbstractRoom {
430
455
  ].some((state) => this._state.connection.state === state);
431
456
  if (!canDisconnect)
432
457
  return;
458
+ this._clearInterval(this._intervals.heartbeat);
433
459
  this._closeWs();
434
460
  (_a = this._crdtManager) == null ? void 0 : _a.destroy();
461
+ this._stateNotifier.subjects["my-presence"].next(null);
435
462
  this._updateState((oldState) => {
436
463
  oldState.authorization.token = null;
437
464
  oldState.connection.state = "Closed" /* Closed */;
@@ -503,7 +530,6 @@ var PluvRoom = class extends AbstractRoom {
503
530
  if (!this._state.webSocket)
504
531
  return;
505
532
  (_b = (_a = this._subscriptions).observeCrdt) == null ? void 0 : _b.call(_a);
506
- this._clearInterval(this._intervals.heartbeat);
507
533
  this._clearTimeout(this._timeouts.pong);
508
534
  this._clearTimeout(this._timeouts.reconnect);
509
535
  this._detachWindowListeners();
@@ -517,7 +543,6 @@ var PluvRoom = class extends AbstractRoom {
517
543
  if (canClose)
518
544
  this._state.webSocket.close();
519
545
  this._detachWsListeners();
520
- this._stateNotifier.subjects["my-presence"].next(null);
521
546
  this._stateNotifier.subjects["myself"].next(null);
522
547
  }
523
548
  _detachWindowListeners() {
@@ -592,7 +617,7 @@ var PluvRoom = class extends AbstractRoom {
592
617
  this._otherNotifier.subjects.delete(connectionId);
593
618
  }
594
619
  _handlePresenceUpdatedMessage(message) {
595
- var _a, _b, _c;
620
+ var _a;
596
621
  const { connectionId } = message;
597
622
  if (!connectionId)
598
623
  return;
@@ -603,8 +628,8 @@ var PluvRoom = class extends AbstractRoom {
603
628
  connectionId,
604
629
  data.presence
605
630
  );
606
- const myPresence = (_b = (_a = this._usersManager.myself) == null ? void 0 : _a.presence) != null ? _b : null;
607
- const myself = (_c = this._usersManager.myself) != null ? _c : null;
631
+ const myPresence = this._usersManager.myPresence;
632
+ const myself = (_a = this._usersManager.myself) != null ? _a : null;
608
633
  if ((myself == null ? void 0 : myself.connectionId) === connectionId) {
609
634
  this._stateNotifier.subjects["my-presence"].next(myPresence);
610
635
  this._stateNotifier.subjects["myself"].next(myself);
@@ -644,6 +669,7 @@ var PluvRoom = class extends AbstractRoom {
644
669
  if (!this._state.webSocket)
645
670
  throw new Error("Could not find WebSocket");
646
671
  this._updateState((oldState) => {
672
+ oldState.connection.count += 1;
647
673
  oldState.connection.id = connectionId;
648
674
  oldState.authorization.user = user;
649
675
  return oldState;
@@ -652,11 +678,15 @@ var PluvRoom = class extends AbstractRoom {
652
678
  connectionId,
653
679
  user
654
680
  );
655
- const myPresence = (_b = (_a = this._usersManager.myself) == null ? void 0 : _a.presence) != null ? _b : null;
656
- const myself = (_c = this._usersManager.myself) != null ? _c : null;
657
- this._stateNotifier.subjects["my-presence"].next(myPresence);
681
+ const presence = this._usersManager.myPresence;
682
+ const myself = (_a = this._usersManager.myself) != null ? _a : null;
683
+ this._stateNotifier.subjects["my-presence"].next(presence);
658
684
  this._stateNotifier.subjects["myself"].next(myself);
659
- this._sendMessage({ type: "$GET_OTHERS", data: {} });
685
+ const update = this._state.connection.count > 1 ? (_c = (_b = this._crdtManager) == null ? void 0 : _b.doc.encodeStateAsUpdate()) != null ? _c : null : null;
686
+ this._sendMessage({
687
+ type: "$INITIALIZE_SESSION",
688
+ data: { presence, update }
689
+ });
660
690
  }
661
691
  _handleStorageReceivedMessage(message) {
662
692
  var _a;
@@ -713,20 +743,21 @@ var PluvRoom = class extends AbstractRoom {
713
743
  if (!this._usersManager.myself)
714
744
  return;
715
745
  const data = message.data;
716
- this._usersManager.setUser(connectionId, data.user);
746
+ const myself = this._usersManager.myself;
747
+ if (myself.connectionId === connectionId) {
748
+ this._sendMessage({ type: "$GET_OTHERS", data: {} });
749
+ }
750
+ this._usersManager.setUser(
751
+ connectionId,
752
+ data.user,
753
+ data.presence
754
+ );
717
755
  const other = this._usersManager.getOther(connectionId);
718
756
  const others = this._usersManager.getOthers();
719
757
  this._otherNotifier.subject(connectionId).next(other);
720
758
  this._stateNotifier.subjects["others"].next(others);
721
759
  }
722
760
  _heartbeat() {
723
- if (!this._state.webSocket)
724
- return;
725
- if (!this._state.connection.id)
726
- return;
727
- if (this._state.webSocket.readyState !== this._state.webSocket.OPEN) {
728
- return;
729
- }
730
761
  this._clearTimeout(this._timeouts.pong);
731
762
  this._timeouts.pong = setTimeout(
732
763
  this._reconnect.bind(this),
@@ -758,6 +789,13 @@ var PluvRoom = class extends AbstractRoom {
758
789
  return;
759
790
  if (origin === "$STORAGE_UPDATED")
760
791
  return;
792
+ const sharedTypes = this._crdtManager.doc.getSharedTypes();
793
+ Object.entries(sharedTypes).forEach(([prop, sharedType]) => {
794
+ if (!this._crdtManager)
795
+ return;
796
+ const serialized = sharedType.toJSON();
797
+ this._crdtNotifier.subject(prop).next(serialized);
798
+ });
761
799
  this._sendMessage({
762
800
  type: "$UPDATE_STORAGE",
763
801
  data: { origin, update }
@@ -914,7 +952,7 @@ var PluvRoom = class extends AbstractRoom {
914
952
  this._closeWs();
915
953
  this._updateState((oldState) => {
916
954
  oldState.authorization.token = null;
917
- oldState.connection.state = "Unavailable" /* Unavailable */;
955
+ oldState.connection.state = "Closed" /* Closed */;
918
956
  oldState.webSocket = null;
919
957
  return oldState;
920
958
  });
@@ -1031,6 +1069,7 @@ var MockedRoom = class extends AbstractRoom {
1031
1069
  user: null
1032
1070
  },
1033
1071
  connection: {
1072
+ count: 1,
1034
1073
  id: null,
1035
1074
  state: "Untouched" /* Untouched */
1036
1075
  },
@@ -1069,6 +1108,14 @@ var MockedRoom = class extends AbstractRoom {
1069
1108
  this.subscribe = (name, callback) => {
1070
1109
  return this._stateNotifier.subscribe(name, callback);
1071
1110
  };
1111
+ this.updateMyPresence = (presence) => {
1112
+ var _a;
1113
+ this._usersManager.updateMyPresence(presence);
1114
+ const myPresence = this._usersManager.myPresence;
1115
+ const myself = (_a = this._usersManager.myself) != null ? _a : null;
1116
+ this._stateNotifier.subjects["my-presence"].next(myPresence);
1117
+ !!myself && this._stateNotifier.subjects["myself"].next(myself);
1118
+ };
1072
1119
  this._events = events;
1073
1120
  this._usersManager = new UsersManager({
1074
1121
  initialPresence,
@@ -1112,14 +1159,11 @@ var MockedRoom = class extends AbstractRoom {
1112
1159
  if (origin === "$STORAGE_UPDATED")
1113
1160
  return;
1114
1161
  const sharedTypes = this._crdtManager.doc.getSharedTypes();
1115
- Object.keys(sharedTypes).forEach((key) => {
1162
+ Object.entries(sharedTypes).forEach(([prop, sharedType]) => {
1116
1163
  if (!this._crdtManager)
1117
1164
  return;
1118
- const sharedType = sharedTypes[key];
1119
- if (!sharedType)
1120
- return;
1121
1165
  const serialized = sharedType.toJSON();
1122
- this._crdtNotifier.subject(key).next(serialized);
1166
+ this._crdtNotifier.subject(prop).next(serialized);
1123
1167
  });
1124
1168
  }
1125
1169
  );
package/dist/index.mjs CHANGED
@@ -54,12 +54,12 @@ import { doc } from "@pluv/crdt-yjs";
54
54
  var CrdtManager = class {
55
55
  constructor(options = {}) {
56
56
  const { encodedState, initialStorage } = options;
57
- const doc1 = encodedState ? doc().applyUpdate(encodedState) : null;
58
- if (doc1 && !!Object.keys(doc1.toJSON()).length) {
59
- this.doc = doc1;
57
+ const _doc = encodedState ? doc().applyUpdate(encodedState) : null;
58
+ if (_doc && !!Object.keys(_doc.toJSON()).length) {
59
+ this.doc = _doc;
60
60
  return;
61
61
  }
62
- doc1 == null ? void 0 : doc1.destroy();
62
+ _doc == null ? void 0 : _doc.destroy();
63
63
  this.doc = doc(initialStorage == null ? void 0 : initialStorage());
64
64
  }
65
65
  destroy() {
@@ -164,13 +164,20 @@ var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
164
164
  // src/UsersManager.ts
165
165
  var UsersManager = class {
166
166
  constructor(config) {
167
+ /**
168
+ * @description This is only set when the user is connected.
169
+ */
167
170
  this._myself = null;
168
171
  this._others = /* @__PURE__ */ new Map();
169
172
  this._presence = null;
170
173
  const { initialPresence, presence = null } = config;
171
174
  this._initialPresence = initialPresence;
175
+ this._myPresence = initialPresence;
172
176
  this._presence = presence;
173
177
  }
178
+ get myPresence() {
179
+ return this._myPresence;
180
+ }
174
181
  get myself() {
175
182
  return this._myself;
176
183
  }
@@ -187,21 +194,15 @@ var UsersManager = class {
187
194
  patchPresence(connectionId, patch) {
188
195
  var _a, _b;
189
196
  if (((_a = this._myself) == null ? void 0 : _a.connectionId) === connectionId) {
190
- this._myself.presence = Object.assign(
191
- /* @__PURE__ */ Object.create(null),
192
- this._myself.presence,
193
- patch
194
- );
197
+ const presence2 = __spreadValues(__spreadValues({}, this._myPresence), patch);
198
+ this._myself.presence = presence2;
199
+ this._myPresence = presence2;
195
200
  return;
196
201
  }
197
202
  const other = this._others.get(connectionId);
198
203
  if (!other)
199
204
  return;
200
- const presence = Object.assign(
201
- /* @__PURE__ */ Object.create(null),
202
- other.presence,
203
- patch
204
- );
205
+ const presence = __spreadValues(__spreadValues({}, other.presence), patch);
205
206
  (_b = this._presence) == null ? void 0 : _b.parse(presence);
206
207
  this._others.set(connectionId, __spreadProps(__spreadValues({}, other), { presence }));
207
208
  }
@@ -217,11 +218,13 @@ var UsersManager = class {
217
218
  presence: this._initialPresence,
218
219
  user
219
220
  };
221
+ this._myPresence = this._initialPresence;
220
222
  }
221
223
  setPresence(connectionId, presence) {
222
224
  var _a;
223
225
  if (((_a = this._myself) == null ? void 0 : _a.connectionId) === connectionId) {
224
226
  this._myself.presence = presence;
227
+ this._myPresence = presence;
225
228
  return;
226
229
  }
227
230
  const other = this._others.get(connectionId);
@@ -229,7 +232,7 @@ var UsersManager = class {
229
232
  return;
230
233
  this._others.set(connectionId, __spreadProps(__spreadValues({}, other), { presence }));
231
234
  }
232
- setUser(connectionId, user) {
235
+ setUser(connectionId, user, presence) {
233
236
  var _a;
234
237
  if (((_a = this._myself) == null ? void 0 : _a.connectionId) === connectionId)
235
238
  return;
@@ -238,10 +241,19 @@ var UsersManager = class {
238
241
  return;
239
242
  this._others.set(connectionId, {
240
243
  connectionId,
241
- presence: this._initialPresence,
244
+ presence: presence != null ? presence : this._initialPresence,
242
245
  user
243
246
  });
244
247
  }
248
+ /**
249
+ * @description This method need not care about being connected.
250
+ */
251
+ updateMyPresence(presence) {
252
+ this._myPresence = __spreadValues(__spreadValues({}, this._myPresence), presence);
253
+ if (!this._myself)
254
+ return;
255
+ this._myself.presence = this._myPresence;
256
+ }
245
257
  };
246
258
 
247
259
  // src/PluvRoom.ts
@@ -274,6 +286,7 @@ var PluvRoom = class extends AbstractRoom {
274
286
  user: null
275
287
  },
276
288
  connection: {
289
+ count: 0,
277
290
  id: null,
278
291
  state: "Untouched" /* Untouched */
279
292
  },
@@ -318,6 +331,18 @@ var PluvRoom = class extends AbstractRoom {
318
331
  this.subscribe = (name, callback) => {
319
332
  return this._stateNotifier.subscribe(name, callback);
320
333
  };
334
+ this.updateMyPresence = (presence) => {
335
+ var _a;
336
+ this._usersManager.updateMyPresence(presence);
337
+ const myPresence = this._usersManager.myPresence;
338
+ const myself = (_a = this._usersManager.myself) != null ? _a : null;
339
+ this._stateNotifier.subjects["my-presence"].next(myPresence);
340
+ !!myself && this._stateNotifier.subjects["myself"].next(myself);
341
+ this.broadcast(
342
+ "$UPDATE_PRESENCE",
343
+ { presence }
344
+ );
345
+ };
321
346
  this._debug = debug;
322
347
  this._endpoints = {
323
348
  authEndpoint,
@@ -405,8 +430,10 @@ var PluvRoom = class extends AbstractRoom {
405
430
  ].some((state) => this._state.connection.state === state);
406
431
  if (!canDisconnect)
407
432
  return;
433
+ this._clearInterval(this._intervals.heartbeat);
408
434
  this._closeWs();
409
435
  (_a = this._crdtManager) == null ? void 0 : _a.destroy();
436
+ this._stateNotifier.subjects["my-presence"].next(null);
410
437
  this._updateState((oldState) => {
411
438
  oldState.authorization.token = null;
412
439
  oldState.connection.state = "Closed" /* Closed */;
@@ -478,7 +505,6 @@ var PluvRoom = class extends AbstractRoom {
478
505
  if (!this._state.webSocket)
479
506
  return;
480
507
  (_b = (_a = this._subscriptions).observeCrdt) == null ? void 0 : _b.call(_a);
481
- this._clearInterval(this._intervals.heartbeat);
482
508
  this._clearTimeout(this._timeouts.pong);
483
509
  this._clearTimeout(this._timeouts.reconnect);
484
510
  this._detachWindowListeners();
@@ -492,7 +518,6 @@ var PluvRoom = class extends AbstractRoom {
492
518
  if (canClose)
493
519
  this._state.webSocket.close();
494
520
  this._detachWsListeners();
495
- this._stateNotifier.subjects["my-presence"].next(null);
496
521
  this._stateNotifier.subjects["myself"].next(null);
497
522
  }
498
523
  _detachWindowListeners() {
@@ -567,7 +592,7 @@ var PluvRoom = class extends AbstractRoom {
567
592
  this._otherNotifier.subjects.delete(connectionId);
568
593
  }
569
594
  _handlePresenceUpdatedMessage(message) {
570
- var _a, _b, _c;
595
+ var _a;
571
596
  const { connectionId } = message;
572
597
  if (!connectionId)
573
598
  return;
@@ -578,8 +603,8 @@ var PluvRoom = class extends AbstractRoom {
578
603
  connectionId,
579
604
  data.presence
580
605
  );
581
- const myPresence = (_b = (_a = this._usersManager.myself) == null ? void 0 : _a.presence) != null ? _b : null;
582
- const myself = (_c = this._usersManager.myself) != null ? _c : null;
606
+ const myPresence = this._usersManager.myPresence;
607
+ const myself = (_a = this._usersManager.myself) != null ? _a : null;
583
608
  if ((myself == null ? void 0 : myself.connectionId) === connectionId) {
584
609
  this._stateNotifier.subjects["my-presence"].next(myPresence);
585
610
  this._stateNotifier.subjects["myself"].next(myself);
@@ -619,6 +644,7 @@ var PluvRoom = class extends AbstractRoom {
619
644
  if (!this._state.webSocket)
620
645
  throw new Error("Could not find WebSocket");
621
646
  this._updateState((oldState) => {
647
+ oldState.connection.count += 1;
622
648
  oldState.connection.id = connectionId;
623
649
  oldState.authorization.user = user;
624
650
  return oldState;
@@ -627,11 +653,15 @@ var PluvRoom = class extends AbstractRoom {
627
653
  connectionId,
628
654
  user
629
655
  );
630
- const myPresence = (_b = (_a = this._usersManager.myself) == null ? void 0 : _a.presence) != null ? _b : null;
631
- const myself = (_c = this._usersManager.myself) != null ? _c : null;
632
- this._stateNotifier.subjects["my-presence"].next(myPresence);
656
+ const presence = this._usersManager.myPresence;
657
+ const myself = (_a = this._usersManager.myself) != null ? _a : null;
658
+ this._stateNotifier.subjects["my-presence"].next(presence);
633
659
  this._stateNotifier.subjects["myself"].next(myself);
634
- this._sendMessage({ type: "$GET_OTHERS", data: {} });
660
+ const update = this._state.connection.count > 1 ? (_c = (_b = this._crdtManager) == null ? void 0 : _b.doc.encodeStateAsUpdate()) != null ? _c : null : null;
661
+ this._sendMessage({
662
+ type: "$INITIALIZE_SESSION",
663
+ data: { presence, update }
664
+ });
635
665
  }
636
666
  _handleStorageReceivedMessage(message) {
637
667
  var _a;
@@ -688,20 +718,21 @@ var PluvRoom = class extends AbstractRoom {
688
718
  if (!this._usersManager.myself)
689
719
  return;
690
720
  const data = message.data;
691
- this._usersManager.setUser(connectionId, data.user);
721
+ const myself = this._usersManager.myself;
722
+ if (myself.connectionId === connectionId) {
723
+ this._sendMessage({ type: "$GET_OTHERS", data: {} });
724
+ }
725
+ this._usersManager.setUser(
726
+ connectionId,
727
+ data.user,
728
+ data.presence
729
+ );
692
730
  const other = this._usersManager.getOther(connectionId);
693
731
  const others = this._usersManager.getOthers();
694
732
  this._otherNotifier.subject(connectionId).next(other);
695
733
  this._stateNotifier.subjects["others"].next(others);
696
734
  }
697
735
  _heartbeat() {
698
- if (!this._state.webSocket)
699
- return;
700
- if (!this._state.connection.id)
701
- return;
702
- if (this._state.webSocket.readyState !== this._state.webSocket.OPEN) {
703
- return;
704
- }
705
736
  this._clearTimeout(this._timeouts.pong);
706
737
  this._timeouts.pong = setTimeout(
707
738
  this._reconnect.bind(this),
@@ -733,6 +764,13 @@ var PluvRoom = class extends AbstractRoom {
733
764
  return;
734
765
  if (origin === "$STORAGE_UPDATED")
735
766
  return;
767
+ const sharedTypes = this._crdtManager.doc.getSharedTypes();
768
+ Object.entries(sharedTypes).forEach(([prop, sharedType]) => {
769
+ if (!this._crdtManager)
770
+ return;
771
+ const serialized = sharedType.toJSON();
772
+ this._crdtNotifier.subject(prop).next(serialized);
773
+ });
736
774
  this._sendMessage({
737
775
  type: "$UPDATE_STORAGE",
738
776
  data: { origin, update }
@@ -889,7 +927,7 @@ var PluvRoom = class extends AbstractRoom {
889
927
  this._closeWs();
890
928
  this._updateState((oldState) => {
891
929
  oldState.authorization.token = null;
892
- oldState.connection.state = "Unavailable" /* Unavailable */;
930
+ oldState.connection.state = "Closed" /* Closed */;
893
931
  oldState.webSocket = null;
894
932
  return oldState;
895
933
  });
@@ -1006,6 +1044,7 @@ var MockedRoom = class extends AbstractRoom {
1006
1044
  user: null
1007
1045
  },
1008
1046
  connection: {
1047
+ count: 1,
1009
1048
  id: null,
1010
1049
  state: "Untouched" /* Untouched */
1011
1050
  },
@@ -1044,6 +1083,14 @@ var MockedRoom = class extends AbstractRoom {
1044
1083
  this.subscribe = (name, callback) => {
1045
1084
  return this._stateNotifier.subscribe(name, callback);
1046
1085
  };
1086
+ this.updateMyPresence = (presence) => {
1087
+ var _a;
1088
+ this._usersManager.updateMyPresence(presence);
1089
+ const myPresence = this._usersManager.myPresence;
1090
+ const myself = (_a = this._usersManager.myself) != null ? _a : null;
1091
+ this._stateNotifier.subjects["my-presence"].next(myPresence);
1092
+ !!myself && this._stateNotifier.subjects["myself"].next(myself);
1093
+ };
1047
1094
  this._events = events;
1048
1095
  this._usersManager = new UsersManager({
1049
1096
  initialPresence,
@@ -1087,14 +1134,11 @@ var MockedRoom = class extends AbstractRoom {
1087
1134
  if (origin === "$STORAGE_UPDATED")
1088
1135
  return;
1089
1136
  const sharedTypes = this._crdtManager.doc.getSharedTypes();
1090
- Object.keys(sharedTypes).forEach((key) => {
1137
+ Object.entries(sharedTypes).forEach(([prop, sharedType]) => {
1091
1138
  if (!this._crdtManager)
1092
1139
  return;
1093
- const sharedType = sharedTypes[key];
1094
- if (!sharedType)
1095
- return;
1096
1140
  const serialized = sharedType.toJSON();
1097
- this._crdtNotifier.subject(key).next(serialized);
1141
+ this._crdtNotifier.subject(prop).next(serialized);
1098
1142
  });
1099
1143
  }
1100
1144
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pluv/client",
3
- "version": "0.6.4",
3
+ "version": "0.7.0",
4
4
  "description": "@pluv/io frontend client",
5
5
  "author": "leedavidcs",
6
6
  "license": "MIT",
@@ -17,9 +17,9 @@
17
17
  "access": "public"
18
18
  },
19
19
  "dependencies": {
20
- "wonka": "^6.3.1",
21
- "@pluv/crdt-yjs": "^0.3.6",
22
- "@pluv/types": "^0.1.6"
20
+ "wonka": "^6.3.2",
21
+ "@pluv/crdt-yjs": "^0.3.7",
22
+ "@pluv/types": "^0.2.0"
23
23
  },
24
24
  "peerDependencies": {
25
25
  "yjs": "^13.0.0"
@@ -27,9 +27,9 @@
27
27
  "devDependencies": {
28
28
  "tsup": "^6.7.0",
29
29
  "typescript": "^5.0.4",
30
- "yjs": "^13.5.52",
31
- "@pluv/tsconfig": "^0.1.5",
32
- "eslint-config-pluv": "^0.1.10"
30
+ "yjs": "^13.6.0",
31
+ "@pluv/tsconfig": "^0.1.6",
32
+ "eslint-config-pluv": "^0.1.11"
33
33
  },
34
34
  "scripts": {
35
35
  "build": "tsup src/index.ts --format esm,cjs --dts",
@@ -68,4 +68,6 @@ export abstract class AbstractRoom<
68
68
  name: TSubject,
69
69
  callback: SubscriptionCallback<TIO, TPresence, TSubject>
70
70
  ): () => void;
71
+
72
+ public abstract updateMyPresence(presence: Partial<TPresence>): void;
71
73
  }
@@ -15,17 +15,17 @@ export class CrdtManager<TStorage extends Record<string, AbstractType<any>>> {
15
15
  constructor(options: CrdtManagerOptions<TStorage> = {}) {
16
16
  const { encodedState, initialStorage } = options;
17
17
 
18
- const doc1 = encodedState
18
+ const _doc = encodedState
19
19
  ? doc<TStorage>().applyUpdate(encodedState)
20
20
  : null;
21
21
 
22
- if (doc1 && !!Object.keys(doc1.toJSON()).length) {
23
- this.doc = doc1;
22
+ if (_doc && !!Object.keys(_doc.toJSON()).length) {
23
+ this.doc = _doc;
24
24
 
25
25
  return;
26
26
  }
27
27
 
28
- doc1?.destroy();
28
+ _doc?.destroy();
29
29
 
30
30
  this.doc = doc<TStorage>(initialStorage?.());
31
31
  }
package/src/MockedRoom.ts CHANGED
@@ -61,6 +61,7 @@ export class MockedRoom<
61
61
  user: null,
62
62
  },
63
63
  connection: {
64
+ count: 1,
64
65
  id: null,
65
66
  state: ConnectionState.Untouched,
66
67
  },
@@ -177,6 +178,16 @@ export class MockedRoom<
177
178
  return this._stateNotifier.subscribe(name, callback);
178
179
  };
179
180
 
181
+ public updateMyPresence = (presence: Partial<TPresence>): void => {
182
+ this._usersManager.updateMyPresence(presence);
183
+
184
+ const myPresence = this._usersManager.myPresence;
185
+ const myself = this._usersManager.myself ?? null;
186
+
187
+ this._stateNotifier.subjects["my-presence"].next(myPresence);
188
+ !!myself && this._stateNotifier.subjects["myself"].next(myself);
189
+ };
190
+
180
191
  private _observeCrdt(): void {
181
192
  this._subscriptions.observeCrdt?.();
182
193
 
@@ -191,16 +202,12 @@ export class MockedRoom<
191
202
 
192
203
  const sharedTypes = this._crdtManager.doc.getSharedTypes();
193
204
 
194
- Object.keys(sharedTypes).forEach((key) => {
205
+ Object.entries(sharedTypes).forEach(([prop, sharedType]) => {
195
206
  if (!this._crdtManager) return;
196
207
 
197
- const sharedType = sharedTypes[key];
198
-
199
- if (!sharedType) return;
200
-
201
208
  const serialized = sharedType.toJSON();
202
209
 
203
- this._crdtNotifier.subject(key).next(serialized);
210
+ this._crdtNotifier.subject(prop).next(serialized);
204
211
  });
205
212
  }
206
213
  );
package/src/PluvClient.ts CHANGED
@@ -17,7 +17,7 @@ export class PluvClient<TIO extends IOLike = IOLike> {
17
17
  private _debug: boolean;
18
18
  private _wsEndpoint: WsEndpoint;
19
19
 
20
- private _rooms = new Map<string, PluvRoom<TIO, JsonObject, any>>();
20
+ private _rooms = new Map<string, PluvRoom<TIO, any, any>>();
21
21
 
22
22
  constructor(options: PluvClientOptions<TIO>) {
23
23
  const { authEndpoint, debug = false, wsEndpoint } = options;
@@ -55,7 +55,7 @@ export class PluvClient<TIO extends IOLike = IOLike> {
55
55
  };
56
56
 
57
57
  public enter = async (
58
- room: string | PluvRoom<TIO, JsonObject, any>
58
+ room: string | PluvRoom<TIO, any, any>
59
59
  ): Promise<PluvRoom<TIO, JsonObject, any>> => {
60
60
  const toEnter = typeof room === "string" ? this.getRoom(room) : room;
61
61
 
@@ -89,7 +89,7 @@ export class PluvClient<TIO extends IOLike = IOLike> {
89
89
  return Array.from(this._rooms.values());
90
90
  };
91
91
 
92
- public leave = (room: string | PluvRoom<TIO, JsonObject, any>): void => {
92
+ public leave = (room: string | PluvRoom<TIO, any, any>): void => {
93
93
  const toLeave = typeof room === "string" ? this.getRoom(room) : room;
94
94
 
95
95
  if (!toLeave) return;
package/src/PluvRoom.ts CHANGED
@@ -134,6 +134,7 @@ export class PluvRoom<
134
134
  user: null,
135
135
  },
136
136
  connection: {
137
+ count: 0,
137
138
  id: null,
138
139
  state: ConnectionState.Untouched,
139
140
  },
@@ -279,9 +280,13 @@ export class PluvRoom<
279
280
 
280
281
  if (!canDisconnect) return;
281
282
 
283
+ this._clearInterval(this._intervals.heartbeat);
284
+
282
285
  this._closeWs();
283
286
  this._crdtManager?.destroy();
284
287
 
288
+ this._stateNotifier.subjects["my-presence"].next(null);
289
+
285
290
  this._updateState((oldState) => {
286
291
  oldState.authorization.token = null;
287
292
  oldState.connection.state = ConnectionState.Closed;
@@ -352,6 +357,21 @@ export class PluvRoom<
352
357
  return this._stateNotifier.subscribe(name, callback);
353
358
  };
354
359
 
360
+ public updateMyPresence = (presence: Partial<TPresence>): void => {
361
+ this._usersManager.updateMyPresence(presence);
362
+
363
+ const myPresence = this._usersManager.myPresence;
364
+ const myself = this._usersManager.myself ?? null;
365
+
366
+ this._stateNotifier.subjects["my-presence"].next(myPresence);
367
+ !!myself && this._stateNotifier.subjects["myself"].next(myself);
368
+
369
+ this.broadcast(
370
+ "$UPDATE_PRESENCE" as keyof InferIOInput<TIO>,
371
+ { presence } as any
372
+ );
373
+ };
374
+
355
375
  private _attachWindowListeners(): void {
356
376
  if (typeof window === "undefined") return;
357
377
  if (typeof document === "undefined") return;
@@ -416,7 +436,6 @@ export class PluvRoom<
416
436
 
417
437
  this._subscriptions.observeCrdt?.();
418
438
 
419
- this._clearInterval(this._intervals.heartbeat);
420
439
  this._clearTimeout(this._timeouts.pong);
421
440
  this._clearTimeout(this._timeouts.reconnect);
422
441
 
@@ -431,7 +450,6 @@ export class PluvRoom<
431
450
 
432
451
  this._detachWsListeners();
433
452
 
434
- this._stateNotifier.subjects["my-presence"].next(null);
435
453
  this._stateNotifier.subjects["myself"].next(null);
436
454
  }
437
455
 
@@ -537,7 +555,7 @@ export class PluvRoom<
537
555
  data.presence as TPresence
538
556
  );
539
557
 
540
- const myPresence = this._usersManager.myself?.presence ?? null;
558
+ const myPresence = this._usersManager.myPresence;
541
559
  const myself = this._usersManager.myself ?? null;
542
560
 
543
561
  if (myself?.connectionId === connectionId) {
@@ -594,6 +612,7 @@ export class PluvRoom<
594
612
  if (!this._state.webSocket) throw new Error("Could not find WebSocket");
595
613
 
596
614
  this._updateState((oldState) => {
615
+ oldState.connection.count += 1;
597
616
  oldState.connection.id = connectionId;
598
617
  oldState.authorization.user = user;
599
618
 
@@ -605,13 +624,21 @@ export class PluvRoom<
605
624
  user as Id<InferIOAuthorizeUser<InferIOAuthorize<TIO>>>
606
625
  );
607
626
 
608
- const myPresence = this._usersManager.myself?.presence ?? null;
627
+ const presence = this._usersManager.myPresence;
609
628
  const myself = this._usersManager.myself ?? null;
610
629
 
611
- this._stateNotifier.subjects["my-presence"].next(myPresence);
630
+ this._stateNotifier.subjects["my-presence"].next(presence);
612
631
  this._stateNotifier.subjects["myself"].next(myself);
613
632
 
614
- this._sendMessage({ type: "$GET_OTHERS", data: {} });
633
+ const update =
634
+ this._state.connection.count > 1
635
+ ? this._crdtManager?.doc.encodeStateAsUpdate() ?? null
636
+ : null;
637
+
638
+ this._sendMessage({
639
+ type: "$INITIALIZE_SESSION",
640
+ data: { presence, update },
641
+ });
615
642
  }
616
643
 
617
644
  private _handleStorageReceivedMessage(message: IOEventMessage<TIO>): void {
@@ -691,7 +718,17 @@ export class PluvRoom<
691
718
  InferIOAuthorize<TIO>
692
719
  >["$USER_JOINED"];
693
720
 
694
- this._usersManager.setUser(connectionId, data.user);
721
+ const myself = this._usersManager.myself;
722
+
723
+ if (myself.connectionId === connectionId) {
724
+ this._sendMessage({ type: "$GET_OTHERS", data: {} });
725
+ }
726
+
727
+ this._usersManager.setUser(
728
+ connectionId,
729
+ data.user,
730
+ data.presence as TPresence
731
+ );
695
732
 
696
733
  const other = this._usersManager.getOther(connectionId);
697
734
  const others = this._usersManager.getOthers();
@@ -701,12 +738,6 @@ export class PluvRoom<
701
738
  }
702
739
 
703
740
  private _heartbeat(): void {
704
- if (!this._state.webSocket) return;
705
- if (!this._state.connection.id) return;
706
- if (this._state.webSocket.readyState !== this._state.webSocket.OPEN) {
707
- return;
708
- }
709
-
710
741
  this._clearTimeout(this._timeouts.pong);
711
742
  this._timeouts.pong = setTimeout(
712
743
  this._reconnect.bind(this),
@@ -739,6 +770,16 @@ export class PluvRoom<
739
770
 
740
771
  if (origin === "$STORAGE_UPDATED") return;
741
772
 
773
+ const sharedTypes = this._crdtManager.doc.getSharedTypes();
774
+
775
+ Object.entries(sharedTypes).forEach(([prop, sharedType]) => {
776
+ if (!this._crdtManager) return;
777
+
778
+ const serialized = sharedType.toJSON();
779
+
780
+ this._crdtNotifier.subject(prop).next(serialized);
781
+ });
782
+
742
783
  this._sendMessage({
743
784
  type: "$UPDATE_STORAGE",
744
785
  data: { origin, update },
@@ -930,7 +971,7 @@ export class PluvRoom<
930
971
 
931
972
  this._updateState((oldState) => {
932
973
  oldState.authorization.token = null;
933
- oldState.connection.state = ConnectionState.Unavailable;
974
+ oldState.connection.state = ConnectionState.Closed;
934
975
  oldState.webSocket = null;
935
976
 
936
977
  return oldState;
@@ -20,6 +20,14 @@ export class UsersManager<
20
20
  TPresence extends JsonObject = {}
21
21
  > {
22
22
  private _initialPresence: TPresence;
23
+ /**
24
+ * @description This presence can be updated while the user is not
25
+ * connected.
26
+ */
27
+ private _myPresence: TPresence;
28
+ /**
29
+ * @description This is only set when the user is connected.
30
+ */
23
31
  private _myself: UserInfo<TIO, TPresence> | null = null;
24
32
  private _others = new Map<string, UserInfo<TIO, TPresence>>();
25
33
  private _presence: InputZodLike<TPresence> | null = null;
@@ -28,9 +36,14 @@ export class UsersManager<
28
36
  const { initialPresence, presence = null } = config;
29
37
 
30
38
  this._initialPresence = initialPresence as TPresence;
39
+ this._myPresence = initialPresence as TPresence;
31
40
  this._presence = presence;
32
41
  }
33
42
 
43
+ public get myPresence(): TPresence {
44
+ return this._myPresence;
45
+ }
46
+
34
47
  public get myself(): UserInfo<TIO, TPresence> | null {
35
48
  return this._myself;
36
49
  }
@@ -52,11 +65,13 @@ export class UsersManager<
52
65
  patch: Partial<TPresence>
53
66
  ): void {
54
67
  if (this._myself?.connectionId === connectionId) {
55
- this._myself.presence = Object.assign(
56
- Object.create(null),
57
- this._myself.presence,
58
- patch
59
- ) as TPresence;
68
+ const presence = {
69
+ ...this._myPresence,
70
+ ...patch,
71
+ } as TPresence;
72
+
73
+ this._myself.presence = presence;
74
+ this._myPresence = presence;
60
75
 
61
76
  return;
62
77
  }
@@ -65,11 +80,10 @@ export class UsersManager<
65
80
 
66
81
  if (!other) return;
67
82
 
68
- const presence = Object.assign(
69
- Object.create(null),
70
- other.presence,
71
- patch
72
- ) as TPresence;
83
+ const presence = {
84
+ ...other.presence,
85
+ ...patch,
86
+ } as TPresence;
73
87
 
74
88
  this._presence?.parse(presence);
75
89
 
@@ -93,11 +107,14 @@ export class UsersManager<
93
107
  presence: this._initialPresence,
94
108
  user,
95
109
  };
110
+
111
+ this._myPresence = this._initialPresence;
96
112
  }
97
113
 
98
114
  public setPresence(connectionId: string, presence: TPresence): void {
99
115
  if (this._myself?.connectionId === connectionId) {
100
116
  this._myself.presence = presence;
117
+ this._myPresence = presence;
101
118
 
102
119
  return;
103
120
  }
@@ -111,7 +128,8 @@ export class UsersManager<
111
128
 
112
129
  public setUser(
113
130
  connectionId: string,
114
- user: Id<InferIOAuthorizeUser<InferIOAuthorize<TIO>>>
131
+ user: Id<InferIOAuthorizeUser<InferIOAuthorize<TIO>>>,
132
+ presence?: TPresence
115
133
  ): void {
116
134
  if (this._myself?.connectionId === connectionId) return;
117
135
 
@@ -121,8 +139,19 @@ export class UsersManager<
121
139
 
122
140
  this._others.set(connectionId, {
123
141
  connectionId,
124
- presence: this._initialPresence,
142
+ presence: presence ?? this._initialPresence,
125
143
  user,
126
144
  });
127
145
  }
146
+
147
+ /**
148
+ * @description This method need not care about being connected.
149
+ */
150
+ public updateMyPresence(presence: Partial<TPresence>): void {
151
+ this._myPresence = { ...this._myPresence, ...presence };
152
+
153
+ if (!this._myself) return;
154
+
155
+ this._myself.presence = this._myPresence;
156
+ }
128
157
  }
package/src/types.ts CHANGED
@@ -24,6 +24,7 @@ export interface InternalSubscriptions {
24
24
  }
25
25
 
26
26
  export interface WebSocketConnection {
27
+ count: number;
27
28
  id: string | null;
28
29
  state: ConnectionState;
29
30
  }