@colyseus/core 0.17.44 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/build/MatchMaker.cjs +22 -13
  2. package/build/MatchMaker.cjs.map +2 -2
  3. package/build/MatchMaker.d.ts +10 -0
  4. package/build/MatchMaker.mjs +22 -14
  5. package/build/MatchMaker.mjs.map +2 -2
  6. package/build/Protocol.cjs +102 -37
  7. package/build/Protocol.cjs.map +2 -2
  8. package/build/Protocol.d.ts +33 -2
  9. package/build/Protocol.mjs +102 -37
  10. package/build/Protocol.mjs.map +2 -2
  11. package/build/Rewind.cjs +385 -0
  12. package/build/Rewind.cjs.map +7 -0
  13. package/build/Rewind.d.ts +291 -0
  14. package/build/Rewind.mjs +359 -0
  15. package/build/Rewind.mjs.map +7 -0
  16. package/build/Room.cjs +425 -149
  17. package/build/Room.cjs.map +3 -3
  18. package/build/Room.d.ts +314 -27
  19. package/build/Room.mjs +434 -152
  20. package/build/Room.mjs.map +3 -3
  21. package/build/RoomMessages.cjs +265 -0
  22. package/build/RoomMessages.cjs.map +7 -0
  23. package/build/RoomMessages.d.ts +49 -0
  24. package/build/RoomMessages.mjs +240 -0
  25. package/build/RoomMessages.mjs.map +7 -0
  26. package/build/RoomPlugin.cjs +252 -0
  27. package/build/RoomPlugin.cjs.map +7 -0
  28. package/build/RoomPlugin.d.ts +271 -0
  29. package/build/RoomPlugin.mjs +220 -0
  30. package/build/RoomPlugin.mjs.map +7 -0
  31. package/build/Server.cjs +49 -11
  32. package/build/Server.cjs.map +2 -2
  33. package/build/Server.d.ts +26 -0
  34. package/build/Server.mjs +50 -12
  35. package/build/Server.mjs.map +2 -2
  36. package/build/Transport.cjs +64 -2
  37. package/build/Transport.cjs.map +2 -2
  38. package/build/Transport.d.ts +108 -5
  39. package/build/Transport.mjs +63 -2
  40. package/build/Transport.mjs.map +2 -2
  41. package/build/errors/RoomExceptions.cjs +7 -4
  42. package/build/errors/RoomExceptions.cjs.map +2 -2
  43. package/build/errors/RoomExceptions.d.ts +15 -3
  44. package/build/errors/RoomExceptions.mjs +5 -3
  45. package/build/errors/RoomExceptions.mjs.map +2 -2
  46. package/build/index.cjs +30 -4
  47. package/build/index.cjs.map +2 -2
  48. package/build/index.d.ts +9 -5
  49. package/build/index.mjs +29 -6
  50. package/build/index.mjs.map +2 -2
  51. package/build/input/InputBuffer.cjs +536 -0
  52. package/build/input/InputBuffer.cjs.map +7 -0
  53. package/build/input/InputBuffer.d.ts +226 -0
  54. package/build/input/InputBuffer.mjs +507 -0
  55. package/build/input/InputBuffer.mjs.map +7 -0
  56. package/build/input/RoomInput.cjs +306 -0
  57. package/build/input/RoomInput.cjs.map +7 -0
  58. package/build/input/RoomInput.d.ts +91 -0
  59. package/build/input/RoomInput.mjs +287 -0
  60. package/build/input/RoomInput.mjs.map +7 -0
  61. package/build/input/types.cjs +18 -0
  62. package/build/input/types.cjs.map +7 -0
  63. package/build/input/types.d.ts +476 -0
  64. package/build/input/types.mjs +0 -0
  65. package/build/input/types.mjs.map +7 -0
  66. package/build/internal.cjs +61 -0
  67. package/build/internal.cjs.map +7 -0
  68. package/build/internal.d.ts +9 -0
  69. package/build/internal.mjs +29 -0
  70. package/build/internal.mjs.map +7 -0
  71. package/build/matchmaker/LocalDriver/LocalDriver.cjs +13 -0
  72. package/build/matchmaker/LocalDriver/LocalDriver.cjs.map +2 -2
  73. package/build/matchmaker/LocalDriver/LocalDriver.d.ts +1 -0
  74. package/build/matchmaker/LocalDriver/LocalDriver.mjs +13 -0
  75. package/build/matchmaker/LocalDriver/LocalDriver.mjs.map +2 -2
  76. package/build/matchmaker/driver.cjs.map +1 -1
  77. package/build/matchmaker/driver.d.ts +12 -0
  78. package/build/matchmaker/driver.mjs.map +1 -1
  79. package/build/presence/LocalPresence.d.ts +1 -1
  80. package/build/rooms/LobbyRoom.cjs +8 -10
  81. package/build/rooms/LobbyRoom.cjs.map +2 -2
  82. package/build/rooms/LobbyRoom.d.ts +4 -3
  83. package/build/rooms/LobbyRoom.mjs +8 -10
  84. package/build/rooms/LobbyRoom.mjs.map +2 -2
  85. package/build/rooms/QueueRoom.cjs +1 -1
  86. package/build/rooms/QueueRoom.cjs.map +2 -2
  87. package/build/rooms/QueueRoom.mjs +1 -1
  88. package/build/rooms/QueueRoom.mjs.map +2 -2
  89. package/build/rooms/RelayRoom.cjs +12 -16
  90. package/build/rooms/RelayRoom.cjs.map +2 -2
  91. package/build/rooms/RelayRoom.d.ts +32 -11
  92. package/build/rooms/RelayRoom.mjs +10 -16
  93. package/build/rooms/RelayRoom.mjs.map +2 -2
  94. package/build/router/default_routes.cjs +4 -1
  95. package/build/router/default_routes.cjs.map +2 -2
  96. package/build/router/default_routes.mjs +4 -1
  97. package/build/router/default_routes.mjs.map +2 -2
  98. package/build/router/index.cjs +71 -5
  99. package/build/router/index.cjs.map +2 -2
  100. package/build/router/index.d.ts +30 -6
  101. package/build/router/index.mjs +72 -7
  102. package/build/router/index.mjs.map +3 -3
  103. package/build/serializer/NoneSerializer.cjs +2 -2
  104. package/build/serializer/NoneSerializer.cjs.map +2 -2
  105. package/build/serializer/NoneSerializer.d.ts +3 -3
  106. package/build/serializer/NoneSerializer.mjs +2 -2
  107. package/build/serializer/NoneSerializer.mjs.map +2 -2
  108. package/build/serializer/SchemaSerializer.cjs +44 -14
  109. package/build/serializer/SchemaSerializer.cjs.map +2 -2
  110. package/build/serializer/SchemaSerializer.d.ts +3 -3
  111. package/build/serializer/SchemaSerializer.mjs +46 -16
  112. package/build/serializer/SchemaSerializer.mjs.map +2 -2
  113. package/build/serializer/Serializer.cjs.map +1 -1
  114. package/build/serializer/Serializer.d.ts +12 -2
  115. package/build/utils/DevMode.cjs +14 -14
  116. package/build/utils/DevMode.cjs.map +2 -2
  117. package/build/utils/DevMode.d.ts +0 -2
  118. package/build/utils/DevMode.mjs +14 -12
  119. package/build/utils/DevMode.mjs.map +2 -2
  120. package/build/utils/UserSessionIndex.cjs +162 -0
  121. package/build/utils/UserSessionIndex.cjs.map +7 -0
  122. package/build/utils/UserSessionIndex.d.ts +166 -0
  123. package/build/utils/UserSessionIndex.mjs +130 -0
  124. package/build/utils/UserSessionIndex.mjs.map +7 -0
  125. package/build/utils/Utils.cjs +3 -0
  126. package/build/utils/Utils.cjs.map +2 -2
  127. package/build/utils/Utils.d.ts +8 -0
  128. package/build/utils/Utils.mjs +2 -0
  129. package/build/utils/Utils.mjs.map +2 -2
  130. package/package.json +19 -14
  131. package/src/MatchMaker.ts +47 -15
  132. package/src/Protocol.ts +130 -59
  133. package/src/Rewind.ts +572 -0
  134. package/src/Room.ts +649 -235
  135. package/src/RoomMessages.ts +342 -0
  136. package/src/RoomPlugin.ts +563 -0
  137. package/src/Server.ts +82 -15
  138. package/src/Transport.ts +172 -9
  139. package/src/errors/RoomExceptions.ts +18 -4
  140. package/src/index.ts +23 -3
  141. package/src/input/InputBuffer.ts +549 -0
  142. package/src/input/RoomInput.ts +337 -0
  143. package/src/input/types.ts +503 -0
  144. package/src/internal.ts +46 -0
  145. package/src/matchmaker/LocalDriver/LocalDriver.ts +10 -0
  146. package/src/matchmaker/driver.ts +13 -0
  147. package/src/rooms/LobbyRoom.ts +12 -8
  148. package/src/rooms/QueueRoom.ts +1 -1
  149. package/src/rooms/RelayRoom.ts +9 -15
  150. package/src/router/default_routes.ts +6 -1
  151. package/src/router/index.ts +117 -11
  152. package/src/serializer/NoneSerializer.ts +3 -3
  153. package/src/serializer/SchemaSerializer.ts +110 -19
  154. package/src/serializer/Serializer.ts +13 -2
  155. package/src/utils/DevMode.ts +18 -13
  156. package/src/utils/UserSessionIndex.ts +311 -0
  157. package/src/utils/Utils.ts +9 -0
