@colyseus/core 0.17.43 → 0.18.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.
Files changed (95) hide show
  1. package/build/MatchMaker.cjs +19 -6
  2. package/build/MatchMaker.cjs.map +2 -2
  3. package/build/MatchMaker.d.ts +10 -0
  4. package/build/MatchMaker.mjs +18 -6
  5. package/build/MatchMaker.mjs.map +2 -2
  6. package/build/Protocol.cjs +102 -37
  7. package/build/Protocol.cjs.map +2 -2
  8. package/build/Protocol.d.ts +33 -2
  9. package/build/Protocol.mjs +102 -37
  10. package/build/Protocol.mjs.map +2 -2
  11. package/build/Room.cjs +296 -19
  12. package/build/Room.cjs.map +3 -3
  13. package/build/Room.d.ts +186 -3
  14. package/build/Room.mjs +303 -21
  15. package/build/Room.mjs.map +3 -3
  16. package/build/RoomPlugin.cjs +252 -0
  17. package/build/RoomPlugin.cjs.map +7 -0
  18. package/build/RoomPlugin.d.ts +271 -0
  19. package/build/RoomPlugin.mjs +220 -0
  20. package/build/RoomPlugin.mjs.map +7 -0
  21. package/build/Server.cjs +49 -15
  22. package/build/Server.cjs.map +2 -2
  23. package/build/Server.d.ts +25 -0
  24. package/build/Server.mjs +50 -16
  25. package/build/Server.mjs.map +2 -2
  26. package/build/Transport.cjs +38 -2
  27. package/build/Transport.cjs.map +2 -2
  28. package/build/Transport.d.ts +40 -4
  29. package/build/Transport.mjs +38 -2
  30. package/build/Transport.mjs.map +2 -2
  31. package/build/index.cjs +11 -2
  32. package/build/index.cjs.map +2 -2
  33. package/build/index.d.ts +2 -1
  34. package/build/index.mjs +12 -2
  35. package/build/index.mjs.map +2 -2
  36. package/build/input/InputBuffer.cjs +113 -0
  37. package/build/input/InputBuffer.cjs.map +7 -0
  38. package/build/input/InputBuffer.d.ts +136 -0
  39. package/build/input/InputBuffer.mjs +86 -0
  40. package/build/input/InputBuffer.mjs.map +7 -0
  41. package/build/internal.cjs +61 -0
  42. package/build/internal.cjs.map +7 -0
  43. package/build/internal.d.ts +9 -0
  44. package/build/internal.mjs +29 -0
  45. package/build/internal.mjs.map +7 -0
  46. package/build/matchmaker/LocalDriver/LocalDriver.cjs +13 -0
  47. package/build/matchmaker/LocalDriver/LocalDriver.cjs.map +2 -2
  48. package/build/matchmaker/LocalDriver/LocalDriver.d.ts +1 -0
  49. package/build/matchmaker/LocalDriver/LocalDriver.mjs +13 -0
  50. package/build/matchmaker/LocalDriver/LocalDriver.mjs.map +2 -2
  51. package/build/matchmaker/driver.cjs.map +1 -1
  52. package/build/matchmaker/driver.d.ts +12 -0
  53. package/build/matchmaker/driver.mjs.map +1 -1
  54. package/build/presence/LocalPresence.d.ts +1 -1
  55. package/build/rooms/LobbyRoom.cjs +8 -10
  56. package/build/rooms/LobbyRoom.cjs.map +2 -2
  57. package/build/rooms/LobbyRoom.d.ts +4 -3
  58. package/build/rooms/LobbyRoom.mjs +8 -10
  59. package/build/rooms/LobbyRoom.mjs.map +2 -2
  60. package/build/rooms/RelayRoom.cjs +12 -16
  61. package/build/rooms/RelayRoom.cjs.map +2 -2
  62. package/build/rooms/RelayRoom.d.ts +32 -11
  63. package/build/rooms/RelayRoom.mjs +10 -16
  64. package/build/rooms/RelayRoom.mjs.map +2 -2
  65. package/build/router/index.cjs +65 -4
  66. package/build/router/index.cjs.map +2 -2
  67. package/build/router/index.d.ts +30 -6
  68. package/build/router/index.mjs +66 -6
  69. package/build/router/index.mjs.map +3 -3
  70. package/build/utils/Env.cjs +4 -8
  71. package/build/utils/Env.cjs.map +3 -3
  72. package/build/utils/Env.mjs +4 -8
  73. package/build/utils/Env.mjs.map +2 -2
  74. package/build/utils/UserSessionIndex.cjs +162 -0
  75. package/build/utils/UserSessionIndex.cjs.map +7 -0
  76. package/build/utils/UserSessionIndex.d.ts +166 -0
  77. package/build/utils/UserSessionIndex.mjs +130 -0
  78. package/build/utils/UserSessionIndex.mjs.map +7 -0
  79. package/package.json +20 -15
  80. package/src/MatchMaker.ts +40 -6
  81. package/src/Protocol.ts +130 -59
  82. package/src/Room.ts +475 -22
  83. package/src/RoomPlugin.ts +563 -0
  84. package/src/Server.ts +81 -22
  85. package/src/Transport.ts +76 -8
  86. package/src/index.ts +10 -1
  87. package/src/input/InputBuffer.ts +192 -0
  88. package/src/internal.ts +46 -0
  89. package/src/matchmaker/LocalDriver/LocalDriver.ts +10 -0
  90. package/src/matchmaker/driver.ts +13 -0
  91. package/src/rooms/LobbyRoom.ts +12 -8
  92. package/src/rooms/RelayRoom.ts +9 -15
  93. package/src/router/index.ts +112 -11
  94. package/src/utils/Env.ts +4 -12
  95. package/src/utils/UserSessionIndex.ts +311 -0
