@pluv/client 0.6.5 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
 
2
- > @pluv/client@0.6.5 build /home/runner/work/pluv/pluv/packages/client
2
+ > @pluv/client@0.8.0 build /home/runner/work/pluv/pluv/packages/client
3
3
  > tsup src/index.ts --format esm,cjs --dts
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -8,10 +8,10 @@
8
8
  CLI Target: es6
9
9
  ESM Build start
10
10
  CJS Build start
11
- ESM dist/index.mjs 34.41 KB
11
+ ESM dist/index.mjs 35.24 KB
12
12
  ESM ⚡️ Build success in 95ms
13
- CJS dist/index.js 35.55 KB
13
+ CJS dist/index.js 36.38 KB
14
14
  CJS ⚡️ Build success in 96ms
15
15
  DTS Build start
16
- DTS ⚡️ Build success in 3764ms
17
- DTS dist/index.d.ts 11.68 KB
16
+ DTS ⚡️ Build success in 4360ms
17
+ DTS dist/index.d.ts 11.75 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # @pluv/client
2
2
 
3
+ ## 0.8.0
4
+
5
+ ### Minor Changes
6
+
7
+ - fde89cf: added defaults to the client to align it with createPluvClient by default
8
+
9
+ ### Patch Changes
10
+
11
+ - 5bbfb98: fix not properly handling unauthorized checks
12
+ - b85a232: bumped dependencies
13
+ - Updated dependencies [b85a232]
14
+ - @pluv/crdt-yjs@0.3.8
15
+ - @pluv/types@0.2.0
16
+
17
+ ## 0.7.0
18
+
19
+ ### Minor Changes
20
+
21
+ - ae679a8: updated offline presence to be set when reconnecting to a room
22
+
23
+ ### Patch Changes
24
+
25
+ - 0dd847e: updated storage to be synced when reconnected to the room
26
+ - bb2886b: fixed not reconnecting during heartbeat
27
+ - 3518a83: bumped dependencies
28
+ - Updated dependencies [0dd847e]
29
+ - Updated dependencies [bb2886b]
30
+ - Updated dependencies [ae679a8]
31
+ - Updated dependencies [3518a83]
32
+ - @pluv/crdt-yjs@0.3.7
33
+ - @pluv/types@0.2.0
34
+
3
35
  ## 0.6.5
4
36
 
5
37
  ### 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
  }
@@ -80,18 +81,18 @@ declare global {
80
81
  };
81
82
  };
82
83
  }
83
- type AuthEndpoint = string | ((room: string) => FetchOptions);
84
+ type AuthEndpoint = string | ((room: string) => string | FetchOptions) | true;
84
85
  type WsEndpoint = string | ((room: string) => string);
