@pluv/client 0.41.1 → 0.41.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +6 -6
- package/CHANGELOG.md +16 -0
- package/dist/index.js +65 -36
- package/dist/index.mjs +65 -36
- package/package.json +5 -5
- package/src/PluvRoom.ts +27 -11
- package/src/UsersManager.ts +22 -10
- package/src/utils/index.ts +1 -0
- package/src/utils/pickBy.ts +13 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @pluv/client@0.41.
|
|
2
|
+
> @pluv/client@0.41.3 build /home/runner/work/pluv/pluv/packages/client
|
|
3
3
|
> tsup src/index.ts --format esm,cjs --dts
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
[34mCLI[39m Target: es6
|
|
9
9
|
[34mESM[39m Build start
|
|
10
10
|
[34mCJS[39m Build start
|
|
11
|
-
[32mCJS[39m [1mdist/index.js [22m[
|
|
12
|
-
[32mCJS[39m ⚡️ Build success in
|
|
13
|
-
[32mESM[39m [1mdist/index.mjs [22m[
|
|
14
|
-
[32mESM[39m ⚡️ Build success in
|
|
11
|
+
[32mCJS[39m [1mdist/index.js [22m[32m56.67 KB[39m
|
|
12
|
+
[32mCJS[39m ⚡️ Build success in 110ms
|
|
13
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m55.36 KB[39m
|
|
14
|
+
[32mESM[39m ⚡️ Build success in 111ms
|
|
15
15
|
[34mDTS[39m Build start
|
|
16
|
-
[32mDTS[39m ⚡️ Build success in
|
|
16
|
+
[32mDTS[39m ⚡️ Build success in 3194ms
|
|
17
17
|
[32mDTS[39m [1mdist/index.d.mts [22m[32m24.58 KB[39m
|
|
18
18
|
[32mDTS[39m [1mdist/index.d.ts [22m[32m24.58 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @pluv/client
|
|
2
2
|
|
|
3
|
+
## 0.41.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 49051c0: Fix presence keys becoming lost, causing presence objects to be partial and failing schema validation.
|
|
8
|
+
- @pluv/crdt@0.41.3
|
|
9
|
+
- @pluv/types@0.41.3
|
|
10
|
+
|
|
11
|
+
## 0.41.2
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- 37956d3: When presence is received for a user that is not currently being tracked, `PluvRoom` now automatically updates `others` to include the connection of the received presence.
|
|
16
|
+
- @pluv/crdt@0.41.2
|
|
17
|
+
- @pluv/types@0.41.2
|
|
18
|
+
|
|
3
19
|
## 0.41.1
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/dist/index.js
CHANGED
|
@@ -320,6 +320,37 @@ var StorageStore = class extends AbstractStorageStore {
|
|
|
320
320
|
// src/constants.ts
|
|
321
321
|
var MAX_PRESENCE_SIZE_BYTES = 512;
|
|
322
322
|
|
|
323
|
+
// src/utils/debounce.ts
|
|
324
|
+
var debounce = (fn, options) => {
|
|
325
|
+
const { wait, immediate = false } = options != null ? options : {};
|
|
326
|
+
let prevResult;
|
|
327
|
+
let timeout = null;
|
|
328
|
+
return (...args) => {
|
|
329
|
+
if (typeof timeout === "number") clearTimeout(timeout);
|
|
330
|
+
timeout = setTimeout(() => {
|
|
331
|
+
timeout = null;
|
|
332
|
+
if (!immediate) prevResult = fn(args);
|
|
333
|
+
}, wait);
|
|
334
|
+
if (immediate && !timeout) {
|
|
335
|
+
fn(args);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
return prevResult;
|
|
339
|
+
};
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
// src/utils/pickBy.ts
|
|
343
|
+
var pickBy = (obj, by) => {
|
|
344
|
+
const result = {};
|
|
345
|
+
const keys = Object.keys(obj);
|
|
346
|
+
for (let i = 0; i < keys.length; ++i) {
|
|
347
|
+
const key = keys[i];
|
|
348
|
+
const value = obj[key];
|
|
349
|
+
if (by(value)) result[key] = value;
|
|
350
|
+
}
|
|
351
|
+
return result;
|
|
352
|
+
};
|
|
353
|
+
|
|
323
354
|
// src/UsersManager.ts
|
|
324
355
|
var UsersManager = class {
|
|
325
356
|
constructor(config) {
|
|
@@ -334,7 +365,7 @@ var UsersManager = class {
|
|
|
334
365
|
this._others = /* @__PURE__ */ new Map();
|
|
335
366
|
this._presence = null;
|
|
336
367
|
const { initialPresence, presence = null } = config;
|
|
337
|
-
this.
|
|
368
|
+
this.initialPresence = initialPresence;
|
|
338
369
|
this._myPresence = initialPresence;
|
|
339
370
|
this._presence = presence;
|
|
340
371
|
}
|
|
@@ -346,9 +377,9 @@ var UsersManager = class {
|
|
|
346
377
|
}
|
|
347
378
|
addConnection(userInfo) {
|
|
348
379
|
var _a;
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
});
|
|
380
|
+
const cleaned = pickBy((_a = userInfo.presence) != null ? _a : {}, (value) => typeof value !== "undefined");
|
|
381
|
+
const presence = __spreadValues(__spreadValues({}, this.initialPresence), cleaned);
|
|
382
|
+
const data = __spreadProps(__spreadValues({}, userInfo), { presence });
|
|
352
383
|
const myClientId = !!this._myself ? this.getClientId(this._myself) : null;
|
|
353
384
|
const clientId = this.getClientId(data);
|
|
354
385
|
const remaining = this._setConnectionId(userInfo.connectionId, clientId).size;
|
|
@@ -414,13 +445,16 @@ var UsersManager = class {
|
|
|
414
445
|
return Array.from(this._others.values());
|
|
415
446
|
}
|
|
416
447
|
patchPresence(connectionId, patch) {
|
|
448
|
+
var _a;
|
|
417
449
|
const clientId = this.getClientId(connectionId);
|
|
418
450
|
const myClientId = !!this._myself ? this.getClientId(this._myself) : null;
|
|
419
451
|
if (!clientId) return null;
|
|
420
452
|
if (myClientId === clientId) return this.updateMyPresence(patch);
|
|
421
453
|
const other = this._others.get(clientId);
|
|
422
454
|
if (!other) return null;
|
|
423
|
-
const
|
|
455
|
+
const cleanedPatch = pickBy(patch != null ? patch : {}, (value) => typeof value !== "undefined");
|
|
456
|
+
const cleanedPresence = pickBy((_a = other.presence) != null ? _a : {}, (value) => typeof value !== "undefined");
|
|
457
|
+
const presence = __spreadValues(__spreadValues(__spreadValues({}, this.initialPresence), cleanedPresence), cleanedPatch);
|
|
424
458
|
const validated = this._presence ? this._presence.parse(presence) : presence;
|
|
425
459
|
this._others.set(clientId, __spreadProps(__spreadValues({}, other), { presence: validated }));
|
|
426
460
|
return validated;
|
|
@@ -434,7 +468,7 @@ var UsersManager = class {
|
|
|
434
468
|
setMyself(userInfo) {
|
|
435
469
|
var _a;
|
|
436
470
|
const connectionId = userInfo.connectionId;
|
|
437
|
-
const presence = (_a = userInfo.presence) != null ? _a : this.
|
|
471
|
+
const presence = (_a = userInfo.presence) != null ? _a : this.initialPresence;
|
|
438
472
|
this._myself = __spreadProps(__spreadValues({}, userInfo), { presence });
|
|
439
473
|
this._myPresence = presence;
|
|
440
474
|
const clientId = this.getClientId(this._myself);
|
|
@@ -455,10 +489,14 @@ var UsersManager = class {
|
|
|
455
489
|
this._others.set(clientId, __spreadProps(__spreadValues({}, other), { presence }));
|
|
456
490
|
}
|
|
457
491
|
/**
|
|
458
|
-
* @description This method need not care about being connected.
|
|
492
|
+
* @description This method need not care about being connected. This is because a user should
|
|
493
|
+
* be able to view and update their own presence without being online
|
|
494
|
+
* @date April 20, 2025
|
|
459
495
|
*/
|
|
460
496
|
updateMyPresence(patch) {
|
|
461
|
-
const
|
|
497
|
+
const cleanedPatch = pickBy(patch, (value) => typeof value !== "undefined");
|
|
498
|
+
const cleanedPresence = pickBy(this._myPresence, (value) => typeof value !== "undefined");
|
|
499
|
+
const updated = __spreadValues(__spreadValues(__spreadValues({}, this.initialPresence), cleanedPresence), cleanedPatch);
|
|
462
500
|
const bytes = new TextEncoder().encode(JSON.stringify(updated)).length;
|
|
463
501
|
if (bytes > MAX_PRESENCE_SIZE_BYTES) {
|
|
464
502
|
throw new Error(
|
|
@@ -505,25 +543,6 @@ var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
|
|
|
505
543
|
return ConnectionState2;
|
|
506
544
|
})(ConnectionState || {});
|
|
507
545
|
|
|
508
|
-
// src/utils/debounce.ts
|
|
509
|
-
var debounce = (fn, options) => {
|
|
510
|
-
const { wait, immediate = false } = options != null ? options : {};
|
|
511
|
-
let prevResult;
|
|
512
|
-
let timeout = null;
|
|
513
|
-
return (...args) => {
|
|
514
|
-
if (typeof timeout === "number") clearTimeout(timeout);
|
|
515
|
-
timeout = setTimeout(() => {
|
|
516
|
-
timeout = null;
|
|
517
|
-
if (!immediate) prevResult = fn(args);
|
|
518
|
-
}, wait);
|
|
519
|
-
if (immediate && !timeout) {
|
|
520
|
-
fn(args);
|
|
521
|
-
return;
|
|
522
|
-
}
|
|
523
|
-
return prevResult;
|
|
524
|
-
};
|
|
525
|
-
};
|
|
526
|
-
|
|
527
546
|
// src/PluvRoom.ts
|
|
528
547
|
var ADD_TO_STORAGE_STATE_DEBOUNCE_MS = 1e3;
|
|
529
548
|
var HEARTBEAT_INTERVAL_MS = 1e4;
|
|
@@ -983,9 +1002,19 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
983
1002
|
this._stateNotifier.subjects.myself.next(this._usersManager.myself);
|
|
984
1003
|
return;
|
|
985
1004
|
}
|
|
986
|
-
|
|
1005
|
+
if (!!clientId) {
|
|
1006
|
+
const other = this._usersManager.getOther(connectionId);
|
|
1007
|
+
this._otherNotifier.subject(clientId).next(other);
|
|
1008
|
+
} else {
|
|
1009
|
+
const added = this._usersManager.addConnection({
|
|
1010
|
+
connectionId,
|
|
1011
|
+
presence: data.presence,
|
|
1012
|
+
user: message.user
|
|
1013
|
+
});
|
|
1014
|
+
const other = this._usersManager.getOther(connectionId);
|
|
1015
|
+
this._otherNotifier.subject(added.clientId).next(other);
|
|
1016
|
+
}
|
|
987
1017
|
const others = this._usersManager.getOthers();
|
|
988
|
-
if (!!clientId) this._otherNotifier.subject(clientId).next(other);
|
|
989
1018
|
this._stateNotifier.subjects.others.next(others);
|
|
990
1019
|
}
|
|
991
1020
|
_handleReceiveOthers(message) {
|
|
@@ -996,7 +1025,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
996
1025
|
const { presence, user } = data.others[connectionId];
|
|
997
1026
|
const result = this._usersManager.addConnection({
|
|
998
1027
|
connectionId,
|
|
999
|
-
presence
|
|
1028
|
+
presence,
|
|
1000
1029
|
user
|
|
1001
1030
|
});
|
|
1002
1031
|
if (!!presence) this._usersManager.patchPresence(connectionId, presence);
|
|
@@ -1086,7 +1115,6 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1086
1115
|
this._crdtNotifier.rootSubject.next(storageRoot);
|
|
1087
1116
|
}
|
|
1088
1117
|
_handleUserJoinedMessage(message) {
|
|
1089
|
-
var _a;
|
|
1090
1118
|
const { connectionId } = message;
|
|
1091
1119
|
if (!connectionId) return;
|
|
1092
1120
|
if (!this._state.webSocket) throw new Error("Could not find WebSocket");
|
|
@@ -1097,15 +1125,14 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1097
1125
|
this._sendMessage({ type: "$getOthers", data: {} });
|
|
1098
1126
|
return;
|
|
1099
1127
|
}
|
|
1100
|
-
const
|
|
1128
|
+
const added = this._usersManager.addConnection({
|
|
1101
1129
|
connectionId,
|
|
1102
1130
|
presence: data.presence,
|
|
1103
1131
|
user: data.user
|
|
1104
1132
|
});
|
|
1105
|
-
const clientId = (_a = result == null ? void 0 : result.clientId) != null ? _a : null;
|
|
1106
1133
|
const other = this._usersManager.getOther(connectionId);
|
|
1107
1134
|
const others = this._usersManager.getOthers();
|
|
1108
|
-
|
|
1135
|
+
this._otherNotifier.subject(added.clientId).next(other);
|
|
1109
1136
|
this._stateNotifier.subjects.others.next(others);
|
|
1110
1137
|
}
|
|
1111
1138
|
_heartbeat() {
|
|
@@ -1261,13 +1288,15 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1261
1288
|
if (typeof document === "undefined") return;
|
|
1262
1289
|
if (document.visibilityState !== "visible") return;
|
|
1263
1290
|
switch (this._state.connection.state) {
|
|
1264
|
-
case "Open" /* Open */:
|
|
1291
|
+
case "Open" /* Open */: {
|
|
1265
1292
|
this._clearInterval(this._intervals.heartbeat);
|
|
1266
1293
|
setInterval(this._heartbeat.bind(this), HEARTBEAT_INTERVAL_MS);
|
|
1267
1294
|
return;
|
|
1268
|
-
|
|
1295
|
+
}
|
|
1296
|
+
case "Unavailable" /* Unavailable */: {
|
|
1269
1297
|
yield this._reconnect();
|
|
1270
1298
|
return;
|
|
1299
|
+
}
|
|
1271
1300
|
default:
|
|
1272
1301
|
}
|
|
1273
1302
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -289,6 +289,37 @@ var StorageStore = class extends AbstractStorageStore {
|
|
|
289
289
|
// src/constants.ts
|
|
290
290
|
var MAX_PRESENCE_SIZE_BYTES = 512;
|
|
291
291
|
|
|
292
|
+
// src/utils/debounce.ts
|
|
293
|
+
var debounce = (fn, options) => {
|
|
294
|
+
const { wait, immediate = false } = options != null ? options : {};
|
|
295
|
+
let prevResult;
|
|
296
|
+
let timeout = null;
|
|
297
|
+
return (...args) => {
|
|
298
|
+
if (typeof timeout === "number") clearTimeout(timeout);
|
|
299
|
+
timeout = setTimeout(() => {
|
|
300
|
+
timeout = null;
|
|
301
|
+
if (!immediate) prevResult = fn(args);
|
|
302
|
+
}, wait);
|
|
303
|
+
if (immediate && !timeout) {
|
|
304
|
+
fn(args);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
return prevResult;
|
|
308
|
+
};
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
// src/utils/pickBy.ts
|
|
312
|
+
var pickBy = (obj, by) => {
|
|
313
|
+
const result = {};
|
|
314
|
+
const keys = Object.keys(obj);
|
|
315
|
+
for (let i = 0; i < keys.length; ++i) {
|
|
316
|
+
const key = keys[i];
|
|
317
|
+
const value = obj[key];
|
|
318
|
+
if (by(value)) result[key] = value;
|
|
319
|
+
}
|
|
320
|
+
return result;
|
|
321
|
+
};
|
|
322
|
+
|
|
292
323
|
// src/UsersManager.ts
|
|
293
324
|
var UsersManager = class {
|
|
294
325
|
constructor(config) {
|
|
@@ -303,7 +334,7 @@ var UsersManager = class {
|
|
|
303
334
|
this._others = /* @__PURE__ */ new Map();
|
|
304
335
|
this._presence = null;
|
|
305
336
|
const { initialPresence, presence = null } = config;
|
|
306
|
-
this.
|
|
337
|
+
this.initialPresence = initialPresence;
|
|
307
338
|
this._myPresence = initialPresence;
|
|
308
339
|
this._presence = presence;
|
|
309
340
|
}
|
|
@@ -315,9 +346,9 @@ var UsersManager = class {
|
|
|
315
346
|
}
|
|
316
347
|
addConnection(userInfo) {
|
|
317
348
|
var _a;
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
});
|
|
349
|
+
const cleaned = pickBy((_a = userInfo.presence) != null ? _a : {}, (value) => typeof value !== "undefined");
|
|
350
|
+
const presence = __spreadValues(__spreadValues({}, this.initialPresence), cleaned);
|
|
351
|
+
const data = __spreadProps(__spreadValues({}, userInfo), { presence });
|
|
321
352
|
const myClientId = !!this._myself ? this.getClientId(this._myself) : null;
|
|
322
353
|
const clientId = this.getClientId(data);
|
|
323
354
|
const remaining = this._setConnectionId(userInfo.connectionId, clientId).size;
|
|
@@ -383,13 +414,16 @@ var UsersManager = class {
|
|
|
383
414
|
return Array.from(this._others.values());
|
|
384
415
|
}
|
|
385
416
|
patchPresence(connectionId, patch) {
|
|
417
|
+
var _a;
|
|
386
418
|
const clientId = this.getClientId(connectionId);
|
|
387
419
|
const myClientId = !!this._myself ? this.getClientId(this._myself) : null;
|
|
388
420
|
if (!clientId) return null;
|
|
389
421
|
if (myClientId === clientId) return this.updateMyPresence(patch);
|
|
390
422
|
const other = this._others.get(clientId);
|
|
391
423
|
if (!other) return null;
|
|
392
|
-
const
|
|
424
|
+
const cleanedPatch = pickBy(patch != null ? patch : {}, (value) => typeof value !== "undefined");
|
|
425
|
+
const cleanedPresence = pickBy((_a = other.presence) != null ? _a : {}, (value) => typeof value !== "undefined");
|
|
426
|
+
const presence = __spreadValues(__spreadValues(__spreadValues({}, this.initialPresence), cleanedPresence), cleanedPatch);
|
|
393
427
|
const validated = this._presence ? this._presence.parse(presence) : presence;
|
|
394
428
|
this._others.set(clientId, __spreadProps(__spreadValues({}, other), { presence: validated }));
|
|
395
429
|
return validated;
|
|
@@ -403,7 +437,7 @@ var UsersManager = class {
|
|
|
403
437
|
setMyself(userInfo) {
|
|
404
438
|
var _a;
|
|
405
439
|
const connectionId = userInfo.connectionId;
|
|
406
|
-
const presence = (_a = userInfo.presence) != null ? _a : this.
|
|
440
|
+
const presence = (_a = userInfo.presence) != null ? _a : this.initialPresence;
|
|
407
441
|
this._myself = __spreadProps(__spreadValues({}, userInfo), { presence });
|
|
408
442
|
this._myPresence = presence;
|
|
409
443
|
const clientId = this.getClientId(this._myself);
|
|
@@ -424,10 +458,14 @@ var UsersManager = class {
|
|
|
424
458
|
this._others.set(clientId, __spreadProps(__spreadValues({}, other), { presence }));
|
|
425
459
|
}
|
|
426
460
|
/**
|
|
427
|
-
* @description This method need not care about being connected.
|
|
461
|
+
* @description This method need not care about being connected. This is because a user should
|
|
462
|
+
* be able to view and update their own presence without being online
|
|
463
|
+
* @date April 20, 2025
|
|
428
464
|
*/
|
|
429
465
|
updateMyPresence(patch) {
|
|
430
|
-
const
|
|
466
|
+
const cleanedPatch = pickBy(patch, (value) => typeof value !== "undefined");
|
|
467
|
+
const cleanedPresence = pickBy(this._myPresence, (value) => typeof value !== "undefined");
|
|
468
|
+
const updated = __spreadValues(__spreadValues(__spreadValues({}, this.initialPresence), cleanedPresence), cleanedPatch);
|
|
431
469
|
const bytes = new TextEncoder().encode(JSON.stringify(updated)).length;
|
|
432
470
|
if (bytes > MAX_PRESENCE_SIZE_BYTES) {
|
|
433
471
|
throw new Error(
|
|
@@ -474,25 +512,6 @@ var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
|
|
|
474
512
|
return ConnectionState2;
|
|
475
513
|
})(ConnectionState || {});
|
|
476
514
|
|
|
477
|
-
// src/utils/debounce.ts
|
|
478
|
-
var debounce = (fn, options) => {
|
|
479
|
-
const { wait, immediate = false } = options != null ? options : {};
|
|
480
|
-
let prevResult;
|
|
481
|
-
let timeout = null;
|
|
482
|
-
return (...args) => {
|
|
483
|
-
if (typeof timeout === "number") clearTimeout(timeout);
|
|
484
|
-
timeout = setTimeout(() => {
|
|
485
|
-
timeout = null;
|
|
486
|
-
if (!immediate) prevResult = fn(args);
|
|
487
|
-
}, wait);
|
|
488
|
-
if (immediate && !timeout) {
|
|
489
|
-
fn(args);
|
|
490
|
-
return;
|
|
491
|
-
}
|
|
492
|
-
return prevResult;
|
|
493
|
-
};
|
|
494
|
-
};
|
|
495
|
-
|
|
496
515
|
// src/PluvRoom.ts
|
|
497
516
|
var ADD_TO_STORAGE_STATE_DEBOUNCE_MS = 1e3;
|
|
498
517
|
var HEARTBEAT_INTERVAL_MS = 1e4;
|
|
@@ -952,9 +971,19 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
952
971
|
this._stateNotifier.subjects.myself.next(this._usersManager.myself);
|
|
953
972
|
return;
|
|
954
973
|
}
|
|
955
|
-
|
|
974
|
+
if (!!clientId) {
|
|
975
|
+
const other = this._usersManager.getOther(connectionId);
|
|
976
|
+
this._otherNotifier.subject(clientId).next(other);
|
|
977
|
+
} else {
|
|
978
|
+
const added = this._usersManager.addConnection({
|
|
979
|
+
connectionId,
|
|
980
|
+
presence: data.presence,
|
|
981
|
+
user: message.user
|
|
982
|
+
});
|
|
983
|
+
const other = this._usersManager.getOther(connectionId);
|
|
984
|
+
this._otherNotifier.subject(added.clientId).next(other);
|
|
985
|
+
}
|
|
956
986
|
const others = this._usersManager.getOthers();
|
|
957
|
-
if (!!clientId) this._otherNotifier.subject(clientId).next(other);
|
|
958
987
|
this._stateNotifier.subjects.others.next(others);
|
|
959
988
|
}
|
|
960
989
|
_handleReceiveOthers(message) {
|
|
@@ -965,7 +994,7 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
965
994
|
const { presence, user } = data.others[connectionId];
|
|
966
995
|
const result = this._usersManager.addConnection({
|
|
967
996
|
connectionId,
|
|
968
|
-
presence
|
|
997
|
+
presence,
|
|
969
998
|
user
|
|
970
999
|
});
|
|
971
1000
|
if (!!presence) this._usersManager.patchPresence(connectionId, presence);
|
|
@@ -1055,7 +1084,6 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1055
1084
|
this._crdtNotifier.rootSubject.next(storageRoot);
|
|
1056
1085
|
}
|
|
1057
1086
|
_handleUserJoinedMessage(message) {
|
|
1058
|
-
var _a;
|
|
1059
1087
|
const { connectionId } = message;
|
|
1060
1088
|
if (!connectionId) return;
|
|
1061
1089
|
if (!this._state.webSocket) throw new Error("Could not find WebSocket");
|
|
@@ -1066,15 +1094,14 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1066
1094
|
this._sendMessage({ type: "$getOthers", data: {} });
|
|
1067
1095
|
return;
|
|
1068
1096
|
}
|
|
1069
|
-
const
|
|
1097
|
+
const added = this._usersManager.addConnection({
|
|
1070
1098
|
connectionId,
|
|
1071
1099
|
presence: data.presence,
|
|
1072
1100
|
user: data.user
|
|
1073
1101
|
});
|
|
1074
|
-
const clientId = (_a = result == null ? void 0 : result.clientId) != null ? _a : null;
|
|
1075
1102
|
const other = this._usersManager.getOther(connectionId);
|
|
1076
1103
|
const others = this._usersManager.getOthers();
|
|
1077
|
-
|
|
1104
|
+
this._otherNotifier.subject(added.clientId).next(other);
|
|
1078
1105
|
this._stateNotifier.subjects.others.next(others);
|
|
1079
1106
|
}
|
|
1080
1107
|
_heartbeat() {
|
|
@@ -1230,13 +1257,15 @@ var PluvRoom = class extends AbstractRoom {
|
|
|
1230
1257
|
if (typeof document === "undefined") return;
|
|
1231
1258
|
if (document.visibilityState !== "visible") return;
|
|
1232
1259
|
switch (this._state.connection.state) {
|
|
1233
|
-
case "Open" /* Open */:
|
|
1260
|
+
case "Open" /* Open */: {
|
|
1234
1261
|
this._clearInterval(this._intervals.heartbeat);
|
|
1235
1262
|
setInterval(this._heartbeat.bind(this), HEARTBEAT_INTERVAL_MS);
|
|
1236
1263
|
return;
|
|
1237
|
-
|
|
1264
|
+
}
|
|
1265
|
+
case "Unavailable" /* Unavailable */: {
|
|
1238
1266
|
yield this._reconnect();
|
|
1239
1267
|
return;
|
|
1268
|
+
}
|
|
1240
1269
|
default:
|
|
1241
1270
|
}
|
|
1242
1271
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pluv/client",
|
|
3
|
-
"version": "0.41.
|
|
3
|
+
"version": "0.41.3",
|
|
4
4
|
"description": "@pluv/io frontend client",
|
|
5
5
|
"author": "leedavidcs",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,15 +18,15 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"wonka": "^6.3.5",
|
|
21
|
-
"@pluv/crdt": "^0.41.
|
|
22
|
-
"@pluv/types": "^0.41.
|
|
21
|
+
"@pluv/crdt": "^0.41.3",
|
|
22
|
+
"@pluv/types": "^0.41.3"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"eslint": "^9.25.0",
|
|
26
26
|
"tsup": "^8.4.0",
|
|
27
27
|
"typescript": "^5.8.3",
|
|
28
|
-
"@pluv/tsconfig": "^0.41.
|
|
29
|
-
"eslint-config-pluv": "^0.41.
|
|
28
|
+
"@pluv/tsconfig": "^0.41.3",
|
|
29
|
+
"eslint-config-pluv": "^0.41.3"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
32
|
"build": "tsup src/index.ts --format esm,cjs --dts",
|
package/src/PluvRoom.ts
CHANGED
|
@@ -77,9 +77,6 @@ export const DEFAULT_PLUV_CLIENT_ADDON = <
|
|
|
77
77
|
storage: new StorageStore(input.room.id),
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
-
interface CloseWsOptions {
|
|
81
|
-
connectionState?: ConnectionState;
|
|
82
|
-
}
|
|
83
80
|
interface WindowListeners {
|
|
84
81
|
onNavigatorOffline: () => void;
|
|
85
82
|
onNavigatorOnline: () => void;
|
|
@@ -857,10 +854,28 @@ export class PluvRoom<
|
|
|
857
854
|
return;
|
|
858
855
|
}
|
|
859
856
|
|
|
860
|
-
|
|
857
|
+
if (!!clientId) {
|
|
858
|
+
const other = this._usersManager.getOther(connectionId);
|
|
859
|
+
|
|
860
|
+
this._otherNotifier.subject(clientId).next(other);
|
|
861
|
+
} else {
|
|
862
|
+
/**
|
|
863
|
+
* !HACK
|
|
864
|
+
* @description User could not be found. Add the connection to keep others up-to-date
|
|
865
|
+
* @date April 19, 2025
|
|
866
|
+
*/
|
|
867
|
+
const added = this._usersManager.addConnection({
|
|
868
|
+
connectionId,
|
|
869
|
+
presence: data.presence as TPresence,
|
|
870
|
+
user: message.user as Id<InferIOAuthorizeUser<InferIOAuthorize<TIO>>>,
|
|
871
|
+
});
|
|
872
|
+
const other = this._usersManager.getOther(connectionId);
|
|
873
|
+
|
|
874
|
+
this._otherNotifier.subject(added.clientId).next(other);
|
|
875
|
+
}
|
|
876
|
+
|
|
861
877
|
const others = this._usersManager.getOthers();
|
|
862
878
|
|
|
863
|
-
if (!!clientId) this._otherNotifier.subject(clientId).next(other);
|
|
864
879
|
this._stateNotifier.subjects.others.next(others);
|
|
865
880
|
}
|
|
866
881
|
|
|
@@ -876,7 +891,7 @@ export class PluvRoom<
|
|
|
876
891
|
|
|
877
892
|
const result = this._usersManager.addConnection({
|
|
878
893
|
connectionId,
|
|
879
|
-
presence:
|
|
894
|
+
presence: presence as TPresence,
|
|
880
895
|
user,
|
|
881
896
|
});
|
|
882
897
|
|
|
@@ -1020,17 +1035,16 @@ export class PluvRoom<
|
|
|
1020
1035
|
return;
|
|
1021
1036
|
}
|
|
1022
1037
|
|
|
1023
|
-
const
|
|
1038
|
+
const added = this._usersManager.addConnection({
|
|
1024
1039
|
connectionId,
|
|
1025
1040
|
presence: data.presence as TPresence,
|
|
1026
1041
|
user: data.user,
|
|
1027
1042
|
});
|
|
1028
1043
|
|
|
1029
|
-
const clientId = result?.clientId ?? null;
|
|
1030
1044
|
const other = this._usersManager.getOther(connectionId);
|
|
1031
1045
|
const others = this._usersManager.getOthers();
|
|
1032
1046
|
|
|
1033
|
-
|
|
1047
|
+
this._otherNotifier.subject(added.clientId).next(other);
|
|
1034
1048
|
this._stateNotifier.subjects.others.next(others);
|
|
1035
1049
|
}
|
|
1036
1050
|
|
|
@@ -1222,15 +1236,17 @@ export class PluvRoom<
|
|
|
1222
1236
|
if (document.visibilityState !== "visible") return;
|
|
1223
1237
|
|
|
1224
1238
|
switch (this._state.connection.state) {
|
|
1225
|
-
case ConnectionState.Open:
|
|
1239
|
+
case ConnectionState.Open: {
|
|
1226
1240
|
this._clearInterval(this._intervals.heartbeat);
|
|
1227
1241
|
setInterval(this._heartbeat.bind(this), HEARTBEAT_INTERVAL_MS);
|
|
1228
1242
|
|
|
1229
1243
|
return;
|
|
1230
|
-
|
|
1244
|
+
}
|
|
1245
|
+
case ConnectionState.Unavailable: {
|
|
1231
1246
|
await this._reconnect();
|
|
1232
1247
|
|
|
1233
1248
|
return;
|
|
1249
|
+
}
|
|
1234
1250
|
default:
|
|
1235
1251
|
}
|
|
1236
1252
|
}
|
package/src/UsersManager.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { InputZodLike, IOLike, JsonObject, OptionalProps } from "@pluv/types";
|
|
2
2
|
import { MAX_PRESENCE_SIZE_BYTES } from "./constants";
|
|
3
3
|
import type { UserInfo } from "./types";
|
|
4
|
+
import { pickBy } from "./utils";
|
|
4
5
|
|
|
5
6
|
export type Presence = Record<string, unknown>;
|
|
6
7
|
|
|
@@ -28,7 +29,7 @@ export class UsersManager<TIO extends IOLike, TPresence extends JsonObject = {}>
|
|
|
28
29
|
fromClientId: new Map<[clientId: string][0], Set<[connectionId: string][0]>>(),
|
|
29
30
|
};
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
public readonly initialPresence: TPresence;
|
|
32
33
|
/**
|
|
33
34
|
* @description This presence can be updated while the user is not
|
|
34
35
|
* connected.
|
|
@@ -44,7 +45,8 @@ export class UsersManager<TIO extends IOLike, TPresence extends JsonObject = {}>
|
|
|
44
45
|
constructor(config: UsersManagerConfig<TPresence>) {
|
|
45
46
|
const { initialPresence, presence = null } = config;
|
|
46
47
|
|
|
47
|
-
this.
|
|
48
|
+
this.initialPresence = initialPresence as TPresence;
|
|
49
|
+
|
|
48
50
|
this._myPresence = initialPresence as TPresence;
|
|
49
51
|
this._presence = presence;
|
|
50
52
|
}
|
|
@@ -60,10 +62,14 @@ export class UsersManager<TIO extends IOLike, TPresence extends JsonObject = {}>
|
|
|
60
62
|
public addConnection(
|
|
61
63
|
userInfo: OptionalProps<UserInfo<TIO, TPresence>, "presence">,
|
|
62
64
|
): AddConnectionResult<TIO, TPresence> {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
/**
|
|
66
|
+
* @description Ensure that the presence is complete, so that it does not fail schema
|
|
67
|
+
* validation
|
|
68
|
+
* @date April 20, 2025
|
|
69
|
+
*/
|
|
70
|
+
const cleaned = pickBy(userInfo.presence ?? {}, (value) => typeof value !== "undefined");
|
|
71
|
+
const presence = { ...this.initialPresence, ...cleaned };
|
|
72
|
+
const data: UserInfo<TIO, TPresence> = { ...userInfo, presence };
|
|
67
73
|
|
|
68
74
|
const myClientId = !!this._myself ? this.getClientId(this._myself) : null;
|
|
69
75
|
const clientId = this.getClientId(data);
|
|
@@ -186,7 +192,9 @@ export class UsersManager<TIO extends IOLike, TPresence extends JsonObject = {}>
|
|
|
186
192
|
|
|
187
193
|
if (!other) return null;
|
|
188
194
|
|
|
189
|
-
const
|
|
195
|
+
const cleanedPatch = pickBy(patch ?? {}, (value) => typeof value !== "undefined");
|
|
196
|
+
const cleanedPresence = pickBy(other.presence ?? {}, (value) => typeof value !== "undefined");
|
|
197
|
+
const presence = { ...this.initialPresence, ...cleanedPresence, ...cleanedPatch } as TPresence;
|
|
190
198
|
const validated = this._presence ? this._presence.parse(presence) : presence;
|
|
191
199
|
|
|
192
200
|
this._others.set(clientId, { ...other, presence: validated });
|
|
@@ -205,7 +213,7 @@ export class UsersManager<TIO extends IOLike, TPresence extends JsonObject = {}>
|
|
|
205
213
|
|
|
206
214
|
public setMyself(userInfo: OptionalProps<UserInfo<TIO, TPresence>, "presence">): void {
|
|
207
215
|
const connectionId = userInfo.connectionId;
|
|
208
|
-
const presence = userInfo.presence ?? this.
|
|
216
|
+
const presence = userInfo.presence ?? this.initialPresence;
|
|
209
217
|
|
|
210
218
|
this._myself = { ...userInfo, presence };
|
|
211
219
|
this._myPresence = presence;
|
|
@@ -238,10 +246,14 @@ export class UsersManager<TIO extends IOLike, TPresence extends JsonObject = {}>
|
|
|
238
246
|
}
|
|
239
247
|
|
|
240
248
|
/**
|
|
241
|
-
* @description This method need not care about being connected.
|
|
249
|
+
* @description This method need not care about being connected. This is because a user should
|
|
250
|
+
* be able to view and update their own presence without being online
|
|
251
|
+
* @date April 20, 2025
|
|
242
252
|
*/
|
|
243
253
|
public updateMyPresence(patch: Partial<TPresence>): TPresence {
|
|
244
|
-
const
|
|
254
|
+
const cleanedPatch = pickBy(patch, (value) => typeof value !== "undefined");
|
|
255
|
+
const cleanedPresence = pickBy(this._myPresence, (value) => typeof value !== "undefined");
|
|
256
|
+
const updated = { ...this.initialPresence, ...cleanedPresence, ...cleanedPatch } as TPresence;
|
|
245
257
|
const bytes = new TextEncoder().encode(JSON.stringify(updated)).length;
|
|
246
258
|
|
|
247
259
|
if (bytes > MAX_PRESENCE_SIZE_BYTES) {
|
package/src/utils/index.ts
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const pickBy = <T extends Record<string, any>>(obj: T, by: (value: T[keyof T]) => boolean): Partial<T> => {
|
|
2
|
+
const result: Partial<T> = {};
|
|
3
|
+
const keys: (keyof T)[] = Object.keys(obj);
|
|
4
|
+
|
|
5
|
+
for (let i = 0; i < keys.length; ++i) {
|
|
6
|
+
const key = keys[i];
|
|
7
|
+
const value = obj[key];
|
|
8
|
+
|
|
9
|
+
if (by(value)) result[key] = value;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return result;
|
|
13
|
+
};
|