@colyseus/core 0.16.23 → 0.17.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 (224) hide show
  1. package/build/Debug.js +16 -4
  2. package/build/Debug.js.map +2 -2
  3. package/build/Debug.mjs +11 -1
  4. package/build/Debug.mjs.map +2 -2
  5. package/build/IPC.js +5 -3
  6. package/build/IPC.js.map +2 -2
  7. package/build/IPC.mjs +1 -0
  8. package/build/IPC.mjs.map +2 -2
  9. package/build/Logger.js +1 -0
  10. package/build/Logger.js.map +1 -1
  11. package/build/MatchMaker.js +148 -84
  12. package/build/MatchMaker.js.map +3 -3
  13. package/build/MatchMaker.mjs +130 -70
  14. package/build/MatchMaker.mjs.map +3 -3
  15. package/build/Protocol.js +54 -41
  16. package/build/Protocol.js.map +3 -3
  17. package/build/Protocol.mjs +52 -41
  18. package/build/Protocol.mjs.map +3 -3
  19. package/build/Room.js +430 -195
  20. package/build/Room.js.map +3 -3
  21. package/build/Room.mjs +417 -175
  22. package/build/Room.mjs.map +3 -3
  23. package/build/Server.js +48 -108
  24. package/build/Server.js.map +3 -3
  25. package/build/Server.mjs +39 -102
  26. package/build/Server.mjs.map +3 -3
  27. package/build/Stats.js +2 -1
  28. package/build/Stats.js.map +2 -2
  29. package/build/Stats.mjs.map +1 -1
  30. package/build/Transport.js +29 -11
  31. package/build/Transport.js.map +3 -3
  32. package/build/Transport.mjs +15 -9
  33. package/build/Transport.mjs.map +3 -3
  34. package/build/errors/RoomExceptions.js +9 -5
  35. package/build/errors/RoomExceptions.js.map +2 -2
  36. package/build/errors/RoomExceptions.mjs +8 -5
  37. package/build/errors/RoomExceptions.mjs.map +2 -2
  38. package/build/errors/SeatReservationError.js +1 -0
  39. package/build/errors/SeatReservationError.js.map +1 -1
  40. package/build/errors/ServerError.js +2 -1
  41. package/build/errors/ServerError.js.map +2 -2
  42. package/build/errors/ServerError.mjs.map +1 -1
  43. package/build/index.js +60 -26
  44. package/build/index.js.map +2 -2
  45. package/build/index.mjs +33 -7
  46. package/build/index.mjs.map +2 -2
  47. package/build/matchmaker/Lobby.js +13 -3
  48. package/build/matchmaker/Lobby.js.map +2 -2
  49. package/build/matchmaker/Lobby.mjs +11 -2
  50. package/build/matchmaker/Lobby.mjs.map +2 -2
  51. package/build/matchmaker/LocalDriver/LocalDriver.js +94 -0
  52. package/build/matchmaker/LocalDriver/LocalDriver.js.map +7 -0
  53. package/build/matchmaker/LocalDriver/LocalDriver.mjs +71 -0
  54. package/build/matchmaker/LocalDriver/LocalDriver.mjs.map +7 -0
  55. package/build/matchmaker/LocalDriver/Query.js +111 -0
  56. package/build/matchmaker/LocalDriver/Query.js.map +7 -0
  57. package/build/matchmaker/LocalDriver/Query.mjs +88 -0
  58. package/build/matchmaker/LocalDriver/Query.mjs.map +7 -0
  59. package/build/matchmaker/RegisteredHandler.js +57 -7
  60. package/build/matchmaker/RegisteredHandler.js.map +2 -2
  61. package/build/matchmaker/RegisteredHandler.mjs +54 -5
  62. package/build/matchmaker/RegisteredHandler.mjs.map +2 -2
  63. package/build/matchmaker/controller.js +8 -8
  64. package/build/matchmaker/controller.js.map +2 -2
  65. package/build/matchmaker/controller.mjs +4 -5
  66. package/build/matchmaker/controller.mjs.map +2 -2
  67. package/build/matchmaker/driver/api.js +21 -2
  68. package/build/matchmaker/driver/api.js.map +2 -2
  69. package/build/matchmaker/driver/api.mjs +18 -1
  70. package/build/matchmaker/driver/api.mjs.map +2 -2
  71. package/build/matchmaker/driver/local/LocalDriver.js +36 -7
  72. package/build/matchmaker/driver/local/LocalDriver.js.map +2 -2
  73. package/build/matchmaker/driver/local/LocalDriver.mjs +33 -5
  74. package/build/matchmaker/driver/local/LocalDriver.mjs.map +2 -2
  75. package/build/matchmaker/driver/local/Query.js +51 -18
  76. package/build/matchmaker/driver/local/Query.js.map +2 -2
  77. package/build/matchmaker/driver/local/Query.mjs +50 -18
  78. package/build/matchmaker/driver/local/Query.mjs.map +2 -2
  79. package/build/matchmaker/driver.js +44 -0
  80. package/build/matchmaker/driver.js.map +7 -0
  81. package/build/matchmaker/driver.mjs +20 -0
  82. package/build/matchmaker/driver.mjs.map +7 -0
  83. package/build/matchmaker/routes.js +79 -0
  84. package/build/matchmaker/routes.js.map +7 -0
  85. package/build/matchmaker/routes.mjs +45 -0
  86. package/build/matchmaker/routes.mjs.map +7 -0
  87. package/build/presence/LocalPresence.js +13 -27
  88. package/build/presence/LocalPresence.js.map +3 -3
  89. package/build/presence/LocalPresence.mjs +11 -16
  90. package/build/presence/LocalPresence.mjs.map +2 -2
  91. package/build/presence/Presence.js +37 -0
  92. package/build/presence/Presence.js.map +2 -2
  93. package/build/presence/Presence.mjs +29 -0
  94. package/build/presence/Presence.mjs.map +3 -3
  95. package/build/rooms/LobbyRoom.js +5 -5
  96. package/build/rooms/LobbyRoom.js.map +2 -2
  97. package/build/rooms/LobbyRoom.mjs +1 -2
  98. package/build/rooms/LobbyRoom.mjs.map +2 -2
  99. package/build/rooms/RankedQueueRoom.js +224 -0
  100. package/build/rooms/RankedQueueRoom.js.map +7 -0
  101. package/build/rooms/RankedQueueRoom.mjs +201 -0
  102. package/build/rooms/RankedQueueRoom.mjs.map +7 -0
  103. package/build/rooms/RelayRoom.js +6 -6
  104. package/build/rooms/RelayRoom.js.map +2 -2
  105. package/build/rooms/RelayRoom.mjs +4 -5
  106. package/build/rooms/RelayRoom.mjs.map +2 -2
  107. package/build/rooms/createRoom.js +51 -0
  108. package/build/rooms/createRoom.js.map +7 -0
  109. package/build/rooms/createRoom.mjs +28 -0
  110. package/build/rooms/createRoom.mjs.map +7 -0
  111. package/build/router/default_routes.js +79 -0
  112. package/build/router/default_routes.js.map +7 -0
  113. package/build/router/default_routes.mjs +45 -0
  114. package/build/router/default_routes.mjs.map +7 -0
  115. package/build/router/index.js +55 -0
  116. package/build/router/index.js.map +7 -0
  117. package/build/router/index.mjs +30 -0
  118. package/build/router/index.mjs.map +7 -0
  119. package/build/serializer/NoneSerializer.js +1 -0
  120. package/build/serializer/NoneSerializer.js.map +2 -2
  121. package/build/serializer/NoneSerializer.mjs.map +2 -2
  122. package/build/serializer/SchemaSerializer.js +6 -7
  123. package/build/serializer/SchemaSerializer.js.map +2 -2
  124. package/build/serializer/SchemaSerializer.mjs +3 -5
  125. package/build/serializer/SchemaSerializer.mjs.map +2 -2
  126. package/build/serializer/SchemaSerializerDebug.js +29 -0
  127. package/build/serializer/SchemaSerializerDebug.js.map +3 -3
  128. package/build/serializer/SchemaSerializerDebug.mjs +7 -0
  129. package/build/serializer/SchemaSerializerDebug.mjs.map +3 -3
  130. package/build/serializer/Serializer.js +1 -0
  131. package/build/serializer/Serializer.js.map +2 -2
  132. package/build/{Debug.d.ts → src/Debug.d.ts} +2 -1
  133. package/build/{IPC.d.ts → src/IPC.d.ts} +2 -2
  134. package/build/{MatchMaker.d.ts → src/MatchMaker.d.ts} +58 -33
  135. package/build/src/Protocol.d.ts +53 -0
  136. package/build/src/Room.d.ts +497 -0
  137. package/build/{Server.d.ts → src/Server.d.ts} +23 -25
  138. package/build/{Transport.d.ts → src/Transport.d.ts} +65 -22
  139. package/build/{errors → src/errors}/RoomExceptions.d.ts +8 -7
  140. package/build/src/index.d.ts +27 -0
  141. package/build/src/matchmaker/Lobby.d.ts +4 -0
  142. package/build/src/matchmaker/LocalDriver/LocalDriver.d.ts +17 -0
  143. package/build/src/matchmaker/LocalDriver/Query.d.ts +12 -0
  144. package/build/src/matchmaker/RegisteredHandler.d.ts +81 -0
  145. package/build/{matchmaker → src/matchmaker}/controller.d.ts +5 -6
  146. package/build/src/matchmaker/driver/api.d.ts +145 -0
  147. package/build/src/matchmaker/driver/local/LocalDriver.d.ts +17 -0
  148. package/build/src/matchmaker/driver/local/Query.d.ts +12 -0
  149. package/build/src/matchmaker/driver.d.ts +145 -0
  150. package/build/src/matchmaker/routes.d.ts +92 -0
  151. package/build/{presence → src/presence}/LocalPresence.d.ts +1 -1
  152. package/build/{presence → src/presence}/Presence.d.ts +2 -0
  153. package/build/{rooms → src/rooms}/LobbyRoom.d.ts +4 -4
  154. package/build/src/rooms/RankedQueueRoom.d.ts +125 -0
  155. package/build/{rooms → src/rooms}/RelayRoom.d.ts +5 -4
  156. package/build/src/rooms/createRoom.d.ts +65 -0
  157. package/build/src/router/default_routes.d.ts +103 -0
  158. package/build/src/router/index.d.ts +68 -0
  159. package/build/{serializer → src/serializer}/NoneSerializer.d.ts +2 -2
  160. package/build/{serializer → src/serializer}/SchemaSerializer.d.ts +9 -9
  161. package/build/{serializer → src/serializer}/Serializer.d.ts +3 -3
  162. package/build/{utils → src/utils}/DevMode.d.ts +5 -4
  163. package/build/{utils → src/utils}/StandardSchema.d.ts +1 -1
  164. package/build/{utils → src/utils}/Utils.d.ts +15 -4
  165. package/build/utils/DevMode.js +54 -26
  166. package/build/utils/DevMode.js.map +3 -3
  167. package/build/utils/DevMode.mjs +44 -19
  168. package/build/utils/DevMode.mjs.map +2 -2
  169. package/build/utils/StandardSchema.js.map +1 -1
  170. package/build/utils/StandardSchema.mjs.map +1 -1
  171. package/build/utils/Utils.js +8 -15
  172. package/build/utils/Utils.js.map +3 -3
  173. package/build/utils/Utils.mjs +6 -4
  174. package/build/utils/Utils.mjs.map +2 -2
  175. package/package.json +20 -14
  176. package/build/Protocol.d.ts +0 -37
  177. package/build/Room.d.ts +0 -265
  178. package/build/discovery/index.d.ts +0 -8
  179. package/build/discovery/index.js +0 -50
  180. package/build/discovery/index.js.map +0 -7
  181. package/build/discovery/index.mjs +0 -26
  182. package/build/discovery/index.mjs.map +0 -7
  183. package/build/index.d.ts +0 -24
  184. package/build/matchmaker/Lobby.d.ts +0 -4
  185. package/build/matchmaker/RegisteredHandler.d.ts +0 -19
  186. package/build/matchmaker/driver/Query.d.ts +0 -8
  187. package/build/matchmaker/driver/Query.js +0 -68
  188. package/build/matchmaker/driver/Query.js.map +0 -7
  189. package/build/matchmaker/driver/Query.mjs +0 -45
  190. package/build/matchmaker/driver/Query.mjs.map +0 -7
  191. package/build/matchmaker/driver/RoomData.d.ts +0 -19
  192. package/build/matchmaker/driver/RoomData.js +0 -79
  193. package/build/matchmaker/driver/RoomData.js.map +0 -7
  194. package/build/matchmaker/driver/RoomData.mjs +0 -56
  195. package/build/matchmaker/driver/RoomData.mjs.map +0 -7
  196. package/build/matchmaker/driver/api.d.ts +0 -104
  197. package/build/matchmaker/driver/index.d.ts +0 -13
  198. package/build/matchmaker/driver/index.js +0 -64
  199. package/build/matchmaker/driver/index.js.map +0 -7
  200. package/build/matchmaker/driver/index.mjs +0 -42
  201. package/build/matchmaker/driver/index.mjs.map +0 -7
  202. package/build/matchmaker/driver/interfaces.d.ts +0 -73
  203. package/build/matchmaker/driver/interfaces.js +0 -15
  204. package/build/matchmaker/driver/interfaces.js.map +0 -7
  205. package/build/matchmaker/driver/interfaces.mjs +0 -0
  206. package/build/matchmaker/driver/interfaces.mjs.map +0 -7
  207. package/build/matchmaker/driver/local/LocalDriver.d.ts +0 -13
  208. package/build/matchmaker/driver/local/Query.d.ts +0 -9
  209. package/build/matchmaker/driver/local/RoomData.d.ts +0 -19
  210. package/build/matchmaker/driver/local/RoomData.js +0 -79
  211. package/build/matchmaker/driver/local/RoomData.js.map +0 -7
  212. package/build/matchmaker/driver/local/RoomData.mjs +0 -57
  213. package/build/matchmaker/driver/local/RoomData.mjs.map +0 -7
  214. package/build/utils/types.d.ts +0 -1
  215. package/build/utils/types.js +0 -15
  216. package/build/utils/types.js.map +0 -7
  217. package/build/utils/types.mjs +0 -0
  218. package/build/utils/types.mjs.map +0 -7
  219. /package/build/{Logger.d.ts → src/Logger.d.ts} +0 -0
  220. /package/build/{Stats.d.ts → src/Stats.d.ts} +0 -0
  221. /package/build/{errors → src/errors}/SeatReservationError.d.ts +0 -0
  222. /package/build/{errors → src/errors}/ServerError.d.ts +0 -0
  223. /package/build/{serializer → src/serializer}/SchemaSerializerDebug.d.ts +0 -0
  224. /package/build/{utils → src/utils}/nanoevents.d.ts +0 -0
