@pluv/client 0.38.14 → 0.39.1

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/src/PluvRoom.ts CHANGED
@@ -10,6 +10,7 @@ import type {
10
10
  InferIOAuthorizeUser,
11
11
  InferIOInput,
12
12
  InferIOOutput,
13
+ InputZodLike,
13
14
  JsonObject,
14
15
  } from "@pluv/types";
15
16
  import { AbstractRoom } from "./AbstractRoom";
@@ -40,13 +41,18 @@ import type {
40
41
  UserInfo,
41
42
  WebSocketConnection,
42
43
  WebSocketState,
44
+ WithMetadata,
43
45
  } from "./types";
44
46
  import { debounce } from "./utils";
45
47
 
46
48
  const ADD_TO_STORAGE_STATE_DEBOUNCE_MS = 1_000;
47
49
  const HEARTBEAT_INTERVAL_MS = 10_000;
48
50
  const PONG_TIMEOUT_MS = 2_000;
49
- const RECONNECT_TIMEOUT_MS = 30_000;
51
+
52
+ const RECONNECT_TIMEOUT_MS = 5_000;
53
+ const MIN_RECONNECT_TIMEOUT_MS = 1_000;
54
+ const MAX_RECONNECT_TIMEOUT_MS = 60_000;
55
+
50
56
  const ORIGIN_INITIALIZED = "$initialized";
51
57
  const ORIGIN_STORAGE_UPDATED = "$storageUpdated";
52
58
 
@@ -69,7 +75,11 @@ export const DEFAULT_PLUV_CLIENT_ADDON = <
69
75
  storage: new StorageStore(input.room.id),
70
76
  });
71
77
 
78
+ interface CloseWsOptions {
79
+ connectionState?: ConnectionState;
80
+ }
72
81
  interface WindowListeners {
82
+ onNavigatorOffline: () => void;
73
83
  onNavigatorOnline: () => void;
74
84
  onVisibilityChange: () => void;
75
85
  }
@@ -137,6 +147,19 @@ export type PluvRoomDebug<TIO extends IOLike> = Id<{
137
147
  input: readonly (keyof InferIOInput<TIO>)[];
138
148
  }>;
139
149
 
150
+ type GetPublickKeyParams<TMetadata extends JsonObject = {}> = WithMetadata<TMetadata>;
151
+ type GetAuthEndpointParams<TMetadata extends JsonObject = {}> = WithMetadata<TMetadata>;
152
+ type GetWsEndpointParams<TMetadata extends JsonObject = {}> = WithMetadata<TMetadata>;
153
+
154
+ export type RoomConnectParams<TMetadata extends JsonObject = {}> = keyof TMetadata extends never
155
+ ? []
156
+ : [WithMetadata<TMetadata>];
157
+
158
+ export interface ReconnectTimeoutMsParams {
159
+ attempts: number;
160
+ }
161
+ export type ReconnectTimeoutMs = number | ((params: ReconnectTimeoutMsParams) => number);
162
+
140
163
  export type RoomConfig<
141
164
  TIO extends IOLike,
142
165
  TMetadata extends JsonObject = {},
@@ -148,12 +171,13 @@ export type RoomConfig<
148
171
  addons?: readonly PluvRoomAddon<TIO, TMetadata, TPresence, TStorage>[];
149
172
  debug?: boolean | PluvRoomDebug<TIO>;
150
173
  onAuthorizationFail?: (error: Error) => void;
174
+ metadata?: InputZodLike<TMetadata>;
151
175
  publicKey?: PublicKey<TMetadata>;
176
+ reconnectTimeoutMs?: ReconnectTimeoutMs;
152
177
  router?: PluvRouter<TIO, TPresence, TStorage, TEvents>;
153
178
  } & RoomEndpoints<TIO, TMetadata> &
154
179
  Pick<CrdtManagerOptions<TStorage>, "initialStorage"> &
155
- UsersManagerConfig<TPresence> &
156
- (keyof TMetadata extends never ? { metadata?: undefined } : { metadata: TMetadata })
180
+ UsersManagerConfig<TPresence>
157
181
  >;
158
182
 
159
183
  export class PluvRoom<