@@ -0,0 +1,342 @@
1
+ import { decode, type Iterator } from '@colyseus/schema';
2
+ import { unpack } from 'msgpackr';
3
+
4
+ import { CloseCode, ErrorCode, Protocol, ResponseStatus, type MessageContext } from '@colyseus/shared-types';
5
+ import { getMessageBytes } from './Protocol.ts';
6
+ import { createNanoEvents } from './utils/nanoevents.ts';
7
+ import { standardValidate, type StandardSchemaV1 } from './utils/StandardSchema.ts';
8
+ import { isDevMode } from './utils/DevMode.ts';
9
+ import { debugMessage, debugAndPrintError } from './Debug.ts';
10
+ import { OnMessageException } from './errors/RoomExceptions.ts';
11
+ import type { Client, ClientPrivate } from './Transport.ts';
12
+ import type { Room } from './Room.ts';
13
+
14
+ /** Normalize a thrown value into the `{ name, message, code? }` shape echoed in a
15
+ * ROOM_RESPONSE error reply. */
16
+ function toResponseError(e: any): { name: string; message: string; code?: any } {
17
+ if (e instanceof Error) {
18
+ const code = (e as any).code;
19
+ return (code !== undefined)
20
+ ? { name: e.name, message: e.message, code }
21
+ : { name: e.name, message: e.message };
22
+ }
23
+ return { name: "Error", message: String(e) };
24
+ }
25
+
26
+ // The handler's terminal action — NOT the wire ResponseStatus: `none` and
27
+ // `resolved` both reply OK (plain return value vs `ctx.resolve(value)`), a
28
+ // distinction a single OK can't carry; ERROR is never a handler decision
29
+ // (thrown/no-handler). Projected onto ResponseStatus once, in onRequest's reply.
30
+ const OUTCOME_NONE = 0, OUTCOME_REJECTED = 1, OUTCOME_RESOLVED = 2;
31
+
32
+ /**
33
+ * Runtime {@link MessageContext} for a {@link Protocol.ROOM_REQUEST} dispatch.
34
+ * `reject`/`resolve` record the decision on the ctx; `onRequest` reads it after the
35
+ * handler returns, so a bare side-effecting call works as well as `return
36
+ * ctx.reject(r)` (the branded return is compile-time only — the runtime return is
37
+ * unused).
38
+ */
39
+ class DispatchContext implements MessageContext {
40
+ readonly id: number | undefined;
41
+ _outcome = OUTCOME_NONE;
42
+ _reason: any = undefined;
43
+ _value: any = undefined;
44
+
45
+ constructor(id: number | undefined) {
46
+ this.id = id;
47
+ }
48
+
49
+ reject(reason?: any): any {
50
+ this._outcome = OUTCOME_REJECTED;
51
+ this._reason = reason;
52
+ return undefined;
53
+ }
54
+
55
+ resolve(value?: any): any {
56
+ this._outcome = OUTCOME_RESOLVED;
57
+ this._value = value;
58
+ return undefined;
59
+ }
60
+ }
61
+
62
+ /** Shared no-op context for fire-and-forget `ROOM_DATA` (no reply channel, so
63
+ * `reject`/`resolve` go nowhere). `id` is `undefined`; zero per-message allocation. */
64
+ const SEND_CONTEXT: MessageContext = Object.freeze({
65
+ id: undefined,
66
+ reject: () => undefined as any,
67
+ resolve: () => undefined as any,
68
+ });
69
+
70
+ /**
71
+ * Per-room message-routing layer, owned by {@link Room}. Owns the user-message
72
+ * handler registry (`onMessage`/`onMessageBytes` + per-type validators) and
73
+ * decodes/dispatches the user-message wire frames (ROOM_DATA / ROOM_REQUEST /
74
+ * ROOM_DATA_BYTES). Room's `_onMessage` keeps the protocol/lifecycle frames
75
+ * (JOIN/PING/LEAVE/input) and calls one of these per frame, preserving the
76
+ * original dispatch order. Reads back into its Room only for `onUncaughtException`
77
+ * (handler wrapping) and `roomId`/`roomName` (logging).
78
+ *
79
+ * @internal
80
+ */
81
+ export class RoomMessages {
82
+ private room: Room<any>;
83
+
84
+ /** Handler registry (nanoevents). Public so Room can re-expose it as
85
+ * `onMessageEvents` for @colyseus/playground introspection and
86
+ * @colyseus/testing handler-swapping. */
87
+ events = createNanoEvents();
88
+
89
+ /** Per-type StandardSchema validators. Public for the same reason as
90
+ * {@link events} (`onMessageValidators`). */
91
+ validators: { [type: string]: StandardSchemaV1 } = {};
92
+
93
+ constructor(room: Room<any>) {
94
+ this.room = room;
95
+ }
96
+
97
+ /**
98
+ * Register a handler (body of {@link Room.onMessage}). Wraps the callback via
99
+ * the room's `onUncaughtException` when set, stores the validator, and returns
100
+ * the unbind closure.
101
+ */
102
+ on(
103
+ _messageType: '*' | string | number,
104
+ _validationSchema: StandardSchemaV1 | ((...args: any[]) => void),
105
+ _callback?: (...args: any[]) => void,
106
+ ): () => void {
107
+ const messageType = _messageType.toString();
108
+
109
+ const validationSchema = (typeof _callback === 'function')
110
+ ? _validationSchema as StandardSchemaV1
111
+ : undefined;
112
+
113
+ const callback = (validationSchema === undefined)
114
+ ? _validationSchema as (...args: any[]) => void
115
+ : _callback;
116
+
117
+ const removeListener = this.events.on(messageType, (this.room.onUncaughtException !== undefined)
118
+ ? this.#wrapMessageHandler(callback, _messageType)
119
+ : callback);
120
+
121
+ if (validationSchema !== undefined) {
122
+ this.validators[messageType] = validationSchema;
123
+ }
124
+
125
+ // returns a method to unbind the callback
126
+ return () => {
127
+ removeListener();
128
+ if (this.events.events[messageType].length === 0) {
129
+ delete this.validators[messageType];
130
+ }
131
+ };
132
+ }
133
+
134
+ /**
135
+ * Wrap a user message handler so a sync throw or async rejection is reported to
136
+ * `room.onUncaughtException` as an {@link OnMessageException} carrying the correct
137
+ * `client` / `payload` / `type`. The handler is invoked with a different argument
138
+ * shape per dispatch path, so those fields are normalized here rather than relying
139
+ * on positional forwarding (which silently mis-mapped `type` once a context arg was
140
+ * added — see ROOM_DATA/ROOM_REQUEST passing `SEND_CONTEXT`/`ctx`):
141
+ *
142
+ * - typed handler (DATA / REQUEST / BYTES): `(client, payload, ctx?)` → `type` is
143
+ * the registered `_messageType`.
144
+ * - wildcard `'*'` handler: `(client, type, payload)` → `type` is
145
+ * the received message type.
146
+ *
147
+ * The error is swallowed (never rethrown) so a faulty handler can't break dispatch;
148
+ * the handler's return value (or promise) is passed through for the REQUEST path.
149
+ */
150
+ #wrapMessageHandler(callback: (...args: any[]) => any, registeredType: '*' | string | number) {
151
+ const onError = this.room.onUncaughtException!.bind(this.room);
152
+ const isWildcard = (registeredType === '*');
153
+
154
+ return (...args: any[]): any => {
155
+ const report = (e: Error) => onError(
156
+ new OnMessageException(
157
+ e, e.message,
158
+ args[0], // client
159
+ isWildcard ? args[2] : args[1], // payload
160
+ isWildcard ? args[1] : registeredType, // type
161
+ ),
162
+ 'onMessage',
163
+ );
164
+
165
+ try {
166
+ const result = callback(...args);
167
+ return (typeof result?.catch === 'function') ? result.catch(report) : result;
168
+ } catch (e: any) {
169
+ report(e);
170
+ }
171
+ };
172
+ }
173
+
174
+ /** Dispatch a `ROOM_DATA` frame: decode type + msgpack payload, validate, emit. */
175
+ onData(client: Client & ClientPrivate, buffer: Buffer, it: Iterator): void {
176
+ const messageType = (decode.stringCheck(buffer, it))
177
+ ? decode.string(buffer, it)
178
+ : decode.number(buffer, it);
179
+
180
+ let message;
181
+ try {
182
+ message = (buffer.byteLength > it.offset)
183
+ ? unpack(buffer.subarray(it.offset, buffer.byteLength))
184
+ : undefined;
185
+ debugMessage("received: '%s' -> %j (roomId: %s)", messageType, message, this.room.roomId);
186
+
187
+ // custom message validation
188
+ if (this.validators[messageType] !== undefined) {
189
+ message = standardValidate(this.validators[messageType], message);
190
+ }
191
+
192
+ } catch (e: any) {
193
+ debugAndPrintError(e);
194
+ client.leave(CloseCode.WITH_ERROR);
195
+ return;
196
+ }
197
+
198
+ if (this.events.events[messageType]) {
199
+ this.events.emit(messageType as string, client, message, SEND_CONTEXT);
200
+
201
+ } else if (this.events.events['*']) {
202
+ this.events.emit('*', client, messageType, message);
203
+
204
+ } else {
205
+ this.#noHandler(client, messageType, message);
206
+ }
207
+ }
208
+
209
+ /** Dispatch a `ROOM_REQUEST` frame: same handlers as ROOM_DATA, but the client
210
+ * opted into a reply, so echo the `requestId` with the outcome. The handler's
211
+ * reply is decided by what it returns / does to `ctx`: `ctx.reject(reason)` →
212
+ * `REJECTED(reason)`, `ctx.resolve(value)` → `OK(value)`, a thrown error or
213
+ * missing handler → `ERROR`, else `OK(return)`. */
214
+ onRequest(client: Client & ClientPrivate, buffer: Buffer, it: Iterator): void {
215
+ const requestId = decode.number(buffer, it);
216
+
217
+ const messageType = (decode.stringCheck(buffer, it))
218
+ ? decode.string(buffer, it)
219
+ : decode.number(buffer, it);
220
+
221
+ let message;
222
+ try {
223
+ message = (buffer.byteLength > it.offset)
224
+ ? unpack(buffer.subarray(it.offset, buffer.byteLength))
225
+ : undefined;
226
+ debugMessage("request #%d: '%s' -> %j (roomId: %s)", requestId, messageType, message, this.room.roomId);
227
+
228
+ // custom message validation (shared with the ROOM_DATA path)
229
+ if (this.validators[messageType] !== undefined) {
230
+ message = standardValidate(this.validators[messageType], message);
231
+ }
232
+
233
+ } catch (e: any) {
234
+ // Reply with an error so the caller's pending request resolves instead of timing out.
235
+ debugAndPrintError(e);
236
+ this.#replyToRequest(client, requestId, ResponseStatus.ERROR, toResponseError(e));
237
+ return;
238
+ }
239
+
240
+ // Answered by the FIRST handler registered for the type (emit would discard
241
+ // returns); wildcard handlers have no response contract, so they're ineligible.
242
+ const handler = this.events.events[messageType as string]?.[0];
243
+
244
+ if (handler === undefined) {
245
+ this.#replyToRequest(client, requestId, ResponseStatus.ERROR, {
246
+ name: "no_handler",
247
+ message: `room "${this.room.roomName}" has no onMessage("${messageType}") handler to answer this request.`,
248
+ });
249
+ return;
250
+ }
251
+
252
+ const ctx = new DispatchContext(requestId);
253
+
254
+ // Sync handlers reply in this tick with no promise machinery; only a thenable
255
+ // return defers to the microtask queue. A sync throw (unwrapped handler) and an
256
+ // async rejection both fall to the ERROR reply. With onUncaughtException set the
257
+ // handler is wrapped (swallows its own errors), so it never throws / its promise
258
+ // resolves undefined, and the error reports there.
259
+ let response: any;
260
+ try {
261
+ response = handler(client, message, ctx);
262
+ if (response !== null && typeof response === 'object' && typeof response.then === 'function') {
263
+ response.then(
264
+ (resolved: any) => this.#finalizeRequest(client, requestId, ctx, resolved),
265
+ (e: any) => {
266
+ debugAndPrintError(e);
267
+ this.#replyToRequest(client, requestId, ResponseStatus.ERROR, toResponseError(e));
268
+ },
269
+ );
270
+ return;
271
+ }
272
+ } catch (e: any) {
273
+ debugAndPrintError(e);
274
+ this.#replyToRequest(client, requestId, ResponseStatus.ERROR, toResponseError(e));
275
+ return;
276
+ }
277
+
278
+ this.#finalizeRequest(client, requestId, ctx, response);
279
+ }
280
+
281
+ /** Finalize a request: project the handler's outcome onto a ROOM_RESPONSE reply —
282
+ * `ctx.reject` → REJECTED(reason), `ctx.resolve(value)` → OK(value), else OK(return). */
283
+ #finalizeRequest(client: Client, requestId: number, ctx: DispatchContext, response: any): void {
284
+ if (ctx._outcome === OUTCOME_REJECTED) {
285
+ this.#replyToRequest(client, requestId, ResponseStatus.REJECTED, ctx._reason);
286
+ } else if (ctx._outcome === OUTCOME_RESOLVED) {
287
+ this.#replyToRequest(client, requestId, ResponseStatus.OK, ctx._value);
288
+ } else {
289
+ this.#replyToRequest(client, requestId, ResponseStatus.OK, response);
290
+ }
291
+ }
292
+
293
+ /** Dispatch a `ROOM_DATA_BYTES` frame: raw bytes routed to `_$b`-prefixed handlers. */
294
+ onDataBytes(client: Client & ClientPrivate, buffer: Buffer, it: Iterator): void {
295
+ const messageType = (decode.stringCheck(buffer, it))
296
+ ? decode.string(buffer, it)
297
+ : decode.number(buffer, it);
298
+
299
+ let message: any = buffer.subarray(it.offset, buffer.byteLength);
300
+ debugMessage("received: '%s' -> %j (roomId: %s)", messageType, message, this.room.roomId);
301
+
302
+ const bytesMessageType = `_$b${messageType}`;
303
+
304
+ // custom message validation
305
+ try {
306
+ if (this.validators[bytesMessageType] !== undefined) {
307
+ message = standardValidate(this.validators[bytesMessageType], message);
308
+ }
309
+ } catch (e: any) {
310
+ debugAndPrintError(e);
311
+ client.leave(CloseCode.WITH_ERROR);
312
+ return;
313
+ }
314
+
315
+ if (this.events.events[bytesMessageType]) {
316
+ this.events.emit(bytesMessageType, client, message);
317
+
318
+ } else if (this.events.events['*']) {
319
+ this.events.emit('*', client, messageType, message);
320
+
321
+ } else {
322
+ this.#noHandler(client, messageType, message);
323
+ }
324
+ }
325
+
326
+ /** Emit a ROOM_RESPONSE reply immediately. */
327
+ #replyToRequest(client: Client, requestId: number, status: ResponseStatus, payload?: any): void {
328
+ debugMessage("response #%d: status=%d -> %j (roomId: %s)", requestId, status, payload, this.room.roomId);
329
+ client.enqueueRaw(getMessageBytes[Protocol.ROOM_RESPONSE](requestId, status, payload));
330
+ }
331
+
332
+ /** No handler for the type: error the client in dev, drop it in production. */
333
+ #noHandler(client: Client, messageType: string | number, _message: unknown): void {
334
+ const errorMessage = `room onMessage for "${messageType}" not registered.`;
335
+ debugMessage(`${errorMessage} (roomId: ${this.room.roomId})`);
336
+ if (isDevMode) {
337
+ client.error(ErrorCode.INVALID_PAYLOAD, errorMessage);
338
+ } else {
339
+ client.leave(CloseCode.WITH_ERROR, errorMessage);
340
+ }
341
+ }
342
+ }