package/build/Room.js CHANGED
@@ -1,8 +1,7 @@
1
- var __create = Object.create;
1
+ "use strict";
2
2
  var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __getProtoOf = Object.getPrototypeOf;
6
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
6
  var __export = (target, all) => {
8
7
  for (var name in all)
@@ -16,53 +15,52 @@ var __copyProps = (to, from, except, desc) => {
16
15
  }
17
16
  return to;
18
17
  };
19
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
- // If the importer is in node compatibility mode or this is not an ESM
21
- // file that has been converted to a CommonJS file using a Babel-
22
- // compatible transform (i.e. "__esModule" has not been set), then set
23
- // "default" to the CommonJS "module.exports" for node compatibility.
24
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
- mod
26
- ));
27
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
19
  var Room_exports = {};
29
20
  __export(Room_exports, {
30
21
  DEFAULT_SEAT_RESERVATION_TIME: () => DEFAULT_SEAT_RESERVATION_TIME,
31
22
  Room: () => Room,
32
- RoomInternalState: () => RoomInternalState
23
+ RoomInternalState: () => RoomInternalState,
24
+ room: () => room,
25
+ validate: () => validate
33
26
  });
34
27
  module.exports = __toCommonJS(Room_exports);
35
28
  var import_msgpackr = require("@colyseus/msgpackr");
36
29
  var import_schema = require("@colyseus/schema");
37
- var import_timer = __toESM(require("@colyseus/timer"));
30
+ var import_timer = require("@colyseus/timer");
38
31
  var import_events = require("events");
