@pluv/io 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.
@@ -1,9 +1,9 @@
1
1
 
2
- > @pluv/io@0.38.14 build /home/runner/work/pluv/pluv/packages/io
2
+ > @pluv/io@0.39.1 build /home/runner/work/pluv/pluv/packages/io
3
3
  > pnpm run version && tsup src/index.ts --format esm,cjs --dts
4
4
 
5
5
 
6
- > @pluv/io@0.38.14 version /home/runner/work/pluv/pluv/packages/io
6
+ > @pluv/io@0.39.1 version /home/runner/work/pluv/pluv/packages/io
7
7
  > genversion ./src/version.generated.ts --esm
8
8
 
9
9
  CLI Building entry: src/index.ts
@@ -12,11 +12,11 @@
12
12
  CLI Target: es6
13
13
  ESM Build start
14
14
  CJS Build start
15
- ESM dist/index.mjs 44.29 KB
16
- ESM ⚡️ Build success in 118ms
17
- CJS dist/index.js 46.46 KB
18
- CJS ⚡️ Build success in 132ms
15
+ CJS dist/index.js 46.56 KB
16
+ CJS ⚡️ Build success in 144ms
17
+ ESM dist/index.mjs 44.38 KB
18
+ ESM ⚡️ Build success in 145ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 2518ms
21
- DTS dist/index.d.mts 30.95 KB
22
- DTS dist/index.d.ts 30.95 KB
20
+ DTS ⚡️ Build success in 2601ms
21
+ DTS dist/index.d.mts 30.93 KB
22
+ DTS dist/index.d.ts 30.93 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @pluv/io
2
2
 
3
+ ## 0.39.1
4
+
5
+ ### Patch Changes
6
+
7
+ - @pluv/crdt@0.39.1
8
+ - @pluv/types@0.39.1
9
+
10
+ ## 0.39.0
11
+
12
+ ### Patch Changes
13
+
14
+ - fe6e239: Broadcast the quitting user when the server receives an abnormal message event from a session that was marked as having quit.
15
+ - 737d508: Fix not always fully running clean-up effects on the `IORoom` after websocket sessions are closed.
16
+ - @pluv/crdt@0.39.0
17
+ - @pluv/types@0.39.0
18
+
3
19
  ## 0.38.14
4
20
 
5
21
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -7,10 +7,10 @@ declare abstract class AbstractPersistence {
7
7
  abstract deleteStorageState(room: string): Promise<void>;
8
8
  abstract deleteUser(room: string, connectionId: string): Promise<void>;
9
9
  abstract deleteUsers(room: string): Promise<void>;
10
- abstract getSize(room: string): Promise<number>;
11
10
  abstract getStorageState(room: string): Promise<string | null>;
12
11
  abstract getUser(room: string, connectionId: string): Promise<JsonObject | null>;
13
12
  abstract getUsers(room: string): Promise<readonly (JsonObject | null)[]>;
13
+ abstract getUsersSize(room: string): Promise<number>;
14
14
  abstract setStorageState(room: string, state: string): Promise<void>;
15
15
  }
16
16
 
@@ -72,7 +72,6 @@ declare abstract class AbstractWebSocket<TWebSocket = any> {
72
72
  * method, because the implementing class might have to transform the
73
73
  * listener, and removing the same listener might be complicated as a
74
74
  * result.
75
- * @author leedavidcs
76
75
  * @date August 28, 2022
77
76
  */
78
77
  abstract addEventListener<TType extends EventType>(type: TType, handler: AbstractListener<TType>): void;
@@ -330,7 +329,7 @@ type IORoomConfig<TPlatform extends AbstractPlatform<any> = AbstractPlatform<any
330
329
  roomContext: InferRoomContextType<TPlatform>;
331
330
  router: PluvRouter<TPlatform, TAuthorize, TContext, TEvents>;
332
331
  };
333
- type WebsocketRegisterConfig<TPlatform extends AbstractPlatform<any> = AbstractPlatform<any>> = {
332
+ type WebSocketRegisterConfig<TPlatform extends AbstractPlatform<any> = AbstractPlatform<any>> = {
334
333
  token?: string | null;
335
334
  } & InferInitContextType<TPlatform>;
336
335
  declare class IORoom<TPlatform extends AbstractPlatform<any> = AbstractPlatform<any>, TAuthorize extends PluvIOAuthorize<TPlatform, any, InferInitContextType<TPlatform>> | null = null, TContext extends Record<string, any> = {}, TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext> = {}> implements IOLike<TAuthorize, TEvents> {
@@ -375,12 +374,12 @@ declare class IORoom<TPlatform extends AbstractPlatform<any> = AbstractPlatform<
375
374
  onMessage(webSocket: WebSocketType<TPlatform>): (event: AbstractMessageEvent) => Promise<void>;
376
375
  register(webSocket: InferPlatformWebSocketSource<TPlatform>, ...options: keyof InferInitContextType<TPlatform> extends never ? [{
377
376
  token?: string;
378
- }?] : [WebsocketRegisterConfig<TPlatform>]): Promise<void>;
377
+ }?] : [WebSocketRegisterConfig<TPlatform>]): Promise<void>;
379
378
  private _broadcast;
