@colyseus/core 0.18.24 → 0.18.26

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/build/Room.d.ts CHANGED
@@ -456,6 +456,39 @@ export declare class Room<T extends RoomOptions = RoomOptions> {
456
456
  * (Experimental: this feature is subject to change in the future - we're currently getting feedback to improve it)
457
457
  */
458
458
  onUncaughtException?(error: RoomException, methodName: RoomMethodName): void;
459
+ /**
460
+ * Called when a client sends a message type that has no `onMessage()` handler.
461
+ * Use it to report the message, for example to an error tracker.
462
+ *
463
+ * It only observes. After it returns, the server still handles the message the
464
+ * usual way:
465
+ * - `room.send()`/`room.sendBytes()`: the client is disconnected with
466
+ * `CloseCode.WITH_ERROR` (4002). In `devMode`, the client receives an
467
+ * `ErrorCode.INVALID_PAYLOAD` error instead and stays connected.
468
+ * - `room.request()`: the request rejects with a `no_handler` error.
469
+ *
470
+ * To accept every message type without disconnecting the client, register a
471
+ * fallback handler with `onMessage("*", ...)` instead. Messages it receives
472
+ * don't reach this method, except requests: the fallback can't answer those.
473
+ *
474
+ * Without this method, the server logs a warning for unhandled `send()`/`sendBytes()`
475
+ * messages. An error thrown here goes to `onUncaughtException` (as an
476
+ * `OnMessageException`) when defined, or is logged.
477
+ *
478
+ * @param client - The client that sent the message.
479
+ * @param type - The message type it sent.
480
+ * @param message - The decoded payload (a `Buffer` for `sendBytes()`).
481
+ *
482
+ * @example
483
+ * ```typescript
484
+ * onUnhandledMessage(client: Client, type: string | number, message: unknown) {
485
+ * Sentry.captureMessage(`unhandled message "${type}"`, {
486
+ * extra: { roomId: this.roomId, sessionId: client.sessionId, message },
487
+ * });
488
+ * }
489
+ * ```
490
+ */
491
+ onUnhandledMessage?(client: ExtractRoomClient<T>, type: string | number, message: unknown): void | Promise<any>;
459
492
  /**
460
493
  * This method is called before onJoin() - this is where you should authenticate the client
461
494
  * @param client - The client that is authenticating.
package/build/Room.mjs CHANGED
@@ -58,6 +58,8 @@ var Room = class _Room {
58
58
  */
59
59
  this.clock = new Clock();
60
60
  this.#_onLeaveConcurrent = 0;
61
+ // number of onLeave calls in progress
62
+ this.#_pendingCloses = /* @__PURE__ */ new WeakMap();
61
63
  this.#_maxClientsReached = false;
62
64
  this.#_maxClients = Infinity;
63
65
  this.#_autoDispose = true;
@@ -159,6 +161,7 @@ var Room = class _Room {
159
161
  #_roomId;
160
162
  #_roomName;
161
163
  #_onLeaveConcurrent;
164
+ #_pendingCloses;
162
165
  #_maxClientsReached;
163
166
  #_maxClients;
164
167
  #_autoDispose;
@@ -1197,6 +1200,7 @@ var Room = class _Room {
1197
1200
  return this._reconnections[reconnectionToken][1];
1198
1201
  }
1199
1202
  this._reserveSeat(sessionId, true, previousClient.auth, seconds, true);
1203
+ this.#_pendingCloses.get(previousClient)?.();
1200
1204
  const reconnection = new Deferred();
1201
1205
  this._reconnections[reconnectionToken] = [sessionId, reconnection];
1202
1206
  if (seconds !== Infinity) {
@@ -1439,11 +1443,13 @@ var Room = class _Room {
1439
1443
  client.ref.removeAllListeners("message");
1440
1444
  client.ref.removeListener("close", client.ref["onleave"]);
1441
1445
  const ref = client.ref;
1442
- this._onLeave(client, closeCode).then(() => {
1443
- if (client.ref === ref) {
1446
+ const close = () => {
1447
+ if (this.#_pendingCloses.delete(client) && client.ref === ref) {
1444
1448
  client.leave(closeCode, reason);
1445
1449
  }
1446
- });
1450
+ };
1451
+ this.#_pendingCloses.set(client, close);
1452
+ this._onLeave(client, closeCode).then(close);
1447
1453
  }
1448
1454
  async _onLeave(client, code) {
1449
1455
  const method = code === CloseCode.CONSENTED || client.state === ClientState.RECONNECTING ? this.onLeave : this.onDrop || this.onLeave;