@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.
- package/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +27 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.js +83 -39
- package/dist/index.mjs +83 -39
- package/package.json +7 -7
- package/src/AbstractRoom.ts +2 -0
- package/src/CrdtManager.ts +4 -4
- package/src/MockedRoom.ts +13 -6
- package/src/PluvClient.ts +3 -3
- package/src/PluvRoom.ts +55 -14
- package/src/UsersManager.ts +41 -12
- package/src/types.ts +1 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @pluv/client@0.
|
|
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
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
[34mCLI[39m Target: es6
|
|
9
9
|
[34mESM[39m Build start
|
|
10
10
|
[34mCJS[39m Build start
|
|
11
|
-
[
|
|
12
|
-
[
|
|
13
|
-
[
|
|
14
|
-
[
|
|
11
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m34.72 KB[39m
|
|
12
|
+
[32mESM[39m ⚡️ Build success in 138ms
|
|
13
|
+
[32mCJS[39m [1mdist/index.js [22m[32m35.86 KB[39m
|
|
14
|
+
[32mCJS[39m ⚡️ Build success in 138ms
|
|
15
15
|
[34mDTS[39m Build start
|
|
16
|
-
[32mDTS[39m ⚡️ Build success in
|
|
17
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[32m11.
|
|
16
|
+
[32mDTS[39m ⚡️ Build success in 5111ms
|
|
17
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m11.71 KB[39m
|
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,
|
|
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,
|
|
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
|
|
83
|
-
if (
|
|
84
|
-
this.doc =
|
|
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
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
607
|
-
const myself = (
|
|
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
|
|
656
|
-
const myself = (
|
|
657
|
-
this._stateNotifier.subjects["my-presence"].next(
|
|
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.
|
|
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.
|
|
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 = "
|
|
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.
|
|
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(
|
|
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
|
|
58
|
-
if (
|
|
59
|
-
this.doc =
|
|
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
|
-
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
582
|
-
const myself = (
|
|
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
|
|
631
|
-
const myself = (
|
|
632
|
-
this._stateNotifier.subjects["my-presence"].next(
|
|
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.
|
|
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.
|
|
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 = "
|
|
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.
|
|
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(
|
|
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.
|
|
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.
|
|
21
|
-
"@pluv/crdt-yjs": "^0.3.
|
|
22
|
-
"@pluv/types": "^0.
|
|
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.
|
|
31
|
-
"@pluv/tsconfig": "^0.1.
|
|
32
|
-
"eslint-config-pluv": "^0.1.
|
|
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",
|
package/src/AbstractRoom.ts
CHANGED
package/src/CrdtManager.ts
CHANGED
|
@@ -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
|
|
18
|
+
const _doc = encodedState
|
|
19
19
|
? doc<TStorage>().applyUpdate(encodedState)
|
|
20
20
|
: null;
|
|
21
21
|
|
|
22
|
-
if (
|
|
23
|
-
this.doc =
|
|
22
|
+
if (_doc && !!Object.keys(_doc.toJSON()).length) {
|
|
23
|
+
this.doc = _doc;
|
|
24
24
|
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
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.
|
|
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(
|
|
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,
|
|
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,
|
|
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,
|
|
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.
|
|
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
|
|
627
|
+
const presence = this._usersManager.myPresence;
|
|
609
628
|
const myself = this._usersManager.myself ?? null;
|
|
610
629
|
|
|
611
|
-
this._stateNotifier.subjects["my-presence"].next(
|
|
630
|
+
this._stateNotifier.subjects["my-presence"].next(presence);
|
|
612
631
|
this._stateNotifier.subjects["myself"].next(myself);
|
|
613
632
|
|
|
614
|
-
|
|
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.
|
|
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.
|
|
974
|
+
oldState.connection.state = ConnectionState.Closed;
|
|
934
975
|
oldState.webSocket = null;
|
|
935
976
|
|
|
936
977
|
return oldState;
|
package/src/UsersManager.ts
CHANGED
|
@@ -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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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 =
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
}
|