379
+ private _closeWebSockets;
380
380
  private _emitQuitters;
381
381
  private _emitRegistered;
382
382
  private _ensureDetached;
383
- private _exitWebsocket;
384
383
  private _getAbstractWs;
385
384
  private _getAuthorizedUser;
386
385
  private _getInitialDoc;
package/dist/index.d.ts CHANGED
@@ -7,10 +7,10 @@ declare abstract class AbstractPersistence {
7
7
  abstract deleteStorageState(room: string): Promise<void>;
8
8
  abstract deleteUser(room: string, connectionId: string): Promise<void>;
9
9
  abstract deleteUsers(room: string): Promise<void>;
10
- abstract getSize(room: string): Promise<number>;
11
10
  abstract getStorageState(room: string): Promise<string | null>;
12
11
  abstract getUser(room: string, connectionId: string): Promise<JsonObject | null>;
13
12
  abstract getUsers(room: string): Promise<readonly (JsonObject | null)[]>;
13
+ abstract getUsersSize(room: string): Promise<number>;
14
14
  abstract setStorageState(room: string, state: string): Promise<void>;
15
15
  }
16
16
 
@@ -72,7 +72,6 @@ declare abstract class AbstractWebSocket<TWebSocket = any> {
72
72
  * method, because the implementing class might have to transform the
73
73
  * listener, and removing the same listener might be complicated as a
74
74
  * result.
75
- * @author leedavidcs
76
75
  * @date August 28, 2022
77
76
  */
78
77
  abstract addEventListener<TType extends EventType>(type: TType, handler: AbstractListener<TType>): void;
@@ -330,7 +329,7 @@ type IORoomConfig<TPlatform extends AbstractPlatform<any> = AbstractPlatform<any
330
329
  roomContext: InferRoomContextType<TPlatform>;
331
330
  router: PluvRouter<TPlatform, TAuthorize, TContext, TEvents>;
332
331
  };
333
- type WebsocketRegisterConfig<TPlatform extends AbstractPlatform<any> = AbstractPlatform<any>> = {
332
+ type WebSocketRegisterConfig<TPlatform extends AbstractPlatform<any> = AbstractPlatform<any>> = {
334
333
  token?: string | null;
335
334
  } & InferInitContextType<TPlatform>;
336
335
  declare class IORoom<TPlatform extends AbstractPlatform<any> = AbstractPlatform<any>, TAuthorize extends PluvIOAuthorize<TPlatform, any, InferInitContextType<TPlatform>> | null = null, TContext extends Record<string, any> = {}, TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext> = {}> implements IOLike<TAuthorize, TEvents> {
@@ -375,12 +374,12 @@ declare class IORoom<TPlatform extends AbstractPlatform<any> = AbstractPlatform<
375
374
  onMessage(webSocket: WebSocketType<TPlatform>): (event: AbstractMessageEvent) => Promise<void>;
376
375
  register(webSocket: InferPlatformWebSocketSource<TPlatform>, ...options: keyof InferInitContextType<TPlatform> extends never ? [{
377
376
  token?: string;
378
- }?] : [WebsocketRegisterConfig<TPlatform>]): Promise<void>;
377
+ }?] : [WebSocketRegisterConfig<TPlatform>]): Promise<void>;
379
378
  private _broadcast;
379
+ private _closeWebSockets;
380
380
  private _emitQuitters;
381
381
  private _emitRegistered;
382
382
  private _ensureDetached;
383
- private _exitWebsocket;
384
383
  private _getAbstractWs;
385
384
  private _getAuthorizedUser;
386
385
  private _getInitialDoc;
package/dist/index.js CHANGED
@@ -220,7 +220,7 @@ var AbstractPlatform = class {
220
220
  this._initialized = false;
221
221
  const { roomContext, persistence, pubSub } = config;
222
222
  this._meta = config._meta;
223
- roomContext && (this._roomContext = roomContext);
223
+ if (!!roomContext) this._roomContext = roomContext;
224
224
  this.persistence = persistence != null ? persistence : new Persistence();
225
225
  this.pubSub = pubSub != null ? pubSub : new PubSub();
226
226
  }
@@ -594,19 +594,53 @@ var IORoom = class {
594
594
  }, message));
595
595
  });
596
596
  }
