@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.js CHANGED
@@ -347,9 +347,7 @@ var UsersManager = class {
347
347
  patchPresence(connectionId, patch) {
348
348
  var _a, _b;
349
349
  if (((_a = this._myself) == null ? void 0 : _a.connectionId) === connectionId) {
350
- const presence2 = __spreadValues(__spreadValues({}, this._myPresence), patch);
351
- this._myself.presence = presence2;
352
- this._myPresence = presence2;
350
+ this.updateMyPresence(patch);
353
351
  return;
354
352
  }
355
353
  const other = this._others.get(connectionId);
@@ -400,8 +398,8 @@ var UsersManager = class {
400
398
  /**
401
399
  * @description This method need not care about being connected.
402
400
  */
403
- updateMyPresence(presence) {
404
- this._myPresence = __spreadValues(__spreadValues({}, this._myPresence), presence);
401
+ updateMyPresence(patch) {
402
+ this._myPresence = __spreadValues(__spreadValues({}, this._myPresence), patch);
405
403
  if (!this._myself) return;
406
404
  this._myself.presence = this._myPresence;
407
405
  }
@@ -440,7 +438,9 @@ var debounce = (fn, options) => {
440
438
  var ADD_TO_STORAGE_STATE_DEBOUNCE_MS = 1e3;
441
439
  var HEARTBEAT_INTERVAL_MS = 1e4;
442
440
  var PONG_TIMEOUT_MS = 2e3;
443
- var RECONNECT_TIMEOUT_MS = 3e4;
441
+ var RECONNECT_TIMEOUT_MS = 5e3;
442
+ var MIN_RECONNECT_TIMEOUT_MS = 1e3;
443
+ var MAX_RECONNECT_TIMEOUT_MS = 6e4;
444
444
  var ORIGIN_INITIALIZED = "$initialized";
445
445
  var ORIGIN_STORAGE_UPDATED = "$storageUpdated";
446
446
  var DEFAULT_PLUV_CLIENT_ADDON = (input) => ({
@@ -458,6 +458,7 @@ var PluvRoom = class extends AbstractRoom {
458
458
  onAuthorizationFail,
459
459
  presence,
460
460
  publicKey,
461
+ reconnectTimeoutMs = RECONNECT_TIMEOUT_MS,
461
462
  router,
462
463
  wsEndpoint
463
464
  } = options;
@@ -467,7 +468,6 @@ var PluvRoom = class extends AbstractRoom {
467
468
  this._intervals = {
468
469
  heartbeat: null
469
470
  };
470
- this._metadata = {};
471
471
  this._otherNotifier = new OtherNotifier();
472
472
  this._publicKey = null;
473
473
  this._stateNotifier = new StateNotifier();
@@ -478,12 +478,14 @@ var PluvRoom = class extends AbstractRoom {
478
478
  pong: null,
479
479
  reconnect: null
480
480
  };
481
+ this._lastMetadata = null;
481
482
  this._state = {
482
483
  authorization: {
483
484
  token: null,
484
485
  user: null
485
486
  },
486
487
  connection: {
488
+ attempts: 0,
487
489
  count: 0,
488
490
  id: null,
489
491
  state: "Untouched" /* Untouched */
@@ -622,18 +624,14 @@ var PluvRoom = class extends AbstractRoom {
622
624
  };
623
625
  const addon = this._getAddon(addons);
624
626
  const { storage } = __spreadValues(__spreadValues({}, DEFAULT_PLUV_CLIENT_ADDON({ room: this })), addon({ room: this }));
627
+ this.metadata = metadata;
625
628
  this._debug = debug;
626
629
  this._endpoints = { authEndpoint, wsEndpoint };
630
+ this._reconnectTimeoutMs = reconnectTimeoutMs;
627
631
  this._storageStore = storage;
628
- if (typeof metadata !== "undefined") this._metadata = metadata;
629
632
  if (!!publicKey) this._publicKey = publicKey;
630
633
  this._listeners = {
631
634
  onAuthorizationFail: (error) => {
632
- this._clearTimeout(this._timeouts.reconnect);
633
- this._timeouts.reconnect = setTimeout(
634
- this._reconnect.bind(this),
635
- RECONNECT_TIMEOUT_MS
636
- );
637
635
  onAuthorizationFail == null ? void 0 : onAuthorizationFail(error);
638
636
  }
639
637
  };
@@ -647,11 +645,13 @@ var PluvRoom = class extends AbstractRoom {
647
645
  get webSocket() {
648
646
  return this._state.webSocket;
649
647
  }
650
- connect() {
648
+ connect(...args) {
651
649
  return __async(this, null, function* () {
652
- if (!this._endpoints.wsEndpoint) {
653
- throw new Error("Must provide an wsEndpoint.");
654
- }
650
+ var _a;
651
+ const params = (_a = args[0]) != null ? _a : {};
652
+ const metadata = params.metadata;
653
+ this._setMetadata(metadata);
654
+ if (!this._endpoints.wsEndpoint) throw new Error("Must provide a wsEndpoint.");
655
655
  const canConnect = ["Closed" /* Closed */, "Untouched" /* Untouched */].some(
656
656
  (state) => this._state.connection.state === state
657
657
  );
@@ -663,21 +663,27 @@ var PluvRoom = class extends AbstractRoom {
663
663
  });
664
664
  yield this._storageStore.initialize();
665
665
  yield this._applyStorageStore();
666
- const url = new URL(this._getWsEndpoint(this.id));
666
+ const url = new URL(this._getWsEndpoint(this.id, params));
667
667
  let authToken = null;
668
668
  let webSocket;
669
669
  try {
670
- const publicKey = this._getPublicKey();
671
- authToken = yield this._getAuthorization(this.id);
670
+ const publicKey = this._getPublicKey(params);
671
+ authToken = yield this._getAuthorization(this.id, params);
672
672
  authToken && url.searchParams.set("token", encodeURIComponent(authToken));
673
673
  publicKey && url.searchParams.set("public_key", encodeURIComponent(publicKey));
674
674
  webSocket = new WebSocket(url.toString());
675
675
  } catch (err) {
676
+ this._updateState((oldState) => {
677
+ oldState.connection.attempts += 1;
678
+ return oldState;
679
+ });
676
680
  yield this._storageStore.destroy();
677
- this._onAuthorizationFail(err);
681
+ this._pollReconnect();
682
+ yield Promise.resolve(this._onAuthorizationFail(err));
678
683
  return;
679
684
  }
680
685
  this._updateState((oldState) => {
686
+ oldState.connection.attempts = 0;
681
687
  oldState.authorization.token = authToken;
682
688
  oldState.webSocket = webSocket;
683
689
  return oldState;
@@ -688,16 +694,21 @@ var PluvRoom = class extends AbstractRoom {
688
694
  }
689
695
  disconnect() {
690
696
  return __async(this, null, function* () {
691
- if (!this._endpoints.wsEndpoint) {
692
- throw new Error("Must provide an wsEndpoint.");
693
- }
697
+ if (!this._endpoints.wsEndpoint) throw new Error("Must provide a wsEndpoint.");
694
698
  if (!this._state.webSocket) return;
695
699
  const canDisconnect = ["Connecting" /* Connecting */, "Open" /* Open */].some(
696
700
  (state) => this._state.connection.state === state
697
701
  );
698
702
  if (!canDisconnect) return;
699
- this._clearInterval(this._intervals.heartbeat);
700
703
  this._closeWs();
704
+ this._clearInterval(this._intervals.heartbeat);
705
+ this._clearTimeout(this._timeouts.reconnect);
706
+ this._updateState((oldState) => {
707
+ oldState.authorization.token = null;
708
+ oldState.connection.state = "Closed" /* Closed */;
709
+ oldState.webSocket = null;
710
+ return oldState;
711
+ });
701
712
  yield this._storageStore.destroy();
702
713
  this._crdtManager.destroy();
703
714
  this._stateNotifier.subjects["my-presence"].next(null);
@@ -734,9 +745,11 @@ var PluvRoom = class extends AbstractRoom {
734
745
  if (typeof window === "undefined") return;
735
746
  if (typeof document === "undefined") return;
736
747
  this._windowListeners = {
748
+ onNavigatorOffline: this._onNavigatorOffline.bind(this),
737
749
  onNavigatorOnline: this._onNavigatorOnline.bind(this),
738
750
  onVisibilityChange: this._onVisibilityChange.bind(this)
739
751
  };
752
+ window.addEventListener("offline", this._windowListeners.onNavigatorOffline);
740
753
  window.addEventListener("online", this._windowListeners.onNavigatorOnline);
741
754
  document.addEventListener("visibilitychange", this._windowListeners.onVisibilityChange);
742
755
  }
@@ -762,24 +775,17 @@ var PluvRoom = class extends AbstractRoom {
762
775
  clearTimeout(timeout);
763
776
  }
764
777
  _closeWs() {
765
- var _a, _b;
766
- if (!this._state.webSocket) return;
778
+ var _a, _b, _c;
767
779
  (_b = (_a = this._subscriptions).observeCrdt) == null ? void 0 : _b.call(_a);
780
+ this._clearInterval(this._intervals.heartbeat);
768
781
  this._clearTimeout(this._timeouts.pong);
769
- this._clearTimeout(this._timeouts.reconnect);
770
782
  this._detachWindowListeners();
771
783
  this._usersManager.removeMyself();
772
784
  this._usersManager.removeUsers();
773
785
  this._otherNotifier.clear();
774
786
  this._stateNotifier.subjects.myself.next(null);
775
787
  this._stateNotifier.subjects.others.next([]);
776
- const canClose = [WebSocket.CONNECTING, WebSocket.OPEN].some(
777
- (readyState) => {
778
- var _a2;
779
- return readyState === ((_a2 = this._state.webSocket) == null ? void 0 : _a2.readyState);
780
- }
781
- );
782
- if (canClose) this._state.webSocket.close();
788
+ (_c = this._state.webSocket) == null ? void 0 : _c.close();
783
789
  this._detachWsListeners();
784
790
  }
785
791
  _detachWindowListeners() {
@@ -810,7 +816,8 @@ var PluvRoom = class extends AbstractRoom {
810
816
  );
811
817
  this._crdtNotifier.rootSubject.next(storageRoot);
812
818
  }
813
- _getAuthFetchOptions(room) {
819
+ _getAuthFetchOptions(room, params) {
820
+ const metadata = params.metadata;
814
821
  if (typeof this._endpoints.authEndpoint === "undefined") return null;
815
822
  if (this._endpoints.authEndpoint === true) {
816
823
  return {
@@ -824,18 +831,16 @@ var PluvRoom = class extends AbstractRoom {
824
831
  options: {}
825
832
  };
826
833
  }
827
- const result = this._endpoints.authEndpoint({ metadata: this._metadata, room });
834
+ const result = this._endpoints.authEndpoint({ metadata, room });
828
835
  return typeof result === "string" ? { url: result, options: {} } : result;
829
836
  }
830
- _getAuthorization(room) {
837
+ _getAuthorization(room, params) {
831
838
  return __async(this, null, function* () {
832
- const fetchOptions = this._getAuthFetchOptions(room);
839
+ const fetchOptions = this._getAuthFetchOptions(room, params);
833
840
  if (!fetchOptions) return null;
834
841
  const { url, options } = fetchOptions;
835
842
  const res = yield fetch(url, options);
836
- if (!res.ok || res.status !== 200) {
837
- throw new Error("Room is unauthorized");
838
- }
843
+ if (!res.ok || res.status !== 200) throw new Error("Room is unauthorized");
839
844
  try {
840
845
  return yield res.text().then((text) => text.trim());
841
846
  } catch (err) {
@@ -843,19 +848,21 @@ var PluvRoom = class extends AbstractRoom {
843
848
  }
844
849
  });
845
850
  }
846
- _getPublicKey() {
851
+ _getPublicKey(params) {
852
+ const metadata = params.metadata;
847
853
  if (!this._publicKey) return null;
848
854
  if (typeof this._publicKey === "string") return this._publicKey;
849
- return this._publicKey({ metadata: this._metadata });
855
+ return this._publicKey({ metadata });
850
856
  }
851
- _getWsEndpoint(room) {
857
+ _getWsEndpoint(room, params) {
858
+ const metadata = params.metadata;
852
859
  switch (typeof this._endpoints.wsEndpoint) {
853
860
  case "undefined":
854
- return !!this._getPublicKey() ? `wss://rooms.pluv.io/api/room/${room}` : `/api/pluv/room/${room}`;
861
+ return !!this._getPublicKey(params) ? `wss://rooms.pluv.io/api/room/${room}` : `/api/pluv/room/${room}`;
855
862
  case "string":
856
863
  return this._endpoints.wsEndpoint;
857
864
  default:
858
- return this._endpoints.wsEndpoint({ metadata: this._metadata, room });
865
+ return this._endpoints.wsEndpoint({ metadata, room });
859
866
  }
860
867
  }
861
868
  _handleExit(message) {
@@ -874,10 +881,9 @@ var PluvRoom = class extends AbstractRoom {
874
881
  if (!connectionId) return;
875
882
  if (!this._state.webSocket) throw new Error("Could not find WebSocket");
876
883
  const data = message.data;
877
- this._usersManager.patchPresence(connectionId, data.presence);
878
- const myPresence = this._usersManager.myPresence;
879
884
  const myself = (_a = this._usersManager.myself) != null ? _a : null;
880
885
  if ((myself == null ? void 0 : myself.connectionId) === connectionId) return;
886
+ this._usersManager.patchPresence(connectionId, data.presence);
881
887
  const other = this._usersManager.getOther(connectionId);
882
888
  const others = this._usersManager.getOthers();
883
889
  this._otherNotifier.subject(connectionId).next(other);
@@ -1023,23 +1029,30 @@ var PluvRoom = class extends AbstractRoom {
1023
1029
  return oldState;
1024
1030
  });
1025
1031
  }
1026
- /**
1027
- * TODO
1028
- * @description Determine if ws was closed with a code for closing without retries.
1029
- * Otherwise, attempt to retry again with incremental backoff
1030
- * @author leedavidcs
1031
- * @date August 19, 2022
1032
- */
1033
1032
  _onClose(event) {
1034
1033
  return __async(this, null, function* () {
1035
1034
  this._logDebug("WebSocket closed");
1036
1035
  !!event.reason && this._logDebug(event.reason);
1037
- this._clearInterval(this._intervals.heartbeat);
1038
- this._clearTimeout(this._timeouts.pong);
1039
- this._stateNotifier.subjects["myself"].next(null);
1036
+ const shouldRetry = [
1037
+ // Going away: Client/server is shutting down or navigating
1038
+ 1001,
1039
+ // Abnormal closure: Usually network error
1040
+ 1006,
1041
+ // Internal error: may be a larger problem, but try reconnecting for now
1042
+ 1011,
1043
+ // Service restart: Server restarted
1044
+ 1012,
1045
+ // Try again later: server overloaded
1046
+ 1013,
1047
+ // Bad gateway
1048
+ 1014
1049
+ ].some((okToRetryCode) => event.code === okToRetryCode);
1050
+ this._closeWs();
1051
+ if (!shouldRetry) this._clearTimeout(this._timeouts.reconnect);
1052
+ else this._pollReconnect();
1040
1053
  this._updateState((oldState) => {
1041
1054
  oldState.authorization.token = null;
1042
- oldState.connection.state = "Closed" /* Closed */;
1055
+ oldState.connection.state = shouldRetry ? "Unavailable" /* Unavailable */ : "Closed" /* Closed */;
1043
1056
  oldState.webSocket = null;
1044
1057
  return oldState;
1045
1058
  });
@@ -1048,7 +1061,6 @@ var PluvRoom = class extends AbstractRoom {
1048
1061
  /**
1049
1062
  * TODO
1050
1063
  * @description Handle websocket errors in a meaningful way, should they occur
1051
- * @author leedavidcs
1052
1064
  * @date August 19, 2022
1053
1065
  */
1054
1066
  _onError() {
@@ -1107,11 +1119,18 @@ var PluvRoom = class extends AbstractRoom {
1107
1119
  this._clearInterval(this._intervals.heartbeat);
1108
1120
  this._intervals.heartbeat = setInterval(this._heartbeat.bind(this), HEARTBEAT_INTERVAL_MS);
1109
1121
  }
1122
+ _onNavigatorOffline() {
1123
+ return __async(this, null, function* () {
1124
+ if (this._state.connection.state === "Closed" /* Closed */) return;
1125
+ this._updateState((oldState) => {
1126
+ oldState.connection.state = "Unavailable" /* Unavailable */;
1127
+ return oldState;
1128
+ });
1129
+ });
1130
+ }
1110
1131
  _onNavigatorOnline() {
1111
1132
  return __async(this, null, function* () {
1112
- if (this._state.connection.state !== "Unavailable" /* Unavailable */) {
1113
- return;
1114
- }
1133
+ if (this._state.connection.state !== "Unavailable" /* Unavailable */) return;
1115
1134
  yield this._reconnect();
1116
1135
  });
1117
1136
  }
@@ -1143,6 +1162,12 @@ var PluvRoom = class extends AbstractRoom {
1143
1162
  return null;
1144
1163
  }
1145
1164
  }
1165
+ _pollReconnect() {
1166
+ this._clearTimeout(this._timeouts.reconnect);
1167
+ const timeoutMs = typeof this._reconnectTimeoutMs === "number" ? this._reconnectTimeoutMs : this._reconnectTimeoutMs({ attempts: this._state.connection.attempts });
1168
+ const clampedMs = Math.max(Math.min(MIN_RECONNECT_TIMEOUT_MS, timeoutMs), MAX_RECONNECT_TIMEOUT_MS);
1169
+ this._timeouts.reconnect = setTimeout(this._reconnect.bind(this), clampedMs);
1170
+ }
1146
1171
  _reconnect() {
1147
1172
  return __async(this, null, function* () {
1148
1173
  if (!this._endpoints.wsEndpoint) {
@@ -1150,13 +1175,9 @@ var PluvRoom = class extends AbstractRoom {
1150
1175
  }
1151
1176
  if (!this._state.webSocket) return;
1152
1177
  this._closeWs();
1153
- this._updateState((oldState) => {
1154
- oldState.authorization.token = null;
1155
- oldState.connection.state = "Closed" /* Closed */;
1156
- oldState.webSocket = null;
1157
- return oldState;
1158
- });
1159
- yield this.connect();
1178
+ const metadata = this._lastMetadata;
1179
+ const params = typeof metadata === "undefined" ? [] : [{ metadata }];
1180
+ yield this.connect(...params);
1160
1181
  });
1161
1182
  }
1162
1183
  _sendMessage(data) {
@@ -1177,6 +1198,9 @@ var PluvRoom = class extends AbstractRoom {
1177
1198
  const message = typeof data === "string" ? data : JSON.stringify(data);
1178
1199
  webSocket.send(message);
1179
1200
  }
1201
+ _setMetadata(metadata) {
1202
+ return this.metadata ? this.metadata.parse(metadata) : metadata;
1203
+ }
1180
1204
  _updateState(updater) {
1181
1205
  let authorization;
1182
1206
  try {
@@ -1206,17 +1230,17 @@ var PluvClient = class {
1206
1230
  var _a;
1207
1231
  const oldRoom = this.getRoom(room);
1208
1232
  if (oldRoom) return oldRoom;
1209
- const metadata = this._metadata ? this._metadata.parse(options.metadata) : options.metadata;
1210
1233
  const newRoom = new PluvRoom(room, {
1211
1234
  addons: options.addons,
1212
1235
  authEndpoint: this._authEndpoint,
1213
1236
  debug: options.debug,
1214
1237
  initialPresence: options.initialPresence,
1215
1238
  initialStorage: this._initialStorage,
1216
- metadata,
1239
+ metadata: this.metadata,
1217
1240
  onAuthorizationFail: options.onAuthorizationFail,
1218
1241
  presence: this._presence,
1219
1242
  publicKey: (_a = this._publicKey) != null ? _a : void 0,
1243
+ reconnectTimeoutMs: options.reconnectTimeoutMs,
1220
1244
  router: options.router,
1221
1245
  wsEndpoint: this._wsEndpoint
1222
1246
  });
@@ -1224,11 +1248,11 @@ var PluvClient = class {
1224
1248
  this._logDebug(`New room was created: ${room}`);
1225
1249
  return newRoom;
1226
1250
  };
1227
- this.enter = (room) => __async(this, null, function* () {
1251
+ this.enter = (room, ...args) => __async(this, null, function* () {
1228
1252
  const toEnter = typeof room === "string" ? this.getRoom(room) : room;
1229
1253
  if (!toEnter) throw new Error(`Could not find room: ${room}.`);
1230
1254
  this._rooms.set(toEnter.id, toEnter);
1231
- yield toEnter.connect();
1255
+ yield toEnter.connect(...args);
1232
1256
  this._logDebug(`Entered room: ${room}`);
1233
1257
  return toEnter;
1234
1258
  });
@@ -1247,10 +1271,10 @@ var PluvClient = class {
1247
1271
  this._logDebug(`Left and deleted room: ${room}`);
1248
1272
  });
1249
1273
  const { authEndpoint, debug = false, initialStorage, metadata, presence, publicKey, wsEndpoint } = options;
1274
+ this.metadata = metadata;
1250
1275
  this._authEndpoint = authEndpoint;
1251
1276
  this._debug = debug;
1252
1277
  this._initialStorage = initialStorage;
1253
- this._metadata = metadata;
1254
1278
  this._presence = presence;
1255
1279
  this._wsEndpoint = wsEndpoint;
1256
1280
  if (!!publicKey) this._publicKey = publicKey;
@@ -1299,6 +1323,7 @@ var MockedRoom = class extends AbstractRoom {
1299
1323
  user: null
1300
1324
  },
1301
1325
  connection: {
1326
+ attempts: 0,
1302
1327
  count: 1,
1303
1328
  id: null,
1304
1329
  state: "Untouched" /* Untouched */