@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.mjs CHANGED
@@ -1,28 +1,33 @@
1
1
  // packages/core/src/Room.ts
2
2
  import { unpack } from "@colyseus/msgpackr";
3
3
  import { decode, $changes } from "@colyseus/schema";
4
- import Clock from "@colyseus/timer";
4
+ import { ClockTimer as Clock } from "@colyseus/timer";
5
5
  import { EventEmitter } from "events";
6
6
  import { logger } from "./Logger.mjs";
7
7
  import { NoneSerializer } from "./serializer/NoneSerializer.mjs";
8
8
  import { SchemaSerializer } from "./serializer/SchemaSerializer.mjs";
9
- import { ErrorCode, getMessageBytes, Protocol } from "./Protocol.mjs";
9
+ import { CloseCode, ErrorCode, getMessageBytes, Protocol } from "./Protocol.mjs";
10
10
  import { Deferred, generateId, wrapTryCatch } from "./utils/Utils.mjs";
11
+ import { createNanoEvents } from "./utils/nanoevents.mjs";
11
12
  import { isDevMode } from "./utils/DevMode.mjs";
12
13
  import { debugAndPrintError, debugMatchMaking, debugMessage } from "./Debug.mjs";
13
14
  import { ServerError } from "./errors/ServerError.mjs";
14
- import { ClientArray, ClientState } from "./Transport.mjs";
15
+ import { ClientState, ClientArray } from "./Transport.mjs";
15
16
  import { OnAuthException, OnCreateException, OnDisposeException, OnJoinException, OnLeaveException, OnMessageException, SimulationIntervalException, TimedEventException } from "./errors/RoomExceptions.mjs";
17
+ import { standardValidate } from "./utils/StandardSchema.mjs";
18
+ import { matchMaker } from "@colyseus/core";
16
19
  var DEFAULT_PATCH_RATE = 1e3 / 20;
17
20
  var DEFAULT_SIMULATION_INTERVAL = 1e3 / 60;
18
21
  var noneSerializer = new NoneSerializer();
19
22
  var DEFAULT_SEAT_RESERVATION_TIME = Number(process.env.COLYSEUS_SEAT_RESERVATION_TIME || 15);