597
+ _closeWebSockets(webSockets) {
598
+ return __async(this, null, function* () {
599
+ const closeWebSocket = (webSocket) => __async(this, null, function* () {
600
+ webSocket.state = __spreadProps(__spreadValues({}, webSocket.state), { quit: true });
601
+ const sessionId = webSocket.sessionId;
602
+ this._logDebug(`${import_kleur.default.blue(`Unregistering connection for room ${this.id}:`)} ${sessionId}`);
603
+ this._sessions.delete(sessionId);
604
+ yield this._platform.persistence.deleteUser(this.id, sessionId).catch(() => null);
605
+ yield this._broadcast({
606
+ message: { type: "$exit", data: { sessionId } },
607
+ senderId: sessionId
608
+ });
609
+ try {
610
+ const [session, doc] = yield Promise.all([webSocket.getSession(), this._doc]);
611
+ const encodedState = doc.getEncodedState();
612
+ yield Promise.resolve(
613
+ this._listeners.onUserDisconnected({
614
+ context: this._context,
615
+ encodedState,
616
+ room: this.id,
617
+ user: session.user
618
+ })
619
+ );
620
+ } catch (error) {
621
+ console.error(error);
622
+ }
623
+ this._logDebug(`${import_kleur.default.blue(`Unregistered connection for room ${this.id}:`)} ${webSocket.sessionId}`);
624
+ });
625
+ const promises = webSockets.map((webSocket) => __async(this, null, function* () {
626
+ yield closeWebSocket(webSocket).catch(() => null);
627
+ }));
628
+ yield Promise.all(promises).catch(() => null);
629
+ const size = this.getSize();
630
+ this._logDebug(`${import_kleur.default.blue(`Room ${this.id} size:`)} ${size}`);
631
+ if (!!size) return;
632
+ if (!this._uninitialize) return;
633
+ const uninitialize = (yield this._uninitialize).bind(this);
634
+ yield uninitialize();
635
+ });
636
+ }
597
637
  _emitQuitters() {
598
638
  return __async(this, null, function* () {
599
639
  const currentTime = (/* @__PURE__ */ new Date()).getTime();
600
640
  const quitters = Array.from(this._sessions.values()).filter(
601
641
  (pluvWs) => pluvWs.state.quit || currentTime - pluvWs.state.timers.ping > PING_TIMEOUT_MS
602
642
  );
603
- quitters.forEach((pluvWs) => {
604
- this._sessions.delete(pluvWs.sessionId);
605
- });
606
- const promises = quitters.map((quitter) => __async(this, null, function* () {
607
- yield this._exitWebsocket(quitter);
608
- }));
609
- yield Promise.all(promises);
643
+ this._closeWebSockets(quitters);
610
644
  });
611
645
  }
612
646
  _emitRegistered(pluvWs) {
@@ -636,32 +670,6 @@ var IORoom = class {
636
670
  if (this._platform._config.registrationMode === "detached") return;
637
671
  throw new Error("Platform must use detached mode");
638
672
  }
639
- _exitWebsocket(webSocket) {
640
- return __async(this, null, function* () {
641
- const session = yield webSocket.getSession();
642
- yield this._broadcast({
643
- message: {
644
- type: "$exit",
645
- data: { sessionId: session.id }
646
- },
647
- senderId: session.id
648
- });
649
- try {
650
- const doc = yield this._doc;
651
- const encodedState = doc.getEncodedState();
652
- yield Promise.resolve(
653
- this._listeners.onUserDisconnected({
654
- context: this._context,
655
- encodedState,
656
- room: this.id,
657
- user: session.user
658
- })
659
- );
660
- } catch (err) {
661
- console.error(err);
662
- }
663
- });
664
- }
665
673
  _getAbstractWs(webSocket) {
666
674
  var _a, _b;
667
675
  if (webSocket instanceof AbstractWebSocket) return webSocket;
@@ -796,25 +804,12 @@ var IORoom = class {
796
804
  return { doc: this._doc, uninitialize: this._uninitialize };
797
805
  }
798
806
  _logDebug(...data) {
799
- this._debug && console.log(...data);
807
+ if (this._debug) console.log(...data);
800
808
  }
801
809
  _onClose(webSocket) {
802
810
  return () => __async(this, null, function* () {
803
811
  if (!(yield this._initialized)) return;
804
- webSocket.state = __spreadProps(__spreadValues({}, webSocket.state), { quit: true });
805
- this._logDebug(`${import_kleur.default.blue(`Unregistering connection for room ${this.id}:`)} ${webSocket.sessionId}`);
806
- this._sessions.delete(webSocket.sessionId);
807
- this._platform.persistence.deleteUser(this.id, webSocket.sessionId).finally(() => __async(this, null, function* () {
808
- yield this._exitWebsocket(webSocket);
809
- const size = this.getSize();
810
- this._logDebug(`${import_kleur.default.blue(`Unregistered connection for room ${this.id}:`)} ${webSocket.sessionId}`);
811
- this._logDebug(`${import_kleur.default.blue(`Room ${this.id} size:`)} ${size}`);
812
- if (size) return;
813
- if (this._uninitialize) {
814
- const uninitialize = (yield this._uninitialize).bind(this);
815
- yield uninitialize();
816
- }
817
- }));
812
+ yield this._closeWebSockets([webSocket]);
818
813
  });
819
814
  }
820
815
  _onMessage(webSocket) {
@@ -836,6 +831,7 @@ var IORoom = class {
836
831
  sessions
837
832
  };
838
833
  if (pluvWs.state.quit) {
834
+ yield this._closeWebSockets([pluvWs]).catch(() => null);
839
835
  pluvWs.close(1011, "WebSocket broken.");
840
836
  return;
841
837
  }
@@ -1009,7 +1005,7 @@ var IORoom = class {
1009
1005
  };
1010
1006
 
1011
1007
  // src/version.generated.ts
1012
- var version = "0.38.14";
1008
+ var version = "0.39.1";
1013
1009
 
1014
1010
  // src/PluvServer.ts
1015
1011
  var PluvServer = class {
@@ -1207,16 +1203,14 @@ var PluvServer = class {
1207
1203
  });
1208
1204
  }
1209
1205
  _getIOAuthorize(options) {
1210
- if (typeof this._authorize === "function") {
1211
- return this._authorize(options);
1212
- }
1206
+ if (typeof this._authorize === "function") return this._authorize(options);
1213
1207
  return this._authorize;
1214
1208
  }
1215
1209
  _getListeners() {
1216
1210
  return this._listeners;
1217
1211
  }
1218
1212
  _logDebug(...data) {
1219
- this._debug && console.log(...data);
1213
+ if (this._debug) console.log(...data);
1220
1214
  }
1221
1215
  };
1222
1216
 
package/dist/index.mjs CHANGED
@@ -178,7 +178,7 @@ var AbstractPlatform = class {
178
178
  this._initialized = false;
179
179
  const { roomContext, persistence, pubSub } = config;
180
180
  this._meta = config._meta;
181
- roomContext && (this._roomContext = roomContext);
181
+ if (!!roomContext) this._roomContext = roomContext;
182
182
  this.persistence = persistence != null ? persistence : new Persistence();
183
183
  this.pubSub = pubSub != null ? pubSub : new PubSub();
184
184
  }
@@ -552,19 +552,53 @@ var IORoom = class {
552
552
  }, message));