@@ -165,6 +189,8 @@ export class PluvRoom<
165
189
  > extends AbstractRoom<TIO, TPresence, TStorage> {
166
190
  readonly _endpoints: RoomEndpoints<TIO, TMetadata>;
167
191
 
192
+ public readonly metadata?: InputZodLike<TMetadata>;
193
+
168
194
  private readonly _crdtManager: CrdtManager<TStorage>;
169
195
  private readonly _crdtNotifier = new CrdtNotifier<TStorage>();
170
196
  private readonly _debug: boolean | PluvRoomDebug<TIO>;
@@ -173,9 +199,9 @@ export class PluvRoom<
173
199
  heartbeat: null,
174
200
  };
175
201
  private readonly _listeners: InternalListeners;
176
- private readonly _metadata: TMetadata = {} as TMetadata;
177
202
  private readonly _otherNotifier = new OtherNotifier<TIO>();
178
203
  private readonly _publicKey: PublicKey<TMetadata> | null = null;
204
+ private readonly _reconnectTimeoutMs: ReconnectTimeoutMs;
179
205
  private readonly _router: PluvRouter<TIO, TPresence, TStorage, TEvents>;
180
206
  private readonly _stateNotifier = new StateNotifier<TIO, TPresence>();
181
207
  private readonly _storageStore: AbstractStorageStore;
@@ -188,12 +214,14 @@ export class PluvRoom<
188
214
  };
189
215
  private readonly _usersManager: UsersManager<TIO, TPresence>;
190
216
 
217
+ private _lastMetadata: TMetadata | null = null;
191
218
  private _state: WebSocketState<TIO> = {
192
219
  authorization: {
193
220
  token: null,
194
221
  user: null,
195
222
  },
196
223
  connection: {
224
+ attempts: 0,
197
225
  count: 0,
198
226
  id: null,
199
227
  state: ConnectionState.Untouched,
@@ -214,6 +242,7 @@ export class PluvRoom<
214
242
  onAuthorizationFail,
215
243
  presence,
216
244
  publicKey,
245
+ reconnectTimeoutMs = RECONNECT_TIMEOUT_MS,
217
246
  router,
218
247
  wsEndpoint,
219
248
  } = options;
@@ -227,21 +256,17 @@ export class PluvRoom<
227
256
  ...addon({ room: this }),
228
257
  };
229
258
 
259
+ this.metadata = metadata;
260
+
230
261
  this._debug = debug;
231
262
  this._endpoints = { authEndpoint, wsEndpoint } as RoomEndpoints<TIO, TMetadata>;
263
+ this._reconnectTimeoutMs = reconnectTimeoutMs;
232
264
  this._storageStore = storage;
233
265
 
234
- if (typeof metadata !== "undefined") this._metadata = metadata;
235
266
  if (!!publicKey) this._publicKey = publicKey;
236
267
 
237
268
  this._listeners = {
238
269
  onAuthorizationFail: (error) => {
239
- this._clearTimeout(this._timeouts.reconnect);
240
- this._timeouts.reconnect = setTimeout(
241
- this._reconnect.bind(this),
242
- RECONNECT_TIMEOUT_MS,
243
- ) as unknown as number;
244
-
245
270
  onAuthorizationFail?.(error);
246
271
  },
247
272
  };
@@ -328,10 +353,13 @@ export class PluvRoom<
328
353
  return !!this._crdtManager.doc.canUndo();
329
354
  };
330
355
 