39
- var import_Logger = require("./Logger.js");
40
- var import_NoneSerializer = require("./serializer/NoneSerializer.js");
41
- var import_SchemaSerializer = require("./serializer/SchemaSerializer.js");
42
- var import_Protocol = require("./Protocol.js");
43
- var import_Utils = require("./utils/Utils.js");
44
- var import_DevMode = require("./utils/DevMode.js");
45
- var import_Debug = require("./Debug.js");
46
- var import_ServerError = require("./errors/ServerError.js");
47
- var import_Transport = require("./Transport.js");
48
- var import_RoomExceptions = require("./errors/RoomExceptions.js");
32
+ var import_Logger = require("./Logger.ts");
33
+ var import_NoneSerializer = require("./serializer/NoneSerializer.ts");
34
+ var import_SchemaSerializer = require("./serializer/SchemaSerializer.ts");
35
+ var import_Protocol = require("./Protocol.ts");
36
+ var import_Utils = require("./utils/Utils.ts");
37
+ var import_nanoevents = require("./utils/nanoevents.ts");
38
+ var import_DevMode = require("./utils/DevMode.ts");
39
+ var import_Debug = require("./Debug.ts");
40
+ var import_ServerError = require("./errors/ServerError.ts");
41
+ var import_Transport = require("./Transport.ts");
42
+ var import_RoomExceptions = require("./errors/RoomExceptions.ts");
43
+ var import_StandardSchema = require("./utils/StandardSchema.ts");
44
+ var import_core = require("@colyseus/core");
49
45
  const DEFAULT_PATCH_RATE = 1e3 / 20;
50
46
  const DEFAULT_SIMULATION_INTERVAL = 1e3 / 60;
51
47
  const noneSerializer = new import_NoneSerializer.NoneSerializer();
52
48
  const DEFAULT_SEAT_RESERVATION_TIME = Number(process.env.COLYSEUS_SEAT_RESERVATION_TIME || 15);