553
553
  });
554
554
  }
555
+ _closeWebSockets(webSockets) {
556
+ return __async(this, null, function* () {
557
+ const closeWebSocket = (webSocket) => __async(this, null, function* () {
558
+ webSocket.state = __spreadProps(__spreadValues({}, webSocket.state), { quit: true });
559
+ const sessionId = webSocket.sessionId;
560
+ this._logDebug(`${colors.blue(`Unregistering connection for room ${this.id}:`)} ${sessionId}`);
561
+ this._sessions.delete(sessionId);
562
+ yield this._platform.persistence.deleteUser(this.id, sessionId).catch(() => null);
563
+ yield this._broadcast({
564
+ message: { type: "$exit", data: { sessionId } },
565
+ senderId: sessionId
566
+ });
567
+ try {
568
+ const [session, doc] = yield Promise.all([webSocket.getSession(), this._doc]);
569
+ const encodedState = doc.getEncodedState();
570
+ yield Promise.resolve(
571
+ this._listeners.onUserDisconnected({
572
+ context: this._context,
573
+ encodedState,
574
+ room: this.id,
575
+ user: session.user
576
+ })
577
+ );
578
+ } catch (error) {
579
+ console.error(error);
580
+ }
581
+ this._logDebug(`${colors.blue(`Unregistered connection for room ${this.id}:`)} ${webSocket.sessionId}`);
582
+ });
583
+ const promises = webSockets.map((webSocket) => __async(this, null, function* () {
584
+ yield closeWebSocket(webSocket).catch(() => null);
585
+ }));
586
+ yield Promise.all(promises).catch(() => null);
587
+ const size = this.getSize();
588
+ this._logDebug(`${colors.blue(`Room ${this.id} size:`)} ${size}`);
589
+ if (!!size) return;
590
+ if (!this._uninitialize) return;
591
+ const uninitialize = (yield this._uninitialize).bind(this);
592
+ yield uninitialize();
593
+ });
594
+ }
555
595
  _emitQuitters() {
556
596
  return __async(this, null, function* () {
557
597
  const currentTime = (/* @__PURE__ */ new Date()).getTime();
558
598
  const quitters = Array.from(this._sessions.values()).filter(
559
599
  (pluvWs) => pluvWs.state.quit || currentTime - pluvWs.state.timers.ping > PING_TIMEOUT_MS
560
600
  );
561
- quitters.forEach((pluvWs) => {
562
- this._sessions.delete(pluvWs.sessionId);
563
- });
564
- const promises = quitters.map((quitter) => __async(this, null, function* () {
565
- yield this._exitWebsocket(quitter);
566
- }));
567
- yield Promise.all(promises);
601
+ this._closeWebSockets(quitters);
568
602
  });
569
603
  }
