@pluv/client 0.40.1 → 0.41.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.40.1 build /home/runner/work/pluv/pluv/packages/client
2
+ > @pluv/client@0.41.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,11 +8,11 @@
8
8
  CLI Target: es6
9
9
  ESM Build start
10
10
  CJS Build start
11
- CJS dist/index.js 50.76 KB
12
- CJS ⚡️ Build success in 126ms
13
- ESM dist/index.mjs 49.44 KB
14
- ESM ⚡️ Build success in 128ms
11
+ ESM dist/index.mjs 54.19 KB
12
+ ESM ⚡️ Build success in 130ms
13
+ CJS dist/index.js 55.51 KB
14
+ CJS ⚡️ Build success in 131ms
15
15
  DTS Build start
16
- DTS ⚡️ Build success in 3361ms
17
- DTS dist/index.d.mts 24.50 KB
18
- DTS dist/index.d.ts 24.50 KB
16
+ DTS ⚡️ Build success in 3289ms
17
+ DTS dist/index.d.mts 24.58 KB
18
+ DTS dist/index.d.ts 24.58 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @pluv/client
2
2
 
3
+ ## 0.41.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 41aa439: **BREAKING** User's presence is now limited to be at most 512 bytes. If the presence is any larger, the `PluvRoom` will now throw an error.
8
+ - 555b88d: Users can now have multiple simultaneous connections. Previously, if a user tried to open a connection to a room while already being connected to the room, the new connection would be blocked. Now, when the user opens another connection, the new connection will be opened and initialized with their latest presence.
9
+
10
+ Users can only have 1 identity and 1 presence, which means that all presence updates will sync across all of that user's connections. This means that even when a user opens multiple connections, they will only appear once in `others`.
11
+
12
+ ### Patch Changes
13
+
14
+ - eb2d903: `PluvRoom` attempts to reconnect immediately after receiving a `close` event, instead of waiting for the reconnect timer. After failing the initial reconnect will the reconnects poll on the timer.
15
+ - 555b88d: Fixed the `presence` type of `other` and `others` to equal the inferred presence type.
16
+ - @pluv/crdt@0.41.0
17
+ - @pluv/types@0.41.0
18
+
19
+ ## 0.40.2
20
+
21
+ ### Patch Changes
22
+
23
+ - 6fbc2fe: Update `PluvClient.createRoom` so that the 2nd parameter is optional.
24
+ - @pluv/crdt@0.40.2
25
+ - @pluv/types@0.40.2
26
+
3
27
  ## 0.40.1
4
28
 
5
29
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -176,7 +176,7 @@ declare class PluvRoom<TIO extends IOLike<any, any>, TMetadata extends JsonObjec
176
176
  getStorage: <TKey extends keyof TStorage>(type: TKey) => TStorage[TKey] | null;
177
177
  getStorageJson(): InferCrdtJson<TStorage> | null;
178
178
  getStorageJson<TKey extends keyof TStorage>(type: TKey): InferCrdtJson<TStorage[TKey]> | null;
179
- other: (connectionId: string, callback: OtherNotifierSubscriptionCallback<TIO>) => (() => void);
179
+ other: (connectionId: string, callback: OtherNotifierSubscriptionCallback<TIO, TPresence>) => (() => void);
180
180
  redo: () => void;
181
181
  storage: <TKey extends keyof TStorage>(key: TKey, fn: (value: InferCrdtJson<TStorage[TKey]>) => void) => (() => void);
182
182
  storageRoot: (fn: (value: { [P in keyof TStorage]: InferCrdtJson<TStorage[P]>; }) => void) => (() => void);
@@ -263,7 +263,7 @@ declare class PluvClient<TIO extends IOLike<any, any>, TPresence extends JsonObj
263
263
  };
264
264
  get procedure(): PluvProcedure<TIO, {}, {}, TPresence, TStorage>;
265
265
  constructor(options: PluvClientOptions<TIO, TPresence, TStorage, TMetadata>);
266
- createRoom: <TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {}>(room: string, options: CreateRoomOptions<TIO, TPresence, TStorage, TMetadata, TEvents>) => PluvRoom<TIO, TMetadata, TPresence, TStorage, TEvents>;
266
+ createRoom: <TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {}>(room: string, options?: CreateRoomOptions<TIO, TPresence, TStorage, TMetadata, TEvents>) => PluvRoom<TIO, TMetadata, TPresence, TStorage, TEvents>;
267
267
  enter: (room: string | PluvRoom<TIO, TMetadata, any, any, any>, ...args: EnterRoomParams<TMetadata>) => Promise<PluvRoom<TIO, TMetadata, TPresence, TStorage, any>>;
268
268
  getRoom: (room: string) => PluvRoom<TIO, TMetadata, TPresence, TStorage, any> | null;