85
- type FetchOptions = string | {
86
+ type FetchOptions = {
86
87
  url: string;
87
88
  options?: RequestInit;
88
89
  };
89
90
  type RoomEndpoints<TIO extends IOLike> = {
90
- wsEndpoint: WsEndpoint;
91
+ wsEndpoint?: WsEndpoint;
91
92
  } & (InferIOAuthorizeRequired<InferIOAuthorize<TIO>> extends true ? {
92
93
  authEndpoint: AuthEndpoint;
93
94
  } : {
94
- authEndpoint?: AuthEndpoint;
95
+ authEndpoint?: undefined;
95
96
  });
96
97
  type PluvRoomDebug<TIO extends IOLike> = Id<{
97
98
  output: readonly (keyof InferIOOutput<TIO>)[];
@@ -143,6 +144,7 @@ declare class PluvRoom<TIO extends IOLike, TPresence extends JsonObject = {}, TS
143
144
  private _detachWindowListeners;
144
145
  private _detachWsListeners;
145
146
  private _getAuthorization;
147
+ private _getAuthFetchOptions;
146
148
  private _getWsEndpoint;
147
149
  private _handleExit;
148
150
  private _handlePresenceUpdatedMessage;
@@ -197,7 +199,7 @@ declare class PluvClient<TIO extends IOLike = IOLike> {
197
199
  private _logDebug;
198
200
  }
199
201
 
200
- declare const createClient: <TIO extends IOLike<_pluv_types.IOAuthorize<any, any>, {}, {}> = IOLike<_pluv_types.IOAuthorize<any, any>, {}, {}>>(options: PluvClientOptions<TIO>) => PluvClient<TIO>;
202
+ declare const createClient: <TIO extends IOLike<_pluv_types.IOAuthorize<any, any>, {}, {}, {}, {}> = IOLike<_pluv_types.IOAuthorize<any, any>, {}, {}, {}, {}>>(options: PluvClientOptions<TIO>) => PluvClient<TIO>;
201
203
 
202
204
  type MockedRoomEvents<TIO extends IOLike> = Partial<{
203
205
  [P in keyof InferIOInput<TIO>]: (data: Id<InferIOInput<TIO>[P]>) => Partial<InferIOOutput<TIO>>;
package/dist/index.js CHANGED
@@ -79,12 +79,12 @@ var import_crdt_yjs = require("@pluv/crdt-yjs");
79
79
  var CrdtManager = class {
80
80
  constructor(options = {}) {
81
81
  const { encodedState, initialStorage } = options;
82
- const doc1 = encodedState ? (0, import_crdt_yjs.doc)().applyUpdate(encodedState) : null;
83
- if (doc1 && !!Object.keys(doc1.toJSON()).length) {
84
- this.doc = doc1;
82
+ const _doc = encodedState ? (0, import_crdt_yjs.doc)().applyUpdate(encodedState) : null;
83
+ if (_doc && !!Object.keys(_doc.toJSON()).length) {
84
+ this.doc = _doc;
85
85
  return;
86
86
  }
87
- doc1 == null ? void 0 : doc1.destroy();
87
+ _doc == null ? void 0 : _doc.destroy();
88
88
  this.doc = (0, import_crdt_yjs.doc)(initialStorage == null ? void 0 : initialStorage());
89
89
  }
90
90
  destroy() {
@@ -257,7 +257,7 @@ var UsersManager = class {
257
257
  return;
258
258
  this._others.set(connectionId, __spreadProps(__spreadValues({}, other), { presence }));
259
259
  }
260
- setUser(connectionId, user) {
260
+ setUser(connectionId, user, presence) {
261
261
  var _a;
262
262
  if (((_a = this._myself) == null ? void 0 : _a.connectionId) === connectionId)
263
263
  return;
@@ -266,7 +266,7 @@ var UsersManager = class {
266
266
  return;
267
267
  this._others.set(connectionId, {
268
268
  connectionId,
269
- presence: this._initialPresence,
269
+ presence: presence != null ? presence : this._initialPresence,
270
270
  user
271
271
  });
272
272
  }
@@ -311,6 +311,7 @@ var PluvRoom = class extends AbstractRoom {
311
311
  user: null
312
312
  },
313
313
  connection: {
314
+ count: 0,
314
315
  id: null,
315
316
  state: "Untouched" /* Untouched */
316
317
  },
@@ -368,10 +369,7 @@ var PluvRoom = class extends AbstractRoom {
368
369
  );
369
370
  };
370
371
  this._debug = debug;
371
- this._endpoints = {
372
- authEndpoint,
373
- wsEndpoint
374
- };
372
+ this._endpoints = { authEndpoint, wsEndpoint };
375
373
  this._listeners = {
376
374
  onAuthorizationFail: (error) => {
377
375
  this._clearTimeout(this._timeouts.reconnect);
@@ -584,24 +582,50 @@ var PluvRoom = class extends AbstractRoom {
584
582
  }
585
583
  _getAuthorization(room) {
586
584
  return __async(this, null, function* () {
587
- if (!this._endpoints.authEndpoint)
585
+ const fetchOptions = this._getAuthFetchOptions(room);
586
+ if (!fetchOptions)
588
587
  return null;
589
- if (typeof this._endpoints.authEndpoint === "string") {
590
- const token2 = yield fetch(this._endpoints.authEndpoint).then((res) => res.text()).then((text2) => text2.trim());
591
- return token2;
588
+ const { url, options } = fetchOptions;
589
+ const res = yield fetch(url, options);
590
+ if (!res.ok || res.status !== 200) {
591
+ throw new Error("Room is unauthorized");
592
+ }
593
+ try {
594
+ return yield res.text().then((text2) => text2.trim());
595
+ } catch (err) {
596
+ throw new Error(
597
+ err instanceof Error ? err.message : "Room is unauthorized"
598
+ );
592
599
  }
593
- const config = this._endpoints.authEndpoint(room);
594
- const endpoint = typeof config === "string" ? config : config.url;
595
- const options = typeof config === "string" ? void 0 : config.options;
596
- const token = yield fetch(endpoint, options).then((res) => res.text()).then((text2) => text2.trim());
597
- return token;
598
600
  });
599
601
  }
602
+ _getAuthFetchOptions(room) {
603
+ if (typeof this._endpoints.authEndpoint === "undefined")
604
+ return null;
605
+ if (this._endpoints.authEndpoint === true) {
606
+ return {
607
+ url: `/api/pluv/authorize?room=${room}`,
608
+ options: {}
609
+ };
610
+ }
611
+ if (typeof this._endpoints.authEndpoint === "string") {
612
+ return {
613
+ url: this._endpoints.authEndpoint,
614
+ options: {}
615
+ };
616
+ }
617
+ const result = this._endpoints.authEndpoint(room);
618
+ return typeof result === "string" ? { url: result, options: {} } : result;
619
+ }
600
620
  _getWsEndpoint(room) {
601
- if (typeof this._endpoints.wsEndpoint === "string") {
602
- return this._endpoints.wsEndpoint;
621
+ switch (typeof this._endpoints.wsEndpoint) {
622
+ case "undefined":
623
+ return `/api/pluv/room/${room}`;
624
+ case "string":
625
+ return this._endpoints.wsEndpoint;
626
+ default:
627
+ return this._endpoints.wsEndpoint(room);
603
628
  }
604
- return this._endpoints.wsEndpoint(room);
605
629
  }
606
630
  _handleExit(message) {
607
631
  const { connectionId } = message;
@@ -661,13 +685,14 @@ var PluvRoom = class extends AbstractRoom {
661
685
  this._stateNotifier.subjects["others"].next(others);
662
686
  }
663
687
  _handleRegisteredMessage(message) {
664
- var _a;
688
+ var _a, _b, _c;
665
689
  const { connectionId, user } = message;
666
690
  if (!connectionId)
667
691
  return;
668
692
  if (!this._state.webSocket)
669
693
  throw new Error("Could not find WebSocket");
670
694
  this._updateState((oldState) => {
695
+ oldState.connection.count += 1;
671
696
  oldState.connection.id = connectionId;
672
697
  oldState.authorization.user = user;
673
698
  return oldState;
@@ -676,11 +701,15 @@ var PluvRoom = class extends AbstractRoom {
676
701
  connectionId,
677
702
  user
678
703
  );
679
- const myPresence = this._usersManager.myPresence;
704
+ const presence = this._usersManager.myPresence;
680
705
  const myself = (_a = this._usersManager.myself) != null ? _a : null;
681
- this._stateNotifier.subjects["my-presence"].next(myPresence);
706
+ this._stateNotifier.subjects["my-presence"].next(presence);
682
707
  this._stateNotifier.subjects["myself"].next(myself);
683
- this._sendMessage({ type: "$GET_OTHERS", data: {} });
708
+ const update = this._state.connection.count > 1 ? (_c = (_b = this._crdtManager) == null ? void 0 : _b.doc.encodeStateAsUpdate()) != null ? _c : null : null;
709
+ this._sendMessage({
710
+ type: "$INITIALIZE_SESSION",
711
+ data: { presence, update }
712
+ });
684
713
  }
685
714
  _handleStorageReceivedMessage(message) {
686
715
  var _a;
@@ -737,20 +766,21 @@ var PluvRoom = class extends AbstractRoom {
737
766
  if (!this._usersManager.myself)
738
767
  return;
739
768
  const data = message.data;
740
- this._usersManager.setUser(connectionId, data.user);
769
+ const myself = this._usersManager.myself;
770
+ if (myself.connectionId === connectionId) {
771
+ this._sendMessage({ type: "$GET_OTHERS", data: {} });
772
+ }
773
+ this._usersManager.setUser(
774
+ connectionId,
775
+ data.user,
776
+ data.presence
777
+ );
741
778
  const other = this._usersManager.getOther(connectionId);
742
779
  const others = this._usersManager.getOthers();
743
780
  this._otherNotifier.subject(connectionId).next(other);
744
781
  this._stateNotifier.subjects["others"].next(others);
745
782
  }
746
783
  _heartbeat() {
747
- if (!this._state.webSocket)
748
- return;
749
- if (!this._state.connection.id)
750
- return;
751
- if (this._state.webSocket.readyState !== this._state.webSocket.OPEN) {
752
- return;
753
- }
754
784
  this._clearTimeout(this._timeouts.pong);
755
785
  this._timeouts.pong = setTimeout(
756
786
  this._reconnect.bind(this),
@@ -945,7 +975,7 @@ var PluvRoom = class extends AbstractRoom {
945
975
  this._closeWs();
946
976
  this._updateState((oldState) => {
947
977
  oldState.authorization.token = null;
948
- oldState.connection.state = "Unavailable" /* Unavailable */;
978
+ oldState.connection.state = "Closed" /* Closed */;
949
979
  oldState.webSocket = null;
950
980
  return oldState;
951
981
  });
@@ -1062,6 +1092,7 @@ var MockedRoom = class extends AbstractRoom {
1062
1092
  user: null
1063
1093
  },
1064
1094
  connection: {
1095
+ count: 1,
1065
1096
  id: null,
1066
1097
  state: "Untouched" /* Untouched */
1067
1098
  },
package/dist/index.mjs CHANGED
@@ -54,12 +54,12 @@ import { doc } from "@pluv/crdt-yjs";
54
54
  var CrdtManager = class {
55
55
  constructor(options = {}) {
56
56
  const { encodedState, initialStorage } = options;
57
- const doc1 = encodedState ? doc().applyUpdate(encodedState) : null;
58
- if (doc1 && !!Object.keys(doc1.toJSON()).length) {
59
- this.doc = doc1;
57
+ const _doc = encodedState ? doc().applyUpdate(encodedState) : null;
58
+ if (_doc && !!Object.keys(_doc.toJSON()).length) {
59
+ this.doc = _doc;
60
60
  return;
61
61
  }
62
- doc1 == null ? void 0 : doc1.destroy();
62
+ _doc == null ? void 0 : _doc.destroy();
63
63
  this.doc = doc(initialStorage == null ? void 0 : initialStorage());
64
64
  }
65
65
  destroy() {
@@ -232,7 +232,7 @@ var UsersManager = class {
232
232
  return;
233
233
  this._others.set(connectionId, __spreadProps(__spreadValues({}, other), { presence }));
234
234
  }
235
- setUser(connectionId, user) {
235
+ setUser(connectionId, user, presence) {
236
236
  var _a;
237
237
  if (((_a = this._myself) == null ? void 0 : _a.connectionId) === connectionId)
238
238
  return;
@@ -241,7 +241,7 @@ var UsersManager = class {
241
241
  return;
242
242
  this._others.set(connectionId, {
243
243
  connectionId,
244
- presence: this._initialPresence,
244
+ presence: presence != null ? presence : this._initialPresence,
245
245
  user
246
246
  });
247
247
  }
@@ -286,6 +286,7 @@ var PluvRoom = class extends AbstractRoom {
286
286
  user: null
287
287
  },
288
288
  connection: {
289
+ count: 0,
289
290
  id: null,
290
291
  state: "Untouched" /* Untouched */
291
292
  },
@@ -343,10 +344,7 @@ var PluvRoom = class extends AbstractRoom {
343
344
  );
344
345
  };
345
346
  this._debug = debug;
346
- this._endpoints = {
347
- authEndpoint,
348
- wsEndpoint
349
- };
347
+ this._endpoints = { authEndpoint, wsEndpoint };
350
348
  this._listeners = {
351
349
  onAuthorizationFail: (error) => {
352
350
  this._clearTimeout(this._timeouts.reconnect);
@@ -559,24 +557,50 @@ var PluvRoom = class extends AbstractRoom {
559
557
  }
560
558
  _getAuthorization(room) {
561
559
  return __async(this, null, function* () {
562
- if (!this._endpoints.authEndpoint)
560
+ const fetchOptions = this._getAuthFetchOptions(room);
561
+ if (!fetchOptions)
563
562
  return null;
564
- if (typeof this._endpoints.authEndpoint === "string") {
565
- const token2 = yield fetch(this._endpoints.authEndpoint).then((res) => res.text()).then((text2) => text2.trim());
566
- return token2;
563
+ const { url, options } = fetchOptions;
564
+ const res = yield fetch(url, options);
565
+ if (!res.ok || res.status !== 200) {
566
+ throw new Error("Room is unauthorized");
567
+ }
568
+ try {
569
+ return yield res.text().then((text2) => text2.trim());
570
+ } catch (err) {
571
+ throw new Error(
572
+ err instanceof Error ? err.message : "Room is unauthorized"
573
+ );
567
574
  }
568
- const config = this._endpoints.authEndpoint(room);
569
- const endpoint = typeof config === "string" ? config : config.url;
570
- const options = typeof config === "string" ? void 0 : config.options;
571
- const token = yield fetch(endpoint, options).then((res) => res.text()).then((text2) => text2.trim());
572
- return token;
573
575
  });
574
576
  }
577
+ _getAuthFetchOptions(room) {
578
+ if (typeof this._endpoints.authEndpoint === "undefined")
579
+ return null;
580
+ if (this._endpoints.authEndpoint === true) {
581
+ return {
582
+ url: `/api/pluv/authorize?room=${room}`,
583
+ options: {}
584
+ };
585
+ }
586
+ if (typeof this._endpoints.authEndpoint === "string") {
587
+ return {
588
+ url: this._endpoints.authEndpoint,
589
+ options: {}
590
+ };
591
+ }
592
+ const result = this._endpoints.authEndpoint(room);
593
+ return typeof result === "string" ? { url: result, options: {} } : result;
594
+ }
575
595
  _getWsEndpoint(room) {
576
- if (typeof this._endpoints.wsEndpoint === "string") {
577
- return this._endpoints.wsEndpoint;
596
+ switch (typeof this._endpoints.wsEndpoint) {
597
+ case "undefined":
598
+ return `/api/pluv/room/${room}`;
599
+ case "string":
600
+ return this._endpoints.wsEndpoint;
601
+ default:
602
+ return this._endpoints.wsEndpoint(room);
578
603
  }
579
- return this._endpoints.wsEndpoint(room);
580
604
  }
581
605
  _handleExit(message) {
582
606
  const { connectionId } = message;
@@ -636,13 +660,14 @@ var PluvRoom = class extends AbstractRoom {
636
660
  this._stateNotifier.subjects["others"].next(others);
637
661
  }
638
662
  _handleRegisteredMessage(message) {
639
- var _a;
663
+ var _a, _b, _c;
640
664
  const { connectionId, user } = message;
641
665
  if (!connectionId)
642
666
  return;
643
667
  if (!this._state.webSocket)
644
668
  throw new Error("Could not find WebSocket");
645
669
  this._updateState((oldState) => {
670
+ oldState.connection.count += 1;
646
671
  oldState.connection.id = connectionId;
647
672
  oldState.authorization.user = user;
648
673
  return oldState;
@@ -651,11 +676,15 @@ var PluvRoom = class extends AbstractRoom {
651
676
  connectionId,
652
677
  user
653
678
  );
654
- const myPresence = this._usersManager.myPresence;
679
+ const presence = this._usersManager.myPresence;
655
680
  const myself = (_a = this._usersManager.myself) != null ? _a : null;
656
- this._stateNotifier.subjects["my-presence"].next(myPresence);
681
+ this._stateNotifier.subjects["my-presence"].next(presence);
657
682
  this._stateNotifier.subjects["myself"].next(myself);
658
- this._sendMessage({ type: "$GET_OTHERS", data: {} });
683
+ const update = this._state.connection.count > 1 ? (_c = (_b = this._crdtManager) == null ? void 0 : _b.doc.encodeStateAsUpdate()) != null ? _c : null : null;
684
+ this._sendMessage({
685
+ type: "$INITIALIZE_SESSION",
686
+ data: { presence, update }
687
+ });
659
688
  }
660
689
  _handleStorageReceivedMessage(message) {
661
690
  var _a;
@@ -712,20 +741,21 @@ var PluvRoom = class extends AbstractRoom {
712
741
  if (!this._usersManager.myself)
713
742
  return;
714
743
  const data = message.data;
715
- this._usersManager.setUser(connectionId, data.user);
744
+ const myself = this._usersManager.myself;
745
+ if (myself.connectionId === connectionId) {
746
+ this._sendMessage({ type: "$GET_OTHERS", data: {} });
747
+ }
748
+ this._usersManager.setUser(
749
+ connectionId,
750
+ data.user,
751
+ data.presence
752
+ );
716
753
  const other = this._usersManager.getOther(connectionId);
717
754
  const others = this._usersManager.getOthers();
718
755
  this._otherNotifier.subject(connectionId).next(other);
719
756
  this._stateNotifier.subjects["others"].next(others);
720
757
  }
721
758
  _heartbeat() {
722
- if (!this._state.webSocket)
723
- return;
724
- if (!this._state.connection.id)
725
- return;
726
- if (this._state.webSocket.readyState !== this._state.webSocket.OPEN) {
727
- return;
728
- }
729
759
  this._clearTimeout(this._timeouts.pong);
730
760
  this._timeouts.pong = setTimeout(
731
761
  this._reconnect.bind(this),
@@ -920,7 +950,7 @@ var PluvRoom = class extends AbstractRoom {
920
950
  this._closeWs();
921
951
  this._updateState((oldState) => {
922
952
  oldState.authorization.token = null;
923
- oldState.connection.state = "Unavailable" /* Unavailable */;
953
+ oldState.connection.state = "Closed" /* Closed */;
924
954
  oldState.webSocket = null;
925
955
  return oldState;
926
956
  });
@@ -1037,6 +1067,7 @@ var MockedRoom = class extends AbstractRoom {
1037
1067
  user: null
1038
1068
  },
1039
1069
  connection: {
1070
+ count: 1,
1040
1071
  id: null,
1041
1072
  state: "Untouched" /* Untouched */
1042
1073
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pluv/client",
3
- "version": "0.6.5",
3
+ "version": "0.8.0",
4
4
  "description": "@pluv/io frontend client",
5
5
  "author": "leedavidcs",
6
6
  "license": "MIT",
@@ -17,9 +17,9 @@
17
17
  "access": "public"
18
18
  },
19
19
  "dependencies": {
20
- "wonka": "^6.3.1",
21
- "@pluv/crdt-yjs": "^0.3.6",
22
- "@pluv/types": "^0.1.6"
20
+ "wonka": "^6.3.2",
21
+ "@pluv/crdt-yjs": "^0.3.8",
22
+ "@pluv/types": "^0.2.0"
23
23
  },
24
24
  "peerDependencies": {
25
25
  "yjs": "^13.0.0"
@@ -27,9 +27,9 @@
27
27
  "devDependencies": {
28
28
  "tsup": "^6.7.0",
29
29
  "typescript": "^5.0.4",
30
- "yjs": "^13.5.52",
30
+ "yjs": "^13.6.1",
31
31
  "@pluv/tsconfig": "^0.1.6",
32
- "eslint-config-pluv": "^0.1.10"
32
+ "eslint-config-pluv": "^0.1.12"
33
33
  },
34
34
  "scripts": {
35
35
  "build": "tsup src/index.ts --format esm,cjs --dts",
@@ -15,17 +15,17 @@ export class CrdtManager<TStorage extends Record<string, AbstractType<any>>> {
15
15
  constructor(options: CrdtManagerOptions<TStorage> = {}) {
16
16
  const { encodedState, initialStorage } = options;
17
17
 
18
- const doc1 = encodedState
18
+ const _doc = encodedState
19
19
  ? doc<TStorage>().applyUpdate(encodedState)
20
20
  : null;
21
21
 
22
- if (doc1 && !!Object.keys(doc1.toJSON()).length) {
23
- this.doc = doc1;
22
+ if (_doc && !!Object.keys(_doc.toJSON()).length) {
23
+ this.doc = _doc;
24
24
 
25
25
  return;
26
26
  }
27
27
 
28
- doc1?.destroy();
28
+ _doc?.destroy();
29
29
 
30
30
  this.doc = doc<TStorage>(initialStorage?.());
31
31
  }
package/src/MockedRoom.ts CHANGED
@@ -61,6 +61,7 @@ export class MockedRoom<
61
61
  user: null,
62
62
  },
63
63
  connection: {
64
+ count: 1,
64
65
  id: null,
65
66
  state: ConnectionState.Untouched,
66
67
  },
package/src/PluvClient.ts CHANGED
@@ -5,6 +5,7 @@ import type {
5
5
  RoomEndpoints,
6
6
  PluvRoomOptions,
7
7
  WsEndpoint,
8
+ RoomConfig,
8
9
  } from "./PluvRoom";
9
10
  import { PluvRoom } from "./PluvRoom";
10
11
 
@@ -13,9 +14,9 @@ export type PluvClientOptions<TIO extends IOLike> = RoomEndpoints<TIO> & {
13
14
  };
14
15
 
15
16
  export class PluvClient<TIO extends IOLike = IOLike> {
16
- private _authEndpoint: AuthEndpoint;
17
+ private _authEndpoint: AuthEndpoint | undefined;
17
18
  private _debug: boolean;
18
- private _wsEndpoint: WsEndpoint;
19
+ private _wsEndpoint: WsEndpoint | undefined;
19
20
 
20
21
  private _rooms = new Map<string, PluvRoom<TIO, any, any>>();
21
22
 
@@ -45,7 +46,7 @@ export class PluvClient<TIO extends IOLike = IOLike> {
45
46
  initialStorage: options.initialStorage,
46
47
  onAuthorizationFail: options.onAuthorizationFail,
47
48
  wsEndpoint: this._wsEndpoint,
48
- });
49
+ } as RoomConfig<TIO, TPresence, TStorage>);
49
50
 
50
51
  this._rooms.set(room, newRoom);
51
52
 
package/src/PluvRoom.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { doc, InferYjsSharedTypeJson } from "@pluv/crdt-yjs";
1
+ import { InferYjsSharedTypeJson } from "@pluv/crdt-yjs";
2
2
  import type {
3
3
  BaseIOEventRecord,
4
4
  EventMessage,
@@ -70,21 +70,19 @@ interface TimeoutIds {
70
70
  reconnect: number | null;
71
71
  }
72
72
 
73
- export type AuthEndpoint = string | ((room: string) => FetchOptions);
73
+ export type AuthEndpoint =
74
+ | string
75
+ | ((room: string) => string | FetchOptions)
76
+ | true;
74
77
  export type WsEndpoint = string | ((room: string) => string);
75
78
 
76
- export interface RoomConfigOptions {
77
- authEndpoint?: AuthEndpoint;
78
- wsEndpoint: WsEndpoint;
79
- }
80
-
81
- type FetchOptions = string | { url: string; options?: RequestInit };
79
+ type FetchOptions = { url: string; options?: RequestInit };
82
80
 
83
81
  export type RoomEndpoints<TIO extends IOLike> = {
84
- wsEndpoint: WsEndpoint;
82
+ wsEndpoint?: WsEndpoint;
85
83
  } & (InferIOAuthorizeRequired<InferIOAuthorize<TIO>> extends true
86
84
  ? { authEndpoint: AuthEndpoint }
87
- : { authEndpoint?: AuthEndpoint });
85
+ : { authEndpoint?: undefined });
88
86
 
89
87
  interface InternalListeners {
90
88
  onAuthorizationFail: (error: Error) => void;
@@ -134,6 +132,7 @@ export class PluvRoom<
134
132
  user: null,
135
133
  },
136
134
  connection: {
135
+ count: 0,
137
136
  id: null,
138
137
  state: ConnectionState.Untouched,
139
138
  },
@@ -166,12 +165,7 @@ export class PluvRoom<
166
165
 
167
166
  this._debug = debug;
168
167
 
169
- this._endpoints = {
170
- authEndpoint: authEndpoint as
171
- | string
172
- | ((room: string) => FetchOptions),
173
- wsEndpoint,
174
- };
168
+ this._endpoints = { authEndpoint, wsEndpoint } as RoomEndpoints<TIO>;
175
169
 
176
170
  this._listeners = {
177
171
  onAuthorizationFail: (error) => {
@@ -493,33 +487,60 @@ export class PluvRoom<
493
487
  }
494
488
 
495
489
  private async _getAuthorization(room: string): Promise<string | null> {
496
- if (!this._endpoints.authEndpoint) return null;
490
+ const fetchOptions = this._getAuthFetchOptions(room);
497
491
 
498
- if (typeof this._endpoints.authEndpoint === "string") {
499
- const token = await fetch(this._endpoints.authEndpoint)
500
- .then((res) => res.text())
501
- .then((text) => text.trim());
492
+ if (!fetchOptions) return null;
502
493
 
503
- return token;
494
+ const { url, options } = fetchOptions;
495
+
496
+ const res = await fetch(url, options);
497
+
498
+ if (!res.ok || res.status !== 200) {
499
+ throw new Error("Room is unauthorized");
504
500
  }
505
501
 
506
- const config = this._endpoints.authEndpoint(room);
507
- const endpoint = typeof config === "string" ? config : config.url;
508
- const options = typeof config === "string" ? undefined : config.options;
502
+ try {
503
+ return await res.text().then((text) => text.trim());
504
+ } catch (err) {
505
+ throw new Error(
506
+ err instanceof Error ? err.message : "Room is unauthorized"
507
+ );
508
+ }
509
+ }
509
510
 
510
- const token = await fetch(endpoint, options)
511
- .then((res) => res.text())
512
- .then((text) => text.trim());
511
+ private _getAuthFetchOptions(room: string): FetchOptions | null {
512
+ if (typeof this._endpoints.authEndpoint === "undefined") return null;
513
513
 
514
- return token;
514
+ if (this._endpoints.authEndpoint === true) {
515
+ return {
516
+ url: `/api/pluv/authorize?room=${room}`,
517
+ options: {},
518
+ };
519
+ }
520
+
521
+ if (typeof this._endpoints.authEndpoint === "string") {
522
+ return {
523
+ url: this._endpoints.authEndpoint,
524
+ options: {},
525
+ };
526
+ }
527
+
528
+ const result = this._endpoints.authEndpoint(room);
529
+
530
+ return typeof result === "string"
531
+ ? { url: result, options: {} }
532
+ : result;
515
533
  }
516
534
 
517
535
  private _getWsEndpoint(room: string): string {
518
- if (typeof this._endpoints.wsEndpoint === "string") {
519
- return this._endpoints.wsEndpoint;
536
+ switch (typeof this._endpoints.wsEndpoint) {
537
+ case "undefined":
538
+ return `/api/pluv/room/${room}`;
539
+ case "string":
540
+ return this._endpoints.wsEndpoint;
541
+ default:
542
+ return this._endpoints.wsEndpoint(room);
520
543
  }
521
-
522
- return this._endpoints.wsEndpoint(room);
523
544
  }
524
545
 
525
546
  private _handleExit(message: IOEventMessage<TIO>): void {
@@ -611,6 +632,7 @@ export class PluvRoom<
611
632
  if (!this._state.webSocket) throw new Error("Could not find WebSocket");
612
633
 
613
634
  this._updateState((oldState) => {
635
+ oldState.connection.count += 1;
614
636
  oldState.connection.id = connectionId;
615
637
  oldState.authorization.user = user;
616
638
 
@@ -622,13 +644,21 @@ export class PluvRoom<
622
644
  user as Id<InferIOAuthorizeUser<InferIOAuthorize<TIO>>>
623
645
  );
624
646
 
625
- const myPresence = this._usersManager.myPresence;
647
+ const presence = this._usersManager.myPresence;
626
648
  const myself = this._usersManager.myself ?? null;
627
649
 
628
- this._stateNotifier.subjects["my-presence"].next(myPresence);
650
+ this._stateNotifier.subjects["my-presence"].next(presence);
629
651
  this._stateNotifier.subjects["myself"].next(myself);
630
652
 
631
- this._sendMessage({ type: "$GET_OTHERS", data: {} });
653
+ const update =
654
+ this._state.connection.count > 1
655
+ ? this._crdtManager?.doc.encodeStateAsUpdate() ?? null
656
+ : null;
657
+
658
+ this._sendMessage({
659
+ type: "$INITIALIZE_SESSION",
660
+ data: { presence, update },
661
+ });
632
662
  }
633
663
 
634
664
  private _handleStorageReceivedMessage(message: IOEventMessage<TIO>): void {
@@ -708,7 +738,17 @@ export class PluvRoom<
708
738
  InferIOAuthorize<TIO>
709
739
  >["$USER_JOINED"];
710
740
 
711
- this._usersManager.setUser(connectionId, data.user);
741
+ const myself = this._usersManager.myself;
742
+
743
+ if (myself.connectionId === connectionId) {
744
+ this._sendMessage({ type: "$GET_OTHERS", data: {} });
745
+ }
746
+
747
+ this._usersManager.setUser(
748
+ connectionId,
749
+ data.user,
750
+ data.presence as TPresence
751
+ );
712
752
 
713
753
  const other = this._usersManager.getOther(connectionId);
714
754
  const others = this._usersManager.getOthers();
@@ -718,12 +758,6 @@ export class PluvRoom<
718
758
  }
719
759
 
720
760
  private _heartbeat(): void {
721
- if (!this._state.webSocket) return;
722
- if (!this._state.connection.id) return;
723
- if (this._state.webSocket.readyState !== this._state.webSocket.OPEN) {
724
- return;
725
- }
726
-
727
761
  this._clearTimeout(this._timeouts.pong);
728
762
  this._timeouts.pong = setTimeout(
729
763
  this._reconnect.bind(this),
@@ -957,7 +991,7 @@ export class PluvRoom<
957
991
 
958
992
  this._updateState((oldState) => {
959
993
  oldState.authorization.token = null;
960
- oldState.connection.state = ConnectionState.Unavailable;
994
+ oldState.connection.state = ConnectionState.Closed;
961
995
  oldState.webSocket = null;
962
996
 
963
997
  return oldState;
@@ -128,7 +128,8 @@ export class UsersManager<
128
128
 
129
129
  public setUser(
130
130
  connectionId: string,
131
- user: Id<InferIOAuthorizeUser<InferIOAuthorize<TIO>>>
131
+ user: Id<InferIOAuthorizeUser<InferIOAuthorize<TIO>>>,
132
+ presence?: TPresence
132
133
  ): void {
133
134
  if (this._myself?.connectionId === connectionId) return;
134
135
 
@@ -138,7 +139,7 @@ export class UsersManager<
138
139
 
139
140
  this._others.set(connectionId, {
140
141
  connectionId,
141
- presence: this._initialPresence,
142
+ presence: presence ?? this._initialPresence,
142
143
  user,
143
144
  });
144
145
  }
package/src/types.ts CHANGED
@@ -24,6 +24,7 @@ export interface InternalSubscriptions {
24
24
  }
25
25
 
26
26
  export interface WebSocketConnection {
27
+ count: number;
27
28
  id: string | null;
28
29
  state: ConnectionState;
29
30
  }