570
604
  _emitRegistered(pluvWs) {
@@ -594,32 +628,6 @@ var IORoom = class {
594
628
  if (this._platform._config.registrationMode === "detached") return;
595
629
  throw new Error("Platform must use detached mode");
596
630
  }
597
- _exitWebsocket(webSocket) {
598
- return __async(this, null, function* () {
599
- const session = yield webSocket.getSession();
600
- yield this._broadcast({
601
- message: {
602
- type: "$exit",
603
- data: { sessionId: session.id }
604
- },
605
- senderId: session.id
606
- });
607
- try {
608
- const doc = yield this._doc;
609
- const encodedState = doc.getEncodedState();
610
- yield Promise.resolve(
611
- this._listeners.onUserDisconnected({
612
- context: this._context,
613
- encodedState,
614
- room: this.id,
615
- user: session.user
616
- })
617
- );
618
- } catch (err) {
619
- console.error(err);
620
- }
621
- });
622
- }
623
631
  _getAbstractWs(webSocket) {
624
632
  var _a, _b;
625
633
  if (webSocket instanceof AbstractWebSocket) return webSocket;
@@ -754,25 +762,12 @@ var IORoom = class {
754
762
  return { doc: this._doc, uninitialize: this._uninitialize };
755
763
  }
756
764
  _logDebug(...data) {
757
- this._debug && console.log(...data);
765
+ if (this._debug) console.log(...data);
758
766
  }
759
767
  _onClose(webSocket) {
760
768
  return () => __async(this, null, function* () {
761
769
  if (!(yield this._initialized)) return;
762
- webSocket.state = __spreadProps(__spreadValues({}, webSocket.state), { quit: true });
763
- this._logDebug(`${colors.blue(`Unregistering connection for room ${this.id}:`)} ${webSocket.sessionId}`);
764
- this._sessions.delete(webSocket.sessionId);
765
- this._platform.persistence.deleteUser(this.id, webSocket.sessionId).finally(() => __async(this, null, function* () {
766
- yield this._exitWebsocket(webSocket);
767
- const size = this.getSize();
768
- this._logDebug(`${colors.blue(`Unregistered connection for room ${this.id}:`)} ${webSocket.sessionId}`);
769
- this._logDebug(`${colors.blue(`Room ${this.id} size:`)} ${size}`);
770
- if (size) return;
771
- if (this._uninitialize) {
772
- const uninitialize = (yield this._uninitialize).bind(this);
773
- yield uninitialize();
774
- }
775
- }));
770
+ yield this._closeWebSockets([webSocket]);
776
771
  });
777
772
  }
778
773
  _onMessage(webSocket) {
@@ -794,6 +789,7 @@ var IORoom = class {
794
789
  sessions
795
790
  };
796
791
  if (pluvWs.state.quit) {
792
+ yield this._closeWebSockets([pluvWs]).catch(() => null);
797
793
  pluvWs.close(1011, "WebSocket broken.");
798
794
  return;
799
795
  }
@@ -967,7 +963,7 @@ var IORoom = class {
967
963
  };
968
964
 
969
965
  // src/version.generated.ts
970
- var version = "0.38.14";
966
+ var version = "0.39.1";
971
967
 
972
968
  // src/PluvServer.ts
973
969
  var PluvServer = class {
@@ -1165,16 +1161,14 @@ var PluvServer = class {
1165
1161
  });
1166
1162
  }
1167
1163
  _getIOAuthorize(options) {
1168
- if (typeof this._authorize === "function") {
1169
- return this._authorize(options);
1170
- }
1164
+ if (typeof this._authorize === "function") return this._authorize(options);
1171
1165
  return this._authorize;
1172
1166
  }
1173
1167
  _getListeners() {
1174
1168
  return this._listeners;
1175
1169
  }
1176
1170
  _logDebug(...data) {
1177
- this._debug && console.log(...data);
1171
+ if (this._debug) console.log(...data);
1178
1172
  }
1179
1173
  };
1180
1174
 
@@ -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/io",
3
- "version": "0.38.14",
3
+ "version": "0.39.1",
4
4
  "description": "@pluv/io server-side websocket manager",
5
5
  "author": "leedavidcs",
6
6
  "license": "MIT",
@@ -21,21 +21,21 @@
21
21
  "jose": "^5.10.0",
22
22
  "kleur": "^4.1.5",
23
23
  "wonka": "^6.3.5",
24
- "@pluv/crdt": "^0.38.14",
25
- "@pluv/types": "^0.38.14"
24
+ "@pluv/crdt": "^0.39.1",
25
+ "@pluv/types": "^0.39.1"
26
26
  },
27
27
  "devDependencies": {
28
- "eslint": "^8.57.1",
28
+ "eslint": "^9.24.0",
29
29
  "genversion": "^3.2.0",
30
30
  "tsup": "^8.4.0",
31
31
  "typescript": "^5.8.3",
32
- "@pluv/tsconfig": "^0.38.14",
33
- "eslint-config-pluv": "^0.38.14"
32
+ "@pluv/tsconfig": "^0.39.1",
33
+ "eslint-config-pluv": "^0.39.1"
34
34
  },
35
35
  "scripts": {
36
36
  "build": "pnpm run version && tsup src/index.ts --format esm,cjs --dts",
37
37
  "dev": "tsup src/index.ts --format esm,cjs --watch --dts",
38
- "lint": "eslint src/**/*.ts* --fix",
38
+ "lint": "eslint src/**/*.ts* --fix --max-warnings 0",
39
39
  "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
40
40
  "version": "genversion ./src/version.generated.ts --esm"
41
41
  }