269
269
  getRooms: () => readonly PluvRoom<TIO, TMetadata, TPresence, TStorage, any>[];
@@ -274,7 +274,7 @@ declare class PluvClient<TIO extends IOLike<any, any>, TPresence extends JsonObj
274
274
 
275
275
  interface AuthorizationState<TIO extends IOLike> {
276
276
  token: string | null;
277
- user: InferIOAuthorizeUser<InferIOAuthorize<TIO>> | null;
277
+ user: Id<InferIOAuthorizeUser<InferIOAuthorize<TIO>>> | null;
278
278
  }
279
279
  type EventResolver<TIO extends IOLike = IOLike, TInput extends JsonObject = {}, TOutput extends EventRecord<string, any> = {}, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}> = (data: TInput, context: EventResolverContext<TIO, TPresence, TStorage>) => MaybePromise<TOutput | void>;
280
280
  interface EventResolverContext<TIO extends IOLike = IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}> {
@@ -329,7 +329,7 @@ type WithMetadata<TMetadata extends JsonObject = {}> = keyof TMetadata extends n
329
329
  metadata: TMetadata;
330
330
  };
331
331
 
332
- type OtherNotifierSubscriptionCallback<TIO extends IOLike> = (value: Id<UserInfo<TIO>> | null) => void;
332
+ type OtherNotifierSubscriptionCallback<TIO extends IOLike, TPresence extends JsonObject> = (value: Id<UserInfo<TIO, TPresence>> | null) => void;
333
333
 