331
- public async connect(): Promise<void> {
332
- if (!this._endpoints.wsEndpoint) {
333
- throw new Error("Must provide an wsEndpoint.");
334
- }
356
+ public async connect(...args: RoomConnectParams<TMetadata>): Promise<void> {
357
+ const params = (args[0] ?? {}) as WithMetadata<TMetadata>;
358
+ const metadata = params.metadata as TMetadata;
359
+
360
+ this._setMetadata(metadata);
361
+
362
+ if (!this._endpoints.wsEndpoint) throw new Error("Must provide a wsEndpoint.");
335
363
 
336
364
  const canConnect = [ConnectionState.Closed, ConnectionState.Untouched].some(
337
365
  (state) => this._state.connection.state === state,
@@ -340,7 +368,6 @@ export class PluvRoom<
340
368
  if (!canConnect) return;
341
369
 
342
370
  this._clearTimeout(this._timeouts.reconnect);
343
-
344
371
  this._updateState((oldState) => {
345
372
  oldState.connection.state = ConnectionState.Connecting;
346
373
 
@@ -350,29 +377,36 @@ export class PluvRoom<
350
377
  await this._storageStore.initialize();
351
378
  await this._applyStorageStore();
352
379
 
353
- const url = new URL(this._getWsEndpoint(this.id));
380
+ const url = new URL(this._getWsEndpoint(this.id, params));
354
381
 
355
382
  let authToken: string | null = null;
356
383
  let webSocket: WebSocket;
357
384
 
358
385
  try {
359
- const publicKey = this._getPublicKey();
386
+ const publicKey = this._getPublicKey(params);
360
387
 
361
- authToken = await this._getAuthorization(this.id);
388
+ authToken = await this._getAuthorization(this.id, params);
362
389
 
363
390
  authToken && url.searchParams.set("token", encodeURIComponent(authToken));
364
391
  publicKey && url.searchParams.set("public_key", encodeURIComponent(publicKey));
365
392
 
366
393
  webSocket = new WebSocket(url.toString());
367
394
  } catch (err) {
368
- await this._storageStore.destroy();
395
+ this._updateState((oldState) => {
396
+ oldState.connection.attempts += 1;
369
397
 
370
- this._onAuthorizationFail(err);
398
+ return oldState;
399
+ });
400
+
401
+ await this._storageStore.destroy();
402
+ this._pollReconnect();
403
+ await Promise.resolve(this._onAuthorizationFail(err));
371
404
 
372
405
  return;
373
406
  }
374
407
 
375
408
  this._updateState((oldState) => {
409
+ oldState.connection.attempts = 0;
376
410
  oldState.authorization.token = authToken;
377
411
  oldState.webSocket = webSocket;
378
412
 
@@ -384,10 +418,7 @@ export class PluvRoom<
384
418
  }
385
419
 
386
420
  public async disconnect(): Promise<void> {
387
- if (!this._endpoints.wsEndpoint) {
388
- throw new Error("Must provide an wsEndpoint.");
389
- }
390
-
421
+ if (!this._endpoints.wsEndpoint) throw new Error("Must provide a wsEndpoint.");
391
422
  if (!this._state.webSocket) return;
392
423
 
393
424
  const canDisconnect = [ConnectionState.Connecting, ConnectionState.Open].some(
@@ -396,14 +427,19 @@ export class PluvRoom<
396
427
 
397
428
  if (!canDisconnect) return;
398
429
 
430
+ this._closeWs();
399
431
  this._clearInterval(this._intervals.heartbeat);
432
+ this._clearTimeout(this._timeouts.reconnect);
433
+ this._updateState((oldState) => {
434
+ oldState.authorization.token = null;
435
+ oldState.connection.state = ConnectionState.Closed;
436
+ oldState.webSocket = null;
400
437
 
401
- this._closeWs();
438
+ return oldState;
439
+ });
402
440
 
403
441
  await this._storageStore.destroy();
404
-
405
442
  this._crdtManager.destroy();
406
-
407
443
  this._stateNotifier.subjects["my-presence"].next(null);
408
444
  }
409
445
 
@@ -571,10 +607,12 @@ export class PluvRoom<
571
607
  if (typeof document === "undefined") return;
572
608
 
573
609
  this._windowListeners = {
610
+ onNavigatorOffline: this._onNavigatorOffline.bind(this),
574
611
  onNavigatorOnline: this._onNavigatorOnline.bind(this),
575
612
  onVisibilityChange: this._onVisibilityChange.bind(this),
576
613
  };
577
614
 
615
+ window.addEventListener("offline", this._windowListeners.onNavigatorOffline);
578
616
  window.addEventListener("online", this._windowListeners.onNavigatorOnline);
579
617
  document.addEventListener("visibilitychange", this._windowListeners.onVisibilityChange);
580
618
  }
@@ -608,26 +646,17 @@ export class PluvRoom<
608
646
  }
609
647
 
610
648
  private _closeWs(): void {
611
- if (!this._state.webSocket) return;
612
-
613
649
  this._subscriptions.observeCrdt?.();
614
650
 
651
+ this._clearInterval(this._intervals.heartbeat);
615
652
  this._clearTimeout(this._timeouts.pong);
616
- this._clearTimeout(this._timeouts.reconnect);
617
-
618
653
  this._detachWindowListeners();
619
-
620
654
  this._usersManager.removeMyself();
621
655
  this._usersManager.removeUsers();
622
656
  this._otherNotifier.clear();
623
657
  this._stateNotifier.subjects.myself.next(null);
624
658
  this._stateNotifier.subjects.others.next([]);
625
-
626
- const canClose = [WebSocket.CONNECTING, WebSocket.OPEN].some(
627
- (readyState) => readyState === this._state.webSocket?.readyState,
628
- );
629
-
630
- if (canClose) this._state.webSocket.close();
659
+ this._state.webSocket?.close();
631
660
 
632
661
  this._detachWsListeners();
633
662
  }
@@ -700,7 +729,9 @@ export class PluvRoom<
700
729
  );
701
730
  };
702
731
 
703
- private _getAuthFetchOptions(room: string): FetchOptions | null {
732
+ private _getAuthFetchOptions(room: string, params: GetAuthEndpointParams<TMetadata>): FetchOptions | null {
733
+ const metadata = params.metadata as TMetadata;
734
+
704
735
  if (typeof this._endpoints.authEndpoint === "undefined") return null;
705
736
 
706
737
  if (this._endpoints.authEndpoint === true) {
@@ -717,23 +748,20 @@ export class PluvRoom<
717
748
  };
718
749
  }
719
750
 
720
- const result = this._endpoints.authEndpoint({ metadata: this._metadata, room });
751
+ const result = this._endpoints.authEndpoint({ metadata, room });
721
752
 
722
753
  return typeof result === "string" ? { url: result, options: {} } : result;
723
754
  }
724
755
 
725
- private async _getAuthorization(room: string): Promise<string | null> {
726
- const fetchOptions = this._getAuthFetchOptions(room);
756
+ private async _getAuthorization(room: string, params: GetAuthEndpointParams<TMetadata>): Promise<string | null> {
757
+ const fetchOptions = this._getAuthFetchOptions(room, params);
727
758
 
728
759
  if (!fetchOptions) return null;
729
760
 
730
761
  const { url, options } = fetchOptions;
731
-
732
762
  const res = await fetch(url, options);
733
763
 
734
- if (!res.ok || res.status !== 200) {
735
- throw new Error("Room is unauthorized");
736
- }
764
+ if (!res.ok || res.status !== 200) throw new Error("Room is unauthorized");
737
765
 
738
766
  try {
739
767
  return await res.text().then((text) => text.trim());
@@ -742,21 +770,25 @@ export class PluvRoom<
742
770
  }
743
771
  }
744
772
 
745
- private _getPublicKey(): string | null {
773
+ private _getPublicKey(params: GetPublickKeyParams<TMetadata>): string | null {
774
+ const metadata = params.metadata as TMetadata;
775
+
746
776
  if (!this._publicKey) return null;
747
777
  if (typeof this._publicKey === "string") return this._publicKey;
748
778
 
749
- return this._publicKey({ metadata: this._metadata });
779
+ return this._publicKey({ metadata });
750
780
  }
751
781
 
752
- private _getWsEndpoint(room: string): string {
782
+ private _getWsEndpoint(room: string, params: GetWsEndpointParams<TMetadata>): string {
783
+ const metadata = params.metadata as TMetadata;
784
+
753
785
  switch (typeof this._endpoints.wsEndpoint) {
754
786
  case "undefined":
755
- return !!this._getPublicKey() ? `wss://rooms.pluv.io/api/room/${room}` : `/api/pluv/room/${room}`;
787
+ return !!this._getPublicKey(params) ? `wss://rooms.pluv.io/api/room/${room}` : `/api/pluv/room/${room}`;
756
788
  case "string":
757
789
  return this._endpoints.wsEndpoint;
758
790
  default:
759
- return this._endpoints.wsEndpoint({ metadata: this._metadata, room });
791
+ return this._endpoints.wsEndpoint({ metadata, room });
760
792
  }
761
793
  }
762
794
 
@@ -1016,25 +1048,33 @@ export class PluvRoom<
1016
1048
  });
1017
1049
  }
1018
1050
 
1019
- /**
1020
- * TODO
1021
- * @description Determine if ws was closed with a code for closing without retries.
1022
- * Otherwise, attempt to retry again with incremental backoff
1023
- * @author leedavidcs
1024
- * @date August 19, 2022
1025
- */
1026
1051
  private async _onClose(event: CloseEvent): Promise<void> {
1027
1052
  this._logDebug("WebSocket closed");
1028
1053
  !!event.reason && this._logDebug(event.reason);
1029
1054
 
1030
- this._clearInterval(this._intervals.heartbeat);
1031
- this._clearTimeout(this._timeouts.pong);
1055
+ const shouldRetry = [
1056
+ // Going away: Client/server is shutting down or navigating
1057
+ 1001,
1058
+ // Abnormal closure: Usually network error
1059
+ 1006,
1060
+ // Internal error: may be a larger problem, but try reconnecting for now
1061
+ 1011,
1062
+ // Service restart: Server restarted
1063
+ 1012,
1064
+ // Try again later: server overloaded
1065
+ 1013,
1066
+ // Bad gateway
1067
+ 1014,
1068
+ ].some((okToRetryCode) => event.code === okToRetryCode);
1069
+
1070
+ this._closeWs();
1032
1071
 
1033
- this._stateNotifier.subjects["myself"].next(null);
1072
+ if (!shouldRetry) this._clearTimeout(this._timeouts.reconnect);
1073
+ else this._pollReconnect();
1034
1074
 
1035
1075
  this._updateState((oldState) => {
1036
1076
  oldState.authorization.token = null;
1037
- oldState.connection.state = ConnectionState.Closed;
1077
+ oldState.connection.state = shouldRetry ? ConnectionState.Unavailable : ConnectionState.Closed;
1038
1078
  oldState.webSocket = null;
1039
1079
 
1040
1080
  return oldState;
@@ -1044,7 +1084,6 @@ export class PluvRoom<
1044
1084
  /**
1045
1085
  * TODO
1046
1086
  * @description Handle websocket errors in a meaningful way, should they occur
1047
- * @author leedavidcs
1048
1087
  * @date August 19, 2022
1049
1088
  */
1050
1089
  private _onError(): void {}
@@ -1115,10 +1154,18 @@ export class PluvRoom<
1115
1154
  this._intervals.heartbeat = setInterval(this._heartbeat.bind(this), HEARTBEAT_INTERVAL_MS) as unknown as number;
1116
1155
  }
1117
1156
 
1157
+ private async _onNavigatorOffline(): Promise<void> {
1158
+ if (this._state.connection.state === ConnectionState.Closed) return;
1159
+
1160
+ this._updateState((oldState) => {
1161
+ oldState.connection.state = ConnectionState.Unavailable;
1162
+
1163
+ return oldState;
1164
+ });
1165
+ }
1166
+
1118
1167
  private async _onNavigatorOnline(): Promise<void> {
1119
- if (this._state.connection.state !== ConnectionState.Unavailable) {
1120
- return;
1121
- }
1168
+ if (this._state.connection.state !== ConnectionState.Unavailable) return;
1122
1169
 
1123
1170
  await this._reconnect();
1124
1171
  }
@@ -1145,7 +1192,6 @@ export class PluvRoom<
1145
1192
  /**
1146
1193
  * !HACK
1147
1194
  * @description We'll only handle stringified JSONs for now
1148
- * @author leedavidcs
1149
1195
  * @date August 9, 2022
1150
1196
  */
1151
1197
  if (typeof message.data !== "string") return null;
@@ -1163,6 +1209,18 @@ export class PluvRoom<
1163
1209
  }
1164
1210
  }
1165
1211
 
1212
+ private _pollReconnect(): void {
1213
+ this._clearTimeout(this._timeouts.reconnect);
1214
+
1215
+ const timeoutMs =
1216
+ typeof this._reconnectTimeoutMs === "number"
1217
+ ? this._reconnectTimeoutMs
1218
+ : this._reconnectTimeoutMs({ attempts: this._state.connection.attempts });
1219
+ const clampedMs = Math.max(Math.min(MIN_RECONNECT_TIMEOUT_MS, timeoutMs), MAX_RECONNECT_TIMEOUT_MS);
1220
+
1221
+ this._timeouts.reconnect = setTimeout(this._reconnect.bind(this), clampedMs) as unknown as number;
1222
+ }
1223
+
1166
1224
  private async _reconnect(): Promise<void> {
1167
1225
  if (!this._endpoints.wsEndpoint) {
1168
1226
  throw new Error("Must provide an wsEndpoint.");
@@ -1172,15 +1230,10 @@ export class PluvRoom<
1172
1230
 
1173
1231
  this._closeWs();
1174
1232
 
1175
- this._updateState((oldState) => {
1176
- oldState.authorization.token = null;
1177
- oldState.connection.state = ConnectionState.Closed;
1178
- oldState.webSocket = null;
1179
-
1180
- return oldState;
1181
- });
1233
+ const metadata = this._lastMetadata;
1234
+ const params = (typeof metadata === "undefined" ? [] : [{ metadata }]) as RoomConnectParams<TMetadata>;
1182
1235
 
1183
- await this.connect();
1236
+ await this.connect(...params);
1184
1237
  }
1185
1238
 
1186
1239
  private _sendMessage(data: string): void;
@@ -1215,6 +1268,10 @@ export class PluvRoom<
1215
1268
  webSocket.send(message);
1216
1269
  }
1217
1270
 
1271
+ private _setMetadata(metadata: TMetadata): TMetadata {
1272
+ return this.metadata ? this.metadata.parse(metadata) : metadata;
1273
+ }
1274
+
1218
1275
  private _updateState(updater: (oldState: WebSocketState<TIO>) => WebSocketState<TIO>): WebSocketState<TIO> {
1219
1276
  let authorization: AuthorizationState<TIO>;
1220
1277
 
package/src/index.ts CHANGED
@@ -9,11 +9,18 @@ export type { InferCallback } from "./infer";
9
9
  export { MockedRoom } from "./MockedRoom";
10
10
  export type { MockedRoomConfig, MockedRoomEvents } from "./MockedRoom";
11
11
  export { PluvClient } from "./PluvClient";
12
- export type { CreateRoomOptions, PluvClientOptions } from "./PluvClient";
12
+ export type { CreateRoomOptions, EnterRoomParams, PluvClientOptions } from "./PluvClient";
13
13
  export type { PluvProcedureConfig } from "./PluvProcedure";
14
14
  export { PluvRoom } from "./PluvRoom";
15
- export type { PluvRoomAddon, PluvRoomAddonInput, PluvRoomAddonResult, PluvRoomDebug, RoomEndpoints } from "./PluvRoom";
15
+ export type {
16
+ PluvRoomAddon,
17
+ PluvRoomAddonInput,
18
+ PluvRoomAddonResult,
19
+ PluvRoomDebug,
20
+ RoomConnectParams,
21
+ RoomEndpoints,
22
+ } from "./PluvRoom";
16
23
  export type { MergeEvents, PluvRouter, PluvRouterEventConfig } from "./PluvRouter";
17
24
  export { register } from "./register";
18
25
  export type { RegisterParams } from "./register";
19
- export type { PublicKey, PublicKeyParams, UserInfo, WebSocketConnection, WebSocketState } from "./types";
26
+ export type { InferMetadata, PublicKey, PublicKeyParams, UserInfo, WebSocketConnection, WebSocketState } from "./types";
package/src/types.ts CHANGED
@@ -9,6 +9,7 @@ import type {
9
9
  MaybePromise,
10
10
  } from "@pluv/types";
11
11
  import type { ConnectionState } from "./enums";
12
+ import type { PluvClient } from "./PluvClient";
12
13
 
13
14
  export interface AuthorizationState<TIO extends IOLike> {
14
15
  token: string | null;
@@ -34,6 +35,9 @@ export interface EventResolverContext<
34
35
  user: UserInfo<TIO, TPresence>;
35
36
  }
36
37
 
38
+ export type InferMetadata<TClient extends PluvClient<any, any, any, any>> =
39
+ TClient extends PluvClient<any, any, any, infer IMetadata> ? IMetadata : never;
40
+
37
41
  export interface InternalSubscriptions {
38
42
  observeCrdt: (() => void) | null;
39
43
  }
@@ -69,6 +73,19 @@ export interface UserInfo<TIO extends IOLike, TPresence extends JsonObject = {}>
69
73
  }
70
74
 
71
75
  export interface WebSocketConnection {
76
+ /**
77
+ * @description How many times a connection attempt was made. This will increment upon each
78
+ * unsuccessful attempt, and will reset upon a successful connection.
79
+ * @date April 13, 2025
80
+ */
81
+ attempts: number;
82
+ /**
83
+ * @description How many times the user has connected to the room. This can be more than 1 in
84
+ * the case of reconnects. This is tracked because on the very first connection (i.e. when
85
+ * count is 0) we don't want to send the storage state as an update to the room and overwrite
86
+ * the upstream state.
87
+ * @date April 13, 2025
88
+ */
72
89
  count: number;
73
90
  id: string | null;
74
91
  state: ConnectionState;
@@ -79,3 +96,7 @@ export interface WebSocketState<TIO extends IOLike> {
79
96
  connection: WebSocketConnection;
80
97
  webSocket: WebSocket | null;
81
98
  }
99
+
100
+ export type WithMetadata<TMetadata extends JsonObject = {}> = keyof TMetadata extends never
101
+ ? { metadata?: undefined }
102
+ : { metadata: TMetadata };
package/.eslintrc.js DELETED
@@ -1,4 +0,0 @@
1
- module.exports = {
2
- root: true,
3
- extends: ["pluv"],
4
- };