@@ -9,13 +9,13 @@ export abstract class AbstractPersistence {
9
9
 
10
10
  public abstract deleteUsers(room: string): Promise<void>;
11
11
 
12
- public abstract getSize(room: string): Promise<number>;
13
-
14
12
  public abstract getStorageState(room: string): Promise<string | null>;
15
13
 
16
14
  public abstract getUser(room: string, connectionId: string): Promise<JsonObject | null>;
17
15
 
18
16
  public abstract getUsers(room: string): Promise<readonly (JsonObject | null)[]>;
19
17
 
18
+ public abstract getUsersSize(room: string): Promise<number>;
19
+
20
20
  public abstract setStorageState(room: string, state: string): Promise<void>;
21
21
  }
@@ -51,7 +51,7 @@ export abstract class AbstractPlatform<
51
51
  const { roomContext, persistence, pubSub } = config;
52
52
 
53
53
  (this as any)._meta = (config as any)._meta;
54
- roomContext && (this._roomContext = roomContext);
54
+ if (!!roomContext) this._roomContext = roomContext;
55
55
 
56
56
  this.persistence = persistence ?? new Persistence();
57
57
  this.pubSub = pubSub ?? new PubSub();
@@ -94,7 +94,6 @@ export abstract class AbstractWebSocket<TWebSocket = any> {
94
94
  * method, because the implementing class might have to transform the
95
95
  * listener, and removing the same listener might be complicated as a
96
96
  * result.
97
- * @author leedavidcs
98
97
  * @date August 28, 2022
99
98
  */
100
99
  public abstract addEventListener<TType extends EventType>(type: TType, handler: AbstractListener<TType>): void;
package/src/IORoom.ts CHANGED
@@ -87,7 +87,7 @@ interface SendMessageSender {
87
87
  user: JsonObject | null;
88
88
  }
89
89
 
90
- export type WebsocketRegisterConfig<TPlatform extends AbstractPlatform<any> = AbstractPlatform<any>> = {
90
+ export type WebSocketRegisterConfig<TPlatform extends AbstractPlatform<any> = AbstractPlatform<any>> = {
91
91
  token?: string | null;
92
92
  } & InferInitContextType<TPlatform>;
93
93
 
@@ -237,7 +237,6 @@ export class IORoom<
237
237
  /**
238
238
  * @description Doing this instead of .size because some sessions
239
239
  * in the map can be considered as "omitted".
240
- * @author leedavidcs
241
240
  * @date December 21, 2022
242
241
  */
243
242
  return Array.from(this._sessions.values()).reduce((count, pluvWs) => {
@@ -283,11 +282,10 @@ export class IORoom<
283
282
  webSocket: InferPlatformWebSocketSource<TPlatform>,
284
283
  ...options: keyof InferInitContextType<TPlatform> extends never
285
284
  ? [{ token?: string }?]
286
- : [WebsocketRegisterConfig<TPlatform>]
285
+ : [WebSocketRegisterConfig<TPlatform>]
287
286
  ): Promise<void> {
288
- const _options = (options[0] ?? {}) as WebsocketRegisterConfig<TPlatform>;
287
+ const _options = (options[0] ?? {}) as WebSocketRegisterConfig<TPlatform>;
289
288
  const token = _options.token ?? null;
290
-
291
289
  const sessionId = this._platform.getSessionId(webSocket);
292
290
  const sessionExists = typeof sessionId === "string" && this._sessions.has(sessionId);
293
291
 
@@ -361,21 +359,64 @@ export class IORoom<
361
359
  });
362
360
  }
363
361
 
362
+ private async _closeWebSockets(webSockets: readonly AbstractWebSocket[]): Promise<void> {
363
+ const closeWebSocket = async (webSocket: AbstractWebSocket): Promise<void> => {
364
+ webSocket.state = { ...webSocket.state, quit: true };
365
+
366
+ const sessionId = webSocket.sessionId;
367
+
368
+ this._logDebug(`${colors.blue(`Unregistering connection for room ${this.id}:`)} ${sessionId}`);
369
+ this._sessions.delete(sessionId);
370
+
371
+ await this._platform.persistence.deleteUser(this.id, sessionId).catch(() => null);
372
+ await this._broadcast({
373
+ message: { type: "$exit", data: { sessionId } },
374
+ senderId: sessionId,
375
+ });
376
+
377
+ try {
378
+ const [session, doc] = await Promise.all([webSocket.getSession(), this._doc]);
379
+ const encodedState = doc.getEncodedState();
380
+
381
+ await Promise.resolve(
382
+ this._listeners.onUserDisconnected({
383
+ context: this._context,
384
+ encodedState,
385
+ room: this.id,
386
+ user: session.user,
387
+ }),
388
+ );
389
+ } catch (error) {
390
+ console.error(error);
391
+ }
392
+
393
+ this._logDebug(`${colors.blue(`Unregistered connection for room ${this.id}:`)} ${webSocket.sessionId}`);
394
+ };
395
+
396
+ const promises = webSockets.map(async (webSocket) => {
397
+ await closeWebSocket(webSocket).catch(() => null);
398
+ });
399
+
400
+ await Promise.all(promises).catch(() => null);
401
+
402
+ const size = this.getSize();
403
+
404
+ this._logDebug(`${colors.blue(`Room ${this.id} size:`)} ${size}`);
405
+
406
+ if (!!size) return;
407
+ if (!this._uninitialize) return;
408
+
409
+ const uninitialize = (await this._uninitialize).bind(this);
410
+ await uninitialize();
411
+ }
412
+
364
413
  private async _emitQuitters(): Promise<void> {
365
414
  const currentTime = new Date().getTime();
366
415
  const quitters = Array.from(this._sessions.values()).filter(
367
416
  (pluvWs) => pluvWs.state.quit || currentTime - pluvWs.state.timers.ping > PING_TIMEOUT_MS,
368
417
  );
369
418
 
370
- quitters.forEach((pluvWs) => {
371
- this._sessions.delete(pluvWs.sessionId);
372
- });
373
-
374
- const promises = quitters.map(async (quitter) => {
375
- await this._exitWebsocket(quitter);
376
- });
377
-
378
- await Promise.all(promises);
419
+ this._closeWebSockets(quitters);
379
420
  }
380
421
 
381
422
  private async _emitRegistered(pluvWs: AbstractWebSocket): Promise<void> {
@@ -409,34 +450,6 @@ export class IORoom<
409
450
  throw new Error("Platform must use detached mode");
410
451
  }
411
452
 
412
- private async _exitWebsocket(webSocket: AbstractWebSocket<any>): Promise<void> {
413
- const session = await webSocket.getSession();
414
-
415
- await this._broadcast({
416
- message: {
417
- type: "$exit",
418
- data: { sessionId: session.id },
419
- },
420
- senderId: session.id,
421
- });
422
-
423
- try {
424
- const doc = await this._doc;
425
- const encodedState = doc.getEncodedState();
426
-
427
- await Promise.resolve(
428
- this._listeners.onUserDisconnected({
429
- context: this._context,
430
- encodedState,
431
- room: this.id,
432
- user: session.user,
433
- }),
434
- );
435
- } catch (err) {
436
- console.error(err);
437
- }
438
- }
439
-
440
453
  private _getAbstractWs(webSocket: WebSocketType<TPlatform>): AbstractWebSocket | null {
441
454
  if ((webSocket as unknown as any) instanceof AbstractWebSocket) return webSocket;
442
455
 
@@ -455,7 +468,7 @@ export class IORoom<
455
468
 
456
469
  private async _getAuthorizedUser(
457
470
  token: Maybe<string>,
458
- options: WebsocketRegisterConfig<TPlatform>,
471
+ options: WebSocketRegisterConfig<TPlatform>,
459
472
  ): Promise<InferIOAuthorizeUser<InferIOAuthorize<this>>> {
460
473
  const ioAuthorize = this._getIOAuthorize(options);
461
474
 
@@ -502,7 +515,7 @@ export class IORoom<
502
515
  return doc;
503
516
  }
504
517
 
505
- private _getIOAuthorize(options: WebsocketRegisterConfig<TPlatform>): ResolvedPluvIOAuthorize<any, any> | null {
518
+ private _getIOAuthorize(options: WebSocketRegisterConfig<TPlatform>): ResolvedPluvIOAuthorize<any, any> | null {
506
519
  if (typeof this._authorize === "function") return this._authorize(options);
507
520
 
508
521
  return this._authorize as ResolvedPluvIOAuthorize<any, any> | null;
@@ -560,12 +573,10 @@ export class IORoom<
560
573
  switch (options.type) {
561
574
  case "self": {
562
575
  await this._sendSelfMessage(message, sender);
563
-
564
576
  return;
565
577
  }
566
578
  case "sync": {
567
579
  await this._sendSyncMessage(message, sender);
568
-
569
580
  return;
570
581
  }
571
582
  case "broadcast":
@@ -610,33 +621,13 @@ export class IORoom<
610
621
  }
611
622
 
612
623
  private _logDebug(...data: any[]): void {
613
- this._debug && console.log(...data);
624
+ if (this._debug) console.log(...data);
614
625
  }
615
626
 
616
627
  private _onClose(webSocket: AbstractWebSocket): () => Promise<void> {
617
628
  return async (): Promise<void> => {
618
629
  if (!(await this._initialized)) return;
619
-
620
- webSocket.state = { ...webSocket.state, quit: true };
621
-
622
- this._logDebug(`${colors.blue(`Unregistering connection for room ${this.id}:`)} ${webSocket.sessionId}`);
623
- this._sessions.delete(webSocket.sessionId);
624
-
625
- this._platform.persistence.deleteUser(this.id, webSocket.sessionId).finally(async () => {
626
- await this._exitWebsocket(webSocket);
627
-
628
- const size = this.getSize();
629
-
630
- this._logDebug(`${colors.blue(`Unregistered connection for room ${this.id}:`)} ${webSocket.sessionId}`);
631
- this._logDebug(`${colors.blue(`Room ${this.id} size:`)} ${size}`);
632
-
633
- if (size) return;
634
-
635
- if (this._uninitialize) {
636
- const uninitialize = (await this._uninitialize).bind(this);
637
- await uninitialize();
638
- }
639
- });
630
+ await this._closeWebSockets([webSocket]);
640
631
  };
641
632
  }
642
633
 
@@ -663,6 +654,7 @@ export class IORoom<
663
654
  };
664
655
 
665
656
  if (pluvWs.state.quit) {
657
+ await this._closeWebSockets([pluvWs]).catch(() => null);
666
658
  pluvWs.close(1011, "WebSocket broken.");
667
659
 
668
660
  return;
package/src/PluvIO.ts CHANGED
@@ -2,7 +2,7 @@ import type { AbstractCrdtDocFactory } from "@pluv/crdt";
2
2
  import { noop } from "@pluv/crdt";
3
3
  import type { IOAuthorize, InferIOAuthorizeUser } from "@pluv/types";
4
4
  import type { AbstractPlatform, InferInitContextType } from "./AbstractPlatform";
5
- import { WebsocketRegisterConfig } from "./IORoom";
5
+ import type { WebSocketRegisterConfig } from "./IORoom";
6
6
  import { PluvProcedure } from "./PluvProcedure";
7
7
  import type { MergedRouter, PluvRouterEventConfig } from "./PluvRouter";
8
8
  import { PluvRouter } from "./PluvRouter";
@@ -133,7 +133,7 @@ export class PluvIO<
133
133
  } as PluvServerConfig<TPlatform, TAuthorize, TContext, TEvents>);
134
134
  }
135
135
 
136
- private _getIOAuthorize(options: WebsocketRegisterConfig<TPlatform>): ResolvedPluvIOAuthorize<any, any> | null {
136
+ private _getIOAuthorize(options: WebSocketRegisterConfig<TPlatform>): ResolvedPluvIOAuthorize<any, any> | null {
137
137
  if (typeof this._authorize === "function") {
138
138
  return this._authorize(options);
139
139
  }
package/src/PluvServer.ts CHANGED
@@ -3,7 +3,7 @@ import { noop } from "@pluv/crdt";
3
3
  import type { IOLike, Id, InferIOAuthorize, InferIOAuthorizeUser, JsonObject, Maybe } from "@pluv/types";
4
4
  import colors from "kleur";
5
5
  import type { AbstractPlatform, InferInitContextType, InferRoomContextType } from "./AbstractPlatform";
6
- import type { IORoomListeners, WebsocketRegisterConfig } from "./IORoom";
6
+ import type { IORoomListeners, WebSocketRegisterConfig } from "./IORoom";
7
7
  import { IORoom } from "./IORoom";
8
8
  import { PluvProcedure } from "./PluvProcedure";
9
9
  import type { PluvRouterEventConfig } from "./PluvRouter";
@@ -347,10 +347,8 @@ export class PluvServer<
347
347
  return await authorize({ platform: this._platform, secret }).encode(params as JWTEncodeParams<any, TPlatform>);
348
348
  }
349
349
 
350
- private _getIOAuthorize(options: WebsocketRegisterConfig<TPlatform>): ResolvedPluvIOAuthorize<any, any> | null {
351
- if (typeof this._authorize === "function") {
352
- return this._authorize(options);
353
- }
350
+ private _getIOAuthorize(options: WebSocketRegisterConfig<TPlatform>): ResolvedPluvIOAuthorize<any, any> | null {
351
+ if (typeof this._authorize === "function") return this._authorize(options);
354
352
 
355
353
  return this._authorize as ResolvedPluvIOAuthorize<any, any> | null;
356
354
  }
@@ -360,6 +358,6 @@ export class PluvServer<
360
358
  }
361
359
 
362
360
  private _logDebug(...data: any[]): void {
363
- this._debug && console.log(...data);
361
+ if (this._debug) console.log(...data);
364
362
  }
365
363
  }
package/src/types.ts CHANGED
@@ -24,6 +24,7 @@ import type { AbstractWebSocket } from "./AbstractWebSocket";
24
24
  import type { PluvRouter, PluvRouterEventConfig } from "./PluvRouter";
25
25
 
26
26
  declare global {
27
+ // eslint-disable-next-line no-var
27
28
  var console: {
28
29
  error: (...data: any[]) => void;
29
30
  log: (...data: any[]) => void;
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- export const version = '0.38.14'
2
+ export const version = '0.39.1'
package/src/version.ts CHANGED
@@ -1,2 +1,3 @@
1
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1
2
  // @ts-ignore
2
3
  export { version as __PLUV_VERSION } from "./version.generated";
package/.eslintrc.js DELETED
@@ -1,4 +0,0 @@
1
- module.exports = {
2
- root: true,
3
- extends: ["pluv"],
4
- };