package/build/Room.cjs CHANGED
@@ -33,12 +33,18 @@ __export(Room_exports, {
33
33
  DEFAULT_SEAT_RESERVATION_TIME: () => DEFAULT_SEAT_RESERVATION_TIME,
34
34
  Room: () => Room,
35
35
  RoomInternalState: () => RoomInternalState,
36
+ RoomPlugin: () => import_RoomPlugin2.RoomPlugin,
37
+ attachToTestRoom: () => import_RoomPlugin2.attachToTestRoom,
38
+ definePlugins: () => import_RoomPlugin2.definePlugins,
36
39
  room: () => room,
37
40
  validate: () => validate
38
41
  });
39
42
  module.exports = __toCommonJS(Room_exports);
40
- var import_msgpackr = require("@colyseus/msgpackr");
43
+ var import_msgpackr = require("msgpackr");
41
44
  var import_schema = require("@colyseus/schema");
45
+ var import_input = require("@colyseus/schema/input");
46
+ var import_InputBuffer = require("./input/InputBuffer.cjs");
47
+ var import_InputBuffer2 = require("./input/InputBuffer.cjs");
42
48
  var import_timer = require("@colyseus/timer");
43
49
  var import_events = require("events");
44
50
  var import_Logger = require("./Logger.cjs");
@@ -55,9 +61,19 @@ var import_RoomExceptions = require("./errors/RoomExceptions.cjs");
55
61
  var import_StandardSchema = require("./utils/StandardSchema.cjs");
56
62
  var matchMaker = __toESM(require("./MatchMaker.cjs"), 1);
57
63
  var import_shared_types = require("@colyseus/shared-types");
64
+ var import_RoomPlugin = require("./RoomPlugin.cjs");
65
+ var import_RoomPlugin2 = require("./RoomPlugin.cjs");
66
+ var _inputReflectionCache = /* @__PURE__ */ new WeakMap();
58
67
  var DEFAULT_PATCH_RATE = 1e3 / 20;
59
68
  var DEFAULT_SIMULATION_INTERVAL = 1e3 / 60;
60
69
  var noneSerializer = new import_NoneSerializer.NoneSerializer();
70
+ function toResponseError(e) {
71
+ if (e instanceof Error) {
72
+ const code = e.code;
73
+ return code !== void 0 ? { name: e.name, message: e.message, code } : { name: e.name, message: e.message };
74
+ }
75
+ return { name: "Error", message: String(e) };
76
+ }
61
77
  var DEFAULT_SEAT_RESERVATION_TIME = Number(process.env.COLYSEUS_SEAT_RESERVATION_TIME || 15);