20
- var RoomInternalState = /* @__PURE__ */ ((RoomInternalState2) => {
21
- RoomInternalState2[RoomInternalState2["CREATING"] = 0] = "CREATING";
22
- RoomInternalState2[RoomInternalState2["CREATED"] = 1] = "CREATED";
23
- RoomInternalState2[RoomInternalState2["DISPOSING"] = 2] = "DISPOSING";
24
- return RoomInternalState2;
25
- })(RoomInternalState || {});
23
+ function validate(format, handler) {
24
+ return { format, handler };
25
+ }
26
+ var RoomInternalState = {
27
+ CREATING: 0,
28
+ CREATED: 1,
29
+ DISPOSING: 2
30
+ };
26
31
  var Room = class _Room {
27
32
  constructor() {
28
33
  /**
@@ -51,40 +56,52 @@ var Room = class _Room {
51
56
  * @default 50ms (20fps)
52
57
  */
53
58
  this.patchRate = DEFAULT_PATCH_RATE;
59
+ /**
60
+ * Maximum number of messages a client can send to the server per second.
61
+ * If a client sends more messages than this, it will be disconnected.
62
+ *
63
+ * @default 60
64
+ */
65
+ this.maxMessagesPerSecond = 60;
54
66
  /**
55
67
  * The array of connected clients.
56
68
  *
57
- * @see {@link https://docs.colyseus.io/colyseus/server/room/#client|Client instance}
69
+ * @see [Client instance](https://docs.colyseus.io/room#client)
58
70
  */
59
71
  this.clients = new ClientArray();
60
- /** @internal */
72
+ /**
73
+ * Set the number of seconds a room can wait for a client to effectively join the room.
74
+ * You should consider how long your `onAuth()` will have to wait for setting a different seat reservation time.
75
+ * The default value is 15 seconds. You may set the `COLYSEUS_SEAT_RESERVATION_TIME`
76
+ * environment variable if you'd like to change the seat reservation time globally.
77
+ *
78
+ * @default 15 seconds
79
+ */
80
+ this.seatReservationTimeout = DEFAULT_SEAT_RESERVATION_TIME;
61
81
  this._events = new EventEmitter();
62
- // seat reservation & reconnection
63
- this.seatReservationTime = DEFAULT_SEAT_RESERVATION_TIME;
64
- this.reservedSeats = {};
65
- this.reservedSeatTimeouts = {};
82
+ this._reservedSeats = {};
83
+ this._reservedSeatTimeouts = {};
66
84
  this._reconnections = {};
67
- this._reconnectingSessionId = /* @__PURE__ */ new Map();
68
- this.onMessageHandlers = {
69
- "__no_message_handler": {
70
- callback: (client, messageType, _) => {
71
- const errorMessage = `room onMessage for "${messageType}" not registered.`;
72
- debugAndPrintError(`${errorMessage} (roomId: ${this.roomId})`);
73
- if (isDevMode) {
74
- client.error(ErrorCode.INVALID_PAYLOAD, errorMessage);
75
- } else {
76
- client.leave(Protocol.WS_CLOSE_WITH_ERROR, errorMessage);
77
- }
85
+ this.onMessageEvents = createNanoEvents();
86
+ this.onMessageValidators = {};
87
+ this.onMessageFallbacks = {
88
+ "__no_message_handler": (client, messageType, _) => {
89
+ const errorMessage = `room onMessage for "${messageType}" not registered.`;
90
+ debugMessage(`${errorMessage} (roomId: ${this.roomId})`);
91
+ if (isDevMode) {
92
+ client.error(ErrorCode.INVALID_PAYLOAD, errorMessage);
93
+ } else {
94
+ client.leave(CloseCode.WITH_ERROR, errorMessage);
78
95
  }
79
96
  }
80
97
  };
81
98
  this._serializer = noneSerializer;
82
99
  this._afterNextPatchQueue = [];
83
- this._internalState = 0 /* CREATING */;
100
+ this._internalState = RoomInternalState.CREATING;
84
101
  this._lockedExplicitly = false;
85
102
  this.#_locked = false;
86
103
  this._events.once("dispose", () => {
87
- this._dispose().catch((e) => debugAndPrintError(`onDispose error: ${e && e.stack || e.message || e || "promise rejected"} (roomId: ${this.roomId})`)).finally(() => this._events.emit("disconnect"));
104
+ this.#_dispose().catch((e) => debugAndPrintError(`onDispose error: ${e && e.stack || e.message || e || "promise rejected"} (roomId: ${this.roomId})`)).finally(() => this._events.emit("disconnect"));
88
105
  });
89
106
  if (this.onUncaughtException !== void 0) {
90
107
  this.#registerUncaughtExceptionHandlers();
@@ -100,8 +117,31 @@ var Room = class _Room {
100
117
  get locked() {
101
118
  return this.#_locked;
102
119
  }
120
+ /**
121
+ * Get the room's matchmaking metadata.
122
+ */
103
123
  get metadata() {
104
- return this.listing.metadata;
124
+ return this._listing.metadata;
125
+ }
126
+ /**
127
+ * Set the room's matchmaking metadata.
128
+ *
129
+ * **Note**: This setter does NOT automatically persist. Use `setMatchmaking()` for automatic persistence.
130
+ *
131
+ * @example
132
+ * ```typescript
133
+ * class MyRoom extends Room<{ metadata: { difficulty: string; rating: number } }> {
134
+ * async onCreate() {
135
+ * this.metadata = { difficulty: "hard", rating: 1500 };
136
+ * }
137
+ * }
138
+ * ```
139
+ */
140
+ set metadata(meta) {
141
+ if (this._internalState !== RoomInternalState.CREATING) {
142
+ throw new ServerError(ErrorCode.APPLICATION_ERROR, "'metadata' can only be manually set during onCreate(). Use setMatchmaking() instead.");
143
+ }
144
+ this._listing.metadata = meta;
105
145
  }
106
146
  #_roomId;
107
147
  #_roomName;
@@ -142,29 +182,14 @@ var Room = class _Room {
142
182
  enumerable: true,
143
183
  get: () => this.#_maxClients,
144
184
  set: (value) => {
145
- this.#_maxClients = value;
146
- if (this._internalState === 1 /* CREATED */) {
147
- const hasReachedMaxClients = this.hasReachedMaxClients();
148
- if (!this._lockedExplicitly && this.#_maxClientsReached && !hasReachedMaxClients) {
149
- this.#_maxClientsReached = false;
150
- this.#_locked = false;
151
- this.listing.locked = false;
152
- }
153
- if (hasReachedMaxClients) {
154
- this.#_maxClientsReached = true;
155
- this.#_locked = true;
156
- this.listing.locked = true;
157
- }
158
- this.listing.maxClients = value;
159
- this.listing.save();
160
- }
185
+ this.setMatchmaking({ maxClients: value });
161
186
  }
162
187
  },
163
188
  autoDispose: {
164
189
  enumerable: true,
165
190
  get: () => this.#_autoDispose,
166
191
  set: (value) => {
167
- if (value !== this.#_autoDispose && this._internalState !== 2 /* DISPOSING */) {
192
+ if (value !== this.#_autoDispose && this._internalState !== RoomInternalState.DISPOSING) {
168
193
  this.#_autoDispose = value;
169
194
  this.resetAutoDisposeTimeout();
170
195
  }
@@ -181,6 +206,8 @@ var Room = class _Room {
181
206
  }
182
207
  if (milliseconds !== null && milliseconds !== 0) {
183
208
  this.#_patchInterval = setInterval(() => this.broadcastPatch(), milliseconds);
209
+ } else if (!this._simulationInterval) {
210
+ this.#_patchInterval = setInterval(() => this.clock.tick(), DEFAULT_SIMULATION_INTERVAL);
184
211
  }
185
212
  }
186
213
  }
@@ -189,7 +216,16 @@ var Room = class _Room {
189
216
  if (this.#_state) {
190
217
  this.state = this.#_state;
191
218
  }
192
- this.resetAutoDisposeTimeout(this.seatReservationTime);
219
+ if (this.messages !== void 0) {
220
+ Object.entries(this.messages).forEach(([messageType, callback]) => {
221
+ if (typeof callback === "function") {
222
+ this.onMessage(messageType, callback.bind(this));
223
+ } else {
224
+ this.onMessage(messageType, callback.format, callback.handler.bind(this));
225
+ }
226
+ });
227
+ }
228
+ this.resetAutoDisposeTimeout(this.seatReservationTimeout);
193
229
  this.clock.start();
194
230
  }
195
231
  /**
@@ -227,11 +263,27 @@ var Room = class _Room {
227
263
  * @returns roomId string
228
264
  */
229
265
  set roomId(roomId) {
230
- if (this._internalState !== 0 /* CREATING */ && !isDevMode) {
266
+ if (this._internalState !== RoomInternalState.CREATING && !isDevMode) {
231
267
  throw new ServerError(ErrorCode.APPLICATION_ERROR, "'roomId' can only be overridden upon room creation.");
232
268
  }
233
269
  this.#_roomId = roomId;
234
270
  }
271
+ /**
272
+ * This method is called before onJoin() - this is where you should authenticate the client
273
+ * @param client - The client that is authenticating.
274
+ * @param options - The options passed to the client when it is authenticating.
275
+ * @param context - The authentication context, including the token and the client's IP address.
276
+ * @returns The authentication result.
277
+ *
278
+ * @example
279
+ * ```typescript
280
+ * return {
281
+ * userId: 123,
282
+ * username: "John Doe",
283
+ * email: "john.doe@example.com",
284
+ * };
285
+ * ```
286
+ */
235
287
  onAuth(client, options, context) {
236
288
  return true;
237
289
  }
@@ -246,7 +298,7 @@ var Room = class _Room {
246
298
  */
247
299
  onBeforeShutdown() {
248
300
  this.disconnect(
249
- isDevMode ? Protocol.WS_CLOSE_DEVMODE_RESTART : Protocol.WS_CLOSE_CONSENTED
301
+ isDevMode ? CloseCode.DEVMODE_RESTART : CloseCode.SERVER_SHUTDOWN
250
302
  );
251
303
  }
252
304
  /**
@@ -255,39 +307,31 @@ var Room = class _Room {
255
307
  * @returns boolean
256
308
  */
257
309
  hasReachedMaxClients() {
258
- return this.clients.length + Object.keys(this.reservedSeats).length >= this.maxClients || this._internalState === 2 /* DISPOSING */;
310
+ return this.clients.length + Object.keys(this._reservedSeats).length >= this.#_maxClients || this._internalState === RoomInternalState.DISPOSING;
259
311
  }
260
312
  /**
261
- * Set the number of seconds a room can wait for a client to effectively join the room.
262
- * You should consider how long your `onAuth()` will have to wait for setting a different seat reservation time.
263
- * The default value is 15 seconds. You may set the `COLYSEUS_SEAT_RESERVATION_TIME`
264
- * environment variable if you'd like to change the seat reservation time globally.
265
- *
266
- * @default 15 seconds
267
- *
268
- * @param seconds - number of seconds.
269
- * @returns The modified Room object.
313
+ * @deprecated Use `seatReservationTimeout=` instead.
270
314
  */
271
315
  setSeatReservationTime(seconds) {
272
- this.seatReservationTime = seconds;
316
+ console.warn(`DEPRECATED: .setSeatReservationTime(${seconds}) is deprecated. Assign a .seatReservationTimeout property value instead.`);
317
+ this.seatReservationTimeout = seconds;
273
318
  return this;
274
319
  }
275
320
  hasReservedSeat(sessionId, reconnectionToken) {
276
- const reservedSeat = this.reservedSeats[sessionId];
321
+ const reservedSeat = this._reservedSeats[sessionId];
277
322
  if (reservedSeat === void 0) {
278
323
  return false;
279
324
  }
280
325
  if (reservedSeat[3]) {
281
- return reconnectionToken && this._reconnections[reconnectionToken]?.[0] === sessionId && this._reconnectingSessionId.has(sessionId);
326
+ return reconnectionToken && this._reconnections[reconnectionToken]?.[0] === sessionId;
282
327
  } else {
283
328
  return reservedSeat[2] === false;
284
329
  }
285
330
  }
286
331
  checkReconnectionToken(reconnectionToken) {
287
332
  const sessionId = this._reconnections[reconnectionToken]?.[0];
288
- const reservedSeat = this.reservedSeats[sessionId];
333
+ const reservedSeat = this._reservedSeats[sessionId];
289
334
  if (reservedSeat && reservedSeat[3]) {
290
- this._reconnectingSessionId.set(sessionId, reconnectionToken);
291
335
  return sessionId;
292
336
  } else {
293
337
  return void 0;
@@ -332,34 +376,125 @@ var Room = class _Room {
332
376
  setSerializer(serializer) {
333
377
  this._serializer = serializer;
334
378
  }
335
- async setMetadata(meta) {
336
- if (!this.listing.metadata) {
337
- this.listing.metadata = meta;
379
+ async setMetadata(meta, persist = true) {
380
+ if (!this._listing.metadata) {
381
+ this._listing.metadata = meta;
338
382
  } else {
339
383
  for (const field in meta) {
340
384
  if (!meta.hasOwnProperty(field)) {
341
385
  continue;
342
386
  }
343
- this.listing.metadata[field] = meta[field];
387
+ this._listing.metadata[field] = meta[field];
344
388
  }
345
- if ("markModified" in this.listing) {
346
- this.listing.markModified("metadata");
389
+ if ("markModified" in this._listing) {
390
+ this._listing.markModified("metadata");
347
391
  }
348
392
  }
349
- if (this._internalState === 1 /* CREATED */) {
350
- await this.listing.save();
393
+ if (persist && this._internalState === RoomInternalState.CREATED) {
394
+ await matchMaker.driver.persist(this._listing);
395
+ this._events.emit("metadata-change");
351
396
  }
352
397
  }
353
- async setPrivate(bool = true) {
354
- if (this.listing.private === bool) return;
355
- this.listing.private = bool;
356
- if (this._internalState === 1 /* CREATED */) {
357
- await this.listing.save();
398
+ async setPrivate(bool = true, persist = true) {
399
+ if (this._listing.private === bool) return;
400
+ this._listing.private = bool;
401
+ if (persist && this._internalState === RoomInternalState.CREATED) {
402
+ await matchMaker.driver.persist(this._listing);
358
403
  }
359
404
  this._events.emit("visibility-change", bool);
360
405
  }
361
406
  /**
362
- * Locking the room will remove it from the pool of available rooms for new clients to connect to.
407
+ * Update multiple matchmaking/listing properties at once with a single persist operation.
408
+ * This is the recommended way to update room listing properties.
409
+ *
410
+ * @param updates - Object containing the properties to update
411
+ *
412
+ * @example
413
+ * ```typescript
414
+ * // Update multiple properties at once
415
+ * await this.setMatchmaking({
416
+ * metadata: { difficulty: "hard", rating: 1500 },
417
+ * private: true,
418
+ * locked: true,
419
+ * maxClients: 10
420
+ * });
421
+ * ```
422
+ *
423
+ * @example
424
+ * ```typescript
425
+ * // Update only metadata
426
+ * await this.setMatchmaking({
427
+ * metadata: { status: "in_progress" }
428
+ * });
429
+ * ```
430
+ *
431
+ * @example
432
+ * ```typescript
433
+ * // Partial metadata update (merges with existing)
434
+ * await this.setMatchmaking({
435
+ * metadata: { ...this.metadata, round: this.metadata.round + 1 }
436
+ * });
437
+ * ```
438
+ */
439
+ async setMatchmaking(updates) {
440
+ for (const key in updates) {
441
+ if (!updates.hasOwnProperty(key)) {
442
+ continue;
443
+ }
444
+ switch (key) {
445
+ case "metadata": {
446
+ this.setMetadata(updates.metadata, false);
447
+ break;
448
+ }
449
+ case "private": {
450
+ this.setPrivate(updates.private, false);
451
+ break;
452
+ }
453
+ case "locked": {
454
+ if (updates[key]) {
455
+ this.lock.call(this, true);
456
+ this._lockedExplicitly = true;
457
+ } else {
458
+ this.unlock.call(this, true);
459
+ this._lockedExplicitly = false;
460
+ }
461
+ break;
462
+ }
463
+ case "maxClients": {
464
+ this.#_maxClients = updates.maxClients;
465
+ this._listing.maxClients = updates.maxClients;
466
+ const hasReachedMaxClients = this.hasReachedMaxClients();
467
+ if (!this._lockedExplicitly && this.#_maxClientsReached && !hasReachedMaxClients) {
468
+ this.#_maxClientsReached = false;
469
+ this.#_locked = false;
470
+ this._listing.locked = false;
471
+ updates.locked = false;
472
+ }
473
+ if (hasReachedMaxClients) {
474
+ this.#_maxClientsReached = true;
475
+ this.#_locked = true;
476
+ this._listing.locked = true;
477
+ updates.locked = true;
478
+ }
479
+ break;
480
+ }
481
+ case "clients": {
482
+ console.warn("setMatchmaking() does not allow updating 'clients' property.");
483
+ break;
484
+ }
485
+ default: {
486
+ this._listing[key] = updates[key];
487
+ break;
488
+ }
489
+ }
490
+ }
491
+ if (this._internalState === RoomInternalState.CREATED) {
492
+ await matchMaker.driver.update(this._listing, { $set: updates });
493
+ this._events.emit("metadata-change");
494
+ }
495
+ }
496
+ /**
497
+ * Lock the room. This prevents new clients from joining this room.
363
498
  */
364
499
  async lock() {
365
500
  this._lockedExplicitly = arguments[0] === void 0;
@@ -367,13 +502,15 @@ var Room = class _Room {
367
502
  return;
368
503
  }
369
504
  this.#_locked = true;
370
- await this.listing.updateOne({
371
- $set: { locked: this.#_locked }
372
- });
505
+ if (this._lockedExplicitly) {
506
+ await matchMaker.driver.update(this._listing, {
507
+ $set: { locked: this.#_locked }
508
+ });
509
+ }
373
510
  this._events.emit("lock");
374
511
  }
375
512
  /**
376
- * Unlocking the room returns it to the pool of available rooms for new clients to connect to.
513
+ * Unlock the room. This allows new clients to join this room, if maxClients is not reached.
377
514
  */
378
515
  async unlock() {
379
516
  if (arguments[0] === void 0) {
@@ -383,19 +520,33 @@ var Room = class _Room {
383
520
  return;
384
521
  }
385
522
  this.#_locked = false;
386
- await this.listing.updateOne({
387
- $set: { locked: this.#_locked }
388
- });
523
+ if (arguments[0] === void 0) {
524
+ await matchMaker.driver.update(this._listing, {
525
+ $set: { locked: this.#_locked }
526
+ });
527
+ }
389
528
  this._events.emit("unlock");
390
529
  }
391
530
  send(client, messageOrType, messageOrOptions, options) {
392
531
  logger.warn("DEPRECATION WARNING: use client.send(...) instead of this.send(client, ...)");
393
532
  client.send(messageOrType, messageOrOptions, options);
394
533
  }
395
- broadcast(type, message, options) {
534
+ /**
535
+ * Broadcast a message to all connected clients.
536
+ * @param type - The type of the message.
537
+ * @param message - The message to broadcast.
538
+ * @param options - The options for the broadcast.
539
+ *
540
+ * @example
541
+ * ```typescript
542
+ * this.broadcast('message', { message: 'Hello, world!' });
543
+ * ```
544
+ */
545
+ broadcast(type, ...args) {
546
+ const [message, options] = args;
396
547
  if (options && options.afterNextPatch) {
397
548
  delete options.afterNextPatch;
398
- this._afterNextPatchQueue.push(["broadcast", arguments]);
549
+ this._afterNextPatchQueue.push(["broadcast", [type, ...args]]);
399
550
  return;
400
551
  }
401
552
  this.broadcastMessageType(type, message, options);
@@ -428,9 +579,30 @@ var Room = class _Room {
428
579
  this._dequeueAfterPatchMessages();
429
580
  return hasChanges;
430
581
  }
431
- onMessage(messageType, callback, validate) {
432
- this.onMessageHandlers[messageType] = this.onUncaughtException !== void 0 ? { validate, callback: wrapTryCatch(callback, this.onUncaughtException.bind(this), OnMessageException, "onMessage", false, messageType) } : { validate, callback };
433
- return () => delete this.onMessageHandlers[messageType];
582
+ onMessage(_messageType, _validationSchema, _callback) {
583
+ const messageType = _messageType.toString();
584
+ const validationSchema = typeof _callback === "function" ? _validationSchema : void 0;
585
+ const callback = validationSchema === void 0 ? _validationSchema : _callback;
586
+ const removeListener = this.onMessageEvents.on(messageType, this.onUncaughtException !== void 0 ? wrapTryCatch(callback, this.onUncaughtException.bind(this), OnMessageException, "onMessage", false, _messageType) : callback);
587
+ if (validationSchema !== void 0) {
588
+ this.onMessageValidators[messageType] = validationSchema;
589
+ }
590
+ return () => {
591
+ removeListener();
592
+ if (this.onMessageEvents.events[messageType].length === 0) {
593
+ delete this.onMessageValidators[messageType];
594
+ }
595
+ };
596
+ }
597
+ onMessageBytes(_messageType, _validationSchema, _callback) {
598
+ const messageType = `_$b${_messageType}`;
599
+ const validationSchema = typeof _callback === "function" ? _validationSchema : void 0;
600
+ const callback = validationSchema === void 0 ? _validationSchema : _callback;
601
+ if (validationSchema !== void 0) {
602
+ return this.onMessage(messageType, validationSchema, callback);
603
+ } else {
604
+ return this.onMessage(messageType, callback);
605
+ }
434
606
  }
435
607
  /**
436
608
  * Disconnect all connected clients, and then dispose the room.
@@ -438,14 +610,14 @@ var Room = class _Room {
438
610
  * @param closeCode WebSocket close code (default = 4000, which is a "consented leave")
439
611
  * @returns Promise<void>
440
612
  */
441
- disconnect(closeCode = Protocol.WS_CLOSE_CONSENTED) {
442
- if (this._internalState === 2 /* DISPOSING */) {
613
+ disconnect(closeCode = CloseCode.CONSENTED) {
614
+ if (this._internalState === RoomInternalState.DISPOSING) {
443
615
  return Promise.resolve(`disconnect() ignored: room (${this.roomId}) is already disposing.`);
444
- } else if (this._internalState === 0 /* CREATING */) {
616
+ } else if (this._internalState === RoomInternalState.CREATING) {
445
617
  throw new Error("cannot disconnect during onCreate()");
446
618
  }
447
- this._internalState = 2 /* DISPOSING */;
448
- this.listing.remove();
619
+ this._internalState = RoomInternalState.DISPOSING;
620
+ matchMaker.driver.remove(this._listing.roomId);
449
621
  this.#_autoDispose = true;
450
622
  const delayedDisconnection = new Promise((resolve) => this._events.once("disconnect", () => resolve()));
451
623
  for (const [_, reconnection] of Object.values(this._reconnections)) {
@@ -454,38 +626,41 @@ var Room = class _Room {
454
626
  let numClients = this.clients.length;
455
627
  if (numClients > 0) {
456
628
  while (numClients--) {
457
- this._forciblyCloseClient(this.clients[numClients], closeCode);
629
+ this.#_forciblyCloseClient(this.clients[numClients], closeCode);
458
630
  }
459
631
  } else {
460
632
  this._events.emit("dispose");
461
633
  }
462
634
  return delayedDisconnection;
463
635
  }
464
- async ["_onJoin"](client, authContext) {
636
+ async _onJoin(client, authContext, connectionOptions) {
465
637
  const sessionId = client.sessionId;
466
638
  client.reconnectionToken = generateId();
467
- if (this.reservedSeatTimeouts[sessionId]) {
468
- clearTimeout(this.reservedSeatTimeouts[sessionId]);
469
- delete this.reservedSeatTimeouts[sessionId];
639
+ if (this._reservedSeatTimeouts[sessionId]) {
640
+ clearTimeout(this._reservedSeatTimeouts[sessionId]);
641
+ delete this._reservedSeatTimeouts[sessionId];
470
642
  }
471
643
  if (this._autoDisposeTimeout) {
472
644
  clearTimeout(this._autoDisposeTimeout);
473
645
  this._autoDisposeTimeout = void 0;
474
646
  }
475
- const [joinOptions, authData, isConsumed, isWaitingReconnection] = this.reservedSeats[sessionId];
647
+ const [joinOptions, authData, isConsumed, isWaitingReconnection] = this._reservedSeats[sessionId];
476
648
  if (isConsumed) {
477
649
  throw new ServerError(ErrorCode.MATCHMAKE_EXPIRED, "already consumed");
478
650
  }
479
- this.reservedSeats[sessionId][2] = true;
651
+ this._reservedSeats[sessionId][2] = true;
480
652
  debugMatchMaking("consuming seat reservation, sessionId: '%s' (roomId: %s)", client.sessionId, this.roomId);
481
653
  client._afterNextPatchQueue = this._afterNextPatchQueue;
482
654
  client.ref["onleave"] = (_) => client.state = ClientState.LEAVING;
483
655
  client.ref.once("close", client.ref["onleave"]);
484
656
  if (isWaitingReconnection) {
485
- const previousReconnectionToken = this._reconnectingSessionId.get(sessionId);
486
- if (previousReconnectionToken) {
657
+ const reconnectionToken = connectionOptions?.reconnectionToken;
658
+ if (reconnectionToken && this._reconnections[reconnectionToken]?.[0] === sessionId) {
487
659
  this.clients.push(client);
488
- await this._reconnections[previousReconnectionToken]?.[1].resolve(client);
660
+ await this._reconnections[reconnectionToken]?.[1].resolve(client);
661
+ if (this.onReconnect) {
662
+ await this.onReconnect(client);
663
+ }
489
664
  } else {
490
665
  const errorMessage = process.env.NODE_ENV === "production" ? "already consumed" : "bad reconnection token";
491
666
  throw new ServerError(ErrorCode.MATCHMAKE_EXPIRED, errorMessage);
@@ -501,31 +676,31 @@ var Room = class _Room {
501
676
  throw new ServerError(ErrorCode.AUTH_FAILED, "onAuth failed");
502
677
  }
503
678
  } catch (e) {
504
- delete this.reservedSeats[sessionId];
505
- await this._decrementClientCount();
679
+ delete this._reservedSeats[sessionId];
680
+ await this.#_decrementClientCount();
506
681
  throw e;
507
682
  }
508
683
  }
509
684
  if (client.state === ClientState.LEAVING) {
510
- throw new ServerError(Protocol.WS_CLOSE_GOING_AWAY, "already disconnected");
685
+ throw new ServerError(CloseCode.WITH_ERROR, "already disconnected");
511
686
  }
512
687
  this.clients.push(client);
513
- Object.defineProperty(this.reservedSeats, sessionId, {
514
- value: this.reservedSeats[sessionId],
688
+ Object.defineProperty(this._reservedSeats, sessionId, {
689
+ value: this._reservedSeats[sessionId],
515
690
  enumerable: false
516
691
  });
517
692
  if (this.onJoin) {
518
- await this.onJoin(client, joinOptions, client.auth);
693
+ await this.onJoin(client, joinOptions);
519
694
  }
520
695
  if (client.state === ClientState.LEAVING) {
521
- throw new Error("early_leave");
696
+ throw new ServerError(ErrorCode.MATCHMAKE_UNHANDLED, "early_leave");
522
697
  } else {
523
- delete this.reservedSeats[sessionId];
698
+ delete this._reservedSeats[sessionId];
524
699
  this._events.emit("join", client);
525
700
  }
526
701
  } catch (e) {
527
- await this._onLeave(client, Protocol.WS_CLOSE_GOING_AWAY);
528
- delete this.reservedSeats[sessionId];
702
+ await this._onLeave(client, CloseCode.WITH_ERROR);
703
+ delete this._reservedSeats[sessionId];
529
704
  if (!e.code) {
530
705
  e.code = ErrorCode.APPLICATION_ERROR;
531
706
  }
@@ -540,7 +715,11 @@ var Room = class _Room {
540
715
  client.raw(getMessageBytes[Protocol.JOIN_ROOM](
541
716
  client.reconnectionToken,
542
717
  this._serializer.id,
543
- this._serializer.handshake && this._serializer.handshake()
718
+ /**
719
+ * if skipHandshake is true, we don't need to send the handshake
720
+ * (in case client already has handshake data)
721
+ */
722
+ connectionOptions?.skipHandshake ? void 0 : this._serializer.handshake && this._serializer.handshake()
544
723
  ));
545
724
  }
546
725
  }
@@ -548,11 +727,19 @@ var Room = class _Room {
548
727
  * Allow the specified client to reconnect into the room. Must be used inside `onLeave()` method.
549
728
  * If seconds is provided, the reconnection is going to be cancelled after the provided amount of seconds.
550
729
  *
551
- * @param previousClient - The client which is to be waiting until re-connection happens.
552
- * @param seconds - Timeout period on re-connection in seconds.
730
+ * @param client - The client that is allowed to reconnect into the room.
731
+ * @param seconds - The time in seconds that the client is allowed to reconnect into the room.
553
732
  *
554
733
  * @returns Deferred<Client> - The differed is a promise like type.
555
734
  * This type can forcibly reject the promise by calling `.reject()`.
735
+ *
736
+ * @example
737
+ * ```typescript
738
+ * onDrop(client: Client, code: CloseCode) {
739
+ * // Allow the client to reconnect into the room with a 15 seconds timeout.
740
+ * this.allowReconnection(client, 15);
741
+ * }
742
+ * ```
556
743
  */
557
744
  allowReconnection(previousClient, seconds) {
558
745
  if (previousClient._enqueuedMessages !== void 0) {
@@ -565,7 +752,7 @@ var Room = class _Room {
565
752
  if (seconds === "manual") {
566
753
  seconds = Infinity;
567
754
  }
568
- if (this._internalState === 2 /* DISPOSING */) {
755
+ if (this._internalState === RoomInternalState.DISPOSING) {
569
756
  return Promise.reject(new Error("disposing"));
570
757
  }
571
758
  const sessionId = previousClient.sessionId;
@@ -574,13 +761,12 @@ var Room = class _Room {
574
761
  const reconnection = new Deferred();
575
762
  this._reconnections[reconnectionToken] = [sessionId, reconnection];
576
763
  if (seconds !== Infinity) {
577
- this.reservedSeatTimeouts[sessionId] = setTimeout(() => reconnection.reject(false), seconds * 1e3);
764
+ this._reservedSeatTimeouts[sessionId] = setTimeout(() => reconnection.reject(false), seconds * 1e3);
578
765
  }
579
766
  const cleanup = () => {
580
767
  delete this._reconnections[reconnectionToken];
581
- delete this.reservedSeats[sessionId];
582
- delete this.reservedSeatTimeouts[sessionId];
583
- this._reconnectingSessionId.delete(sessionId);
768
+ delete this._reservedSeats[sessionId];
769
+ delete this._reservedSeatTimeouts[sessionId];
584
770
  };
585
771
  reconnection.then((newClient) => {
586
772
  newClient.auth = previousClient.auth;
@@ -589,11 +775,11 @@ var Room = class _Room {
589
775
  previousClient.state = ClientState.RECONNECTED;
590
776
  previousClient.ref = newClient.ref;
591
777
  previousClient.reconnectionToken = newClient.reconnectionToken;
592
- clearTimeout(this.reservedSeatTimeouts[sessionId]);
593
- cleanup();
594
- }).catch(() => {
595
- cleanup();
778
+ clearTimeout(this._reservedSeatTimeouts[sessionId]);
779
+ }, () => {
596
780
  this.resetAutoDisposeTimeout();
781
+ }).finally(() => {
782
+ cleanup();
597
783
  });
598
784
  return reconnection;
599
785
  }
@@ -604,7 +790,7 @@ var Room = class _Room {
604
790
  }
605
791
  this._autoDisposeTimeout = setTimeout(() => {
606
792
  this._autoDisposeTimeout = void 0;
607
- this._disposeIfEmpty();
793
+ this.#_disposeIfEmpty();
608
794
  }, timeoutInSeconds * 1e3);
609
795
  }
610
796
  broadcastMessageType(type, message, options = {}) {
@@ -636,36 +822,52 @@ var Room = class _Room {
636
822
  this._afterNextPatchQueue.splice(0, length);
637
823
  }
638
824
  }
639
- async _reserveSeat(sessionId, joinOptions = true, authData = void 0, seconds = this.seatReservationTime, allowReconnection = false, devModeReconnection) {
825
+ async _reserveSeat(sessionId, joinOptions = true, authData = void 0, seconds = this.seatReservationTimeout, allowReconnection = false, devModeReconnectionToken) {
640
826
  if (!allowReconnection && this.hasReachedMaxClients()) {
641
827
  return false;
642
828
  }
643
- this.reservedSeats[sessionId] = [joinOptions, authData, false, allowReconnection];
829
+ debugMatchMaking(
830
+ "reserving seat. sessionId: '%s', roomId: '%s', processId: '%s'",
831
+ sessionId,
832
+ this.roomId,
833
+ matchMaker.processId
834
+ );
835
+ this._reservedSeats[sessionId] = [joinOptions, authData, false, allowReconnection];
644
836
  if (!allowReconnection) {
645
- await this._incrementClientCount();
646
- this.reservedSeatTimeouts[sessionId] = setTimeout(async () => {
647
- delete this.reservedSeats[sessionId];
648
- delete this.reservedSeatTimeouts[sessionId];
649
- await this._decrementClientCount();
837
+ await this.#_incrementClientCount();
838
+ this._reservedSeatTimeouts[sessionId] = setTimeout(async () => {
839
+ delete this._reservedSeats[sessionId];
840
+ delete this._reservedSeatTimeouts[sessionId];
841
+ await this.#_decrementClientCount();
650
842
  }, seconds * 1e3);
651
843
  this.resetAutoDisposeTimeout(seconds);
652
844
  }
653
- if (devModeReconnection) {
654
- this._reconnectingSessionId.set(sessionId, sessionId);
845
+ if (devModeReconnectionToken) {
846
+ const reconnection = new Deferred();
847
+ this._reconnections[devModeReconnectionToken] = [sessionId, reconnection];
655
848
  }
656
849
  return true;
657
850
  }
658
- _disposeIfEmpty() {
851
+ async _reserveMultipleSeats(multipleSessionIds, multipleJoinOptions = true, multipleAuthData = void 0, seconds = this.seatReservationTimeout) {
852
+ let promises = [];
853
+ for (let i = 0; i < multipleSessionIds.length; i++) {
854
+ promises.push(this._reserveSeat(multipleSessionIds[i], multipleJoinOptions[i], multipleAuthData[i], seconds));
855
+ }
856
+ return await Promise.all(promises);
857
+ }
858
+ #_disposeIfEmpty() {
659
859
  const willDispose = this.#_onLeaveConcurrent === 0 && // no "onLeave" calls in progress
660
- this.#_autoDispose && this._autoDisposeTimeout === void 0 && this.clients.length === 0 && Object.keys(this.reservedSeats).length === 0;
860
+ this.#_autoDispose && this._autoDisposeTimeout === void 0 && this.clients.length === 0 && Object.keys(this._reservedSeats).length === 0;
661
861
  if (willDispose) {
662
862
  this._events.emit("dispose");
663
863
  }
664
864
  return willDispose;
665
865
  }
666
- async _dispose() {
667
- this._internalState = 2 /* DISPOSING */;
668
- this.listing.remove();
866
+ async #_dispose() {
867
+ this._internalState = RoomInternalState.DISPOSING;
868
+ if (this._listing?.roomId !== void 0) {
869
+ await matchMaker.driver.remove(this._listing.roomId);
870
+ }
669
871
  let userReturnData;
670
872
  if (this.onDispose) {
671
873
  userReturnData = this.onDispose();
@@ -690,44 +892,59 @@ var Room = class _Room {
690
892
  if (client.state === ClientState.LEAVING) {
691
893
  return;
692
894
  }
693
- const it = { offset: 1 };
694
- const code = buffer[0];
695
895
  if (!buffer) {
696
896
  debugAndPrintError(`${this.roomName} (roomId: ${this.roomId}), couldn't decode message: ${buffer}`);
697
897
  return;
698
898
  }
899
+ if (this.clock.currentTime - client._lastMessageTime >= 1e3) {
900
+ client._numMessagesLastSecond = 0;
901
+ client._lastMessageTime = this.clock.currentTime;
902
+ } else if (++client._numMessagesLastSecond > this.maxMessagesPerSecond) {
903
+ return this.#_forciblyCloseClient(client, CloseCode.WITH_ERROR);
904
+ }
905
+ const it = { offset: 1 };
906
+ const code = buffer[0];
699
907
  if (code === Protocol.ROOM_DATA) {
700
908
  const messageType = decode.stringCheck(buffer, it) ? decode.string(buffer, it) : decode.number(buffer, it);
701
- const messageTypeHandler = this.onMessageHandlers[messageType];
702
909
  let message;
703
910
  try {
704
911
  message = buffer.byteLength > it.offset ? unpack(buffer.subarray(it.offset, buffer.byteLength)) : void 0;
705
912
  debugMessage("received: '%s' -> %j (roomId: %s)", messageType, message, this.roomId);
706
- if (messageTypeHandler?.validate !== void 0) {
707
- message = messageTypeHandler.validate(message);
913
+ if (this.onMessageValidators[messageType] !== void 0) {
914
+ message = standardValidate(this.onMessageValidators[messageType], message);
708
915
  }
709
916
  } catch (e) {
710
917
  debugAndPrintError(e);
711
- client.leave(Protocol.WS_CLOSE_WITH_ERROR);
918
+ client.leave(CloseCode.WITH_ERROR);
712
919
  return;
713
920
  }
714
- if (messageTypeHandler) {
715
- messageTypeHandler.callback(client, message);
921
+ if (this.onMessageEvents.events[messageType]) {
922
+ this.onMessageEvents.emit(messageType, client, message);
923
+ } else if (this.onMessageEvents.events["*"]) {
924
+ this.onMessageEvents.emit("*", client, messageType, message);
716
925
  } else {
717
- (this.onMessageHandlers["*"] || this.onMessageHandlers["__no_message_handler"]).callback(client, messageType, message);
926
+ this.onMessageFallbacks["__no_message_handler"](client, messageType, message);
718
927
  }
719
928
  } else if (code === Protocol.ROOM_DATA_BYTES) {
720
929
  const messageType = decode.stringCheck(buffer, it) ? decode.string(buffer, it) : decode.number(buffer, it);
721
- const messageTypeHandler = this.onMessageHandlers[messageType];
722
930
  let message = buffer.subarray(it.offset, buffer.byteLength);
723
931
  debugMessage("received: '%s' -> %j (roomId: %s)", messageType, message, this.roomId);
724
- if (messageTypeHandler?.validate !== void 0) {
725
- message = messageTypeHandler.validate(message);
932
+ const bytesMessageType = `_$b${messageType}`;
933
+ try {
934
+ if (this.onMessageValidators[bytesMessageType] !== void 0) {
935
+ message = standardValidate(this.onMessageValidators[bytesMessageType], message);
936
+ }
937
+ } catch (e) {
938
+ debugAndPrintError(e);
939
+ client.leave(CloseCode.WITH_ERROR);
940
+ return;
726
941
  }
727
- if (messageTypeHandler) {
728
- messageTypeHandler.callback(client, message);
942
+ if (this.onMessageEvents.events[bytesMessageType]) {
943
+ this.onMessageEvents.emit(bytesMessageType, client, message);
944
+ } else if (this.onMessageEvents.events["*"]) {
945
+ this.onMessageEvents.emit("*", client, messageType, message);
729
946
  } else {
730
- (this.onMessageHandlers["*"] || this.onMessageHandlers["__no_message_handler"]).callback(client, messageType, message);
947
+ this.onMessageFallbacks["__no_message_handler"](client, messageType, message);
731
948
  }
732
949
  } else if (code === Protocol.JOIN_ROOM && client.state === ClientState.JOINING) {
733
950
  client.state = ClientState.JOINED;
@@ -739,58 +956,64 @@ var Room = class _Room {
739
956
  client._enqueuedMessages.forEach((enqueued) => client.raw(enqueued));
740
957
  }
741
958
  delete client._enqueuedMessages;
959
+ } else if (code === Protocol.PING) {
960
+ client.raw(getMessageBytes[Protocol.PING]());
742
961
  } else if (code === Protocol.LEAVE_ROOM) {
743
- this._forciblyCloseClient(client, Protocol.WS_CLOSE_CONSENTED);
962
+ this.#_forciblyCloseClient(client, CloseCode.CONSENTED);
744
963
  }
745
964
  }
746
- _forciblyCloseClient(client, closeCode) {
965
+ #_forciblyCloseClient(client, closeCode) {
747
966
  client.ref.removeAllListeners("message");
748
967
  client.ref.removeListener("close", client.ref["onleave"]);
749
968
  this._onLeave(client, closeCode).then(() => client.leave(closeCode));
750
969
  }
751
970
  async _onLeave(client, code) {
752
- debugMatchMaking("onLeave, sessionId: '%s' (close code: %d, roomId: %s)", client.sessionId, code, this.roomId);
753
971
  client.state = ClientState.LEAVING;
754
972
  if (!this.clients.delete(client)) {
755
973
  return;
756
974
  }
757
- if (this.onLeave) {
975
+ const method = code === CloseCode.CONSENTED ? this.onLeave : this.onDrop || this.onLeave;
976
+ if (method) {
977
+ debugMatchMaking(`${method.name}, sessionId: '%s' (close code: %d, roomId: %s)`, client.sessionId, code, this.roomId);
758
978
  try {
759
979
  this.#_onLeaveConcurrent++;
760
- await this.onLeave(client, code === Protocol.WS_CLOSE_CONSENTED);
980
+ await method.call(this, client, code);
761
981
  } catch (e) {
762
- debugAndPrintError(`onLeave error: ${e && e.message || e || "promise rejected"} (roomId: ${this.roomId})`);
982
+ debugAndPrintError(`${method.name} error: ${e && e.message || e || "promise rejected"} (roomId: ${this.roomId})`);
763
983
  } finally {
764
984
  this.#_onLeaveConcurrent--;
765
985
  }
766
986
  }
767
987
  if (this._reconnections[client.reconnectionToken]) {
768
988
  this._reconnections[client.reconnectionToken][1].catch(async () => {
769
- await this._onAfterLeave(client);
989
+ await this.#_onAfterLeave(client, code, method === this.onDrop);
770
990
  });
771
991
  } else if (client.state !== ClientState.RECONNECTED) {
772
- await this._onAfterLeave(client);
992
+ await this.#_onAfterLeave(client, code, method === this.onDrop);
773
993
  }
774
994
  }
775
- async _onAfterLeave(client) {
776
- const willDispose = await this._decrementClientCount();
777
- if (this.reservedSeats[client.sessionId] === void 0) {
995
+ async #_onAfterLeave(client, code, isDrop = false) {
996
+ if (isDrop && this.onLeave) {
997
+ await this.onLeave(client, code);
998
+ }
999
+ const willDispose = await this.#_decrementClientCount();
1000
+ if (this._reservedSeats[client.sessionId] === void 0) {
778
1001
  this._events.emit("leave", client, willDispose);
779
1002
  }
780
1003
  }
781
- async _incrementClientCount() {
1004
+ async #_incrementClientCount() {
782
1005
  if (!this.#_locked && this.hasReachedMaxClients()) {
783
1006
  this.#_maxClientsReached = true;
784
1007
  this.lock.call(this, true);
785
1008
  }
786
- await this.listing.updateOne({
1009
+ await matchMaker.driver.update(this._listing, {
787
1010
  $inc: { clients: 1 },
788
1011
  $set: { locked: this.#_locked }
789
1012
  });
790
1013
  }
791
- async _decrementClientCount() {
792
- const willDispose = this._disposeIfEmpty();
793
- if (this._internalState === 2 /* DISPOSING */) {
1014
+ async #_decrementClientCount() {
1015
+ const willDispose = this.#_disposeIfEmpty();
1016
+ if (this._internalState === RoomInternalState.DISPOSING) {
794
1017
  return true;
795
1018
  }
796
1019
  if (!willDispose) {
@@ -798,7 +1021,7 @@ var Room = class _Room {
798
1021
  this.#_maxClientsReached = false;
799
1022
  this.unlock.call(this, true);
800
1023
  }
801
- await this.listing.updateOne({
1024
+ await matchMaker.driver.update(this._listing, {
802
1025
  $inc: { clients: -1 },
803
1026
  $set: { locked: this.#_locked }
804
1027
  });
@@ -832,8 +1055,27 @@ var Room = class _Room {
832
1055
  }
833
1056
  }
834
1057
  };
1058
+ function room(options) {
1059
+ class _ extends Room {
1060
+ constructor() {
1061
+ super();
1062
+ this.messages = options.messages;
1063
+ if (options.state && typeof options.state === "function") {
1064
+ this.state = options.state();
1065
+ }
1066
+ }
1067
+ }
1068
+ for (const key in options) {
1069
+ if (typeof options[key] === "function") {
1070
+ _.prototype[key] = options[key];
1071
+ }
1072
+ }
1073
+ return _;
1074
+ }
835
1075
  export {
836
1076
  DEFAULT_SEAT_RESERVATION_TIME,
837
1077
  Room,
838
- RoomInternalState
1078
+ RoomInternalState,
1079
+ room,
1080
+ validate
839
1081
  };