@pluv/client 0.38.13 → 0.39.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/dist/index.mjs CHANGED
@@ -316,9 +316,7 @@ var UsersManager = class {
316
316
  patchPresence(connectionId, patch) {
317
317
  var _a, _b;
318
318
  if (((_a = this._myself) == null ? void 0 : _a.connectionId) === connectionId) {
319
- const presence2 = __spreadValues(__spreadValues({}, this._myPresence), patch);
320
- this._myself.presence = presence2;
321
- this._myPresence = presence2;
319
+ this.updateMyPresence(patch);
322
320
  return;
323
321
  }
324
322
  const other = this._others.get(connectionId);
@@ -369,8 +367,8 @@ var UsersManager = class {
369
367
  /**
370
368
  * @description This method need not care about being connected.
371
369
  */
372
- updateMyPresence(presence) {
373
- this._myPresence = __spreadValues(__spreadValues({}, this._myPresence), presence);
370
+ updateMyPresence(patch) {
371
+ this._myPresence = __spreadValues(__spreadValues({}, this._myPresence), patch);
374
372
  if (!this._myself) return;
375
373
  this._myself.presence = this._myPresence;
376
374
  }
@@ -409,7 +407,9 @@ var debounce = (fn, options) => {
409
407
  var ADD_TO_STORAGE_STATE_DEBOUNCE_MS = 1e3;
410
408
  var HEARTBEAT_INTERVAL_MS = 1e4;
411
409
  var PONG_TIMEOUT_MS = 2e3;
412
- var RECONNECT_TIMEOUT_MS = 3e4;
410
+ var RECONNECT_TIMEOUT_MS = 5e3;
411
+ var MIN_RECONNECT_TIMEOUT_MS = 1e3;
412
+ var MAX_RECONNECT_TIMEOUT_MS = 6e4;
413
413
  var ORIGIN_INITIALIZED = "$initialized";
414
414
  var ORIGIN_STORAGE_UPDATED = "$storageUpdated";
415
415
  var DEFAULT_PLUV_CLIENT_ADDON = (input) => ({
@@ -427,6 +427,7 @@ var PluvRoom = class extends AbstractRoom {
427
427
  onAuthorizationFail,
428
428
  presence,
429
429
  publicKey,
430
+ reconnectTimeoutMs = RECONNECT_TIMEOUT_MS,
430
431
  router,
431
432
  wsEndpoint
432
433
  } = options;
@@ -436,7 +437,6 @@ var PluvRoom = class extends AbstractRoom {
436
437
  this._intervals = {
437
438
  heartbeat: null
438
439
  };
439
- this._metadata = {};
440
440
  this._otherNotifier = new OtherNotifier();
441
441
  this._publicKey = null;
442
442
  this._stateNotifier = new StateNotifier();
@@ -447,12 +447,14 @@ var PluvRoom = class extends AbstractRoom {
447
447
  pong: null,
448
448
  reconnect: null
449
449
  };
450
+ this._lastMetadata = null;
450
451
  this._state = {
451
452
  authorization: {
452
453
  token: null,
453
454
  user: null
454
455
  },
455
456
  connection: {
457
+ attempts: 0,
456
458
  count: 0,
457
459
  id: null,
458
460
  state: "Untouched" /* Untouched */
@@ -591,18 +593,14 @@ var PluvRoom = class extends AbstractRoom {
591
593
  };
592
594
  const addon = this._getAddon(addons);
593
595
  const { storage } = __spreadValues(__spreadValues({}, DEFAULT_PLUV_CLIENT_ADDON({ room: this })), addon({ room: this }));
596
+ this.metadata = metadata;
594
597
  this._debug = debug;
595
598
  this._endpoints = { authEndpoint, wsEndpoint };
599
+ this._reconnectTimeoutMs = reconnectTimeoutMs;
596
600
  this._storageStore = storage;
597
- if (typeof metadata !== "undefined") this._metadata = metadata;
598
601
  if (!!publicKey) this._publicKey = publicKey;
599
602
  this._listeners = {
600
603
  onAuthorizationFail: (error) => {
601
- this._clearTimeout(this._timeouts.reconnect);
602
- this._timeouts.reconnect = setTimeout(
603
- this._reconnect.bind(this),
604
- RECONNECT_TIMEOUT_MS
605
- );
606
604
  onAuthorizationFail == null ? void 0 : onAuthorizationFail(error);
607
605
  }
608
606
  };
@@ -616,11 +614,13 @@ var PluvRoom = class extends AbstractRoom {
616
614
  get webSocket() {
617
615
  return this._state.webSocket;
618
616
  }
619
- connect() {
617
+ connect(...args) {
620
618
  return __async(this, null, function* () {
621
- if (!this._endpoints.wsEndpoint) {
622
- throw new Error("Must provide an wsEndpoint.");
623
- }
619
+ var _a;
620
+ const params = (_a = args[0]) != null ? _a : {};
621
+ const metadata = params.metadata;
622
+ this._setMetadata(metadata);
623
+ if (!this._endpoints.wsEndpoint) throw new Error("Must provide a wsEndpoint.");
624
624
  const canConnect = ["Closed" /* Closed */, "Untouched" /* Untouched */].some(
625
625
  (state) => this._state.connection.state === state
626
626
  );
@@ -632,21 +632,27 @@ var PluvRoom = class extends AbstractRoom {
632
632
  });
633
633
  yield this._storageStore.initialize();
634
634
  yield this._applyStorageStore();
635
- const url = new URL(this._getWsEndpoint(this.id));
635
+ const url = new URL(this._getWsEndpoint(this.id, params));
636
636
  let authToken = null;
637
637
  let webSocket;
638
638
  try {
639
- const publicKey = this._getPublicKey();
640
- authToken = yield this._getAuthorization(this.id);
639
+ const publicKey = this._getPublicKey(params);
640
+ authToken = yield this._getAuthorization(this.id, params);
641
641
  authToken && url.searchParams.set("token", encodeURIComponent(authToken));
642
642
  publicKey && url.searchParams.set("public_key", encodeURIComponent(publicKey));
643
643
  webSocket = new WebSocket(url.toString());
644
644
  } catch (err) {
645
+ this._updateState((oldState) => {
646
+ oldState.connection.attempts += 1;
647
+ return oldState;
648
+ });
645
649
  yield this._storageStore.destroy();
646
- this._onAuthorizationFail(err);
650
+ this._pollReconnect();
651
+ yield Promise.resolve(this._onAuthorizationFail(err));
647
652
  return;
648
653
  }
649
654
  this._updateState((oldState) => {
655
+ oldState.connection.attempts = 0;
650
656
  oldState.authorization.token = authToken;
651
657
  oldState.webSocket = webSocket;
652
658
  return oldState;
@@ -657,16 +663,21 @@ var PluvRoom = class extends AbstractRoom {
657
663
  }
658
664
  disconnect() {
659
665
  return __async(this, null, function* () {
660
- if (!this._endpoints.wsEndpoint) {
661
- throw new Error("Must provide an wsEndpoint.");
662
- }
666
+ if (!this._endpoints.wsEndpoint) throw new Error("Must provide a wsEndpoint.");
663
667
  if (!this._state.webSocket) return;
664
668
  const canDisconnect = ["Connecting" /* Connecting */, "Open" /* Open */].some(
665
669
  (state) => this._state.connection.state === state
666
670
  );
667
671
  if (!canDisconnect) return;
668
- this._clearInterval(this._intervals.heartbeat);
669
672
  this._closeWs();
673
+ this._clearInterval(this._intervals.heartbeat);
674
+ this._clearTimeout(this._timeouts.reconnect);
675
+ this._updateState((oldState) => {
676
+ oldState.authorization.token = null;
677
+ oldState.connection.state = "Closed" /* Closed */;
678
+ oldState.webSocket = null;
679
+ return oldState;
680
+ });
670
681
  yield this._storageStore.destroy();
671
682
  this._crdtManager.destroy();
672
683
  this._stateNotifier.subjects["my-presence"].next(null);
@@ -703,9 +714,11 @@ var PluvRoom = class extends AbstractRoom {
703
714
  if (typeof window === "undefined") return;
704
715
  if (typeof document === "undefined") return;
705
716
  this._windowListeners = {
717
+ onNavigatorOffline: this._onNavigatorOffline.bind(this),
706
718
  onNavigatorOnline: this._onNavigatorOnline.bind(this),
707
719
  onVisibilityChange: this._onVisibilityChange.bind(this)
708
720
  };
721
+ window.addEventListener("offline", this._windowListeners.onNavigatorOffline);
709
722
  window.addEventListener("online", this._windowListeners.onNavigatorOnline);
710
723
  document.addEventListener("visibilitychange", this._windowListeners.onVisibilityChange);
711
724
  }
@@ -731,24 +744,17 @@ var PluvRoom = class extends AbstractRoom {
731
744
  clearTimeout(timeout);
732
745
  }
733
746
  _closeWs() {
734
- var _a, _b;
735
- if (!this._state.webSocket) return;
747
+ var _a, _b, _c;
736
748
  (_b = (_a = this._subscriptions).observeCrdt) == null ? void 0 : _b.call(_a);
749
+ this._clearInterval(this._intervals.heartbeat);
737
750
  this._clearTimeout(this._timeouts.pong);
738
- this._clearTimeout(this._timeouts.reconnect);
739
751
  this._detachWindowListeners();
740
752
  this._usersManager.removeMyself();
741
753
  this._usersManager.removeUsers();
742
754
  this._otherNotifier.clear();
743
755
  this._stateNotifier.subjects.myself.next(null);
744
756
  this._stateNotifier.subjects.others.next([]);
745
- const canClose = [WebSocket.CONNECTING, WebSocket.OPEN].some(
746
- (readyState) => {
747
- var _a2;
748
- return readyState === ((_a2 = this._state.webSocket) == null ? void 0 : _a2.readyState);
749
- }
750
- );
751
- if (canClose) this._state.webSocket.close();
757
+ (_c = this._state.webSocket) == null ? void 0 : _c.close();
752
758
  this._detachWsListeners();
753
759
  }
754
760
  _detachWindowListeners() {
@@ -779,7 +785,8 @@ var PluvRoom = class extends AbstractRoom {
779
785
  );
780
786
  this._crdtNotifier.rootSubject.next(storageRoot);
781
787
  }
782
- _getAuthFetchOptions(room) {
788
+ _getAuthFetchOptions(room, params) {
789
+ const metadata = params.metadata;
783
790
  if (typeof this._endpoints.authEndpoint === "undefined") return null;
784
791
  if (this._endpoints.authEndpoint === true) {
785
792
  return {
@@ -793,18 +800,16 @@ var PluvRoom = class extends AbstractRoom {
793
800
  options: {}
794
801
  };
795
802
  }
796
- const result = this._endpoints.authEndpoint({ metadata: this._metadata, room });
803
+ const result = this._endpoints.authEndpoint({ metadata, room });
797
804
  return typeof result === "string" ? { url: result, options: {} } : result;
798
805
  }
799
- _getAuthorization(room) {
806
+ _getAuthorization(room, params) {
800
807
  return __async(this, null, function* () {
801
- const fetchOptions = this._getAuthFetchOptions(room);
808
+ const fetchOptions = this._getAuthFetchOptions(room, params);
802
809
  if (!fetchOptions) return null;
803
810
  const { url, options } = fetchOptions;
804
811
  const res = yield fetch(url, options);
805
- if (!res.ok || res.status !== 200) {
806
- throw new Error("Room is unauthorized");
807
- }
812
+ if (!res.ok || res.status !== 200) throw new Error("Room is unauthorized");
808
813
  try {
809
814
  return yield res.text().then((text) => text.trim());
810
815
  } catch (err) {
@@ -812,19 +817,21 @@ var PluvRoom = class extends AbstractRoom {
812
817
  }
813
818
  });
814
819
  }
815
- _getPublicKey() {
820
+ _getPublicKey(params) {
821
+ const metadata = params.metadata;
816
822
  if (!this._publicKey) return null;
817
823
  if (typeof this._publicKey === "string") return this._publicKey;
818
- return this._publicKey({ metadata: this._metadata });
824
+ return this._publicKey({ metadata });
819
825
  }
820
- _getWsEndpoint(room) {
826
+ _getWsEndpoint(room, params) {
827
+ const metadata = params.metadata;
821
828
  switch (typeof this._endpoints.wsEndpoint) {
822
829
  case "undefined":
823
- return !!this._getPublicKey() ? `wss://rooms.pluv.io/api/room/${room}` : `/api/pluv/room/${room}`;
830
+ return !!this._getPublicKey(params) ? `wss://rooms.pluv.io/api/room/${room}` : `/api/pluv/room/${room}`;
824
831
  case "string":
825
832
  return this._endpoints.wsEndpoint;
826
833
  default:
827
- return this._endpoints.wsEndpoint({ metadata: this._metadata, room });
834
+ return this._endpoints.wsEndpoint({ metadata, room });
828
835
  }
829
836
  }
830
837
  _handleExit(message) {
@@ -843,10 +850,9 @@ var PluvRoom = class extends AbstractRoom {
843
850
  if (!connectionId) return;
844
851
  if (!this._state.webSocket) throw new Error("Could not find WebSocket");
845
852
  const data = message.data;
846
- this._usersManager.patchPresence(connectionId, data.presence);
847
- const myPresence = this._usersManager.myPresence;
848
853
  const myself = (_a = this._usersManager.myself) != null ? _a : null;
849
854
  if ((myself == null ? void 0 : myself.connectionId) === connectionId) return;
855
+ this._usersManager.patchPresence(connectionId, data.presence);
850
856
  const other = this._usersManager.getOther(connectionId);
851
857
  const others = this._usersManager.getOthers();
852
858
  this._otherNotifier.subject(connectionId).next(other);
@@ -992,23 +998,30 @@ var PluvRoom = class extends AbstractRoom {
992
998
  return oldState;
993
999
  });
994
1000
  }
995
- /**
996
- * TODO
997
- * @description Determine if ws was closed with a code for closing without retries.
998
- * Otherwise, attempt to retry again with incremental backoff
999
- * @author leedavidcs
1000
- * @date August 19, 2022
1001
- */
1002
1001
  _onClose(event) {
1003
1002
  return __async(this, null, function* () {
1004
1003
  this._logDebug("WebSocket closed");
1005
1004
  !!event.reason && this._logDebug(event.reason);
1006
- this._clearInterval(this._intervals.heartbeat);
1007
- this._clearTimeout(this._timeouts.pong);
1008
- this._stateNotifier.subjects["myself"].next(null);
1005
+ const shouldRetry = [
1006
+ // Going away: Client/server is shutting down or navigating
1007
+ 1001,
1008
+ // Abnormal closure: Usually network error
1009
+ 1006,
1010
+ // Internal error: may be a larger problem, but try reconnecting for now
1011
+ 1011,
1012
+ // Service restart: Server restarted
1013
+ 1012,
1014
+ // Try again later: server overloaded
1015
+ 1013,
1016
+ // Bad gateway
1017
+ 1014
1018
+ ].some((okToRetryCode) => event.code === okToRetryCode);
1019
+ this._closeWs();
1020
+ if (!shouldRetry) this._clearTimeout(this._timeouts.reconnect);
1021
+ else this._pollReconnect();
1009
1022
  this._updateState((oldState) => {
1010
1023
  oldState.authorization.token = null;
1011
- oldState.connection.state = "Closed" /* Closed */;
1024
+ oldState.connection.state = shouldRetry ? "Unavailable" /* Unavailable */ : "Closed" /* Closed */;
1012
1025
  oldState.webSocket = null;
1013
1026
  return oldState;
1014
1027
  });
@@ -1017,7 +1030,6 @@ var PluvRoom = class extends AbstractRoom {
1017
1030
  /**
1018
1031
  * TODO
1019
1032
  * @description Handle websocket errors in a meaningful way, should they occur
1020
- * @author leedavidcs
1021
1033
  * @date August 19, 2022
1022
1034
  */
1023
1035
  _onError() {
@@ -1076,11 +1088,18 @@ var PluvRoom = class extends AbstractRoom {
1076
1088
  this._clearInterval(this._intervals.heartbeat);
1077
1089
  this._intervals.heartbeat = setInterval(this._heartbeat.bind(this), HEARTBEAT_INTERVAL_MS);
1078
1090
  }
1091
+ _onNavigatorOffline() {
1092
+ return __async(this, null, function* () {
1093
+ if (this._state.connection.state === "Closed" /* Closed */) return;
1094
+ this._updateState((oldState) => {
1095
+ oldState.connection.state = "Unavailable" /* Unavailable */;
1096
+ return oldState;
1097
+ });
1098
+ });
1099
+ }
1079
1100
  _onNavigatorOnline() {
1080
1101
  return __async(this, null, function* () {
1081
- if (this._state.connection.state !== "Unavailable" /* Unavailable */) {
1082
- return;
1083
- }
1102
+ if (this._state.connection.state !== "Unavailable" /* Unavailable */) return;
1084
1103
  yield this._reconnect();
1085
1104
  });
1086
1105
  }
@@ -1112,6 +1131,12 @@ var PluvRoom = class extends AbstractRoom {
1112
1131
  return null;
1113
1132
  }
1114
1133
  }
1134
+ _pollReconnect() {
1135
+ this._clearTimeout(this._timeouts.reconnect);
1136
+ const timeoutMs = typeof this._reconnectTimeoutMs === "number" ? this._reconnectTimeoutMs : this._reconnectTimeoutMs({ attempts: this._state.connection.attempts });
1137
+ const clampedMs = Math.max(Math.min(MIN_RECONNECT_TIMEOUT_MS, timeoutMs), MAX_RECONNECT_TIMEOUT_MS);
1138
+ this._timeouts.reconnect = setTimeout(this._reconnect.bind(this), clampedMs);
1139
+ }
1115
1140
  _reconnect() {
1116
1141
  return __async(this, null, function* () {
1117
1142
  if (!this._endpoints.wsEndpoint) {
@@ -1119,13 +1144,9 @@ var PluvRoom = class extends AbstractRoom {
1119
1144
  }
1120
1145
  if (!this._state.webSocket) return;
1121
1146
  this._closeWs();
1122
- this._updateState((oldState) => {
1123
- oldState.authorization.token = null;
1124
- oldState.connection.state = "Closed" /* Closed */;
1125
- oldState.webSocket = null;
1126
- return oldState;
1127
- });
1128
- yield this.connect();
1147
+ const metadata = this._lastMetadata;
1148
+ const params = typeof metadata === "undefined" ? [] : [{ metadata }];
1149
+ yield this.connect(...params);
1129
1150
  });
1130
1151
  }
1131
1152
  _sendMessage(data) {
@@ -1146,6 +1167,9 @@ var PluvRoom = class extends AbstractRoom {
1146
1167
  const message = typeof data === "string" ? data : JSON.stringify(data);
1147
1168
  webSocket.send(message);
1148
1169
  }
1170
+ _setMetadata(metadata) {
1171
+ return this.metadata ? this.metadata.parse(metadata) : metadata;
1172
+ }
1149
1173
  _updateState(updater) {
1150
1174
  let authorization;
1151
1175
  try {
@@ -1175,17 +1199,17 @@ var PluvClient = class {
1175
1199
  var _a;
1176
1200
  const oldRoom = this.getRoom(room);
1177
1201
  if (oldRoom) return oldRoom;
1178
- const metadata = this._metadata ? this._metadata.parse(options.metadata) : options.metadata;
1179
1202
  const newRoom = new PluvRoom(room, {
1180
1203
  addons: options.addons,
1181
1204
  authEndpoint: this._authEndpoint,
1182
1205
  debug: options.debug,
1183
1206
  initialPresence: options.initialPresence,
1184
1207
  initialStorage: this._initialStorage,
1185
- metadata,
1208
+ metadata: this.metadata,
1186
1209
  onAuthorizationFail: options.onAuthorizationFail,
1187
1210
  presence: this._presence,
1188
1211
  publicKey: (_a = this._publicKey) != null ? _a : void 0,
1212
+ reconnectTimeoutMs: options.reconnectTimeoutMs,
1189
1213
  router: options.router,
1190
1214
  wsEndpoint: this._wsEndpoint
1191
1215
  });
@@ -1193,11 +1217,11 @@ var PluvClient = class {
1193
1217
  this._logDebug(`New room was created: ${room}`);
1194
1218
  return newRoom;
1195
1219
  };
1196
- this.enter = (room) => __async(this, null, function* () {
1220
+ this.enter = (room, ...args) => __async(this, null, function* () {
1197
1221
  const toEnter = typeof room === "string" ? this.getRoom(room) : room;
1198
1222
  if (!toEnter) throw new Error(`Could not find room: ${room}.`);
1199
1223
  this._rooms.set(toEnter.id, toEnter);
1200
- yield toEnter.connect();
1224
+ yield toEnter.connect(...args);
1201
1225
  this._logDebug(`Entered room: ${room}`);
1202
1226
  return toEnter;
1203
1227
  });
@@ -1216,10 +1240,10 @@ var PluvClient = class {
1216
1240
  this._logDebug(`Left and deleted room: ${room}`);
1217
1241
  });
1218
1242
  const { authEndpoint, debug = false, initialStorage, metadata, presence, publicKey, wsEndpoint } = options;
1243
+ this.metadata = metadata;
1219
1244
  this._authEndpoint = authEndpoint;
1220
1245
  this._debug = debug;
1221
1246
  this._initialStorage = initialStorage;
1222
- this._metadata = metadata;
1223
1247
  this._presence = presence;
1224
1248
  this._wsEndpoint = wsEndpoint;
1225
1249
  if (!!publicKey) this._publicKey = publicKey;
@@ -1268,6 +1292,7 @@ var MockedRoom = class extends AbstractRoom {
1268
1292
  user: null
1269
1293
  },
1270
1294
  connection: {
1295
+ attempts: 0,
1271
1296
  count: 1,
1272
1297
  id: null,
1273
1298
  state: "Untouched" /* Untouched */
@@ -0,0 +1,4 @@
1
+ import { config } from "eslint-config-pluv/base";
2
+
3
+ /** @type {import("eslint").Linter.Config} */
4
+ export default config;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pluv/client",
3
- "version": "0.38.13",
3
+ "version": "0.39.0",
4
4
  "description": "@pluv/io frontend client",
5
5
  "author": "leedavidcs",
6
6
  "license": "MIT",
@@ -18,20 +18,20 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "wonka": "^6.3.5",
21
- "@pluv/crdt": "^0.38.13",
22
- "@pluv/types": "^0.38.13"
21
+ "@pluv/crdt": "^0.39.0",
22
+ "@pluv/types": "^0.39.0"
23
23
  },
24
24
  "devDependencies": {
25
- "eslint": "^8.57.1",
25
+ "eslint": "^9.24.0",
26
26
  "tsup": "^8.4.0",
27
27
  "typescript": "^5.8.3",
28
- "@pluv/tsconfig": "^0.38.13",
29
- "eslint-config-pluv": "^0.38.13"
28
+ "@pluv/tsconfig": "^0.39.0",
29
+ "eslint-config-pluv": "^0.39.0"
30
30
  },
31
31
  "scripts": {
32
32
  "build": "tsup src/index.ts --format esm,cjs --dts",
33
33
  "dev": "tsup src/index.ts --format esm,cjs --watch --dts",
34
- "lint": "eslint src/**/*.ts* --fix",
34
+ "lint": "eslint src/**/*.ts* --fix --max-warnings 0",
35
35
  "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
36
36
  }
37
37
  }
package/src/MockedRoom.ts CHANGED
@@ -59,6 +59,7 @@ export class MockedRoom<
59
59
  user: null,
60
60
  },
61
61
  connection: {
62
+ attempts: 0,
62
63
  count: 1,
63
64
  id: null,
64
65
  state: ConnectionState.Untouched,
package/src/PluvClient.ts CHANGED
@@ -2,11 +2,19 @@ import type { AbstractCrdtDocFactory, CrdtType } from "@pluv/crdt";
2
2
  import type { InputZodLike, IOLike, JsonObject } from "@pluv/types";
3
3
  import type { InferCallback } from "./infer";
4
4
  import { PluvProcedure } from "./PluvProcedure";
5
- import type { AuthEndpoint, PluvRoomAddon, PluvRoomDebug, RoomConfig, RoomEndpoints, WsEndpoint } from "./PluvRoom";
5
+ import type {
6
+ AuthEndpoint,
7
+ PluvRoomAddon,
8
+ PluvRoomDebug,
9
+ ReconnectTimeoutMs,
10
+ RoomConfig,
11
+ RoomEndpoints,
12
+ WsEndpoint,
13
+ } from "./PluvRoom";
6
14
  import { PluvRoom } from "./PluvRoom";
7
15
  import type { PluvRouterEventConfig } from "./PluvRouter";
8
16
  import { PluvRouter } from "./PluvRouter";
9
- import type { PublicKey } from "./types";
17
+ import type { PublicKey, WithMetadata } from "./types";
10
18
 
11
19
  export type PluvClientOptions<
12
20
  TIO extends IOLike<any, any>,
@@ -34,8 +42,13 @@ export type CreateRoomOptions<
34
42
  initialPresence?: TPresence;
35
43
  initialStorage?: AbstractCrdtDocFactory<TStorage>;
36
44
  onAuthorizationFail?: (error: Error) => void;
45
+ reconnectTimeoutMs?: ReconnectTimeoutMs;
37
46
  router?: PluvRouter<TIO, TPresence, TStorage, TEvents>;
38
- } & (keyof TMetadata extends never ? { metadata?: undefined } : { metadata: TMetadata });
47
+ };
48
+
49
+ export type EnterRoomParams<TMetadata extends JsonObject = {}> = keyof TMetadata extends never
50
+ ? []
51
+ : [WithMetadata<TMetadata>];
39
52
 
40
53
  export class PluvClient<
41
54
  TIO extends IOLike<any, any>,
@@ -43,10 +56,11 @@ export class PluvClient<
43
56
  TStorage extends Record<string, CrdtType<any, any>> = {},
44
57
  TMetadata extends JsonObject = {},
45
58
  > {
59
+ public readonly metadata?: InputZodLike<TMetadata>;
60
+
46
61
  private readonly _authEndpoint: AuthEndpoint<TMetadata> | undefined;
47
62
  private readonly _debug: boolean;
48
63
  private readonly _initialStorage?: AbstractCrdtDocFactory<TStorage>;
49
- private readonly _metadata?: InputZodLike<TMetadata>;
50
64
  private readonly _presence?: InputZodLike<TPresence>;
51
65
  private readonly _publicKey: PublicKey<TMetadata> | null = null;
52
66
  private readonly _rooms = new Map<string, PluvRoom<TIO, TMetadata, TPresence, TStorage, any>>();
@@ -65,10 +79,11 @@ export class PluvClient<
65
79
  constructor(options: PluvClientOptions<TIO, TPresence, TStorage, TMetadata>) {
66
80
  const { authEndpoint, debug = false, initialStorage, metadata, presence, publicKey, wsEndpoint } = options;
67
81
 
82
+ this.metadata = metadata;
83
+
68
84
  this._authEndpoint = authEndpoint as AuthEndpoint<TMetadata>;
69
85
  this._debug = debug;
70
86
  this._initialStorage = initialStorage;
71
- this._metadata = metadata;
72
87
  this._presence = presence;
73
88
  this._wsEndpoint = wsEndpoint;
74
89
 
@@ -83,18 +98,17 @@ export class PluvClient<
83
98
 
84
99
  if (oldRoom) return oldRoom;
85
100
 
86
- const metadata = this._metadata ? this._metadata.parse(options.metadata) : options.metadata;
87
-
88
101
  const newRoom = new PluvRoom<TIO, TMetadata, TPresence, TStorage, TEvents>(room, {
89
102
  addons: options.addons,
90
103
  authEndpoint: this._authEndpoint,
91
104
  debug: options.debug,
92
105
  initialPresence: options.initialPresence,
93
106
  initialStorage: this._initialStorage,
94
- metadata,
107
+ metadata: this.metadata,
95
108
  onAuthorizationFail: options.onAuthorizationFail,
96
109
  presence: this._presence,
97
110
  publicKey: this._publicKey ?? undefined,
111
+ reconnectTimeoutMs: options.reconnectTimeoutMs,
98
112
  router: options.router,
99
113
  wsEndpoint: this._wsEndpoint,
100
114
  } as RoomConfig<TIO, TMetadata, TPresence, TStorage, TEvents>);
@@ -107,15 +121,15 @@ export class PluvClient<
107
121
  };
108
122
 
109
123
  public enter = async (
110
- room: string | PluvRoom<TIO, any, any, any>,
124
+ room: string | PluvRoom<TIO, TMetadata, any, any, any>,
125
+ ...args: EnterRoomParams<TMetadata>
111
126
  ): Promise<PluvRoom<TIO, TMetadata, TPresence, TStorage, any>> => {
112
127
  const toEnter = typeof room === "string" ? this.getRoom(room) : room;
113
128
 
114
129
  if (!toEnter) throw new Error(`Could not find room: ${room}.`);
115
130
 
116
131
  this._rooms.set(toEnter.id, toEnter);
117
-
118
- await toEnter.connect();
132
+ await toEnter.connect(...args);
119
133
 
120
134
  this._logDebug(`Entered room: ${room}`);
121
135