@pluv/client 0.40.2 → 0.41.1

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/dist/index.mjs CHANGED
@@ -192,24 +192,27 @@ var EventNotifier = class {
192
192
  import { makeSubject as makeSubject3, subscribe as subscribe3 } from "wonka";
193
193
  var OtherNotifier = class {
194
194
  constructor() {
195
- this.subjects = /* @__PURE__ */ new Map();
195
+ this._subjects = /* @__PURE__ */ new Map();
196
196
  }
197
197
  clear() {
198
- const connectionIds = Array.from(this.subjects.keys());
199
- connectionIds.forEach((connectionId) => {
200
- this.subject(connectionId).next(null);
201
- this.subjects.delete(connectionId);
198
+ this._subjects.forEach((subject) => {
199
+ subject.next(null);
202
200
  });
201
+ this._subjects.clear();
203
202
  }
204
- subject(name) {
205
- const subject = this.subjects.get(name);
203
+ delete(clientId) {
204
+ this.subject(clientId).next(null);
205
+ this._subjects.delete(clientId);
206
+ }
207
+ subject(clientId) {
208
+ const subject = this._subjects.get(clientId);
206
209
  if (subject) return subject;
207
210
  const newSubject = makeSubject3();
208
- this.subjects.set(name, newSubject);
211
+ this._subjects.set(clientId, newSubject);
209
212
  return newSubject;
210
213
  }
211
- subscribe(name, callback) {
212
- const source = this.subject(name).source;
214
+ subscribe(clientId, callback) {
215
+ const source = this.subject(clientId).source;
213
216
  return subscribe3(callback)(source).unsubscribe;
214
217
  }
215
218
  };
@@ -283,9 +286,16 @@ var StorageStore = class extends AbstractStorageStore {
283
286
  }
284
287
  };
285
288
 
289
+ // src/constants.ts
290
+ var MAX_PRESENCE_SIZE_BYTES = 512;
291
+
286
292
  // src/UsersManager.ts
287
293
  var UsersManager = class {
288
294
  constructor(config) {
295
+ this._idMap = {
296
+ fromConnectionId: /* @__PURE__ */ new Map(),
297
+ fromClientId: /* @__PURE__ */ new Map()
298
+ };
289
299
  /**
290
300
  * @description This is only set when the user is connected.
291
301
  */
@@ -303,74 +313,154 @@ var UsersManager = class {
303
313
  get myself() {
304
314
  return this._myself;
305
315
  }
306
- clear() {
316
+ addConnection(userInfo) {
317
+ var _a;
318
+ const data = __spreadProps(__spreadValues({}, userInfo), {
319
+ presence: (_a = userInfo.presence) != null ? _a : this._initialPresence
320
+ });
321
+ const myClientId = !!this._myself ? this.getClientId(this._myself) : null;
322
+ const clientId = this.getClientId(data);
323
+ const remaining = this._setConnectionId(userInfo.connectionId, clientId).size;
324
+ if (myClientId === clientId) {
325
+ const result2 = {
326
+ clientId,
327
+ data,
328
+ isMyself: true,
329
+ remaining
330
+ };
331
+ return result2;
332
+ }
333
+ const other = this._others.get(clientId);
334
+ if (!other) this._others.set(clientId, data);
335
+ const result = {
336
+ clientId,
337
+ data,
338
+ isMyself: false,
339
+ remaining
340
+ };
341
+ return result;
342
+ }
343
+ clearConnections() {
344
+ this.removeMyself();
307
345
  this._others.clear();
346
+ this._idMap.fromClientId.clear();
347
+ this._idMap.fromConnectionId.clear();
348
+ }
349
+ deleteConnection(connectionId) {
350
+ var _a, _b, _c;
351
+ const clientId = this.getClientId(connectionId);
352
+ this._deleteConnectionId(connectionId);
353
+ if (!clientId) return null;
354
+ const userInfo = (_a = this._others.get(clientId)) != null ? _a : null;
355
+ if (!userInfo) return null;
356
+ const remaining = (_c = (_b = this._getConnectionIds(clientId)) == null ? void 0 : _b.size) != null ? _c : 0;
357
+ const result = { clientId, remaining, data: userInfo };
358
+ if (!remaining) this._others.delete(clientId);
359
+ return result;
360
+ }
361
+ getClientId(input) {
362
+ var _a, _b, _c, _d;
363
+ if (typeof input !== "string") {
364
+ const userInfo = input;
365
+ const connectionId2 = userInfo.connectionId;
366
+ const userId = (_b = (_a = userInfo.user) == null ? void 0 : _a.id) != null ? _b : null;
367
+ return userId != null ? userId : connectionId2;
368
+ }
369
+ const connectionId = input;
370
+ if (((_c = this._myself) == null ? void 0 : _c.connectionId) === connectionId) {
371
+ return this.getClientId(this._myself);
372
+ }
373
+ const clientId = (_d = this._idMap.fromConnectionId.get(connectionId)) != null ? _d : null;
374
+ return clientId;
308
375
  }
309
376
  getOther(connectionId) {
310
377
  var _a;
311
- return (_a = this._others.get(connectionId)) != null ? _a : null;
378
+ const clientId = this.getClientId(connectionId);
379
+ if (!clientId) return null;
380
+ return (_a = this._others.get(clientId)) != null ? _a : null;
312
381
  }
313
382
  getOthers() {
314
383
  return Array.from(this._others.values());
315
384
  }
316
385
  patchPresence(connectionId, patch) {
317
- var _a, _b;
318
- if (((_a = this._myself) == null ? void 0 : _a.connectionId) === connectionId) {
319
- this.updateMyPresence(patch);
320
- return;
321
- }
322
- const other = this._others.get(connectionId);
323
- if (!other) return;
386
+ const clientId = this.getClientId(connectionId);
387
+ const myClientId = !!this._myself ? this.getClientId(this._myself) : null;
388
+ if (!clientId) return null;
389
+ if (myClientId === clientId) return this.updateMyPresence(patch);
390
+ const other = this._others.get(clientId);
391
+ if (!other) return null;
324
392
  const presence = __spreadValues(__spreadValues({}, other.presence), patch);
325
- (_b = this._presence) == null ? void 0 : _b.parse(presence);
326
- this._others.set(connectionId, __spreadProps(__spreadValues({}, other), { presence }));
393
+ const validated = this._presence ? this._presence.parse(presence) : presence;
394
+ this._others.set(clientId, __spreadProps(__spreadValues({}, other), { presence: validated }));
395
+ return validated;
327
396
  }
328
397
  removeMyself() {
398
+ if (!this._myself) return;
399
+ const connectionId = this._myself.connectionId;
400
+ this._deleteConnectionId(connectionId);
329
401
  this._myself = null;
330
402
  }
331
- removeUser(connectionId) {
332
- this._others.delete(connectionId);
333
- }
334
- removeUsers() {
335
- this._others.clear();
336
- }
337
- setMyself(connectionId, user) {
338
- this._myself = {
339
- connectionId,
340
- presence: this._initialPresence,
341
- user
342
- };
343
- this._myPresence = this._initialPresence;
403
+ setMyself(userInfo) {
404
+ var _a;
405
+ const connectionId = userInfo.connectionId;
406
+ const presence = (_a = userInfo.presence) != null ? _a : this._initialPresence;
407
+ this._myself = __spreadProps(__spreadValues({}, userInfo), { presence });
408
+ this._myPresence = presence;
409
+ const clientId = this.getClientId(this._myself);
410
+ this._setConnectionId(connectionId, clientId);
344
411
  }
345
412
  setPresence(connectionId, presence) {
346
- var _a;
347
- if (((_a = this._myself) == null ? void 0 : _a.connectionId) === connectionId) {
413
+ const clientId = this.getClientId(connectionId);
414
+ const myClientId = !!this._myself ? this.getClientId(this._myself) : null;
415
+ if (!clientId) return;
416
+ if (myClientId === clientId) {
417
+ if (!this._myself) return;
348
418
  this._myself.presence = presence;
349
419
  this._myPresence = presence;
350
420
  return;
351
421
  }
352
- const other = this._others.get(connectionId);
422
+ const other = this._others.get(clientId);
353
423
  if (!other) return;
354
- this._others.set(connectionId, __spreadProps(__spreadValues({}, other), { presence }));
355
- }
356
- setUser(connectionId, user, presence) {
357
- var _a;
358
- if (((_a = this._myself) == null ? void 0 : _a.connectionId) === connectionId) return;
359
- const other = this._others.get(connectionId);
360
- if (other) return;
361
- this._others.set(connectionId, {
362
- connectionId,
363
- presence: presence != null ? presence : this._initialPresence,
364
- user
365
- });
424
+ this._others.set(clientId, __spreadProps(__spreadValues({}, other), { presence }));
366
425
  }
367
426
  /**
368
427
  * @description This method need not care about being connected.
369
428
  */
370
429
  updateMyPresence(patch) {
371
- this._myPresence = __spreadValues(__spreadValues({}, this._myPresence), patch);
372
- if (!this._myself) return;
373
- this._myself.presence = this._myPresence;
430
+ const updated = __spreadValues(__spreadValues({}, this._myPresence), patch);
431
+ const bytes = new TextEncoder().encode(JSON.stringify(updated)).length;
432
+ if (bytes > MAX_PRESENCE_SIZE_BYTES) {
433
+ throw new Error(
434
+ `Large presence. Presence must be at most 512 bytes. Current size: ${bytes.toLocaleString()}`
435
+ );
436
+ }
437
+ this._myPresence = updated;
438
+ if (!this._myself) return updated;
439
+ this._myself.presence = updated;
440
+ return updated;
441
+ }
442
+ _deleteConnectionId(connectionId) {
443
+ var _a;
444
+ const clientId = this.getClientId(connectionId);
445
+ this._idMap.fromConnectionId.delete(connectionId);
446
+ if (!clientId) return;
447
+ const set = (_a = this._idMap.fromClientId.get(clientId)) != null ? _a : null;
448
+ if (!set) return;
449
+ set.delete(connectionId);
450
+ if (!!set.size) return;
451
+ this._idMap.fromClientId.delete(clientId);
452
+ }
453
+ _getConnectionIds(clientId) {
454
+ const set = this._idMap.fromClientId.get(clientId);
455
+ return set != null ? set : null;
456
+ }
457
+ _setConnectionId(connectionId, clientId) {
458
+ var _a;
459
+ const set = (_a = this._idMap.fromClientId.get(clientId)) != null ? _a : /* @__PURE__ */ new Set();
460
+ const updated = set.add(connectionId);
461
+ this._idMap.fromConnectionId.set(connectionId, clientId);
462
+ this._idMap.fromClientId.set(clientId, updated);
463
+ return updated;
374
464
  }
375
465
  };
376
466
 
@@ -536,7 +626,9 @@ var PluvRoom = class extends AbstractRoom {
536
626
  return sharedType;
537
627
  };
538
628
  this.other = (connectionId, callback) => {
539
- return this._otherNotifier.subscribe(connectionId, callback);
629
+ const clientId = this._usersManager.getClientId(connectionId);
630
+ if (!clientId) return () => void 0;
631
+ return this._otherNotifier.subscribe(clientId, callback);
540
632
  };
541
633
  this.redo = () => {
542
634
  this._crdtManager.doc.redo();
@@ -568,7 +660,7 @@ var PluvRoom = class extends AbstractRoom {
568
660
  const myPresence = this._usersManager.myPresence;
569
661
  const myself = (_a = this._usersManager.myself) != null ? _a : null;
570
662
  this._stateNotifier.subjects["my-presence"].next(myPresence);
571
- !!myself && this._stateNotifier.subjects["myself"].next(myself);
663
+ if (!!myself) this._stateNotifier.subjects.myself.next(myself);
572
664
  this.broadcast(
573
665
  "$updatePresence",
574
666
  { presence: newPresence }
@@ -638,8 +730,8 @@ var PluvRoom = class extends AbstractRoom {
638
730
  try {
639
731
  const publicKey = this._getPublicKey(params);
640
732
  authToken = yield this._getAuthorization(this.id, params);
641
- authToken && url.searchParams.set("token", encodeURIComponent(authToken));
642
- publicKey && url.searchParams.set("public_key", encodeURIComponent(publicKey));
733
+ if (authToken) url.searchParams.set("token", encodeURIComponent(authToken));
734
+ if (publicKey) url.searchParams.set("public_key", encodeURIComponent(publicKey));
643
735
  webSocket = new WebSocket(url.toString());
644
736
  } catch (err) {
645
737
  this._updateState((oldState) => {
@@ -750,7 +842,7 @@ var PluvRoom = class extends AbstractRoom {
750
842
  this._clearTimeout(this._timeouts.pong);
751
843
  this._detachWindowListeners();
752
844
  this._usersManager.removeMyself();
753
- this._usersManager.removeUsers();
845
+ this._usersManager.clearConnections();
754
846
  this._otherNotifier.clear();
755
847
  this._stateNotifier.subjects.myself.next(null);
756
848
  this._stateNotifier.subjects.others.next([]);
@@ -838,11 +930,11 @@ var PluvRoom = class extends AbstractRoom {
838
930
  const { connectionId } = message;
839
931
  if (!connectionId) return;
840
932
  if (!this._state.webSocket) throw new Error("Could not find WebSocket");
841
- this._usersManager.removeUser(connectionId);
933
+ const clientId = this._usersManager.getClientId(connectionId);
934
+ const deleted = this._usersManager.deleteConnection(connectionId);
842
935
  const others = this._usersManager.getOthers();
843
936
  this._stateNotifier.subjects.others.next(others);
844
- this._otherNotifier.subject(connectionId).next(null);
845
- this._otherNotifier.subjects.delete(connectionId);
937
+ if (!(deleted == null ? void 0 : deleted.remaining) && !!clientId) this._otherNotifier.delete(clientId);
846
938
  }
847
939
  _handlePresenceUpdatedMessage(message) {
848
940
  var _a;
@@ -852,11 +944,18 @@ var PluvRoom = class extends AbstractRoom {
852
944
  const data = message.data;
853
945
  const myself = (_a = this._usersManager.myself) != null ? _a : null;
854
946
  if ((myself == null ? void 0 : myself.connectionId) === connectionId) return;
855
- this._usersManager.patchPresence(connectionId, data.presence);
947
+ const updated = this._usersManager.patchPresence(connectionId, data.presence);
948
+ const myClientId = !!myself ? this._usersManager.getClientId(myself) : null;
949
+ const clientId = this._usersManager.getClientId(connectionId);
950
+ if (!!clientId && myClientId === clientId) {
951
+ this._stateNotifier.subjects["my-presence"].next(updated);
952
+ this._stateNotifier.subjects.myself.next(this._usersManager.myself);
953
+ return;
954
+ }
856
955
  const other = this._usersManager.getOther(connectionId);
857
956
  const others = this._usersManager.getOthers();
858
- this._otherNotifier.subject(connectionId).next(other);
859
- this._stateNotifier.subjects["others"].next(others);
957
+ if (!!clientId) this._otherNotifier.subject(clientId).next(other);
958
+ this._stateNotifier.subjects.others.next(others);
860
959
  }
861
960
  _handleReceiveOthers(message) {
862
961
  if (!message.connectionId) return;
@@ -864,31 +963,44 @@ var PluvRoom = class extends AbstractRoom {
864
963
  const data = message.data;
865
964
  Object.keys(data.others).forEach((connectionId) => {
866
965
  const { presence, user } = data.others[connectionId];
867
- this._usersManager.setUser(connectionId, user);
966
+ const result = this._usersManager.addConnection({
967
+ connectionId,
968
+ presence: presence != null ? presence : void 0,
969
+ user
970
+ });
971
+ if (!!presence) this._usersManager.patchPresence(connectionId, presence);
972
+ if (result.isMyself) return;
973
+ const clientId = result.clientId;
868
974
  const other = this._usersManager.getOther(connectionId);
869
- !!presence && this._usersManager.patchPresence(connectionId, presence);
870
- this._otherNotifier.subject(connectionId).next(other);
975
+ this._otherNotifier.subject(clientId).next(other);
871
976
  });
872
977
  const others = this._usersManager.getOthers();
873
- this._stateNotifier.subjects["others"].next(others);
978
+ this._stateNotifier.subjects.others.next(others);
874
979
  }
875
980
  _handleRegisteredMessage(message) {
876
- var _a, _b;
877
- const { connectionId, user } = message;
981
+ var _a, _b, _c;
982
+ const { connectionId } = message;
983
+ const user = message.user;
878
984
  if (!connectionId) return;
879
985
  if (!this._state.webSocket) throw new Error("Could not find WebSocket");
986
+ const data = message.data;
880
987
  this._updateState((oldState) => {
881
988
  oldState.connection.count += 1;
882
989
  oldState.connection.id = connectionId;
883
990
  oldState.authorization.user = user != null ? user : null;
884
991
  return oldState;
885
992
  });
886
- this._usersManager.setMyself(connectionId, user);
993
+ const userInfo = {
994
+ connectionId,
995
+ presence: (_a = data.presence) != null ? _a : void 0,
996
+ user
997
+ };
998
+ this._usersManager.setMyself(userInfo);
887
999
  const presence = this._usersManager.myPresence;
888
- const myself = (_a = this._usersManager.myself) != null ? _a : null;
1000
+ const myself = (_b = this._usersManager.myself) != null ? _b : null;
889
1001
  this._stateNotifier.subjects["my-presence"].next(presence);
890
- this._stateNotifier.subjects["myself"].next(myself);
891
- const update = this._state.connection.count > 1 ? (_b = this._crdtManager.doc.getEncodedState()) != null ? _b : null : null;
1002
+ this._stateNotifier.subjects.myself.next(myself);
1003
+ const update = this._state.connection.count > 1 ? (_c = this._crdtManager.doc.getEncodedState()) != null ? _c : null : null;
892
1004
  this._sendMessage({
893
1005
  type: "$initializeSession",
894
1006
  data: { presence, update }
@@ -943,6 +1055,7 @@ var PluvRoom = class extends AbstractRoom {
943
1055
  this._crdtNotifier.rootSubject.next(storageRoot);
944
1056
  }
945
1057
  _handleUserJoinedMessage(message) {
1058
+ var _a;
946
1059
  const { connectionId } = message;
947
1060
  if (!connectionId) return;
948
1061
  if (!this._state.webSocket) throw new Error("Could not find WebSocket");
@@ -951,12 +1064,18 @@ var PluvRoom = class extends AbstractRoom {
951
1064
  const myself = this._usersManager.myself;
952
1065
  if (myself.connectionId === connectionId) {
953
1066
  this._sendMessage({ type: "$getOthers", data: {} });
1067
+ return;
954
1068
  }
955
- this._usersManager.setUser(connectionId, data.user, data.presence);
1069
+ const result = this._usersManager.addConnection({
1070
+ connectionId,
1071
+ presence: data.presence,
1072
+ user: data.user
1073
+ });
1074
+ const clientId = (_a = result == null ? void 0 : result.clientId) != null ? _a : null;
956
1075
  const other = this._usersManager.getOther(connectionId);
957
1076
  const others = this._usersManager.getOthers();
958
- this._otherNotifier.subject(connectionId).next(other);
959
- this._stateNotifier.subjects["others"].next(others);
1077
+ if (!!clientId) this._otherNotifier.subject(clientId).next(other);
1078
+ this._stateNotifier.subjects.others.next(others);
960
1079
  }
961
1080
  _heartbeat() {
962
1081
  this._clearTimeout(this._timeouts.pong);
@@ -966,7 +1085,7 @@ var PluvRoom = class extends AbstractRoom {
966
1085
  _logDebug(...data) {
967
1086
  var _a;
968
1087
  if (((_a = process == null ? void 0 : process.env) == null ? void 0 : _a.NODE_ENV) === "production") return;
969
- this._debug && console.log(...data);
1088
+ if (this._debug) console.log(...data);
970
1089
  }
971
1090
  _observeCrdt() {
972
1091
  var _a, _b;
@@ -1001,7 +1120,7 @@ var PluvRoom = class extends AbstractRoom {
1001
1120
  _onClose(event) {
1002
1121
  return __async(this, null, function* () {
1003
1122
  this._logDebug("WebSocket closed");
1004
- !!event.reason && this._logDebug(event.reason);
1123
+ if (!!event.reason) this._logDebug(event.reason);
1005
1124
  const shouldRetry = [
1006
1125
  // Going away: Client/server is shutting down or navigating
1007
1126
  1001,
@@ -1016,15 +1135,18 @@ var PluvRoom = class extends AbstractRoom {
1016
1135
  // Bad gateway
1017
1136
  1014
1018
1137
  ].some((okToRetryCode) => event.code === okToRetryCode);
1019
- this._closeWs();
1020
- if (!shouldRetry) this._clearTimeout(this._timeouts.reconnect);
1021
- else this._pollReconnect();
1022
1138
  this._updateState((oldState) => {
1023
1139
  oldState.authorization.token = null;
1024
1140
  oldState.connection.state = shouldRetry ? "Unavailable" /* Unavailable */ : "Closed" /* Closed */;
1025
1141
  oldState.webSocket = null;
1026
1142
  return oldState;
1027
1143
  });
1144
+ if (shouldRetry) {
1145
+ yield this._reconnect();
1146
+ return;
1147
+ }
1148
+ this._closeWs();
1149
+ this._clearTimeout(this._timeouts.reconnect);
1028
1150
  });
1029
1151
  }
1030
1152
  /**
@@ -1373,7 +1495,9 @@ var MockedRoom = class extends AbstractRoom {
1373
1495
  return sharedType;
1374
1496
  };
1375
1497
  this.other = (connectionId, callback) => {
1376
- return this._otherNotifier.subscribe(connectionId, callback);
1498
+ const clientId = this._usersManager.getClientId(connectionId);
1499
+ if (!clientId) return () => void 0;
1500
+ return this._otherNotifier.subscribe(clientId, callback);
1377
1501
  };
1378
1502
  this.redo = () => {
1379
1503
  this._crdtManager.doc.redo();
@@ -1407,7 +1531,7 @@ var MockedRoom = class extends AbstractRoom {
1407
1531
  const myPresence = this._usersManager.myPresence;
1408
1532
  const myself = (_a = this._usersManager.myself) != null ? _a : null;
1409
1533
  this._stateNotifier.subjects["my-presence"].next(myPresence);
1410
- !!myself && this._stateNotifier.subjects["myself"].next(myself);
1534
+ if (!!myself) this._stateNotifier.subjects["myself"].next(myself);
1411
1535
  };
1412
1536
  this._events = events;
1413
1537
  this._router = router != null ? router : new PluvRouter({});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pluv/client",
3
- "version": "0.40.2",
3
+ "version": "0.41.1",
4
4
  "description": "@pluv/io frontend client",
5
5
  "author": "leedavidcs",
6
6
  "license": "MIT",
@@ -18,20 +18,20 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "wonka": "^6.3.5",
21
- "@pluv/crdt": "^0.40.2",
22
- "@pluv/types": "^0.40.2"
21
+ "@pluv/crdt": "^0.41.1",
22
+ "@pluv/types": "^0.41.1"
23
23
  },
24
24
  "devDependencies": {
25
- "eslint": "^9.24.0",
25
+ "eslint": "^9.25.0",
26
26
  "tsup": "^8.4.0",
27
27
  "typescript": "^5.8.3",
28
- "@pluv/tsconfig": "^0.40.2",
29
- "eslint-config-pluv": "^0.40.2"
28
+ "@pluv/tsconfig": "^0.41.1",
29
+ "eslint-config-pluv": "^0.41.1"
30
30
  },
31
31
  "scripts": {
32
32
  "build": "tsup src/index.ts --format esm,cjs --dts",
33
33
  "dev": "tsup src/index.ts --format esm,cjs --watch --dts",
34
- "lint": "eslint src/**/*.ts* --fix --max-warnings 0",
34
+ "lint": "eslint \"src/**/*.ts*\" --fix --max-warnings 0",
35
35
  "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
36
36
  }
37
37
  }
@@ -51,7 +51,10 @@ export abstract class AbstractRoom<
51
51
  public abstract getStorageJson(): InferCrdtJson<TStorage> | null;
52
52
  public abstract getStorageJson<TKey extends keyof TStorage>(type: TKey): InferCrdtJson<TStorage[TKey]> | null;
53
53
 
54
- public abstract other(connectionId: string, callback: OtherNotifierSubscriptionCallback<TIO>): () => void;
54
+ public abstract other(
55
+ connectionId: string,
56
+ callback: OtherNotifierSubscriptionCallback<TIO, TPresence>,
57
+ ): () => void;
55
58
 
56
59
  public abstract redo(): void;
57
60
 
package/src/MockedRoom.ts CHANGED
@@ -51,7 +51,7 @@ export class MockedRoom<
51
51
  private _crdtNotifier = new CrdtNotifier<TStorage>();
52
52
  private _eventNotifier = new EventNotifier<MergeEvents<TEvents, TIO>>();
53
53
  private _events?: MockedRoomEvents<MergeEvents<TEvents, TIO>>;
54
- private _otherNotifier = new OtherNotifier<TIO>();
54
+ private _otherNotifier = new OtherNotifier<TIO, TPresence>();
55
55
  private _router: PluvRouter<TIO, TPresence, TStorage, TEvents>;
56
56
  private _state: WebSocketState<TIO> = {
57
57
  authorization: {
@@ -226,8 +226,15 @@ export class MockedRoom<
226
226
  return this._crdtManager.doc.toJson(type);
227
227
  }
228
228
 
229
- public other = (connectionId: string, callback: OtherNotifierSubscriptionCallback<TIO>): (() => void) => {
230
- return this._otherNotifier.subscribe(connectionId, callback);
229
+ public other = (
230
+ connectionId: string,
231
+ callback: OtherNotifierSubscriptionCallback<TIO, TPresence>,
232
+ ): (() => void) => {
233
+ const clientId = this._usersManager.getClientId(connectionId);
234
+
235
+ if (!clientId) return () => undefined;
236
+
237
+ return this._otherNotifier.subscribe(clientId, callback);
231
238
  };
232
239
 
233
240
  public redo = (): void => {
@@ -290,7 +297,7 @@ export class MockedRoom<
290
297
  const myself = this._usersManager.myself ?? null;
291
298
 
292
299
  this._stateNotifier.subjects["my-presence"].next(myPresence);
293
- !!myself && this._stateNotifier.subjects["myself"].next(myself);
300
+ if (!!myself) this._stateNotifier.subjects["myself"].next(myself);
294
301
  };
295
302
 
296
303
  private _observeCrdt(): void {
@@ -1,39 +1,42 @@
1
- import type { Id, IOLike } from "@pluv/types";
1
+ import type { Id, IOLike, JsonObject } from "@pluv/types";
2
2
  import type { Subject } from "wonka";
3
3
  import { makeSubject, subscribe } from "wonka";
4
4
  import type { UserInfo } from "./types";
5
5
 
6
- export type OtherNotifierSubscriptionCallback<TIO extends IOLike> = (value: Id<UserInfo<TIO>> | null) => void;
6
+ export type OtherNotifierSubscriptionCallback<TIO extends IOLike, TPresence extends JsonObject> = (
7
+ value: Id<UserInfo<TIO, TPresence>> | null,
8
+ ) => void;
7
9
 
8
- export class OtherNotifier<TIO extends IOLike> {
9
- public subjects = new Map<string, Subject<Id<UserInfo<TIO>> | null>>();
10
+ export class OtherNotifier<TIO extends IOLike, TPresence extends JsonObject = {}> {
11
+ private _subjects = new Map<[clientId: string][0], Subject<Id<UserInfo<TIO, TPresence>> | null>>();
10
12
 
11
13
  public clear(): void {
12
- const connectionIds = Array.from(this.subjects.keys());
13
-
14
- connectionIds.forEach((connectionId) => {
15
- this.subject(connectionId).next(null);
16
- this.subjects.delete(connectionId);
14
+ this._subjects.forEach((subject) => {
15
+ subject.next(null);
17
16
  });
17
+
18
+ this._subjects.clear();
19
+ }
20
+
21
+ public delete(clientId: string): void {
22
+ this.subject(clientId).next(null);
23
+ this._subjects.delete(clientId);
18
24
  }
19
25
 
20
- public subject<TEvent extends string>(name: TEvent): Subject<Id<UserInfo<TIO>> | null> {
21
- const subject = this.subjects.get(name);
26
+ public subject(clientId: string): Subject<Id<UserInfo<TIO, TPresence>> | null> {
27
+ const subject = this._subjects.get(clientId);
22
28
 
23
29
  if (subject) return subject;
24
30
 
25
- const newSubject = makeSubject<Id<UserInfo<TIO>>>();
31
+ const newSubject = makeSubject<Id<UserInfo<TIO, TPresence>>>();
26
32
 
27
- this.subjects.set(name, newSubject);
33
+ this._subjects.set(clientId, newSubject);
28
34
 
29
35
  return newSubject;
30
36
  }
31
37
 
32
- public subscribe<TEvent extends string>(
33
- name: TEvent,
34
- callback: OtherNotifierSubscriptionCallback<TIO>,
35
- ): () => void {
36
- const source = this.subject(name).source;
38
+ public subscribe(clientId: string, callback: OtherNotifierSubscriptionCallback<TIO, TPresence>): () => void {
39
+ const source = this.subject(clientId).source;
37
40
 
38
41
  return subscribe(callback)(source).unsubscribe;
39
42
  }