62
78
  function validate(format, handler) {
63
79
  return { format, handler };
@@ -140,6 +156,7 @@ var Room = class _Room {
140
156
  this._internalState = RoomInternalState.CREATING;
141
157
  this._lockedExplicitly = false;
142
158
  this.#_locked = false;
159
+ this.#_tick = 0;
143
160
  this._events.once("dispose", () => {
144
161
  this.#_dispose().catch((e) => (0, import_Debug.debugAndPrintError)(`onDispose error: ${e && e.stack || e.message || e || "promise rejected"} (roomId: ${this.roomId})`)).finally(() => this._events.emit("disconnect"));
145
162
  });
@@ -256,6 +273,9 @@ var Room = class _Room {
256
273
  if (this.#_state) {
257
274
  this.state = this.#_state;
258
275
  }
276
+ if (this.plugins !== void 0) {
277
+ (0, import_RoomPlugin.setupRoomPlugins)(this);
278
+ }
259
279
  if (this.messages !== void 0) {
260
280
  if (this.messages["_"]) {
261
281
  this.onMessage("*", this.messages["_"].bind(this));
@@ -312,6 +332,44 @@ var Room = class _Room {
312
332
  }
313
333
  this.#_roomId = roomId;
314
334
  }
335
+ /**
336
+ * Declare the input schema and configuration in a single line. Returns the
337
+ * callable accessor that gets assigned to `this.input` — call
338
+ * `this.input(sessionId)` per tick to consume.
339
+ *
340
+ * ```typescript
341
+ * class FpsRoom extends Room<{ input: MoveInput }> {
342
+ * input = this.defineInput(MoveInput, {
343
+ * seqField: "tick", // typed: only numeric fields of MoveInput
344
+ * bufferMaxSize: 64,
345
+ * });
346
+ *
347
+ * // …or without options — defaults to seqField: "seq", bufferMaxSize: 32:
348
+ * // input = this.defineInput(MoveInput);
349
+ * }
350
+ * ```
351
+ *
352
+ * **Defaults** when `opts` (or individual fields) are omitted:
353
+ * - `seqField`: `"seq"` — framework dedupes by `input.seq` if the schema has
354
+ * such a field. Schemas without it gracefully skip dedupe.
355
+ * - `bufferMaxSize`: `32` — enables per-client snapshot buffering for
356
+ * `room.input(sessionId).drain() / .peek() / .at()`. Set to `0` to disable
357
+ * buffering (the `.latest` read still works).
358
+ */
359
+ defineInput(type, opts) {
360
+ this.inputOptions = {
361
+ ctor: type,
362
+ seqField: opts?.seqField ?? "seq",
363
+ bufferMaxSize: opts?.bufferMaxSize ?? 32
364
+ };
365
+ if (!_inputReflectionCache.has(type)) {
366
+ _inputReflectionCache.set(type, import_schema.Reflection.encode(new import_schema.Encoder(new type())));
367
+ }
368
+ return ((sessionId) => {
369
+ const c = this.clients.getById(sessionId);
370
+ return c?._inputAccessor ?? import_InputBuffer.NO_OP_INPUT_ACCESSOR;
371
+ });
372
+ }
315
373
  /**
316
374
  * This method is called before onJoin() - this is where you should authenticate the client
317
375
  * @param client - The client that is authenticating.
@@ -412,6 +470,47 @@ var Room = class _Room {
412
470
  }, delay);
413
471
  }
414
472
  }
473
+ /**
474
+ * Run a fixed-rate simulation tagged with a monotonic server tick number.
475
+ * Combine with `room.input(sessionId).at(tick)` to retrieve each client's
476
+ * input *for a specific tick* — the building block for lockstep / rollback
477
+ * netcode.
478
+ *
479
+ * Replaces any previous {@link setSimulationInterval}. The current tick is
480
+ * exposed via {@link tick}.
481
+ *
482
+ * @example
483
+ * ```typescript
484
+ * class LockstepRoom extends Room<{ input: MoveInput }> {
485
+ * input = this.defineInput(MoveInput, { seqField: "tick", bufferMaxSize: 64 });
486
+ *
487
+ * onCreate() {
488
+ * this.setTickedSimulation((tick, dt) => {
489
+ * for (const c of this.clients) {
490
+ * const snapshot = this.input(c.sessionId).at(tick);
491
+ * if (snapshot) this.apply(c, snapshot);
492
+ * // else: predict, freeze, etc. — game-level decision
493
+ * }
494
+ * }, 1000 / 60);
495
+ * }
496
+ * }
497
+ * ```
498
+ */
499
+ setTickedSimulation(onTickCallback, delay = DEFAULT_SIMULATION_INTERVAL, startTick = 0) {
500
+ this.#_tick = startTick;
501
+ this.setSimulationInterval((dt) => {
502
+ onTickCallback(this.#_tick, dt);
503
+ this.#_tick++;
504
+ }, delay);
505
+ }
506
+ /**
507
+ * Current server tick. Incremented by {@link setTickedSimulation} after each
508
+ * tick callback returns. Returns 0 when no ticked simulation is running.
509
+ */
510
+ get tick() {
511
+ return this.#_tick;
512
+ }
513
+ #_tick;
415
514
  /**
416
515
  * @deprecated Use `.patchRate=` instead.
417
516
  */
@@ -428,19 +527,7 @@ var Room = class _Room {
428
527
  this._serializer = serializer;
429
528
  }
430
529
  async setMetadata(meta, persist = true) {
431
- if (!this._listing.metadata) {
432
- this._listing.metadata = meta;
433
- } else {
434
- for (const field in meta) {
435
- if (!meta.hasOwnProperty(field)) {
436
- continue;
437
- }
438
- this._listing.metadata[field] = meta[field];
439
- }
440
- if ("markModified" in this._listing) {
441
- this._listing.markModified("metadata");
442
- }
443
- }
530
+ this._listing.metadata = meta;
444
531
  if (persist && this._internalState === RoomInternalState.CREATED) {
445
532
  await matchMaker.driver.persist(this._listing);
446
533
  this._events.emit("metadata-change");
@@ -481,7 +568,8 @@ var Room = class _Room {
481
568
  *
482
569
  * @example
483
570
  * ```typescript
484
- * // Partial metadata update (merges with existing)
571
+ * // Merging with existing metadata: spread `this.metadata` yourself.
572
+ * // `metadata` is always REPLACED (not merged) by setMatchmaking()/setMetadata().
485
573
  * await this.setMatchmaking({
486
574
  * metadata: { ...this.metadata, round: this.metadata.round + 1 }
487
575
  * });
@@ -655,6 +743,100 @@ var Room = class _Room {
655
743
  return this.onMessage(messageType, callback);
656
744
  }
657
745
  }
746
+ // ---------------------------------------------------------------------------
747
+ // Operator API — used by @colyseus/admin (and monitor in due course)
748
+ // through `remoteRoomCall(roomId, methodName)`. Marked `@internal` because
749
+ // they're framework-tooling primitives, not part of the game-code surface.
750
+ // ---------------------------------------------------------------------------
751
+ /**
752
+ * Snapshot the room's live state for an inspector / admin UI. Includes:
753
+ *
754
+ * roomId, name, maxClients, locked, elapsedTime (ms),
755
+ * metadata, clients (sessionId + per-client elapsed + userId when set),
756
+ * state (the schema/json the SDK would see),
757
+ * stateSize (bytes of the encoded full state, or 0 when no serializer).
758
+ *
759
+ * The payload is intentionally plain JSON — `remoteRoomCall` serializes
760
+ * the return value across process boundaries.
761
+ *
762
+ * @internal Operator-only. Game code should not call this.
763
+ */
764
+ getInspectorView() {
765
+ const elapsed = this.clock.elapsedTime;
766
+ return {
767
+ roomId: this.roomId,
768
+ name: this.roomName,
769
+ clients: this.clients.length,
770
+ maxClients: this.maxClients,
771
+ locked: this.#_locked,
772
+ elapsedTime: elapsed,
773
+ metadata: this.metadata ?? null,
774
+ // Cast through `unknown`: `ExtractRoomClient<T>` vs `Client & ClientPrivate`
775
+ // don't structurally overlap (the user's `client` generic may carry a
776
+ // narrower userData/auth shape), but the runtime objects we walk here
777
+ // always have the private join-time field. The narrow per-property
778
+ // `(c as any)` reads below keep the cast scoped.
779
+ //
780
+ // userId / userEmail read straight off `client.auth` — the JWT
781
+ // payload @colyseus/auth's default onAuth decodes carries both
782
+ // when the user has them on file. Saves the admin a per-client
783
+ // database round-trip; falls back to null when the client signed
784
+ // in anonymously (or with a custom onAuth that returns a shape
785
+ // without those fields).
786
+ clientList: this.clients.map((c) => {
787
+ const auth = c.auth;
788
+ return {
789
+ sessionId: c.sessionId,
790
+ userId: c.userId ?? auth?.id ?? null,
791
+ userEmail: auth?.email ?? null,
792
+ elapsedTime: elapsed - (c._joinedAt ?? elapsed)
793
+ };
794
+ }),
795
+ state: this.state ?? null,
796
+ stateSize: this.#_inspectorStateSize()
797
+ };
798
+ }
799
+ /**
800
+ * Force-disconnect a single client by sessionId. No-op when the client
801
+ * isn't connected (idempotent — the caller doesn't need to race-check).
802
+ *
803
+ * @internal Operator-only. Game code disconnects clients by calling
804
+ * `.leave()` on the Client object directly.
805
+ */
806
+ kickClient(sessionId, closeCode = import_shared_types.CloseCode.CONSENTED, reason) {
807
+ for (const client of this.clients) {
808
+ if (client.sessionId === sessionId) {
809
+ this.#_forciblyCloseClient(client, closeCode, reason);
810
+ return;
811
+ }
812
+ }
813
+ }
814
+ /**
815
+ * Best-effort byte size of the current full state. Falls back to `0`
816
+ * when the room has no serializer or the serializer can't produce a
817
+ * payload (raw rooms, very-early-onCreate, etc.). The serializer
818
+ * detection mirrors `@colyseus/monitor`'s — we read whichever buffer
819
+ * is available across schema v2 and v3.
820
+ */
821
+ #_inspectorStateSize() {
822
+ const ser = this._serializer;
823
+ if (!ser) {
824
+ return 0;
825
+ }
826
+ const hasState = ser.encoder || ser.state;
827
+ if (!hasState) {
828
+ return 0;
829
+ }
830
+ try {
831
+ const full = ser.getFullState?.();
832
+ if (!full) {
833
+ return 0;
834
+ }
835
+ return full.byteLength ?? full.length ?? 0;
836
+ } catch {
837
+ return 0;
838
+ }
839
+ }
658
840
  /**
659
841
  * Disconnect all connected clients, and then dispose the room.
660
842
  *
@@ -692,6 +874,15 @@ var Room = class _Room {
692
874
  async _onJoin(client, authContext, connectionOptions) {
693
875
  const sessionId = client.sessionId;
694
876
  client.reconnectionToken = (0, import_Utils.generateId)();
877
+ if (this.inputOptions !== void 0) {
878
+ client._input = new this.inputOptions.ctor();
879
+ client._inputDecoder = new import_input.InputDecoder(client._input);
880
+ const maxSize = this.inputOptions.bufferMaxSize;
881
+ if (maxSize > 0) {
882
+ client._inputBuffer = new import_InputBuffer.InputBufferImpl(maxSize, this.inputOptions.seqField);
883
+ }
884
+ client._inputAccessor = new import_InputBuffer.InputAccessorImpl(client);
885
+ }
695
886
  if (this._reservedSeatTimeouts[sessionId]) {
696
887
  clearTimeout(this._reservedSeatTimeouts[sessionId]);
697
888
  delete this._reservedSeatTimeouts[sessionId];
@@ -794,6 +985,13 @@ var Room = class _Room {
794
985
  client.ref["onleave"] = this._onLeave.bind(this, client);
795
986
  client.ref.once("close", client.ref["onleave"]);
796
987
  client.ref.on("message", this._onMessage.bind(this, client));
988
+ let extraSections;
989
+ if (!connectionOptions?.skipHandshake && this.inputOptions !== void 0) {
990
+ const inputBytes = _inputReflectionCache.get(this.inputOptions.ctor);
991
+ if (inputBytes !== void 0) {
992
+ extraSections = [{ tag: import_shared_types.HandshakeSection.INPUT_REFLECTION, bytes: inputBytes }];
993
+ }
994
+ }
797
995
  client.raw(import_Protocol.getMessageBytes[import_shared_types.Protocol.JOIN_ROOM](
798
996
  client.reconnectionToken,
799
997
  this._serializer.id,
@@ -801,7 +999,8 @@ var Room = class _Room {
801
999
  * if skipHandshake is true, we don't need to send the handshake
802
1000
  * (in case client already has handshake data)
803
1001
  */
804
- connectionOptions?.skipHandshake ? void 0 : this._serializer.handshake && this._serializer.handshake()
1002
+ connectionOptions?.skipHandshake ? void 0 : this._serializer.handshake && this._serializer.handshake(),
1003
+ extraSections
805
1004
  ));
806
1005
  }
807
1006
  }
@@ -868,7 +1067,7 @@ var Room = class _Room {
868
1067
  }).finally(() => {
869
1068
  cleanup();
870
1069
  });
871
- if (this._reconnectionAttempts[reconnectionToken]) {
1070
+ if (this._reconnectionAttempts[reconnectionToken] !== void 0) {
872
1071
  (0, import_Debug.debugMatchMaking)("resolving reconnection attempt for client - sessionId: '%s', roomId: '%s'", sessionId, this.roomId);
873
1072
  this._reconnectionAttempts[reconnectionToken].resolve(true);
874
1073
  }
@@ -896,6 +1095,12 @@ var Room = class _Room {
896
1095
  }
897
1096
  }
898
1097
  }
1098
+ // Encode and enqueue a ROOM_RESPONSE for a client request. If the client has
1099
+ // already left, `enqueueRaw` is a no-op — no response is needed.
1100
+ #replyToRequest(client, requestId, status, payload) {
1101
+ (0, import_Debug.debugMessage)("response #%d: status=%d -> %j (roomId: %s)", requestId, status, payload, this.roomId);
1102
+ client.enqueueRaw(import_Protocol.getMessageBytes[import_shared_types.Protocol.ROOM_RESPONSE](requestId, status, payload));
1103
+ }
899
1104
  sendFullState(client) {
900
1105
  client.raw(this._serializer.getFullState(client));
901
1106
  }
@@ -993,6 +1198,26 @@ var Room = class _Room {
993
1198
  this.clock.stop();
994
1199
  return await (userReturnData || Promise.resolve());
995
1200
  }
1201
+ /**
1202
+ * After the decoder has mutated `client._input`, push a clone into the
1203
+ * per-client buffer (when buffering is enabled). Honors
1204
+ * `inputOptions.seqField` for dedupe of redundant frames.
1205
+ */
1206
+ #captureInput(client) {
1207
+ const buf = client._inputBuffer;
1208
+ if (!buf) {
1209
+ return;
1210
+ }
1211
+ const inst = client._input;
1212
+ const seqField = this.inputOptions?.seqField;
1213
+ if (seqField !== void 0) {
1214
+ const value = inst[seqField];
1215
+ if (typeof value === "number" && !buf.accept(value)) {
1216
+ return;
1217
+ }
1218
+ }
1219
+ buf.push(inst.clone());
1220
+ }
996
1221
  _onMessage(client, buffer) {
997
1222
  if (client.state === import_Transport.ClientState.LEAVING) {
998
1223
  return;
@@ -1031,6 +1256,36 @@ var Room = class _Room {
1031
1256
  } else {
1032
1257
  this.onMessageFallbacks["__no_message_handler"](client, messageType, message);
1033
1258
  }
1259
+ } else if (code === import_shared_types.Protocol.ROOM_REQUEST) {
1260
+ const requestId = import_schema.decode.number(buffer, it);
1261
+ const messageType = import_schema.decode.stringCheck(buffer, it) ? import_schema.decode.string(buffer, it) : import_schema.decode.number(buffer, it);
1262
+ let message;
1263
+ try {
1264
+ message = buffer.byteLength > it.offset ? (0, import_msgpackr.unpack)(buffer.subarray(it.offset, buffer.byteLength)) : void 0;
1265
+ (0, import_Debug.debugMessage)("request #%d: '%s' -> %j (roomId: %s)", requestId, messageType, message, this.roomId);
1266
+ if (this.onMessageValidators[messageType] !== void 0) {
1267
+ message = (0, import_StandardSchema.standardValidate)(this.onMessageValidators[messageType], message);
1268
+ }
1269
+ } catch (e) {
1270
+ (0, import_Debug.debugAndPrintError)(e);
1271
+ this.#replyToRequest(client, requestId, import_shared_types.ResponseStatus.ERROR, toResponseError(e));
1272
+ return;
1273
+ }
1274
+ const handler = this.onMessageEvents.events[messageType]?.[0];
1275
+ if (handler === void 0) {
1276
+ this.#replyToRequest(client, requestId, import_shared_types.ResponseStatus.ERROR, {
1277
+ name: "no_handler",
1278
+ message: `room "${this.roomName}" has no onMessage("${messageType}") handler to answer this request.`
1279
+ });
1280
+ return;
1281
+ }
1282
+ Promise.resolve().then(() => handler(client, message)).then(
1283
+ (response) => this.#replyToRequest(client, requestId, import_shared_types.ResponseStatus.OK, response),
1284
+ (e) => {
1285
+ (0, import_Debug.debugAndPrintError)(e);
1286
+ this.#replyToRequest(client, requestId, import_shared_types.ResponseStatus.ERROR, toResponseError(e));
1287
+ }
1288
+ );
1034
1289
  } else if (code === import_shared_types.Protocol.ROOM_DATA_BYTES) {
1035
1290
  const messageType = import_schema.decode.stringCheck(buffer, it) ? import_schema.decode.string(buffer, it) : import_schema.decode.number(buffer, it);
1036
1291
  let message = buffer.subarray(it.offset, buffer.byteLength);
@@ -1052,6 +1307,25 @@ var Room = class _Room {
1052
1307
  } else {
1053
1308
  this.onMessageFallbacks["__no_message_handler"](client, messageType, message);
1054
1309
  }
1310
+ } else if (code === import_shared_types.Protocol.ROOM_INPUT_RELIABLE) {
1311
+ if (client._inputDecoder) {
1312
+ try {
1313
+ client._inputDecoder.decode(buffer.subarray(1));
1314
+ } catch (e) {
1315
+ (0, import_Debug.debugAndPrintError)(e);
1316
+ return;
1317
+ }
1318
+ this.#captureInput(client);
1319
+ }
1320
+ } else if (code === import_shared_types.Protocol.ROOM_INPUT_UNRELIABLE) {
1321
+ if (client._inputDecoder) {
1322
+ try {
1323
+ client._inputDecoder.decodeAll(buffer.subarray(1), () => this.#captureInput(client));
1324
+ } catch (e) {
1325
+ (0, import_Debug.debugAndPrintError)(e);
1326
+ return;
1327
+ }
1328
+ }
1055
1329
  } else if (code === import_shared_types.Protocol.JOIN_ROOM && client.state === import_Transport.ClientState.JOINING) {
1056
1330
  client.state = import_Transport.ClientState.JOINED;
1057
1331
  client._joinedAt = this.clock.elapsedTime;
@@ -1068,10 +1342,10 @@ var Room = class _Room {
1068
1342
  this.#_forciblyCloseClient(client, import_shared_types.CloseCode.CONSENTED);
1069
1343
  }
1070
1344
  }
1071
- #_forciblyCloseClient(client, closeCode) {
1345
+ #_forciblyCloseClient(client, closeCode, reason) {
1072
1346
  client.ref.removeAllListeners("message");
1073
1347
  client.ref.removeListener("close", client.ref["onleave"]);
1074
- this._onLeave(client, closeCode).then(() => client.leave(closeCode));
1348
+ this._onLeave(client, closeCode).then(() => client.leave(closeCode, reason));
1075
1349
  }
1076
1350
  async _onLeave(client, code) {
1077
1351
  const method = code === import_shared_types.CloseCode.CONSENTED || client.state === import_Transport.ClientState.RECONNECTING ? this.onLeave : this.onDrop || this.onLeave;
@@ -1190,6 +1464,9 @@ function room(options) {
1190
1464
  DEFAULT_SEAT_RESERVATION_TIME,
1191
1465
  Room,
1192
1466
  RoomInternalState,
1467
+ RoomPlugin,
1468
+ attachToTestRoom,
1469
+ definePlugins,
1193
1470
  room,
1194
1471
  validate
1195
1472
  });