334
334
  declare abstract class AbstractRoom<TIO extends IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}, TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {}> {
335
335
  readonly id: string;
@@ -348,7 +348,7 @@ declare abstract class AbstractRoom<TIO extends IOLike, TPresence extends JsonOb
348
348
  abstract getStorage<TKey extends keyof TStorage>(type: TKey): TStorage[TKey] | null;
349
349
  abstract getStorageJson(): InferCrdtJson<TStorage> | null;
350
350
  abstract getStorageJson<TKey extends keyof TStorage>(type: TKey): InferCrdtJson<TStorage[TKey]> | null;
351
- abstract other(connectionId: string, callback: OtherNotifierSubscriptionCallback<TIO>): () => void;
351
+ abstract other(connectionId: string, callback: OtherNotifierSubscriptionCallback<TIO, TPresence>): () => void;
352
352
  abstract redo(): void;
353
353
  abstract storage<TKey extends keyof TStorage>(key: TKey, fn: (value: InferCrdtJson<TStorage[TKey]>) => void): () => void;
354
354
  abstract storageRoot(fn: (value: {
@@ -395,7 +395,7 @@ declare class MockedRoom<TIO extends IOLike, TPresence extends JsonObject = {},
395
395
  getStorage: <TKey extends keyof TStorage>(type: TKey) => TStorage[TKey] | null;
396
396
  getStorageJson(): InferCrdtJson<TStorage> | null;
397
397
  getStorageJson<TKey extends keyof TStorage>(type: TKey): InferCrdtJson<TStorage[TKey]> | null;
398
- other: (connectionId: string, callback: OtherNotifierSubscriptionCallback<TIO>) => (() => void);
398
+ other: (connectionId: string, callback: OtherNotifierSubscriptionCallback<TIO, TPresence>) => (() => void);
399
399
  redo: () => void;
400
400
  storage: <TKey extends keyof TStorage>(key: TKey, fn: (value: InferCrdtJson<TStorage[TKey]>) => void) => (() => void);
401
401
  storageRoot: (fn: (value: { [P in keyof TStorage]: InferCrdtJson<TStorage[P]>; }) => void) => (() => void);
package/dist/index.d.ts CHANGED
@@ -176,7 +176,7 @@ declare class PluvRoom<TIO extends IOLike<any, any>, TMetadata extends JsonObjec
176
176
  getStorage: <TKey extends keyof TStorage>(type: TKey) => TStorage[TKey] | null;
177
177
  getStorageJson(): InferCrdtJson<TStorage> | null;
178
178
  getStorageJson<TKey extends keyof TStorage>(type: TKey): InferCrdtJson<TStorage[TKey]> | null;
179
- other: (connectionId: string, callback: OtherNotifierSubscriptionCallback<TIO>) => (() => void);
179
+ other: (connectionId: string, callback: OtherNotifierSubscriptionCallback<TIO, TPresence>) => (() => void);
180
180
  redo: () => void;
181
181
  storage: <TKey extends keyof TStorage>(key: TKey, fn: (value: InferCrdtJson<TStorage[TKey]>) => void) => (() => void);
182
182
  storageRoot: (fn: (value: { [P in keyof TStorage]: InferCrdtJson<TStorage[P]>; }) => void) => (() => void);
@@ -263,7 +263,7 @@ declare class PluvClient<TIO extends IOLike<any, any>, TPresence extends JsonObj
263
263
  };
264
264
  get procedure(): PluvProcedure<TIO, {}, {}, TPresence, TStorage>;
265
265
  constructor(options: PluvClientOptions<TIO, TPresence, TStorage, TMetadata>);
266
- createRoom: <TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {}>(room: string, options: CreateRoomOptions<TIO, TPresence, TStorage, TMetadata, TEvents>) => PluvRoom<TIO, TMetadata, TPresence, TStorage, TEvents>;
266
+ createRoom: <TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {}>(room: string, options?: CreateRoomOptions<TIO, TPresence, TStorage, TMetadata, TEvents>) => PluvRoom<TIO, TMetadata, TPresence, TStorage, TEvents>;
267
267
  enter: (room: string | PluvRoom<TIO, TMetadata, any, any, any>, ...args: EnterRoomParams<TMetadata>) => Promise<PluvRoom<TIO, TMetadata, TPresence, TStorage, any>>;
268
268
  getRoom: (room: string) => PluvRoom<TIO, TMetadata, TPresence, TStorage, any> | null;
269
269
  getRooms: () => readonly PluvRoom<TIO, TMetadata, TPresence, TStorage, any>[];
@@ -274,7 +274,7 @@ declare class PluvClient<TIO extends IOLike<any, any>, TPresence extends JsonObj
274
274
 
275
275
  interface AuthorizationState<TIO extends IOLike> {
276
276
  token: string | null;
277
- user: InferIOAuthorizeUser<InferIOAuthorize<TIO>> | null;
277
+ user: Id<InferIOAuthorizeUser<InferIOAuthorize<TIO>>> | null;
278
278
  }
279
279
  type EventResolver<TIO extends IOLike = IOLike, TInput extends JsonObject = {}, TOutput extends EventRecord<string, any> = {}, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}> = (data: TInput, context: EventResolverContext<TIO, TPresence, TStorage>) => MaybePromise<TOutput | void>;
280
280
  interface EventResolverContext<TIO extends IOLike = IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}> {
@@ -329,7 +329,7 @@ type WithMetadata<TMetadata extends JsonObject = {}> = keyof TMetadata extends n
329
329
  metadata: TMetadata;
330
330
  };
331
331
 
332
- type OtherNotifierSubscriptionCallback<TIO extends IOLike> = (value: Id<UserInfo<TIO>> | null) => void;
332
+ type OtherNotifierSubscriptionCallback<TIO extends IOLike, TPresence extends JsonObject> = (value: Id<UserInfo<TIO, TPresence>> | null) => void;
333
333
 
334
334
  declare abstract class AbstractRoom<TIO extends IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}, TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {}> {
335
335
  readonly id: string;
@@ -348,7 +348,7 @@ declare abstract class AbstractRoom<TIO extends IOLike, TPresence extends JsonOb
348
348
  abstract getStorage<TKey extends keyof TStorage>(type: TKey): TStorage[TKey] | null;
349
349
  abstract getStorageJson(): InferCrdtJson<TStorage> | null;
350
350
  abstract getStorageJson<TKey extends keyof TStorage>(type: TKey): InferCrdtJson<TStorage[TKey]> | null;
351
- abstract other(connectionId: string, callback: OtherNotifierSubscriptionCallback<TIO>): () => void;
351
+ abstract other(connectionId: string, callback: OtherNotifierSubscriptionCallback<TIO, TPresence>): () => void;
352
352
  abstract redo(): void;
353
353
  abstract storage<TKey extends keyof TStorage>(key: TKey, fn: (value: InferCrdtJson<TStorage[TKey]>) => void): () => void;
354
354
  abstract storageRoot(fn: (value: {
@@ -395,7 +395,7 @@ declare class MockedRoom<TIO extends IOLike, TPresence extends JsonObject = {},
395
395
  getStorage: <TKey extends keyof TStorage>(type: TKey) => TStorage[TKey] | null;
396
396
  getStorageJson(): InferCrdtJson<TStorage> | null;
397
397
  getStorageJson<TKey extends keyof TStorage>(type: TKey): InferCrdtJson<TStorage[TKey]> | null;
398
- other: (connectionId: string, callback: OtherNotifierSubscriptionCallback<TIO>) => (() => void);
398
+ other: (connectionId: string, callback: OtherNotifierSubscriptionCallback<TIO, TPresence>) => (() => void);
399
399
  redo: () => void;
400
400
  storage: <TKey extends keyof TStorage>(key: TKey, fn: (value: InferCrdtJson<TStorage[TKey]>) => void) => (() => void);
401
401
  storageRoot: (fn: (value: { [P in keyof TStorage]: InferCrdtJson<TStorage[P]>; }) => void) => (() => void);
package/dist/index.js CHANGED
@@ -223,24 +223,27 @@ var EventNotifier = class {
223
223
  var import_wonka3 = require("wonka");
224
224
  var OtherNotifier = class {
225
225
  constructor() {
226
- this.subjects = /* @__PURE__ */ new Map();
226
+ this._subjects = /* @__PURE__ */ new Map();
227
227
  }
228
228
  clear() {
229
- const connectionIds = Array.from(this.subjects.keys());
230
- connectionIds.forEach((connectionId) => {
231
- this.subject(connectionId).next(null);
232
- this.subjects.delete(connectionId);
229
+ this._subjects.forEach((subject) => {
230
+ subject.next(null);
233
231
  });
232
+ this._subjects.clear();
234
233
  }
235
- subject(name) {
236
- const subject = this.subjects.get(name);
234
+ delete(clientId) {
235
+ this.subject(clientId).next(null);
236
+ this._subjects.delete(clientId);
237
+ }
238
+ subject(clientId) {
239
+ const subject = this._subjects.get(clientId);
237
240
  if (subject) return subject;
238
241
  const newSubject = (0, import_wonka3.makeSubject)();
239
- this.subjects.set(name, newSubject);
242
+ this._subjects.set(clientId, newSubject);
240
243
  return newSubject;
241
244
  }
242
- subscribe(name, callback) {
243
- const source = this.subject(name).source;
245
+ subscribe(clientId, callback) {
246
+ const source = this.subject(clientId).source;
244
247
  return (0, import_wonka3.subscribe)(callback)(source).unsubscribe;
245
248
  }
246
249
  };
@@ -314,9 +317,16 @@ var StorageStore = class extends AbstractStorageStore {
314
317
  }
315
318
  };
316
319
 
320
+ // src/constants.ts
321
+ var MAX_PRESENCE_SIZE_BYTES = 512;
322
+
317
323
  // src/UsersManager.ts
318
324
  var UsersManager = class {
319
325
  constructor(config) {
326
+ this._idMap = {
327
+ fromConnectionId: /* @__PURE__ */ new Map(),
328
+ fromClientId: /* @__PURE__ */ new Map()
329
+ };
320
330
  /**
321
331
  * @description This is only set when the user is connected.
322
332
  */
@@ -334,74 +344,154 @@ var UsersManager = class {
334
344
  get myself() {
335
345
  return this._myself;
336
346
  }
337
- clear() {
347
+ addConnection(userInfo) {
348
+ var _a;
349
+ const data = __spreadProps(__spreadValues({}, userInfo), {
350
+ presence: (_a = userInfo.presence) != null ? _a : this._initialPresence
351
+ });
352
+ const myClientId = !!this._myself ? this.getClientId(this._myself) : null;
353
+ const clientId = this.getClientId(data);
354
+ const remaining = this._setConnectionId(userInfo.connectionId, clientId).size;
355
+ if (myClientId === clientId) {
356
+ const result2 = {
357
+ clientId,
358
+ data,
359
+ isMyself: true,
360
+ remaining
361
+ };
362
+ return result2;
363
+ }
364
+ const other = this._others.get(clientId);
365
+ if (!other) this._others.set(clientId, data);
366
+ const result = {
367
+ clientId,
368
+ data,
369
+ isMyself: false,
370
+ remaining
371
+ };
372
+ return result;
373
+ }
374
+ clearConnections() {
375
+ this.removeMyself();
338
376
  this._others.clear();
377
+ this._idMap.fromClientId.clear();
378
+ this._idMap.fromConnectionId.clear();
379
+ }
380
+ deleteConnection(connectionId) {
381
+ var _a, _b, _c;
382
+ const clientId = this.getClientId(connectionId);
383
+ this._deleteConnectionId(connectionId);
384
+ if (!clientId) return null;
385
+ const userInfo = (_a = this._others.get(clientId)) != null ? _a : null;
386
+ if (!userInfo) return null;
387
+ const remaining = (_c = (_b = this._getConnectionIds(clientId)) == null ? void 0 : _b.size) != null ? _c : 0;
388
+ const result = { clientId, remaining, data: userInfo };
389
+ if (!remaining) this._others.delete(clientId);
390
+ return result;
391
+ }
392
+ getClientId(input) {
393
+ var _a, _b, _c, _d;
394
+ if (typeof input !== "string") {
395
+ const userInfo = input;
396
+ const connectionId2 = userInfo.connectionId;
397
+ const userId = (_b = (_a = userInfo.user) == null ? void 0 : _a.id) != null ? _b : null;
398
+ return userId != null ? userId : connectionId2;
399
+ }
400
+ const connectionId = input;
401
+ if (((_c = this._myself) == null ? void 0 : _c.connectionId) === connectionId) {
402
+ return this.getClientId(this._myself);
403
+ }
404
+ const clientId = (_d = this._idMap.fromConnectionId.get(connectionId)) != null ? _d : null;
405
+ return clientId;
339
406
  }
340
407
  getOther(connectionId) {
341
408
  var _a;
342
- return (_a = this._others.get(connectionId)) != null ? _a : null;
409
+ const clientId = this.getClientId(connectionId);
410
+ if (!clientId) return null;
411
+ return (_a = this._others.get(clientId)) != null ? _a : null;
343
412
  }
344
413
  getOthers() {
345
414
  return Array.from(this._others.values());
346
415
  }
347
416
  patchPresence(connectionId, patch) {
348
- var _a, _b;
349
- if (((_a = this._myself) == null ? void 0 : _a.connectionId) === connectionId) {
350
- this.updateMyPresence(patch);
351
- return;
352
- }
353
- const other = this._others.get(connectionId);
354
- if (!other) return;
417
+ const clientId = this.getClientId(connectionId);
418
+ const myClientId = !!this._myself ? this.getClientId(this._myself) : null;
419
+ if (!clientId) return null;
420
+ if (myClientId === clientId) return this.updateMyPresence(patch);
421
+ const other = this._others.get(clientId);
422
+ if (!other) return null;
355
423
  const presence = __spreadValues(__spreadValues({}, other.presence), patch);
356
- (_b = this._presence) == null ? void 0 : _b.parse(presence);
357
- this._others.set(connectionId, __spreadProps(__spreadValues({}, other), { presence }));
424
+ const validated = this._presence ? this._presence.parse(presence) : presence;
425
+ this._others.set(clientId, __spreadProps(__spreadValues({}, other), { presence: validated }));
426
+ return validated;
358
427
  }
359
428
  removeMyself() {
429
+ if (!this._myself) return;
430
+ const connectionId = this._myself.connectionId;
431
+ this._deleteConnectionId(connectionId);
360
432
  this._myself = null;
361
433
  }
362
- removeUser(connectionId) {
363
- this._others.delete(connectionId);
364
- }
365
- removeUsers() {
366
- this._others.clear();
367
- }
368
- setMyself(connectionId, user) {
369
- this._myself = {
370
- connectionId,
371
- presence: this._initialPresence,
372
- user
373
- };
374
- this._myPresence = this._initialPresence;
434
+ setMyself(userInfo) {
435
+ var _a;
436
+ const connectionId = userInfo.connectionId;
437
+ const presence = (_a = userInfo.presence) != null ? _a : this._initialPresence;
438
+ this._myself = __spreadProps(__spreadValues({}, userInfo), { presence });
439
+ this._myPresence = presence;
440
+ const clientId = this.getClientId(this._myself);
441
+ this._setConnectionId(connectionId, clientId);
375
442
  }
376
443
  setPresence(connectionId, presence) {
377
- var _a;
378
- if (((_a = this._myself) == null ? void 0 : _a.connectionId) === connectionId) {
444
+ const clientId = this.getClientId(connectionId);
445
+ const myClientId = !!this._myself ? this.getClientId(this._myself) : null;
446
+ if (!clientId) return;
447
+ if (myClientId === clientId) {
448
+ if (!this._myself) return;
379
449
  this._myself.presence = presence;
380
450
  this._myPresence = presence;
381
451
  return;
382
452
  }
383
- const other = this._others.get(connectionId);
453
+ const other = this._others.get(clientId);
384
454
  if (!other) return;
385
- this._others.set(connectionId, __spreadProps(__spreadValues({}, other), { presence }));
386
- }
387
- setUser(connectionId, user, presence) {
388
- var _a;
389
- if (((_a = this._myself) == null ? void 0 : _a.connectionId) === connectionId) return;
390
- const other = this._others.get(connectionId);
391
- if (other) return;
392
- this._others.set(connectionId, {
393
- connectionId,
394
- presence: presence != null ? presence : this._initialPresence,
395
- user
396
- });
455
+ this._others.set(clientId, __spreadProps(__spreadValues({}, other), { presence }));
397
456
  }
398
457
  /**
399
458
  * @description This method need not care about being connected.
400
459
  */
401
460
  updateMyPresence(patch) {
402
- this._myPresence = __spreadValues(__spreadValues({}, this._myPresence), patch);
403
- if (!this._myself) return;
404
- this._myself.presence = this._myPresence;
461
+ const updated = __spreadValues(__spreadValues({}, this._myPresence), patch);
462
+ const bytes = new TextEncoder().encode(JSON.stringify(updated)).length;
463
+ if (bytes > MAX_PRESENCE_SIZE_BYTES) {
464
+ throw new Error(
465
+ `Large presence. Presence must be at most 512 bytes. Current size: ${bytes.toLocaleString()}`
466
+ );
467
+ }
468
+ this._myPresence = updated;
469
+ if (!this._myself) return updated;
470
+ this._myself.presence = updated;
471
+ return updated;
472
+ }
473
+ _deleteConnectionId(connectionId) {
474
+ var _a;
475
+ const clientId = this.getClientId(connectionId);
476
+ this._idMap.fromConnectionId.delete(connectionId);
477
+ if (!clientId) return;
478
+ const set = (_a = this._idMap.fromClientId.get(clientId)) != null ? _a : null;
479
+ if (!set) return;
480
+ set.delete(connectionId);
481
+ if (!!set.size) return;
482
+ this._idMap.fromClientId.delete(clientId);
483
+ }
484
+ _getConnectionIds(clientId) {
485
+ const set = this._idMap.fromClientId.get(clientId);
486
+ return set != null ? set : null;
487
+ }
488
+ _setConnectionId(connectionId, clientId) {
489
+ var _a;
490
+ const set = (_a = this._idMap.fromClientId.get(clientId)) != null ? _a : /* @__PURE__ */ new Set();
491
+ const updated = set.add(connectionId);
492
+ this._idMap.fromConnectionId.set(connectionId, clientId);
493
+ this._idMap.fromClientId.set(clientId, updated);
494
+ return updated;
405
495
  }
406
496
  };
407
497
 
@@ -567,7 +657,9 @@ var PluvRoom = class extends AbstractRoom {
567
657
  return sharedType;
568
658
  };
569
659
  this.other = (connectionId, callback) => {
570
- return this._otherNotifier.subscribe(connectionId, callback);
660
+ const clientId = this._usersManager.getClientId(connectionId);
661
+ if (!clientId) return () => void 0;
662
+ return this._otherNotifier.subscribe(clientId, callback);
571
663
  };
572
664
  this.redo = () => {
573
665
  this._crdtManager.doc.redo();
@@ -599,7 +691,7 @@ var PluvRoom = class extends AbstractRoom {
599
691
  const myPresence = this._usersManager.myPresence;
600
692
  const myself = (_a = this._usersManager.myself) != null ? _a : null;
601
693
  this._stateNotifier.subjects["my-presence"].next(myPresence);
602
- !!myself && this._stateNotifier.subjects["myself"].next(myself);
694
+ if (!!myself) this._stateNotifier.subjects.myself.next(myself);
603
695
  this.broadcast(
604
696
  "$updatePresence",
605
697
  { presence: newPresence }
@@ -669,8 +761,8 @@ var PluvRoom = class extends AbstractRoom {
669
761
  try {
670
762
  const publicKey = this._getPublicKey(params);
671
763
  authToken = yield this._getAuthorization(this.id, params);
672
- authToken && url.searchParams.set("token", encodeURIComponent(authToken));
673
- publicKey && url.searchParams.set("public_key", encodeURIComponent(publicKey));
764
+ if (authToken) url.searchParams.set("token", encodeURIComponent(authToken));
765
+ if (publicKey) url.searchParams.set("public_key", encodeURIComponent(publicKey));
674
766
  webSocket = new WebSocket(url.toString());
675
767
  } catch (err) {
676
768
  this._updateState((oldState) => {
@@ -781,7 +873,7 @@ var PluvRoom = class extends AbstractRoom {
781
873
  this._clearTimeout(this._timeouts.pong);
782
874
  this._detachWindowListeners();
783
875
  this._usersManager.removeMyself();
784
- this._usersManager.removeUsers();
876
+ this._usersManager.clearConnections();
785
877
  this._otherNotifier.clear();
786
878
  this._stateNotifier.subjects.myself.next(null);
787
879
  this._stateNotifier.subjects.others.next([]);
@@ -869,11 +961,11 @@ var PluvRoom = class extends AbstractRoom {
869
961
  const { connectionId } = message;
870
962
  if (!connectionId) return;
871
963
  if (!this._state.webSocket) throw new Error("Could not find WebSocket");
872
- this._usersManager.removeUser(connectionId);
964
+ const clientId = this._usersManager.getClientId(connectionId);
965
+ const deleted = this._usersManager.deleteConnection(connectionId);
873
966
  const others = this._usersManager.getOthers();
874
967
  this._stateNotifier.subjects.others.next(others);
875
- this._otherNotifier.subject(connectionId).next(null);
876
- this._otherNotifier.subjects.delete(connectionId);
968
+ if (!(deleted == null ? void 0 : deleted.remaining) && !!clientId) this._otherNotifier.delete(clientId);
877
969
  }
878
970
  _handlePresenceUpdatedMessage(message) {
879
971
  var _a;
@@ -883,11 +975,18 @@ var PluvRoom = class extends AbstractRoom {
883
975
  const data = message.data;
884
976
  const myself = (_a = this._usersManager.myself) != null ? _a : null;
885
977
  if ((myself == null ? void 0 : myself.connectionId) === connectionId) return;
886
- this._usersManager.patchPresence(connectionId, data.presence);
978
+ const updated = this._usersManager.patchPresence(connectionId, data.presence);
979
+ const myClientId = !!myself ? this._usersManager.getClientId(myself) : null;
980
+ const clientId = this._usersManager.getClientId(connectionId);
981
+ if (!!clientId && myClientId === clientId) {
982
+ this._stateNotifier.subjects["my-presence"].next(updated);
983
+ this._stateNotifier.subjects.myself.next(this._usersManager.myself);
984
+ return;
985
+ }
887
986
  const other = this._usersManager.getOther(connectionId);
888
987
  const others = this._usersManager.getOthers();
889
- this._otherNotifier.subject(connectionId).next(other);
890
- this._stateNotifier.subjects["others"].next(others);
988
+ if (!!clientId) this._otherNotifier.subject(clientId).next(other);
989
+ this._stateNotifier.subjects.others.next(others);
891
990
  }
892
991
  _handleReceiveOthers(message) {
893
992
  if (!message.connectionId) return;
@@ -895,31 +994,44 @@ var PluvRoom = class extends AbstractRoom {
895
994
  const data = message.data;
896
995
  Object.keys(data.others).forEach((connectionId) => {
897
996
  const { presence, user } = data.others[connectionId];
898
- this._usersManager.setUser(connectionId, user);
997
+ const result = this._usersManager.addConnection({
998
+ connectionId,
999
+ presence: presence != null ? presence : void 0,
1000
+ user
1001
+ });
1002
+ if (!!presence) this._usersManager.patchPresence(connectionId, presence);
1003
+ if (result.isMyself) return;
1004
+ const clientId = result.clientId;
899
1005
  const other = this._usersManager.getOther(connectionId);
900
- !!presence && this._usersManager.patchPresence(connectionId, presence);
901
- this._otherNotifier.subject(connectionId).next(other);
1006
+ this._otherNotifier.subject(clientId).next(other);
902
1007
  });
903
1008
  const others = this._usersManager.getOthers();
904
- this._stateNotifier.subjects["others"].next(others);
1009
+ this._stateNotifier.subjects.others.next(others);
905
1010
  }
906
1011
  _handleRegisteredMessage(message) {
907
- var _a, _b;
908
- const { connectionId, user } = message;
1012
+ var _a, _b, _c;
1013
+ const { connectionId } = message;
1014
+ const user = message.user;
909
1015
  if (!connectionId) return;
910
1016
  if (!this._state.webSocket) throw new Error("Could not find WebSocket");
1017
+ const data = message.data;
911
1018
  this._updateState((oldState) => {
912
1019
  oldState.connection.count += 1;
913
1020
  oldState.connection.id = connectionId;
914
1021
  oldState.authorization.user = user != null ? user : null;
915
1022
  return oldState;
916
1023
  });
917
- this._usersManager.setMyself(connectionId, user);
1024
+ const userInfo = {
1025
+ connectionId,
1026
+ presence: (_a = data.presence) != null ? _a : void 0,
1027
+ user
1028
+ };
1029
+ this._usersManager.setMyself(userInfo);
918
1030
  const presence = this._usersManager.myPresence;
919
- const myself = (_a = this._usersManager.myself) != null ? _a : null;
1031
+ const myself = (_b = this._usersManager.myself) != null ? _b : null;
920
1032
  this._stateNotifier.subjects["my-presence"].next(presence);
921
- this._stateNotifier.subjects["myself"].next(myself);
922
- const update = this._state.connection.count > 1 ? (_b = this._crdtManager.doc.getEncodedState()) != null ? _b : null : null;
1033
+ this._stateNotifier.subjects.myself.next(myself);
1034
+ const update = this._state.connection.count > 1 ? (_c = this._crdtManager.doc.getEncodedState()) != null ? _c : null : null;
923
1035
  this._sendMessage({
924
1036
  type: "$initializeSession",
925
1037
  data: { presence, update }
@@ -974,6 +1086,7 @@ var PluvRoom = class extends AbstractRoom {
974
1086
  this._crdtNotifier.rootSubject.next(storageRoot);
975
1087
  }
976
1088
  _handleUserJoinedMessage(message) {
1089
+ var _a;
977
1090
  const { connectionId } = message;
978
1091
  if (!connectionId) return;
979
1092
  if (!this._state.webSocket) throw new Error("Could not find WebSocket");
@@ -982,12 +1095,18 @@ var PluvRoom = class extends AbstractRoom {
982
1095
  const myself = this._usersManager.myself;
983
1096
  if (myself.connectionId === connectionId) {
984
1097
  this._sendMessage({ type: "$getOthers", data: {} });
1098
+ return;
985
1099
  }
986
- this._usersManager.setUser(connectionId, data.user, data.presence);
1100
+ const result = this._usersManager.addConnection({
1101
+ connectionId,
1102
+ presence: data.presence,
1103
+ user: data.user
1104
+ });
1105
+ const clientId = (_a = result == null ? void 0 : result.clientId) != null ? _a : null;
987
1106
  const other = this._usersManager.getOther(connectionId);
988
1107
  const others = this._usersManager.getOthers();
989
- this._otherNotifier.subject(connectionId).next(other);
990
- this._stateNotifier.subjects["others"].next(others);
1108
+ if (!!clientId) this._otherNotifier.subject(clientId).next(other);
1109
+ this._stateNotifier.subjects.others.next(others);
991
1110
  }
992
1111
  _heartbeat() {
993
1112
  this._clearTimeout(this._timeouts.pong);
@@ -997,7 +1116,7 @@ var PluvRoom = class extends AbstractRoom {
997
1116
  _logDebug(...data) {
998
1117
  var _a;
999
1118
  if (((_a = process == null ? void 0 : process.env) == null ? void 0 : _a.NODE_ENV) === "production") return;
1000
- this._debug && console.log(...data);
1119
+ if (this._debug) console.log(...data);
1001
1120
  }
1002
1121
  _observeCrdt() {
1003
1122
  var _a, _b;
@@ -1032,7 +1151,7 @@ var PluvRoom = class extends AbstractRoom {
1032
1151
  _onClose(event) {
1033
1152
  return __async(this, null, function* () {
1034
1153
  this._logDebug("WebSocket closed");
1035
- !!event.reason && this._logDebug(event.reason);
1154
+ if (!!event.reason) this._logDebug(event.reason);
1036
1155
  const shouldRetry = [
1037
1156
  // Going away: Client/server is shutting down or navigating
1038
1157
  1001,
@@ -1047,15 +1166,18 @@ var PluvRoom = class extends AbstractRoom {
1047
1166
  // Bad gateway
1048
1167
  1014
1049
1168
  ].some((okToRetryCode) => event.code === okToRetryCode);
1050
- this._closeWs();
1051
- if (!shouldRetry) this._clearTimeout(this._timeouts.reconnect);
1052
- else this._pollReconnect();
1053
1169
  this._updateState((oldState) => {
1054
1170
  oldState.authorization.token = null;
1055
1171
  oldState.connection.state = shouldRetry ? "Unavailable" /* Unavailable */ : "Closed" /* Closed */;
1056
1172
  oldState.webSocket = null;
1057
1173
  return oldState;
1058
1174
  });
1175
+ if (shouldRetry) {
1176
+ yield this._reconnect();
1177
+ return;
1178
+ }
1179
+ this._closeWs();
1180
+ this._clearTimeout(this._timeouts.reconnect);
1059
1181
  });
1060
1182
  }
1061
1183
  /**
@@ -1226,7 +1348,7 @@ var PluvClient = class {
1226
1348
  constructor(options) {
1227
1349
  this._publicKey = null;
1228
1350
  this._rooms = /* @__PURE__ */ new Map();
1229
- this.createRoom = (room, options) => {
1351
+ this.createRoom = (room, options = {}) => {
1230
1352
  var _a;
1231
1353
  const oldRoom = this.getRoom(room);
1232
1354
  if (oldRoom) return oldRoom;
@@ -1297,7 +1419,7 @@ var PluvClient = class {
1297
1419
  _logDebug(...data) {
1298
1420
  var _a;
1299
1421
  if (((_a = process == null ? void 0 : process.env) == null ? void 0 : _a.NODE_ENV) === "production") return;
1300
- this._debug && console.log(...data);
1422
+ if (this._debug) console.log(...data);
1301
1423
  }
1302
1424
  };
1303
1425
 
@@ -1404,7 +1526,9 @@ var MockedRoom = class extends AbstractRoom {
1404
1526
  return sharedType;
1405
1527
  };
1406
1528
  this.other = (connectionId, callback) => {
1407
- return this._otherNotifier.subscribe(connectionId, callback);
1529
+ const clientId = this._usersManager.getClientId(connectionId);
1530
+ if (!clientId) return () => void 0;
1531
+ return this._otherNotifier.subscribe(clientId, callback);
1408
1532
  };
1409
1533
  this.redo = () => {
1410
1534
  this._crdtManager.doc.redo();
@@ -1438,7 +1562,7 @@ var MockedRoom = class extends AbstractRoom {
1438
1562
  const myPresence = this._usersManager.myPresence;
1439
1563
  const myself = (_a = this._usersManager.myself) != null ? _a : null;
1440
1564
  this._stateNotifier.subjects["my-presence"].next(myPresence);
1441
- !!myself && this._stateNotifier.subjects["myself"].next(myself);
1565
+ if (!!myself) this._stateNotifier.subjects["myself"].next(myself);
1442
1566
  };
1443
1567
  this._events = events;
1444
1568
  this._router = router != null ? router : new PluvRouter({});