53
- var RoomInternalState = /* @__PURE__ */ ((RoomInternalState2) => {
54
- RoomInternalState2[RoomInternalState2["CREATING"] = 0] = "CREATING";
55
- RoomInternalState2[RoomInternalState2["CREATED"] = 1] = "CREATED";
56
- RoomInternalState2[RoomInternalState2["DISPOSING"] = 2] = "DISPOSING";
57
- return RoomInternalState2;
58
- })(RoomInternalState || {});
49
+ function validate(format, handler) {
50
+ return { format, handler };
51
+ }
52
+ const RoomInternalState = {
53
+ CREATING: 0,
54
+ CREATED: 1,
55
+ DISPOSING: 2
56
+ };
59
57
  class Room {
60
58
  constructor() {
61
59
  /**
62
60
  * Timing events tied to the room instance.
63
61
  * Intervals and timeouts are cleared when the room is disposed.
64
62
  */
65
- this.clock = new import_timer.default();
63
+ this.clock = new import_timer.ClockTimer();
66
64
  this.#_onLeaveConcurrent = 0;
67
65
  // number of onLeave calls in progress
68
66
  /**
@@ -84,40 +82,52 @@ class Room {
84
82
  * @default 50ms (20fps)
85
83
  */
86
84
  this.patchRate = DEFAULT_PATCH_RATE;
85
+ /**
86
+ * Maximum number of messages a client can send to the server per second.
87
+ * If a client sends more messages than this, it will be disconnected.
88
+ *
89
+ * @default 60
90
+ */
91
+ this.maxMessagesPerSecond = 60;
87
92
  /**
88
93
  * The array of connected clients.
89
94
  *
90
- * @see {@link https://docs.colyseus.io/colyseus/server/room/#client|Client instance}
95
+ * @see [Client instance](https://docs.colyseus.io/room#client)
91
96
  */
92
97
  this.clients = new import_Transport.ClientArray();
93
- /** @internal */
98
+ /**
99
+ * Set the number of seconds a room can wait for a client to effectively join the room.
100
+ * You should consider how long your `onAuth()` will have to wait for setting a different seat reservation time.
101
+ * The default value is 15 seconds. You may set the `COLYSEUS_SEAT_RESERVATION_TIME`
102
+ * environment variable if you'd like to change the seat reservation time globally.
103
+ *
104
+ * @default 15 seconds
105
+ */
106
+ this.seatReservationTimeout = DEFAULT_SEAT_RESERVATION_TIME;
94
107
  this._events = new import_events.EventEmitter();
95
- // seat reservation & reconnection
96
- this.seatReservationTime = DEFAULT_SEAT_RESERVATION_TIME;
97
- this.reservedSeats = {};
98
- this.reservedSeatTimeouts = {};
108
+ this._reservedSeats = {};
109
+ this._reservedSeatTimeouts = {};
99
110
  this._reconnections = {};
100
- this._reconnectingSessionId = /* @__PURE__ */ new Map();
101
- this.onMessageHandlers = {
102
- "__no_message_handler": {
103
- callback: (client, messageType, _) => {
104
- const errorMessage = `room onMessage for "${messageType}" not registered.`;
105
- (0, import_Debug.debugAndPrintError)(`${errorMessage} (roomId: ${this.roomId})`);
106
- if (import_DevMode.isDevMode) {
107
- client.error(import_Protocol.ErrorCode.INVALID_PAYLOAD, errorMessage);
108
- } else {
109
- client.leave(import_Protocol.Protocol.WS_CLOSE_WITH_ERROR, errorMessage);
110
- }
111
+ this.onMessageEvents = (0, import_nanoevents.createNanoEvents)();
112
+ this.onMessageValidators = {};
113
+ this.onMessageFallbacks = {
114
+ "__no_message_handler": (client, messageType, _) => {
115
+ const errorMessage = `room onMessage for "${messageType}" not registered.`;
116
+ (0, import_Debug.debugMessage)(`${errorMessage} (roomId: ${this.roomId})`);
117
+ if (import_DevMode.isDevMode) {
118
+ client.error(import_Protocol.ErrorCode.INVALID_PAYLOAD, errorMessage);
119
+ } else {
120
+ client.leave(import_Protocol.CloseCode.WITH_ERROR, errorMessage);
111
121
  }
112
122
  }
113
123
  };
114
124
  this._serializer = noneSerializer;
115
125
  this._afterNextPatchQueue = [];
116
- this._internalState = 0 /* CREATING */;
126
+ this._internalState = RoomInternalState.CREATING;
117
127
  this._lockedExplicitly = false;
118
128
  this.#_locked = false;
119
129
  this._events.once("dispose", () => {
120
- 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"));
130
+ 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"));
121
131
  });
122
132
  if (this.onUncaughtException !== void 0) {
123
133
  this.#registerUncaughtExceptionHandlers();
@@ -133,8 +143,31 @@ class Room {
133
143
  get locked() {
134
144
  return this.#_locked;
135
145
  }
146
+ /**
147
+ * Get the room's matchmaking metadata.
148
+ */
136
149
  get metadata() {
137
- return this.listing.metadata;
150
+ return this._listing.metadata;
151
+ }
152
+ /**
153
+ * Set the room's matchmaking metadata.
154
+ *
155
+ * **Note**: This setter does NOT automatically persist. Use `setMatchmaking()` for automatic persistence.
156
+ *
157
+ * @example
158
+ * ```typescript
159
+ * class MyRoom extends Room<{ metadata: { difficulty: string; rating: number } }> {
160
+ * async onCreate() {
161
+ * this.metadata = { difficulty: "hard", rating: 1500 };
162
+ * }
163
+ * }
164
+ * ```
165
+ */
166
+ set metadata(meta) {
167
+ if (this._internalState !== RoomInternalState.CREATING) {
168
+ throw new import_ServerError.ServerError(import_Protocol.ErrorCode.APPLICATION_ERROR, "'metadata' can only be manually set during onCreate(). Use setMatchmaking() instead.");
169
+ }
170
+ this._listing.metadata = meta;
138
171
  }
139
172
  #_roomId;
140
173
  #_roomName;
@@ -175,29 +208,14 @@ class Room {
175
208
  enumerable: true,
176
209
  get: () => this.#_maxClients,
177
210
  set: (value) => {
178
- this.#_maxClients = value;
179
- if (this._internalState === 1 /* CREATED */) {
180
- const hasReachedMaxClients = this.hasReachedMaxClients();
181
- if (!this._lockedExplicitly && this.#_maxClientsReached && !hasReachedMaxClients) {
182
- this.#_maxClientsReached = false;
183
- this.#_locked = false;
184
- this.listing.locked = false;
185
- }
186
- if (hasReachedMaxClients) {
187
- this.#_maxClientsReached = true;
188
- this.#_locked = true;
189
- this.listing.locked = true;
190
- }
191
- this.listing.maxClients = value;
192
- this.listing.save();
193
- }
211
+ this.setMatchmaking({ maxClients: value });
194
212
  }
195
213
  },
196
214
  autoDispose: {
197
215
  enumerable: true,
198
216
  get: () => this.#_autoDispose,
199
217
  set: (value) => {
200
- if (value !== this.#_autoDispose && this._internalState !== 2 /* DISPOSING */) {
218
+ if (value !== this.#_autoDispose && this._internalState !== RoomInternalState.DISPOSING) {
201
219
  this.#_autoDispose = value;
202
220
  this.resetAutoDisposeTimeout();
203
221
  }
@@ -214,6 +232,8 @@ class Room {
214
232
  }
215
233
  if (milliseconds !== null && milliseconds !== 0) {
216
234
  this.#_patchInterval = setInterval(() => this.broadcastPatch(), milliseconds);
235
+ } else if (!this._simulationInterval) {
236
+ this.#_patchInterval = setInterval(() => this.clock.tick(), DEFAULT_SIMULATION_INTERVAL);
217
237
  }
218
238
  }
219
239
  }
@@ -222,7 +242,16 @@ class Room {
222
242
  if (this.#_state) {
223
243
  this.state = this.#_state;
224
244
  }
225
- this.resetAutoDisposeTimeout(this.seatReservationTime);
245
+ if (this.messages !== void 0) {
246
+ Object.entries(this.messages).forEach(([messageType, callback]) => {
247
+ if (typeof callback === "function") {
248
+ this.onMessage(messageType, callback.bind(this));
249
+ } else {
250
+ this.onMessage(messageType, callback.format, callback.handler.bind(this));
251
+ }
252
+ });
253
+ }
254
+ this.resetAutoDisposeTimeout(this.seatReservationTimeout);
226
255
  this.clock.start();
227
256
  }
228
257
  /**
@@ -260,11 +289,27 @@ class Room {
260
289
  * @returns roomId string
261
290
  */
262
291
  set roomId(roomId) {
263
- if (this._internalState !== 0 /* CREATING */ && !import_DevMode.isDevMode) {
292
+ if (this._internalState !== RoomInternalState.CREATING && !import_DevMode.isDevMode) {
264
293
  throw new import_ServerError.ServerError(import_Protocol.ErrorCode.APPLICATION_ERROR, "'roomId' can only be overridden upon room creation.");
265
294
  }
266
295
  this.#_roomId = roomId;
267
296
  }
297
+ /**
298
+ * This method is called before onJoin() - this is where you should authenticate the client
299
+ * @param client - The client that is authenticating.
300
+ * @param options - The options passed to the client when it is authenticating.
301
+ * @param context - The authentication context, including the token and the client's IP address.
302
+ * @returns The authentication result.
303
+ *
304
+ * @example
305
+ * ```typescript
306
+ * return {
307
+ * userId: 123,
308
+ * username: "John Doe",
309
+ * email: "john.doe@example.com",
310
+ * };
311
+ * ```
312
+ */
268
313
  onAuth(client, options, context) {
269
314
  return true;
270
315
  }
@@ -279,7 +324,7 @@ class Room {
279
324
  */
280
325
  onBeforeShutdown() {
281
326
  this.disconnect(
282
- import_DevMode.isDevMode ? import_Protocol.Protocol.WS_CLOSE_DEVMODE_RESTART : import_Protocol.Protocol.WS_CLOSE_CONSENTED
327
+ import_DevMode.isDevMode ? import_Protocol.CloseCode.DEVMODE_RESTART : import_Protocol.CloseCode.SERVER_SHUTDOWN
283
328
  );
284
329
  }
285
330
  /**
@@ -288,39 +333,31 @@ class Room {
288
333
  * @returns boolean
289
334
  */
290
335
  hasReachedMaxClients() {
291
- return this.clients.length + Object.keys(this.reservedSeats).length >= this.maxClients || this._internalState === 2 /* DISPOSING */;
336
+ return this.clients.length + Object.keys(this._reservedSeats).length >= this.#_maxClients || this._internalState === RoomInternalState.DISPOSING;
292
337
  }
293
338
  /**
294
- * Set the number of seconds a room can wait for a client to effectively join the room.
295
- * You should consider how long your `onAuth()` will have to wait for setting a different seat reservation time.
296
- * The default value is 15 seconds. You may set the `COLYSEUS_SEAT_RESERVATION_TIME`
297
- * environment variable if you'd like to change the seat reservation time globally.
298
- *
299
- * @default 15 seconds
300
- *
301
- * @param seconds - number of seconds.
302
- * @returns The modified Room object.
339
+ * @deprecated Use `seatReservationTimeout=` instead.
303
340
  */
304
341
  setSeatReservationTime(seconds) {
305
- this.seatReservationTime = seconds;
342
+ console.warn(`DEPRECATED: .setSeatReservationTime(${seconds}) is deprecated. Assign a .seatReservationTimeout property value instead.`);
343
+ this.seatReservationTimeout = seconds;
306
344
  return this;
307
345
  }
308
346
  hasReservedSeat(sessionId, reconnectionToken) {
309
- const reservedSeat = this.reservedSeats[sessionId];
347
+ const reservedSeat = this._reservedSeats[sessionId];
310
348
  if (reservedSeat === void 0) {
311
349
  return false;
312
350
  }
313
351
  if (reservedSeat[3]) {
314
- return reconnectionToken && this._reconnections[reconnectionToken]?.[0] === sessionId && this._reconnectingSessionId.has(sessionId);
352
+ return reconnectionToken && this._reconnections[reconnectionToken]?.[0] === sessionId;
315
353
  } else {
316
354
  return reservedSeat[2] === false;
317
355
  }
318
356
  }
319
357
  checkReconnectionToken(reconnectionToken) {
320
358
  const sessionId = this._reconnections[reconnectionToken]?.[0];
321
- const reservedSeat = this.reservedSeats[sessionId];
359
+ const reservedSeat = this._reservedSeats[sessionId];
322
360
  if (reservedSeat && reservedSeat[3]) {
323
- this._reconnectingSessionId.set(sessionId, reconnectionToken);
324
361
  return sessionId;
325
362
  } else {
326
363
  return void 0;
@@ -365,34 +402,125 @@ class Room {
365
402
  setSerializer(serializer) {
366
403
  this._serializer = serializer;
367
404
  }
368
- async setMetadata(meta) {
369
- if (!this.listing.metadata) {
370
- this.listing.metadata = meta;
405
+ async setMetadata(meta, persist = true) {
406
+ if (!this._listing.metadata) {
407
+ this._listing.metadata = meta;
371
408
  } else {
372
409
  for (const field in meta) {
373
410
  if (!meta.hasOwnProperty(field)) {
374
411
  continue;
375
412
  }
376
- this.listing.metadata[field] = meta[field];
413
+ this._listing.metadata[field] = meta[field];
377
414
  }
378
- if ("markModified" in this.listing) {
379
- this.listing.markModified("metadata");
415
+ if ("markModified" in this._listing) {
416
+ this._listing.markModified("metadata");
380
417
  }
381
418
  }
382
- if (this._internalState === 1 /* CREATED */) {
383
- await this.listing.save();
419
+ if (persist && this._internalState === RoomInternalState.CREATED) {
420
+ await import_core.matchMaker.driver.persist(this._listing);
421
+ this._events.emit("metadata-change");
384
422
  }
385
423
  }
386
- async setPrivate(bool = true) {
387
- if (this.listing.private === bool) return;
388
- this.listing.private = bool;
389
- if (this._internalState === 1 /* CREATED */) {
390
- await this.listing.save();
424
+ async setPrivate(bool = true, persist = true) {
425
+ if (this._listing.private === bool) return;
426
+ this._listing.private = bool;
427
+ if (persist && this._internalState === RoomInternalState.CREATED) {
428
+ await import_core.matchMaker.driver.persist(this._listing);
391
429
  }
392
430
  this._events.emit("visibility-change", bool);
393
431
  }
394
432
  /**
395
- * Locking the room will remove it from the pool of available rooms for new clients to connect to.
433
+ * Update multiple matchmaking/listing properties at once with a single persist operation.
434
+ * This is the recommended way to update room listing properties.
435
+ *
436
+ * @param updates - Object containing the properties to update
437
+ *
438
+ * @example
439
+ * ```typescript
440
+ * // Update multiple properties at once
441
+ * await this.setMatchmaking({
442
+ * metadata: { difficulty: "hard", rating: 1500 },
443
+ * private: true,
444
+ * locked: true,
445
+ * maxClients: 10
446
+ * });
447
+ * ```
448
+ *
449
+ * @example
450
+ * ```typescript
451
+ * // Update only metadata
452
+ * await this.setMatchmaking({
453
+ * metadata: { status: "in_progress" }
454
+ * });
455
+ * ```
456
+ *
457
+ * @example
458
+ * ```typescript
459
+ * // Partial metadata update (merges with existing)
460
+ * await this.setMatchmaking({
461
+ * metadata: { ...this.metadata, round: this.metadata.round + 1 }
462
+ * });
463
+ * ```
464
+ */
465
+ async setMatchmaking(updates) {
466
+ for (const key in updates) {
467
+ if (!updates.hasOwnProperty(key)) {
468
+ continue;
469
+ }
470
+ switch (key) {
471
+ case "metadata": {
472
+ this.setMetadata(updates.metadata, false);
473
+ break;
474
+ }
475
+ case "private": {
476
+ this.setPrivate(updates.private, false);
477
+ break;
478
+ }
479
+ case "locked": {
480
+ if (updates[key]) {
481
+ this.lock.call(this, true);
482
+ this._lockedExplicitly = true;
483
+ } else {
484
+ this.unlock.call(this, true);
485
+ this._lockedExplicitly = false;
486
+ }
487
+ break;
488
+ }
489
+ case "maxClients": {
490
+ this.#_maxClients = updates.maxClients;
491
+ this._listing.maxClients = updates.maxClients;
492
+ const hasReachedMaxClients = this.hasReachedMaxClients();
493
+ if (!this._lockedExplicitly && this.#_maxClientsReached && !hasReachedMaxClients) {
494
+ this.#_maxClientsReached = false;
495
+ this.#_locked = false;
496
+ this._listing.locked = false;
497
+ updates.locked = false;
498
+ }
499
+ if (hasReachedMaxClients) {
500
+ this.#_maxClientsReached = true;
501
+ this.#_locked = true;
502
+ this._listing.locked = true;
503
+ updates.locked = true;
504
+ }
505
+ break;
506
+ }
507
+ case "clients": {
508
+ console.warn("setMatchmaking() does not allow updating 'clients' property.");
509
+ break;
510
+ }
511
+ default: {
512
+ this._listing[key] = updates[key];
513
+ break;
514
+ }
515
+ }
516
+ }
517
+ if (this._internalState === RoomInternalState.CREATED) {
518
+ await import_core.matchMaker.driver.update(this._listing, { $set: updates });
519
+ this._events.emit("metadata-change");
520
+ }
521
+ }
522
+ /**
523
+ * Lock the room. This prevents new clients from joining this room.
396
524
  */
397
525
  async lock() {
398
526
  this._lockedExplicitly = arguments[0] === void 0;
@@ -400,13 +528,15 @@ class Room {
400
528
  return;
401
529
  }
402
530
  this.#_locked = true;
403
- await this.listing.updateOne({
404
- $set: { locked: this.#_locked }
405
- });
531
+ if (this._lockedExplicitly) {
532
+ await import_core.matchMaker.driver.update(this._listing, {
533
+ $set: { locked: this.#_locked }
534
+ });
535
+ }
406
536
  this._events.emit("lock");
407
537
  }
408
538
  /**
409
- * Unlocking the room returns it to the pool of available rooms for new clients to connect to.
539
+ * Unlock the room. This allows new clients to join this room, if maxClients is not reached.
410
540
  */
411
541
  async unlock() {
412
542
  if (arguments[0] === void 0) {
@@ -416,19 +546,33 @@ class Room {
416
546
  return;
417
547
  }
418
548
  this.#_locked = false;
419
- await this.listing.updateOne({
420
- $set: { locked: this.#_locked }
421
- });
549
+ if (arguments[0] === void 0) {
550
+ await import_core.matchMaker.driver.update(this._listing, {
551
+ $set: { locked: this.#_locked }
552
+ });
553
+ }
422
554
  this._events.emit("unlock");
423
555
  }
424
556
  send(client, messageOrType, messageOrOptions, options) {
425
557
  import_Logger.logger.warn("DEPRECATION WARNING: use client.send(...) instead of this.send(client, ...)");
426
558
  client.send(messageOrType, messageOrOptions, options);
427
559
  }
428
- broadcast(type, message, options) {
560
+ /**
561
+ * Broadcast a message to all connected clients.
562
+ * @param type - The type of the message.
563
+ * @param message - The message to broadcast.
564
+ * @param options - The options for the broadcast.
565
+ *
566
+ * @example
567
+ * ```typescript
568
+ * this.broadcast('message', { message: 'Hello, world!' });
569
+ * ```
570
+ */
571
+ broadcast(type, ...args) {
572
+ const [message, options] = args;
429
573
  if (options && options.afterNextPatch) {
430
574
  delete options.afterNextPatch;
431
- this._afterNextPatchQueue.push(["broadcast", arguments]);
575
+ this._afterNextPatchQueue.push(["broadcast", [type, ...args]]);
432
576
  return;
433
577
  }
434
578
  this.broadcastMessageType(type, message, options);
@@ -461,9 +605,30 @@ class Room {
461
605
  this._dequeueAfterPatchMessages();
462
606
  return hasChanges;
463
607
  }
464
- onMessage(messageType, callback, validate) {
465
- this.onMessageHandlers[messageType] = this.onUncaughtException !== void 0 ? { validate, callback: (0, import_Utils.wrapTryCatch)(callback, this.onUncaughtException.bind(this), import_RoomExceptions.OnMessageException, "onMessage", false, messageType) } : { validate, callback };
466
- return () => delete this.onMessageHandlers[messageType];
608
+ onMessage(_messageType, _validationSchema, _callback) {
609
+ const messageType = _messageType.toString();
610
+ const validationSchema = typeof _callback === "function" ? _validationSchema : void 0;
611
+ const callback = validationSchema === void 0 ? _validationSchema : _callback;
612
+ const removeListener = this.onMessageEvents.on(messageType, this.onUncaughtException !== void 0 ? (0, import_Utils.wrapTryCatch)(callback, this.onUncaughtException.bind(this), import_RoomExceptions.OnMessageException, "onMessage", false, _messageType) : callback);
613
+ if (validationSchema !== void 0) {
614
+ this.onMessageValidators[messageType] = validationSchema;
615
+ }
616
+ return () => {
617
+ removeListener();
618
+ if (this.onMessageEvents.events[messageType].length === 0) {
619
+ delete this.onMessageValidators[messageType];
620
+ }
621
+ };
622
+ }
623
+ onMessageBytes(_messageType, _validationSchema, _callback) {
624
+ const messageType = `_$b${_messageType}`;
625
+ const validationSchema = typeof _callback === "function" ? _validationSchema : void 0;
626
+ const callback = validationSchema === void 0 ? _validationSchema : _callback;
627
+ if (validationSchema !== void 0) {
628
+ return this.onMessage(messageType, validationSchema, callback);
629
+ } else {
630
+ return this.onMessage(messageType, callback);
631
+ }
467
632
  }
468
633
  /**
469
634
  * Disconnect all connected clients, and then dispose the room.
@@ -471,14 +636,14 @@ class Room {
471
636
  * @param closeCode WebSocket close code (default = 4000, which is a "consented leave")
472
637
  * @returns Promise<void>
473
638
  */
474
- disconnect(closeCode = import_Protocol.Protocol.WS_CLOSE_CONSENTED) {
475
- if (this._internalState === 2 /* DISPOSING */) {
639
+ disconnect(closeCode = import_Protocol.CloseCode.CONSENTED) {
640
+ if (this._internalState === RoomInternalState.DISPOSING) {
476
641
  return Promise.resolve(`disconnect() ignored: room (${this.roomId}) is already disposing.`);
477
- } else if (this._internalState === 0 /* CREATING */) {
642
+ } else if (this._internalState === RoomInternalState.CREATING) {
478
643
  throw new Error("cannot disconnect during onCreate()");
479
644
  }
480
- this._internalState = 2 /* DISPOSING */;
481
- this.listing.remove();
645
+ this._internalState = RoomInternalState.DISPOSING;
646
+ import_core.matchMaker.driver.remove(this._listing.roomId);
482
647
  this.#_autoDispose = true;
483
648
  const delayedDisconnection = new Promise((resolve) => this._events.once("disconnect", () => resolve()));
484
649
  for (const [_, reconnection] of Object.values(this._reconnections)) {
@@ -487,38 +652,41 @@ class Room {
487
652
  let numClients = this.clients.length;
488
653
  if (numClients > 0) {
489
654
  while (numClients--) {
490
- this._forciblyCloseClient(this.clients[numClients], closeCode);
655
+ this.#_forciblyCloseClient(this.clients[numClients], closeCode);
491
656
  }
492
657
  } else {
493
658
  this._events.emit("dispose");
494
659
  }
495
660
  return delayedDisconnection;
496
661
  }
497
- async ["_onJoin"](client, authContext) {
662
+ async _onJoin(client, authContext, connectionOptions) {
498
663
  const sessionId = client.sessionId;
499
664
  client.reconnectionToken = (0, import_Utils.generateId)();
500
- if (this.reservedSeatTimeouts[sessionId]) {
501
- clearTimeout(this.reservedSeatTimeouts[sessionId]);
502
- delete this.reservedSeatTimeouts[sessionId];
665
+ if (this._reservedSeatTimeouts[sessionId]) {
666
+ clearTimeout(this._reservedSeatTimeouts[sessionId]);
667
+ delete this._reservedSeatTimeouts[sessionId];
503
668
  }
504
669
  if (this._autoDisposeTimeout) {
505
670
  clearTimeout(this._autoDisposeTimeout);
506
671
  this._autoDisposeTimeout = void 0;
507
672
  }
508
- const [joinOptions, authData, isConsumed, isWaitingReconnection] = this.reservedSeats[sessionId];
673
+ const [joinOptions, authData, isConsumed, isWaitingReconnection] = this._reservedSeats[sessionId];
509
674
  if (isConsumed) {
510
675
  throw new import_ServerError.ServerError(import_Protocol.ErrorCode.MATCHMAKE_EXPIRED, "already consumed");
511
676
  }
512
- this.reservedSeats[sessionId][2] = true;
677
+ this._reservedSeats[sessionId][2] = true;
513
678
  (0, import_Debug.debugMatchMaking)("consuming seat reservation, sessionId: '%s' (roomId: %s)", client.sessionId, this.roomId);
514
679
  client._afterNextPatchQueue = this._afterNextPatchQueue;
515
680
  client.ref["onleave"] = (_) => client.state = import_Transport.ClientState.LEAVING;
516
681
  client.ref.once("close", client.ref["onleave"]);
517
682
  if (isWaitingReconnection) {
518
- const previousReconnectionToken = this._reconnectingSessionId.get(sessionId);
519
- if (previousReconnectionToken) {
683
+ const reconnectionToken = connectionOptions?.reconnectionToken;
684
+ if (reconnectionToken && this._reconnections[reconnectionToken]?.[0] === sessionId) {
520
685
  this.clients.push(client);
521
- await this._reconnections[previousReconnectionToken]?.[1].resolve(client);
686
+ await this._reconnections[reconnectionToken]?.[1].resolve(client);
687
+ if (this.onReconnect) {
688
+ await this.onReconnect(client);
689
+ }
522
690
  } else {
523
691
  const errorMessage = process.env.NODE_ENV === "production" ? "already consumed" : "bad reconnection token";
524
692
  throw new import_ServerError.ServerError(import_Protocol.ErrorCode.MATCHMAKE_EXPIRED, errorMessage);
@@ -534,31 +702,31 @@ class Room {
534
702
  throw new import_ServerError.ServerError(import_Protocol.ErrorCode.AUTH_FAILED, "onAuth failed");
535
703
  }
536
704
  } catch (e) {
537
- delete this.reservedSeats[sessionId];
538
- await this._decrementClientCount();
705
+ delete this._reservedSeats[sessionId];
706
+ await this.#_decrementClientCount();
539
707
  throw e;
540
708
  }
541
709
  }
542
710
  if (client.state === import_Transport.ClientState.LEAVING) {
543
- throw new import_ServerError.ServerError(import_Protocol.Protocol.WS_CLOSE_GOING_AWAY, "already disconnected");
711
+ throw new import_ServerError.ServerError(import_Protocol.CloseCode.WITH_ERROR, "already disconnected");
544
712
  }
545
713
  this.clients.push(client);
546
- Object.defineProperty(this.reservedSeats, sessionId, {
547
- value: this.reservedSeats[sessionId],
714
+ Object.defineProperty(this._reservedSeats, sessionId, {
715
+ value: this._reservedSeats[sessionId],
548
716
  enumerable: false
549
717
  });
550
718
  if (this.onJoin) {
551
- await this.onJoin(client, joinOptions, client.auth);
719
+ await this.onJoin(client, joinOptions);
552
720
  }
553
721
  if (client.state === import_Transport.ClientState.LEAVING) {
554
- throw new Error("early_leave");
722
+ throw new import_ServerError.ServerError(import_Protocol.ErrorCode.MATCHMAKE_UNHANDLED, "early_leave");
555
723
  } else {
556
- delete this.reservedSeats[sessionId];
724
+ delete this._reservedSeats[sessionId];
557
725
  this._events.emit("join", client);
558
726
  }
559
727
  } catch (e) {
560
- await this._onLeave(client, import_Protocol.Protocol.WS_CLOSE_GOING_AWAY);
561
- delete this.reservedSeats[sessionId];
728
+ await this._onLeave(client, import_Protocol.CloseCode.WITH_ERROR);
729
+ delete this._reservedSeats[sessionId];
562
730
  if (!e.code) {
563
731
  e.code = import_Protocol.ErrorCode.APPLICATION_ERROR;
564
732
  }
@@ -573,7 +741,11 @@ class Room {
573
741
  client.raw(import_Protocol.getMessageBytes[import_Protocol.Protocol.JOIN_ROOM](
574
742
  client.reconnectionToken,
575
743
  this._serializer.id,
576
- this._serializer.handshake && this._serializer.handshake()
744
+ /**
745
+ * if skipHandshake is true, we don't need to send the handshake
746
+ * (in case client already has handshake data)
747
+ */
748
+ connectionOptions?.skipHandshake ? void 0 : this._serializer.handshake && this._serializer.handshake()
577
749
  ));
578
750
  }
579
751
  }
@@ -581,11 +753,19 @@ class Room {
581
753
  * Allow the specified client to reconnect into the room. Must be used inside `onLeave()` method.
582
754
  * If seconds is provided, the reconnection is going to be cancelled after the provided amount of seconds.
583
755
  *
584
- * @param previousClient - The client which is to be waiting until re-connection happens.
585
- * @param seconds - Timeout period on re-connection in seconds.
756
+ * @param client - The client that is allowed to reconnect into the room.
757
+ * @param seconds - The time in seconds that the client is allowed to reconnect into the room.
586
758
  *
587
759
  * @returns Deferred<Client> - The differed is a promise like type.
588
760
  * This type can forcibly reject the promise by calling `.reject()`.
761
+ *
762
+ * @example
763
+ * ```typescript
764
+ * onDrop(client: Client, code: CloseCode) {
765
+ * // Allow the client to reconnect into the room with a 15 seconds timeout.
766
+ * this.allowReconnection(client, 15);
767
+ * }
768
+ * ```
589
769
  */
590
770
  allowReconnection(previousClient, seconds) {
591
771
  if (previousClient._enqueuedMessages !== void 0) {
@@ -598,7 +778,7 @@ class Room {
598
778
  if (seconds === "manual") {
599
779
  seconds = Infinity;
600
780
  }
601
- if (this._internalState === 2 /* DISPOSING */) {
781
+ if (this._internalState === RoomInternalState.DISPOSING) {
602
782
  return Promise.reject(new Error("disposing"));
603
783
  }
604
784
  const sessionId = previousClient.sessionId;
@@ -607,13 +787,12 @@ class Room {
607
787
  const reconnection = new import_Utils.Deferred();
608
788
  this._reconnections[reconnectionToken] = [sessionId, reconnection];
609
789
  if (seconds !== Infinity) {
610
- this.reservedSeatTimeouts[sessionId] = setTimeout(() => reconnection.reject(false), seconds * 1e3);
790
+ this._reservedSeatTimeouts[sessionId] = setTimeout(() => reconnection.reject(false), seconds * 1e3);
611
791
  }
612
792
  const cleanup = () => {
613
793
  delete this._reconnections[reconnectionToken];
614
- delete this.reservedSeats[sessionId];
615
- delete this.reservedSeatTimeouts[sessionId];
616
- this._reconnectingSessionId.delete(sessionId);
794
+ delete this._reservedSeats[sessionId];
795
+ delete this._reservedSeatTimeouts[sessionId];
617
796
  };
618
797
  reconnection.then((newClient) => {
619
798
  newClient.auth = previousClient.auth;
@@ -622,11 +801,11 @@ class Room {
622
801
  previousClient.state = import_Transport.ClientState.RECONNECTED;
623
802
  previousClient.ref = newClient.ref;
624
803
  previousClient.reconnectionToken = newClient.reconnectionToken;
625
- clearTimeout(this.reservedSeatTimeouts[sessionId]);
626
- cleanup();
627
- }).catch(() => {
628
- cleanup();
804
+ clearTimeout(this._reservedSeatTimeouts[sessionId]);
805
+ }, () => {
629
806
  this.resetAutoDisposeTimeout();
807
+ }).finally(() => {
808
+ cleanup();
630
809
  });
631
810
  return reconnection;
632
811
  }
@@ -637,7 +816,7 @@ class Room {
637
816
  }
638
817
  this._autoDisposeTimeout = setTimeout(() => {
639
818
  this._autoDisposeTimeout = void 0;
640
- this._disposeIfEmpty();
819
+ this.#_disposeIfEmpty();
641
820
  }, timeoutInSeconds * 1e3);
642
821
  }
643
822
  broadcastMessageType(type, message, options = {}) {
@@ -669,36 +848,52 @@ class Room {
669
848
  this._afterNextPatchQueue.splice(0, length);
670
849
  }
671
850
  }
672
- async _reserveSeat(sessionId, joinOptions = true, authData = void 0, seconds = this.seatReservationTime, allowReconnection = false, devModeReconnection) {
851
+ async _reserveSeat(sessionId, joinOptions = true, authData = void 0, seconds = this.seatReservationTimeout, allowReconnection = false, devModeReconnectionToken) {
673
852
  if (!allowReconnection && this.hasReachedMaxClients()) {
674
853
  return false;
675
854
  }
676
- this.reservedSeats[sessionId] = [joinOptions, authData, false, allowReconnection];
855
+ (0, import_Debug.debugMatchMaking)(
856
+ "reserving seat. sessionId: '%s', roomId: '%s', processId: '%s'",
857
+ sessionId,
858
+ this.roomId,
859
+ import_core.matchMaker.processId
860
+ );
861
+ this._reservedSeats[sessionId] = [joinOptions, authData, false, allowReconnection];
677
862
  if (!allowReconnection) {
678
- await this._incrementClientCount();
679
- this.reservedSeatTimeouts[sessionId] = setTimeout(async () => {
680
- delete this.reservedSeats[sessionId];
681
- delete this.reservedSeatTimeouts[sessionId];
682
- await this._decrementClientCount();
863
+ await this.#_incrementClientCount();
864
+ this._reservedSeatTimeouts[sessionId] = setTimeout(async () => {
865
+ delete this._reservedSeats[sessionId];
866
+ delete this._reservedSeatTimeouts[sessionId];
867
+ await this.#_decrementClientCount();
683
868
  }, seconds * 1e3);
684
869
  this.resetAutoDisposeTimeout(seconds);
685
870
  }
686
- if (devModeReconnection) {
687
- this._reconnectingSessionId.set(sessionId, sessionId);
871
+ if (devModeReconnectionToken) {
872
+ const reconnection = new import_Utils.Deferred();
873
+ this._reconnections[devModeReconnectionToken] = [sessionId, reconnection];
688
874
  }
689
875
  return true;
690
876
  }
691
- _disposeIfEmpty() {
877
+ async _reserveMultipleSeats(multipleSessionIds, multipleJoinOptions = true, multipleAuthData = void 0, seconds = this.seatReservationTimeout) {
878
+ let promises = [];
879
+ for (let i = 0; i < multipleSessionIds.length; i++) {
880
+ promises.push(this._reserveSeat(multipleSessionIds[i], multipleJoinOptions[i], multipleAuthData[i], seconds));
881
+ }
882
+ return await Promise.all(promises);
883
+ }
884
+ #_disposeIfEmpty() {
692
885
  const willDispose = this.#_onLeaveConcurrent === 0 && // no "onLeave" calls in progress
693
- this.#_autoDispose && this._autoDisposeTimeout === void 0 && this.clients.length === 0 && Object.keys(this.reservedSeats).length === 0;
886
+ this.#_autoDispose && this._autoDisposeTimeout === void 0 && this.clients.length === 0 && Object.keys(this._reservedSeats).length === 0;
694
887
  if (willDispose) {
695
888
  this._events.emit("dispose");
696
889
  }
697
890
  return willDispose;
698
891
  }
699
- async _dispose() {
700
- this._internalState = 2 /* DISPOSING */;
701
- this.listing.remove();
892
+ async #_dispose() {
893
+ this._internalState = RoomInternalState.DISPOSING;
894
+ if (this._listing?.roomId !== void 0) {
895
+ await import_core.matchMaker.driver.remove(this._listing.roomId);
896
+ }
702
897
  let userReturnData;
703
898
  if (this.onDispose) {
704
899
  userReturnData = this.onDispose();
@@ -723,44 +918,59 @@ class Room {
723
918
  if (client.state === import_Transport.ClientState.LEAVING) {
724
919
  return;
725
920
  }
726
- const it = { offset: 1 };
727
- const code = buffer[0];
728
921
  if (!buffer) {
729
922
  (0, import_Debug.debugAndPrintError)(`${this.roomName} (roomId: ${this.roomId}), couldn't decode message: ${buffer}`);
730
923
  return;
731
924
  }
925
+ if (this.clock.currentTime - client._lastMessageTime >= 1e3) {
926
+ client._numMessagesLastSecond = 0;
927
+ client._lastMessageTime = this.clock.currentTime;
928
+ } else if (++client._numMessagesLastSecond > this.maxMessagesPerSecond) {
929
+ return this.#_forciblyCloseClient(client, import_Protocol.CloseCode.WITH_ERROR);
930
+ }
931
+ const it = { offset: 1 };
932
+ const code = buffer[0];
732
933
  if (code === import_Protocol.Protocol.ROOM_DATA) {
733
934
  const messageType = import_schema.decode.stringCheck(buffer, it) ? import_schema.decode.string(buffer, it) : import_schema.decode.number(buffer, it);
734
- const messageTypeHandler = this.onMessageHandlers[messageType];
735
935
  let message;
736
936
  try {
737
937
  message = buffer.byteLength > it.offset ? (0, import_msgpackr.unpack)(buffer.subarray(it.offset, buffer.byteLength)) : void 0;
738
938
  (0, import_Debug.debugMessage)("received: '%s' -> %j (roomId: %s)", messageType, message, this.roomId);
739
- if (messageTypeHandler?.validate !== void 0) {
740
- message = messageTypeHandler.validate(message);
939
+ if (this.onMessageValidators[messageType] !== void 0) {
940
+ message = (0, import_StandardSchema.standardValidate)(this.onMessageValidators[messageType], message);
741
941
  }
742
942
  } catch (e) {
743
943
  (0, import_Debug.debugAndPrintError)(e);
744
- client.leave(import_Protocol.Protocol.WS_CLOSE_WITH_ERROR);
944
+ client.leave(import_Protocol.CloseCode.WITH_ERROR);
745
945
  return;
746
946
  }
747
- if (messageTypeHandler) {
748
- messageTypeHandler.callback(client, message);
947
+ if (this.onMessageEvents.events[messageType]) {
948
+ this.onMessageEvents.emit(messageType, client, message);
949
+ } else if (this.onMessageEvents.events["*"]) {
950
+ this.onMessageEvents.emit("*", client, messageType, message);
749
951
  } else {
750
- (this.onMessageHandlers["*"] || this.onMessageHandlers["__no_message_handler"]).callback(client, messageType, message);
952
+ this.onMessageFallbacks["__no_message_handler"](client, messageType, message);
751
953
  }
752
954
  } else if (code === import_Protocol.Protocol.ROOM_DATA_BYTES) {
753
955
  const messageType = import_schema.decode.stringCheck(buffer, it) ? import_schema.decode.string(buffer, it) : import_schema.decode.number(buffer, it);
754
- const messageTypeHandler = this.onMessageHandlers[messageType];
755
956
  let message = buffer.subarray(it.offset, buffer.byteLength);
756
957
  (0, import_Debug.debugMessage)("received: '%s' -> %j (roomId: %s)", messageType, message, this.roomId);
757
- if (messageTypeHandler?.validate !== void 0) {
758
- message = messageTypeHandler.validate(message);
958
+ const bytesMessageType = `_$b${messageType}`;
959
+ try {
960
+ if (this.onMessageValidators[bytesMessageType] !== void 0) {
961
+ message = (0, import_StandardSchema.standardValidate)(this.onMessageValidators[bytesMessageType], message);
962
+ }
963
+ } catch (e) {
964
+ (0, import_Debug.debugAndPrintError)(e);
965
+ client.leave(import_Protocol.CloseCode.WITH_ERROR);
966
+ return;
759
967
  }
760
- if (messageTypeHandler) {
761
- messageTypeHandler.callback(client, message);
968
+ if (this.onMessageEvents.events[bytesMessageType]) {
969
+ this.onMessageEvents.emit(bytesMessageType, client, message);
970
+ } else if (this.onMessageEvents.events["*"]) {
971
+ this.onMessageEvents.emit("*", client, messageType, message);
762
972
  } else {
763
- (this.onMessageHandlers["*"] || this.onMessageHandlers["__no_message_handler"]).callback(client, messageType, message);
973
+ this.onMessageFallbacks["__no_message_handler"](client, messageType, message);
764
974
  }
765
975
  } else if (code === import_Protocol.Protocol.JOIN_ROOM && client.state === import_Transport.ClientState.JOINING) {
766
976
  client.state = import_Transport.ClientState.JOINED;
@@ -772,58 +982,64 @@ class Room {
772
982
  client._enqueuedMessages.forEach((enqueued) => client.raw(enqueued));
773
983
  }
774
984
  delete client._enqueuedMessages;
985
+ } else if (code === import_Protocol.Protocol.PING) {
986
+ client.raw(import_Protocol.getMessageBytes[import_Protocol.Protocol.PING]());
775
987
  } else if (code === import_Protocol.Protocol.LEAVE_ROOM) {
776
- this._forciblyCloseClient(client, import_Protocol.Protocol.WS_CLOSE_CONSENTED);
988
+ this.#_forciblyCloseClient(client, import_Protocol.CloseCode.CONSENTED);
777
989
  }
778
990
  }
779
- _forciblyCloseClient(client, closeCode) {
991
+ #_forciblyCloseClient(client, closeCode) {
780
992
  client.ref.removeAllListeners("message");
781
993
  client.ref.removeListener("close", client.ref["onleave"]);
782
994
  this._onLeave(client, closeCode).then(() => client.leave(closeCode));
783
995
  }
784
996
  async _onLeave(client, code) {
785
- (0, import_Debug.debugMatchMaking)("onLeave, sessionId: '%s' (close code: %d, roomId: %s)", client.sessionId, code, this.roomId);
786
997
  client.state = import_Transport.ClientState.LEAVING;
787
998
  if (!this.clients.delete(client)) {
788
999
  return;
789
1000
  }
790
- if (this.onLeave) {
1001
+ const method = code === import_Protocol.CloseCode.CONSENTED ? this.onLeave : this.onDrop || this.onLeave;
1002
+ if (method) {
1003
+ (0, import_Debug.debugMatchMaking)(`${method.name}, sessionId: '%s' (close code: %d, roomId: %s)`, client.sessionId, code, this.roomId);
791
1004
  try {
792
1005
  this.#_onLeaveConcurrent++;
793
- await this.onLeave(client, code === import_Protocol.Protocol.WS_CLOSE_CONSENTED);
1006
+ await method.call(this, client, code);
794
1007
  } catch (e) {
795
- (0, import_Debug.debugAndPrintError)(`onLeave error: ${e && e.message || e || "promise rejected"} (roomId: ${this.roomId})`);
1008
+ (0, import_Debug.debugAndPrintError)(`${method.name} error: ${e && e.message || e || "promise rejected"} (roomId: ${this.roomId})`);
796
1009
  } finally {
797
1010
  this.#_onLeaveConcurrent--;
798
1011
  }
799
1012
  }
800
1013
  if (this._reconnections[client.reconnectionToken]) {
801
1014
  this._reconnections[client.reconnectionToken][1].catch(async () => {
802
- await this._onAfterLeave(client);
1015
+ await this.#_onAfterLeave(client, code, method === this.onDrop);
803
1016
  });
804
1017
  } else if (client.state !== import_Transport.ClientState.RECONNECTED) {
805
- await this._onAfterLeave(client);
1018
+ await this.#_onAfterLeave(client, code, method === this.onDrop);
806
1019
  }
807
1020
  }
808
- async _onAfterLeave(client) {
809
- const willDispose = await this._decrementClientCount();
810
- if (this.reservedSeats[client.sessionId] === void 0) {
1021
+ async #_onAfterLeave(client, code, isDrop = false) {
1022
+ if (isDrop && this.onLeave) {
1023
+ await this.onLeave(client, code);
1024
+ }
1025
+ const willDispose = await this.#_decrementClientCount();
1026
+ if (this._reservedSeats[client.sessionId] === void 0) {
811
1027
  this._events.emit("leave", client, willDispose);
812
1028
  }
813
1029
  }
814
- async _incrementClientCount() {
1030
+ async #_incrementClientCount() {
815
1031
  if (!this.#_locked && this.hasReachedMaxClients()) {
816
1032
  this.#_maxClientsReached = true;
817
1033
  this.lock.call(this, true);
818
1034
  }
819
- await this.listing.updateOne({
1035
+ await import_core.matchMaker.driver.update(this._listing, {
820
1036
  $inc: { clients: 1 },
821
1037
  $set: { locked: this.#_locked }
822
1038
  });
823
1039
  }
824
- async _decrementClientCount() {
825
- const willDispose = this._disposeIfEmpty();
826
- if (this._internalState === 2 /* DISPOSING */) {
1040
+ async #_decrementClientCount() {
1041
+ const willDispose = this.#_disposeIfEmpty();
1042
+ if (this._internalState === RoomInternalState.DISPOSING) {
827
1043
  return true;
828
1044
  }
829
1045
  if (!willDispose) {
@@ -831,7 +1047,7 @@ class Room {
831
1047
  this.#_maxClientsReached = false;
832
1048
  this.unlock.call(this, true);
833
1049
  }
834
- await this.listing.updateOne({
1050
+ await import_core.matchMaker.driver.update(this._listing, {
835
1051
  $inc: { clients: -1 },
836
1052
  $set: { locked: this.#_locked }
837
1053
  });
@@ -865,9 +1081,28 @@ class Room {
865
1081
  }
866
1082
  }
867
1083
  }
1084
+ function room(options) {
1085
+ class _ extends Room {
1086
+ constructor() {
1087
+ super();
1088
+ this.messages = options.messages;
1089
+ if (options.state && typeof options.state === "function") {
1090
+ this.state = options.state();
1091
+ }
1092
+ }
1093
+ }
1094
+ for (const key in options) {
1095
+ if (typeof options[key] === "function") {
1096
+ _.prototype[key] = options[key];
1097
+ }
1098
+ }
1099
+ return _;
1100
+ }
868
1101
  // Annotate the CommonJS export names for ESM import in node:
869
1102
  0 && (module.exports = {
870
1103
  DEFAULT_SEAT_RESERVATION_TIME,
871
1104
  Room,
872
- RoomInternalState
1105
+ RoomInternalState,
1106
+ room,
1107
+ validate
873
1108
  });