@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,265 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // packages/core/src/RoomMessages.ts
21
+ var RoomMessages_exports = {};
22
+ __export(RoomMessages_exports, {
23
+ RoomMessages: () => RoomMessages
24
+ });
25
+ module.exports = __toCommonJS(RoomMessages_exports);
26
+ var import_schema = require("@colyseus/schema");
27
+ var import_msgpackr = require("msgpackr");
28
+ var import_shared_types = require("@colyseus/shared-types");
29
+ var import_Protocol = require("./Protocol.cjs");
30
+ var import_nanoevents = require("./utils/nanoevents.cjs");
31
+ var import_StandardSchema = require("./utils/StandardSchema.cjs");
32
+ var import_DevMode = require("./utils/DevMode.cjs");
33
+ var import_Debug = require("./Debug.cjs");
34
+ var import_RoomExceptions = require("./errors/RoomExceptions.cjs");
35
+ function toResponseError(e) {
36
+ if (e instanceof Error) {
37
+ const code = e.code;
38
+ return code !== void 0 ? { name: e.name, message: e.message, code } : { name: e.name, message: e.message };
39
+ }
40
+ return { name: "Error", message: String(e) };
41
+ }
42
+ var OUTCOME_NONE = 0;
43
+ var OUTCOME_REJECTED = 1;
44
+ var OUTCOME_RESOLVED = 2;
45
+ var DispatchContext = class {
46
+ constructor(id) {
47
+ this._outcome = OUTCOME_NONE;
48
+ this._reason = void 0;
49
+ this._value = void 0;
50
+ this.id = id;
51
+ }
52
+ reject(reason) {
53
+ this._outcome = OUTCOME_REJECTED;
54
+ this._reason = reason;
55
+ return void 0;
56
+ }
57
+ resolve(value) {
58
+ this._outcome = OUTCOME_RESOLVED;
59
+ this._value = value;
60
+ return void 0;
61
+ }
62
+ };
63
+ var SEND_CONTEXT = Object.freeze({
64
+ id: void 0,
65
+ reject: () => void 0,
66
+ resolve: () => void 0
67
+ });
68
+ var RoomMessages = class {
69
+ constructor(room) {
70
+ /** Handler registry (nanoevents). Public so Room can re-expose it as
71
+ * `onMessageEvents` for @colyseus/playground introspection and
72
+ * @colyseus/testing handler-swapping. */
73
+ this.events = (0, import_nanoevents.createNanoEvents)();
74
+ /** Per-type StandardSchema validators. Public for the same reason as
75
+ * {@link events} (`onMessageValidators`). */
76
+ this.validators = {};
77
+ this.room = room;
78
+ }
79
+ /**
80
+ * Register a handler (body of {@link Room.onMessage}). Wraps the callback via
81
+ * the room's `onUncaughtException` when set, stores the validator, and returns
82
+ * the unbind closure.
83
+ */
84
+ on(_messageType, _validationSchema, _callback) {
85
+ const messageType = _messageType.toString();
86
+ const validationSchema = typeof _callback === "function" ? _validationSchema : void 0;
87
+ const callback = validationSchema === void 0 ? _validationSchema : _callback;
88
+ const removeListener = this.events.on(messageType, this.room.onUncaughtException !== void 0 ? this.#wrapMessageHandler(callback, _messageType) : callback);
89
+ if (validationSchema !== void 0) {
90
+ this.validators[messageType] = validationSchema;
91
+ }
92
+ return () => {
93
+ removeListener();
94
+ if (this.events.events[messageType].length === 0) {
95
+ delete this.validators[messageType];
96
+ }
97
+ };
98
+ }
99
+ /**
100
+ * Wrap a user message handler so a sync throw or async rejection is reported to
101
+ * `room.onUncaughtException` as an {@link OnMessageException} carrying the correct
102
+ * `client` / `payload` / `type`. The handler is invoked with a different argument
103
+ * shape per dispatch path, so those fields are normalized here rather than relying
104
+ * on positional forwarding (which silently mis-mapped `type` once a context arg was
105
+ * added — see ROOM_DATA/ROOM_REQUEST passing `SEND_CONTEXT`/`ctx`):
106
+ *
107
+ * - typed handler (DATA / REQUEST / BYTES): `(client, payload, ctx?)` → `type` is
108
+ * the registered `_messageType`.
109
+ * - wildcard `'*'` handler: `(client, type, payload)` → `type` is
110
+ * the received message type.
111
+ *
112
+ * The error is swallowed (never rethrown) so a faulty handler can't break dispatch;
113
+ * the handler's return value (or promise) is passed through for the REQUEST path.
114
+ */
115
+ #wrapMessageHandler(callback, registeredType) {
116
+ const onError = this.room.onUncaughtException.bind(this.room);
117
+ const isWildcard = registeredType === "*";
118
+ return (...args) => {
119
+ const report = (e) => onError(
120
+ new import_RoomExceptions.OnMessageException(
121
+ e,
122
+ e.message,
123
+ args[0],
124
+ // client
125
+ isWildcard ? args[2] : args[1],
126
+ // payload
127
+ isWildcard ? args[1] : registeredType
128
+ // type
129
+ ),
130
+ "onMessage"
131
+ );
132
+ try {
133
+ const result = callback(...args);
134
+ return typeof result?.catch === "function" ? result.catch(report) : result;
135
+ } catch (e) {
136
+ report(e);
137
+ }
138
+ };
139
+ }
140
+ /** Dispatch a `ROOM_DATA` frame: decode type + msgpack payload, validate, emit. */
141
+ onData(client, buffer, it) {
142
+ const messageType = import_schema.decode.stringCheck(buffer, it) ? import_schema.decode.string(buffer, it) : import_schema.decode.number(buffer, it);
143
+ let message;
144
+ try {
145
+ message = buffer.byteLength > it.offset ? (0, import_msgpackr.unpack)(buffer.subarray(it.offset, buffer.byteLength)) : void 0;
146
+ (0, import_Debug.debugMessage)("received: '%s' -> %j (roomId: %s)", messageType, message, this.room.roomId);
147
+ if (this.validators[messageType] !== void 0) {
148
+ message = (0, import_StandardSchema.standardValidate)(this.validators[messageType], message);
149
+ }
150
+ } catch (e) {
151
+ (0, import_Debug.debugAndPrintError)(e);
152
+ client.leave(import_shared_types.CloseCode.WITH_ERROR);
153
+ return;
154
+ }
155
+ if (this.events.events[messageType]) {
156
+ this.events.emit(messageType, client, message, SEND_CONTEXT);
157
+ } else if (this.events.events["*"]) {
158
+ this.events.emit("*", client, messageType, message);
159
+ } else {
160
+ this.#noHandler(client, messageType, message);
161
+ }
162
+ }
163
+ /** Dispatch a `ROOM_REQUEST` frame: same handlers as ROOM_DATA, but the client
164
+ * opted into a reply, so echo the `requestId` with the outcome. The handler's
165
+ * reply is decided by what it returns / does to `ctx`: `ctx.reject(reason)` →
166
+ * `REJECTED(reason)`, `ctx.resolve(value)` → `OK(value)`, a thrown error or
167
+ * missing handler → `ERROR`, else `OK(return)`. */
168
+ onRequest(client, buffer, it) {
169
+ const requestId = import_schema.decode.number(buffer, it);
170
+ const messageType = import_schema.decode.stringCheck(buffer, it) ? import_schema.decode.string(buffer, it) : import_schema.decode.number(buffer, it);
171
+ let message;
172
+ try {
173
+ message = buffer.byteLength > it.offset ? (0, import_msgpackr.unpack)(buffer.subarray(it.offset, buffer.byteLength)) : void 0;
174
+ (0, import_Debug.debugMessage)("request #%d: '%s' -> %j (roomId: %s)", requestId, messageType, message, this.room.roomId);
175
+ if (this.validators[messageType] !== void 0) {
176
+ message = (0, import_StandardSchema.standardValidate)(this.validators[messageType], message);
177
+ }
178
+ } catch (e) {
179
+ (0, import_Debug.debugAndPrintError)(e);
180
+ this.#replyToRequest(client, requestId, import_shared_types.ResponseStatus.ERROR, toResponseError(e));
181
+ return;
182
+ }
183
+ const handler = this.events.events[messageType]?.[0];
184
+ if (handler === void 0) {
185
+ this.#replyToRequest(client, requestId, import_shared_types.ResponseStatus.ERROR, {
186
+ name: "no_handler",
187
+ message: `room "${this.room.roomName}" has no onMessage("${messageType}") handler to answer this request.`
188
+ });
189
+ return;
190
+ }
191
+ const ctx = new DispatchContext(requestId);
192
+ let response;
193
+ try {
194
+ response = handler(client, message, ctx);
195
+ if (response !== null && typeof response === "object" && typeof response.then === "function") {
196
+ response.then(
197
+ (resolved) => this.#finalizeRequest(client, requestId, ctx, resolved),
198
+ (e) => {
199
+ (0, import_Debug.debugAndPrintError)(e);
200
+ this.#replyToRequest(client, requestId, import_shared_types.ResponseStatus.ERROR, toResponseError(e));
201
+ }
202
+ );
203
+ return;
204
+ }
205
+ } catch (e) {
206
+ (0, import_Debug.debugAndPrintError)(e);
207
+ this.#replyToRequest(client, requestId, import_shared_types.ResponseStatus.ERROR, toResponseError(e));
208
+ return;
209
+ }
210
+ this.#finalizeRequest(client, requestId, ctx, response);
211
+ }
212
+ /** Finalize a request: project the handler's outcome onto a ROOM_RESPONSE reply —
213
+ * `ctx.reject` → REJECTED(reason), `ctx.resolve(value)` → OK(value), else OK(return). */
214
+ #finalizeRequest(client, requestId, ctx, response) {
215
+ if (ctx._outcome === OUTCOME_REJECTED) {
216
+ this.#replyToRequest(client, requestId, import_shared_types.ResponseStatus.REJECTED, ctx._reason);
217
+ } else if (ctx._outcome === OUTCOME_RESOLVED) {
218
+ this.#replyToRequest(client, requestId, import_shared_types.ResponseStatus.OK, ctx._value);
219
+ } else {
220
+ this.#replyToRequest(client, requestId, import_shared_types.ResponseStatus.OK, response);
221
+ }
222
+ }
223
+ /** Dispatch a `ROOM_DATA_BYTES` frame: raw bytes routed to `_$b`-prefixed handlers. */
224
+ onDataBytes(client, buffer, it) {
225
+ const messageType = import_schema.decode.stringCheck(buffer, it) ? import_schema.decode.string(buffer, it) : import_schema.decode.number(buffer, it);
226
+ let message = buffer.subarray(it.offset, buffer.byteLength);
227
+ (0, import_Debug.debugMessage)("received: '%s' -> %j (roomId: %s)", messageType, message, this.room.roomId);
228
+ const bytesMessageType = `_$b${messageType}`;
229
+ try {
230
+ if (this.validators[bytesMessageType] !== void 0) {
231
+ message = (0, import_StandardSchema.standardValidate)(this.validators[bytesMessageType], message);
232
+ }
233
+ } catch (e) {
234
+ (0, import_Debug.debugAndPrintError)(e);
235
+ client.leave(import_shared_types.CloseCode.WITH_ERROR);
236
+ return;
237
+ }
238
+ if (this.events.events[bytesMessageType]) {
239
+ this.events.emit(bytesMessageType, client, message);
240
+ } else if (this.events.events["*"]) {
241
+ this.events.emit("*", client, messageType, message);
242
+ } else {
243
+ this.#noHandler(client, messageType, message);
244
+ }
245
+ }
246
+ /** Emit a ROOM_RESPONSE reply immediately. */
247
+ #replyToRequest(client, requestId, status, payload) {
248
+ (0, import_Debug.debugMessage)("response #%d: status=%d -> %j (roomId: %s)", requestId, status, payload, this.room.roomId);
249
+ client.enqueueRaw(import_Protocol.getMessageBytes[import_shared_types.Protocol.ROOM_RESPONSE](requestId, status, payload));
250
+ }
251
+ /** No handler for the type: error the client in dev, drop it in production. */
252
+ #noHandler(client, messageType, _message) {
253
+ const errorMessage = `room onMessage for "${messageType}" not registered.`;
254
+ (0, import_Debug.debugMessage)(`${errorMessage} (roomId: ${this.room.roomId})`);
255
+ if (import_DevMode.isDevMode) {
256
+ client.error(import_shared_types.ErrorCode.INVALID_PAYLOAD, errorMessage);
257
+ } else {
258
+ client.leave(import_shared_types.CloseCode.WITH_ERROR, errorMessage);
259
+ }
260
+ }
261
+ };
262
+ // Annotate the CommonJS export names for ESM import in node:
263
+ 0 && (module.exports = {
264
+ RoomMessages
265
+ });
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/RoomMessages.ts"],
4
+ "sourcesContent": ["import { decode, type Iterator } from '@colyseus/schema';\nimport { unpack } from 'msgpackr';\n\nimport { CloseCode, ErrorCode, Protocol, ResponseStatus, type MessageContext } from '@colyseus/shared-types';\nimport { getMessageBytes } from './Protocol.ts';\nimport { createNanoEvents } from './utils/nanoevents.ts';\nimport { standardValidate, type StandardSchemaV1 } from './utils/StandardSchema.ts';\nimport { isDevMode } from './utils/DevMode.ts';\nimport { debugMessage, debugAndPrintError } from './Debug.ts';\nimport { OnMessageException } from './errors/RoomExceptions.ts';\nimport type { Client, ClientPrivate } from './Transport.ts';\nimport type { Room } from './Room.ts';\n\n/** Normalize a thrown value into the `{ name, message, code? }` shape echoed in a\n * ROOM_RESPONSE error reply. */\nfunction toResponseError(e: any): { name: string; message: string; code?: any } {\n if (e instanceof Error) {\n const code = (e as any).code;\n return (code !== undefined)\n ? { name: e.name, message: e.message, code }\n : { name: e.name, message: e.message };\n }\n return { name: \"Error\", message: String(e) };\n}\n\n// The handler's terminal action \u2014 NOT the wire ResponseStatus: `none` and\n// `resolved` both reply OK (plain return value vs `ctx.resolve(value)`), a\n// distinction a single OK can't carry; ERROR is never a handler decision\n// (thrown/no-handler). Projected onto ResponseStatus once, in onRequest's reply.\nconst OUTCOME_NONE = 0, OUTCOME_REJECTED = 1, OUTCOME_RESOLVED = 2;\n\n/**\n * Runtime {@link MessageContext} for a {@link Protocol.ROOM_REQUEST} dispatch.\n * `reject`/`resolve` record the decision on the ctx; `onRequest` reads it after the\n * handler returns, so a bare side-effecting call works as well as `return\n * ctx.reject(r)` (the branded return is compile-time only \u2014 the runtime return is\n * unused).\n */\nclass DispatchContext implements MessageContext {\n readonly id: number | undefined;\n _outcome = OUTCOME_NONE;\n _reason: any = undefined;\n _value: any = undefined;\n\n constructor(id: number | undefined) {\n this.id = id;\n }\n\n reject(reason?: any): any {\n this._outcome = OUTCOME_REJECTED;\n this._reason = reason;\n return undefined;\n }\n\n resolve(value?: any): any {\n this._outcome = OUTCOME_RESOLVED;\n this._value = value;\n return undefined;\n }\n}\n\n/** Shared no-op context for fire-and-forget `ROOM_DATA` (no reply channel, so\n * `reject`/`resolve` go nowhere). `id` is `undefined`; zero per-message allocation. */\nconst SEND_CONTEXT: MessageContext = Object.freeze({\n id: undefined,\n reject: () => undefined as any,\n resolve: () => undefined as any,\n});\n\n/**\n * Per-room message-routing layer, owned by {@link Room}. Owns the user-message\n * handler registry (`onMessage`/`onMessageBytes` + per-type validators) and\n * decodes/dispatches the user-message wire frames (ROOM_DATA / ROOM_REQUEST /\n * ROOM_DATA_BYTES). Room's `_onMessage` keeps the protocol/lifecycle frames\n * (JOIN/PING/LEAVE/input) and calls one of these per frame, preserving the\n * original dispatch order. Reads back into its Room only for `onUncaughtException`\n * (handler wrapping) and `roomId`/`roomName` (logging).\n *\n * @internal\n */\nexport class RoomMessages {\n private room: Room<any>;\n\n /** Handler registry (nanoevents). Public so Room can re-expose it as\n * `onMessageEvents` for @colyseus/playground introspection and\n * @colyseus/testing handler-swapping. */\n events = createNanoEvents();\n\n /** Per-type StandardSchema validators. Public for the same reason as\n * {@link events} (`onMessageValidators`). */\n validators: { [type: string]: StandardSchemaV1 } = {};\n\n constructor(room: Room<any>) {\n this.room = room;\n }\n\n /**\n * Register a handler (body of {@link Room.onMessage}). Wraps the callback via\n * the room's `onUncaughtException` when set, stores the validator, and returns\n * the unbind closure.\n */\n on(\n _messageType: '*' | string | number,\n _validationSchema: StandardSchemaV1 | ((...args: any[]) => void),\n _callback?: (...args: any[]) => void,\n ): () => void {\n const messageType = _messageType.toString();\n\n const validationSchema = (typeof _callback === 'function')\n ? _validationSchema as StandardSchemaV1\n : undefined;\n\n const callback = (validationSchema === undefined)\n ? _validationSchema as (...args: any[]) => void\n : _callback;\n\n const removeListener = this.events.on(messageType, (this.room.onUncaughtException !== undefined)\n ? this.#wrapMessageHandler(callback, _messageType)\n : callback);\n\n if (validationSchema !== undefined) {\n this.validators[messageType] = validationSchema;\n }\n\n // returns a method to unbind the callback\n return () => {\n removeListener();\n if (this.events.events[messageType].length === 0) {\n delete this.validators[messageType];\n }\n };\n }\n\n /**\n * Wrap a user message handler so a sync throw or async rejection is reported to\n * `room.onUncaughtException` as an {@link OnMessageException} carrying the correct\n * `client` / `payload` / `type`. The handler is invoked with a different argument\n * shape per dispatch path, so those fields are normalized here rather than relying\n * on positional forwarding (which silently mis-mapped `type` once a context arg was\n * added \u2014 see ROOM_DATA/ROOM_REQUEST passing `SEND_CONTEXT`/`ctx`):\n *\n * - typed handler (DATA / REQUEST / BYTES): `(client, payload, ctx?)` \u2192 `type` is\n * the registered `_messageType`.\n * - wildcard `'*'` handler: `(client, type, payload)` \u2192 `type` is\n * the received message type.\n *\n * The error is swallowed (never rethrown) so a faulty handler can't break dispatch;\n * the handler's return value (or promise) is passed through for the REQUEST path.\n */\n #wrapMessageHandler(callback: (...args: any[]) => any, registeredType: '*' | string | number) {\n const onError = this.room.onUncaughtException!.bind(this.room);\n const isWildcard = (registeredType === '*');\n\n return (...args: any[]): any => {\n const report = (e: Error) => onError(\n new OnMessageException(\n e, e.message,\n args[0], // client\n isWildcard ? args[2] : args[1], // payload\n isWildcard ? args[1] : registeredType, // type\n ),\n 'onMessage',\n );\n\n try {\n const result = callback(...args);\n return (typeof result?.catch === 'function') ? result.catch(report) : result;\n } catch (e: any) {\n report(e);\n }\n };\n }\n\n /** Dispatch a `ROOM_DATA` frame: decode type + msgpack payload, validate, emit. */\n onData(client: Client & ClientPrivate, buffer: Buffer, it: Iterator): void {\n const messageType = (decode.stringCheck(buffer, it))\n ? decode.string(buffer, it)\n : decode.number(buffer, it);\n\n let message;\n try {\n message = (buffer.byteLength > it.offset)\n ? unpack(buffer.subarray(it.offset, buffer.byteLength))\n : undefined;\n debugMessage(\"received: '%s' -> %j (roomId: %s)\", messageType, message, this.room.roomId);\n\n // custom message validation\n if (this.validators[messageType] !== undefined) {\n message = standardValidate(this.validators[messageType], message);\n }\n\n } catch (e: any) {\n debugAndPrintError(e);\n client.leave(CloseCode.WITH_ERROR);\n return;\n }\n\n if (this.events.events[messageType]) {\n this.events.emit(messageType as string, client, message, SEND_CONTEXT);\n\n } else if (this.events.events['*']) {\n this.events.emit('*', client, messageType, message);\n\n } else {\n this.#noHandler(client, messageType, message);\n }\n }\n\n /** Dispatch a `ROOM_REQUEST` frame: same handlers as ROOM_DATA, but the client\n * opted into a reply, so echo the `requestId` with the outcome. The handler's\n * reply is decided by what it returns / does to `ctx`: `ctx.reject(reason)` \u2192\n * `REJECTED(reason)`, `ctx.resolve(value)` \u2192 `OK(value)`, a thrown error or\n * missing handler \u2192 `ERROR`, else `OK(return)`. */\n onRequest(client: Client & ClientPrivate, buffer: Buffer, it: Iterator): void {\n const requestId = decode.number(buffer, it);\n\n const messageType = (decode.stringCheck(buffer, it))\n ? decode.string(buffer, it)\n : decode.number(buffer, it);\n\n let message;\n try {\n message = (buffer.byteLength > it.offset)\n ? unpack(buffer.subarray(it.offset, buffer.byteLength))\n : undefined;\n debugMessage(\"request #%d: '%s' -> %j (roomId: %s)\", requestId, messageType, message, this.room.roomId);\n\n // custom message validation (shared with the ROOM_DATA path)\n if (this.validators[messageType] !== undefined) {\n message = standardValidate(this.validators[messageType], message);\n }\n\n } catch (e: any) {\n // Reply with an error so the caller's pending request resolves instead of timing out.\n debugAndPrintError(e);\n this.#replyToRequest(client, requestId, ResponseStatus.ERROR, toResponseError(e));\n return;\n }\n\n // Answered by the FIRST handler registered for the type (emit would discard\n // returns); wildcard handlers have no response contract, so they're ineligible.\n const handler = this.events.events[messageType as string]?.[0];\n\n if (handler === undefined) {\n this.#replyToRequest(client, requestId, ResponseStatus.ERROR, {\n name: \"no_handler\",\n message: `room \"${this.room.roomName}\" has no onMessage(\"${messageType}\") handler to answer this request.`,\n });\n return;\n }\n\n const ctx = new DispatchContext(requestId);\n\n // Sync handlers reply in this tick with no promise machinery; only a thenable\n // return defers to the microtask queue. A sync throw (unwrapped handler) and an\n // async rejection both fall to the ERROR reply. With onUncaughtException set the\n // handler is wrapped (swallows its own errors), so it never throws / its promise\n // resolves undefined, and the error reports there.\n let response: any;\n try {\n response = handler(client, message, ctx);\n if (response !== null && typeof response === 'object' && typeof response.then === 'function') {\n response.then(\n (resolved: any) => this.#finalizeRequest(client, requestId, ctx, resolved),\n (e: any) => {\n debugAndPrintError(e);\n this.#replyToRequest(client, requestId, ResponseStatus.ERROR, toResponseError(e));\n },\n );\n return;\n }\n } catch (e: any) {\n debugAndPrintError(e);\n this.#replyToRequest(client, requestId, ResponseStatus.ERROR, toResponseError(e));\n return;\n }\n\n this.#finalizeRequest(client, requestId, ctx, response);\n }\n\n /** Finalize a request: project the handler's outcome onto a ROOM_RESPONSE reply \u2014\n * `ctx.reject` \u2192 REJECTED(reason), `ctx.resolve(value)` \u2192 OK(value), else OK(return). */\n #finalizeRequest(client: Client, requestId: number, ctx: DispatchContext, response: any): void {\n if (ctx._outcome === OUTCOME_REJECTED) {\n this.#replyToRequest(client, requestId, ResponseStatus.REJECTED, ctx._reason);\n } else if (ctx._outcome === OUTCOME_RESOLVED) {\n this.#replyToRequest(client, requestId, ResponseStatus.OK, ctx._value);\n } else {\n this.#replyToRequest(client, requestId, ResponseStatus.OK, response);\n }\n }\n\n /** Dispatch a `ROOM_DATA_BYTES` frame: raw bytes routed to `_$b`-prefixed handlers. */\n onDataBytes(client: Client & ClientPrivate, buffer: Buffer, it: Iterator): void {\n const messageType = (decode.stringCheck(buffer, it))\n ? decode.string(buffer, it)\n : decode.number(buffer, it);\n\n let message: any = buffer.subarray(it.offset, buffer.byteLength);\n debugMessage(\"received: '%s' -> %j (roomId: %s)\", messageType, message, this.room.roomId);\n\n const bytesMessageType = `_$b${messageType}`;\n\n // custom message validation\n try {\n if (this.validators[bytesMessageType] !== undefined) {\n message = standardValidate(this.validators[bytesMessageType], message);\n }\n } catch (e: any) {\n debugAndPrintError(e);\n client.leave(CloseCode.WITH_ERROR);\n return;\n }\n\n if (this.events.events[bytesMessageType]) {\n this.events.emit(bytesMessageType, client, message);\n\n } else if (this.events.events['*']) {\n this.events.emit('*', client, messageType, message);\n\n } else {\n this.#noHandler(client, messageType, message);\n }\n }\n\n /** Emit a ROOM_RESPONSE reply immediately. */\n #replyToRequest(client: Client, requestId: number, status: ResponseStatus, payload?: any): void {\n debugMessage(\"response #%d: status=%d -> %j (roomId: %s)\", requestId, status, payload, this.room.roomId);\n client.enqueueRaw(getMessageBytes[Protocol.ROOM_RESPONSE](requestId, status, payload));\n }\n\n /** No handler for the type: error the client in dev, drop it in production. */\n #noHandler(client: Client, messageType: string | number, _message: unknown): void {\n const errorMessage = `room onMessage for \"${messageType}\" not registered.`;\n debugMessage(`${errorMessage} (roomId: ${this.room.roomId})`);\n if (isDevMode) {\n client.error(ErrorCode.INVALID_PAYLOAD, errorMessage);\n } else {\n client.leave(CloseCode.WITH_ERROR, errorMessage);\n }\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,49 @@
1
+ import { type Iterator } from '@colyseus/schema';
2
+ import { type StandardSchemaV1 } from './utils/StandardSchema.ts';
3
+ import type { Client, ClientPrivate } from './Transport.ts';
4
+ import type { Room } from './Room.ts';
5
+ /**
6
+ * Per-room message-routing layer, owned by {@link Room}. Owns the user-message
7
+ * handler registry (`onMessage`/`onMessageBytes` + per-type validators) and
8
+ * decodes/dispatches the user-message wire frames (ROOM_DATA / ROOM_REQUEST /
9
+ * ROOM_DATA_BYTES). Room's `_onMessage` keeps the protocol/lifecycle frames
10
+ * (JOIN/PING/LEAVE/input) and calls one of these per frame, preserving the
11
+ * original dispatch order. Reads back into its Room only for `onUncaughtException`
12
+ * (handler wrapping) and `roomId`/`roomName` (logging).
13
+ *
14
+ * @internal
15
+ */
16
+ export declare class RoomMessages {
17
+ #private;
18
+ private room;
19
+ /** Handler registry (nanoevents). Public so Room can re-expose it as
20
+ * `onMessageEvents` for @colyseus/playground introspection and
21
+ * @colyseus/testing handler-swapping. */
22
+ events: {
23
+ emit(event: string, ...args: any[]): void;
24
+ events: {};
25
+ on(event: string, cb: (...args: any[]) => void): () => void;
26
+ };
27
+ /** Per-type StandardSchema validators. Public for the same reason as
28
+ * {@link events} (`onMessageValidators`). */
29
+ validators: {
30
+ [type: string]: StandardSchemaV1;
31
+ };
32
+ constructor(room: Room<any>);
33
+ /**
34
+ * Register a handler (body of {@link Room.onMessage}). Wraps the callback via
35
+ * the room's `onUncaughtException` when set, stores the validator, and returns
36
+ * the unbind closure.
37
+ */
38
+ on(_messageType: '*' | string | number, _validationSchema: StandardSchemaV1 | ((...args: any[]) => void), _callback?: (...args: any[]) => void): () => void;
39
+ /** Dispatch a `ROOM_DATA` frame: decode type + msgpack payload, validate, emit. */
40
+ onData(client: Client & ClientPrivate, buffer: Buffer, it: Iterator): void;
41
+ /** Dispatch a `ROOM_REQUEST` frame: same handlers as ROOM_DATA, but the client
42
+ * opted into a reply, so echo the `requestId` with the outcome. The handler's
43
+ * reply is decided by what it returns / does to `ctx`: `ctx.reject(reason)` →
44
+ * `REJECTED(reason)`, `ctx.resolve(value)` → `OK(value)`, a thrown error or
45
+ * missing handler → `ERROR`, else `OK(return)`. */
46
+ onRequest(client: Client & ClientPrivate, buffer: Buffer, it: Iterator): void;
47
+ /** Dispatch a `ROOM_DATA_BYTES` frame: raw bytes routed to `_$b`-prefixed handlers. */
48
+ onDataBytes(client: Client & ClientPrivate, buffer: Buffer, it: Iterator): void;
49
+ }
@@ -0,0 +1,240 @@
1
+ // packages/core/src/RoomMessages.ts
2
+ import { decode } from "@colyseus/schema";
3
+ import { unpack } from "msgpackr";
4
+ import { CloseCode, ErrorCode, Protocol, ResponseStatus } from "@colyseus/shared-types";
5
+ import { getMessageBytes } from "./Protocol.mjs";
6
+ import { createNanoEvents } from "./utils/nanoevents.mjs";
7
+ import { standardValidate } from "./utils/StandardSchema.mjs";
8
+ import { isDevMode } from "./utils/DevMode.mjs";
9
+ import { debugMessage, debugAndPrintError } from "./Debug.mjs";
10
+ import { OnMessageException } from "./errors/RoomExceptions.mjs";
11
+ function toResponseError(e) {
12
+ if (e instanceof Error) {
13
+ const code = e.code;
14
+ return code !== void 0 ? { name: e.name, message: e.message, code } : { name: e.name, message: e.message };
15
+ }
16
+ return { name: "Error", message: String(e) };
17
+ }
18
+ var OUTCOME_NONE = 0;
19
+ var OUTCOME_REJECTED = 1;
20
+ var OUTCOME_RESOLVED = 2;
21
+ var DispatchContext = class {
22
+ constructor(id) {
23
+ this._outcome = OUTCOME_NONE;
24
+ this._reason = void 0;
25
+ this._value = void 0;
26
+ this.id = id;
27
+ }
28
+ reject(reason) {
29
+ this._outcome = OUTCOME_REJECTED;
30
+ this._reason = reason;
31
+ return void 0;
32
+ }
33
+ resolve(value) {
34
+ this._outcome = OUTCOME_RESOLVED;
35
+ this._value = value;
36
+ return void 0;
37
+ }
38
+ };
39
+ var SEND_CONTEXT = Object.freeze({
40
+ id: void 0,
41
+ reject: () => void 0,
42
+ resolve: () => void 0
43
+ });
44
+ var RoomMessages = class {
45
+ constructor(room) {
46
+ /** Handler registry (nanoevents). Public so Room can re-expose it as
47
+ * `onMessageEvents` for @colyseus/playground introspection and
48
+ * @colyseus/testing handler-swapping. */
49
+ this.events = createNanoEvents();
50
+ /** Per-type StandardSchema validators. Public for the same reason as
51
+ * {@link events} (`onMessageValidators`). */
52
+ this.validators = {};
53
+ this.room = room;
54
+ }
55
+ /**
56
+ * Register a handler (body of {@link Room.onMessage}). Wraps the callback via
57
+ * the room's `onUncaughtException` when set, stores the validator, and returns
58
+ * the unbind closure.
59
+ */
60
+ on(_messageType, _validationSchema, _callback) {
61
+ const messageType = _messageType.toString();
62
+ const validationSchema = typeof _callback === "function" ? _validationSchema : void 0;
63
+ const callback = validationSchema === void 0 ? _validationSchema : _callback;
64
+ const removeListener = this.events.on(messageType, this.room.onUncaughtException !== void 0 ? this.#wrapMessageHandler(callback, _messageType) : callback);
65
+ if (validationSchema !== void 0) {
66
+ this.validators[messageType] = validationSchema;
67
+ }
68
+ return () => {
69
+ removeListener();
70
+ if (this.events.events[messageType].length === 0) {
71
+ delete this.validators[messageType];
72
+ }
73
+ };
74
+ }
75
+ /**
76
+ * Wrap a user message handler so a sync throw or async rejection is reported to
77
+ * `room.onUncaughtException` as an {@link OnMessageException} carrying the correct
78
+ * `client` / `payload` / `type`. The handler is invoked with a different argument
79
+ * shape per dispatch path, so those fields are normalized here rather than relying
80
+ * on positional forwarding (which silently mis-mapped `type` once a context arg was
81
+ * added — see ROOM_DATA/ROOM_REQUEST passing `SEND_CONTEXT`/`ctx`):
82
+ *
83
+ * - typed handler (DATA / REQUEST / BYTES): `(client, payload, ctx?)` → `type` is
84
+ * the registered `_messageType`.
85
+ * - wildcard `'*'` handler: `(client, type, payload)` → `type` is
86
+ * the received message type.
87
+ *
88
+ * The error is swallowed (never rethrown) so a faulty handler can't break dispatch;
89
+ * the handler's return value (or promise) is passed through for the REQUEST path.
90
+ */
91
+ #wrapMessageHandler(callback, registeredType) {
92
+ const onError = this.room.onUncaughtException.bind(this.room);
93
+ const isWildcard = registeredType === "*";
94
+ return (...args) => {
95
+ const report = (e) => onError(
96
+ new OnMessageException(
97
+ e,
98
+ e.message,
99
+ args[0],
100
+ // client
101
+ isWildcard ? args[2] : args[1],
102
+ // payload
103
+ isWildcard ? args[1] : registeredType
104
+ // type
105
+ ),
106
+ "onMessage"
107
+ );
108
+ try {
109
+ const result = callback(...args);
110
+ return typeof result?.catch === "function" ? result.catch(report) : result;
111
+ } catch (e) {
112
+ report(e);
113
+ }
114
+ };
115
+ }
116
+ /** Dispatch a `ROOM_DATA` frame: decode type + msgpack payload, validate, emit. */
117
+ onData(client, buffer, it) {
118
+ const messageType = decode.stringCheck(buffer, it) ? decode.string(buffer, it) : decode.number(buffer, it);
119
+ let message;
120
+ try {
121
+ message = buffer.byteLength > it.offset ? unpack(buffer.subarray(it.offset, buffer.byteLength)) : void 0;
122
+ debugMessage("received: '%s' -> %j (roomId: %s)", messageType, message, this.room.roomId);
123
+ if (this.validators[messageType] !== void 0) {
124
+ message = standardValidate(this.validators[messageType], message);
125
+ }
126
+ } catch (e) {
127
+ debugAndPrintError(e);
128
+ client.leave(CloseCode.WITH_ERROR);
129
+ return;
130
+ }
131
+ if (this.events.events[messageType]) {
132
+ this.events.emit(messageType, client, message, SEND_CONTEXT);
133
+ } else if (this.events.events["*"]) {
134
+ this.events.emit("*", client, messageType, message);
135
+ } else {
136
+ this.#noHandler(client, messageType, message);
137
+ }
138
+ }
139
+ /** Dispatch a `ROOM_REQUEST` frame: same handlers as ROOM_DATA, but the client
140
+ * opted into a reply, so echo the `requestId` with the outcome. The handler's
141
+ * reply is decided by what it returns / does to `ctx`: `ctx.reject(reason)` →
142
+ * `REJECTED(reason)`, `ctx.resolve(value)` → `OK(value)`, a thrown error or
143
+ * missing handler → `ERROR`, else `OK(return)`. */
144
+ onRequest(client, buffer, it) {
145
+ const requestId = decode.number(buffer, it);
146
+ const messageType = decode.stringCheck(buffer, it) ? decode.string(buffer, it) : decode.number(buffer, it);
147
+ let message;
148
+ try {
149
+ message = buffer.byteLength > it.offset ? unpack(buffer.subarray(it.offset, buffer.byteLength)) : void 0;
150
+ debugMessage("request #%d: '%s' -> %j (roomId: %s)", requestId, messageType, message, this.room.roomId);
151
+ if (this.validators[messageType] !== void 0) {
152
+ message = standardValidate(this.validators[messageType], message);
153
+ }
154
+ } catch (e) {
155
+ debugAndPrintError(e);
156
+ this.#replyToRequest(client, requestId, ResponseStatus.ERROR, toResponseError(e));
157
+ return;
158
+ }
159
+ const handler = this.events.events[messageType]?.[0];
160
+ if (handler === void 0) {
161
+ this.#replyToRequest(client, requestId, ResponseStatus.ERROR, {
162
+ name: "no_handler",
163
+ message: `room "${this.room.roomName}" has no onMessage("${messageType}") handler to answer this request.`
164
+ });
165
+ return;
166
+ }
167
+ const ctx = new DispatchContext(requestId);
168
+ let response;
169
+ try {
170
+ response = handler(client, message, ctx);
171
+ if (response !== null && typeof response === "object" && typeof response.then === "function") {
172
+ response.then(
173
+ (resolved) => this.#finalizeRequest(client, requestId, ctx, resolved),
174
+ (e) => {
175
+ debugAndPrintError(e);
176
+ this.#replyToRequest(client, requestId, ResponseStatus.ERROR, toResponseError(e));
177
+ }
178
+ );
179
+ return;
180
+ }
181
+ } catch (e) {
182
+ debugAndPrintError(e);
183
+ this.#replyToRequest(client, requestId, ResponseStatus.ERROR, toResponseError(e));
184
+ return;
185
+ }
186
+ this.#finalizeRequest(client, requestId, ctx, response);
187
+ }
188
+ /** Finalize a request: project the handler's outcome onto a ROOM_RESPONSE reply —
189
+ * `ctx.reject` → REJECTED(reason), `ctx.resolve(value)` → OK(value), else OK(return). */
190
+ #finalizeRequest(client, requestId, ctx, response) {
191
+ if (ctx._outcome === OUTCOME_REJECTED) {
192
+ this.#replyToRequest(client, requestId, ResponseStatus.REJECTED, ctx._reason);
193
+ } else if (ctx._outcome === OUTCOME_RESOLVED) {
194
+ this.#replyToRequest(client, requestId, ResponseStatus.OK, ctx._value);
195
+ } else {
196
+ this.#replyToRequest(client, requestId, ResponseStatus.OK, response);
197
+ }
198
+ }
199
+ /** Dispatch a `ROOM_DATA_BYTES` frame: raw bytes routed to `_$b`-prefixed handlers. */
200
+ onDataBytes(client, buffer, it) {
201
+ const messageType = decode.stringCheck(buffer, it) ? decode.string(buffer, it) : decode.number(buffer, it);
202
+ let message = buffer.subarray(it.offset, buffer.byteLength);
203
+ debugMessage("received: '%s' -> %j (roomId: %s)", messageType, message, this.room.roomId);
204
+ const bytesMessageType = `_$b${messageType}`;
205
+ try {
206
+ if (this.validators[bytesMessageType] !== void 0) {
207
+ message = standardValidate(this.validators[bytesMessageType], message);
208
+ }
209
+ } catch (e) {
210
+ debugAndPrintError(e);
211
+ client.leave(CloseCode.WITH_ERROR);
212
+ return;
213
+ }
214
+ if (this.events.events[bytesMessageType]) {
215
+ this.events.emit(bytesMessageType, client, message);
216
+ } else if (this.events.events["*"]) {
217
+ this.events.emit("*", client, messageType, message);
218
+ } else {
219
+ this.#noHandler(client, messageType, message);
220
+ }
221
+ }
222
+ /** Emit a ROOM_RESPONSE reply immediately. */
223
+ #replyToRequest(client, requestId, status, payload) {
224
+ debugMessage("response #%d: status=%d -> %j (roomId: %s)", requestId, status, payload, this.room.roomId);
225
+ client.enqueueRaw(getMessageBytes[Protocol.ROOM_RESPONSE](requestId, status, payload));
226
+ }
227
+ /** No handler for the type: error the client in dev, drop it in production. */
228
+ #noHandler(client, messageType, _message) {
229
+ const errorMessage = `room onMessage for "${messageType}" not registered.`;
230
+ debugMessage(`${errorMessage} (roomId: ${this.room.roomId})`);
231
+ if (isDevMode) {
232
+ client.error(ErrorCode.INVALID_PAYLOAD, errorMessage);
233
+ } else {
234
+ client.leave(CloseCode.WITH_ERROR, errorMessage);
235
+ }
236
+ }
237
+ };
238
+ export {
239
+ RoomMessages
240
+ };
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/RoomMessages.ts"],
4
+ "sourcesContent": ["import { decode, type Iterator } from '@colyseus/schema';\nimport { unpack } from 'msgpackr';\n\nimport { CloseCode, ErrorCode, Protocol, ResponseStatus, type MessageContext } from '@colyseus/shared-types';\nimport { getMessageBytes } from './Protocol.ts';\nimport { createNanoEvents } from './utils/nanoevents.ts';\nimport { standardValidate, type StandardSchemaV1 } from './utils/StandardSchema.ts';\nimport { isDevMode } from './utils/DevMode.ts';\nimport { debugMessage, debugAndPrintError } from './Debug.ts';\nimport { OnMessageException } from './errors/RoomExceptions.ts';\nimport type { Client, ClientPrivate } from './Transport.ts';\nimport type { Room } from './Room.ts';\n\n/** Normalize a thrown value into the `{ name, message, code? }` shape echoed in a\n * ROOM_RESPONSE error reply. */\nfunction toResponseError(e: any): { name: string; message: string; code?: any } {\n if (e instanceof Error) {\n const code = (e as any).code;\n return (code !== undefined)\n ? { name: e.name, message: e.message, code }\n : { name: e.name, message: e.message };\n }\n return { name: \"Error\", message: String(e) };\n}\n\n// The handler's terminal action \u2014 NOT the wire ResponseStatus: `none` and\n// `resolved` both reply OK (plain return value vs `ctx.resolve(value)`), a\n// distinction a single OK can't carry; ERROR is never a handler decision\n// (thrown/no-handler). Projected onto ResponseStatus once, in onRequest's reply.\nconst OUTCOME_NONE = 0, OUTCOME_REJECTED = 1, OUTCOME_RESOLVED = 2;\n\n/**\n * Runtime {@link MessageContext} for a {@link Protocol.ROOM_REQUEST} dispatch.\n * `reject`/`resolve` record the decision on the ctx; `onRequest` reads it after the\n * handler returns, so a bare side-effecting call works as well as `return\n * ctx.reject(r)` (the branded return is compile-time only \u2014 the runtime return is\n * unused).\n */\nclass DispatchContext implements MessageContext {\n readonly id: number | undefined;\n _outcome = OUTCOME_NONE;\n _reason: any = undefined;\n _value: any = undefined;\n\n constructor(id: number | undefined) {\n this.id = id;\n }\n\n reject(reason?: any): any {\n this._outcome = OUTCOME_REJECTED;\n this._reason = reason;\n return undefined;\n }\n\n resolve(value?: any): any {\n this._outcome = OUTCOME_RESOLVED;\n this._value = value;\n return undefined;\n }\n}\n\n/** Shared no-op context for fire-and-forget `ROOM_DATA` (no reply channel, so\n * `reject`/`resolve` go nowhere). `id` is `undefined`; zero per-message allocation. */\nconst SEND_CONTEXT: MessageContext = Object.freeze({\n id: undefined,\n reject: () => undefined as any,\n resolve: () => undefined as any,\n});\n\n/**\n * Per-room message-routing layer, owned by {@link Room}. Owns the user-message\n * handler registry (`onMessage`/`onMessageBytes` + per-type validators) and\n * decodes/dispatches the user-message wire frames (ROOM_DATA / ROOM_REQUEST /\n * ROOM_DATA_BYTES). Room's `_onMessage` keeps the protocol/lifecycle frames\n * (JOIN/PING/LEAVE/input) and calls one of these per frame, preserving the\n * original dispatch order. Reads back into its Room only for `onUncaughtException`\n * (handler wrapping) and `roomId`/`roomName` (logging).\n *\n * @internal\n */\nexport class RoomMessages {\n private room: Room<any>;\n\n /** Handler registry (nanoevents). Public so Room can re-expose it as\n * `onMessageEvents` for @colyseus/playground introspection and\n * @colyseus/testing handler-swapping. */\n events = createNanoEvents();\n\n /** Per-type StandardSchema validators. Public for the same reason as\n * {@link events} (`onMessageValidators`). */\n validators: { [type: string]: StandardSchemaV1 } = {};\n\n constructor(room: Room<any>) {\n this.room = room;\n }\n\n /**\n * Register a handler (body of {@link Room.onMessage}). Wraps the callback via\n * the room's `onUncaughtException` when set, stores the validator, and returns\n * the unbind closure.\n */\n on(\n _messageType: '*' | string | number,\n _validationSchema: StandardSchemaV1 | ((...args: any[]) => void),\n _callback?: (...args: any[]) => void,\n ): () => void {\n const messageType = _messageType.toString();\n\n const validationSchema = (typeof _callback === 'function')\n ? _validationSchema as StandardSchemaV1\n : undefined;\n\n const callback = (validationSchema === undefined)\n ? _validationSchema as (...args: any[]) => void\n : _callback;\n\n const removeListener = this.events.on(messageType, (this.room.onUncaughtException !== undefined)\n ? this.#wrapMessageHandler(callback, _messageType)\n : callback);\n\n if (validationSchema !== undefined) {\n this.validators[messageType] = validationSchema;\n }\n\n // returns a method to unbind the callback\n return () => {\n removeListener();\n if (this.events.events[messageType].length === 0) {\n delete this.validators[messageType];\n }\n };\n }\n\n /**\n * Wrap a user message handler so a sync throw or async rejection is reported to\n * `room.onUncaughtException` as an {@link OnMessageException} carrying the correct\n * `client` / `payload` / `type`. The handler is invoked with a different argument\n * shape per dispatch path, so those fields are normalized here rather than relying\n * on positional forwarding (which silently mis-mapped `type` once a context arg was\n * added \u2014 see ROOM_DATA/ROOM_REQUEST passing `SEND_CONTEXT`/`ctx`):\n *\n * - typed handler (DATA / REQUEST / BYTES): `(client, payload, ctx?)` \u2192 `type` is\n * the registered `_messageType`.\n * - wildcard `'*'` handler: `(client, type, payload)` \u2192 `type` is\n * the received message type.\n *\n * The error is swallowed (never rethrown) so a faulty handler can't break dispatch;\n * the handler's return value (or promise) is passed through for the REQUEST path.\n */\n #wrapMessageHandler(callback: (...args: any[]) => any, registeredType: '*' | string | number) {\n const onError = this.room.onUncaughtException!.bind(this.room);\n const isWildcard = (registeredType === '*');\n\n return (...args: any[]): any => {\n const report = (e: Error) => onError(\n new OnMessageException(\n e, e.message,\n args[0], // client\n isWildcard ? args[2] : args[1], // payload\n isWildcard ? args[1] : registeredType, // type\n ),\n 'onMessage',\n );\n\n try {\n const result = callback(...args);\n return (typeof result?.catch === 'function') ? result.catch(report) : result;\n } catch (e: any) {\n report(e);\n }\n };\n }\n\n /** Dispatch a `ROOM_DATA` frame: decode type + msgpack payload, validate, emit. */\n onData(client: Client & ClientPrivate, buffer: Buffer, it: Iterator): void {\n const messageType = (decode.stringCheck(buffer, it))\n ? decode.string(buffer, it)\n : decode.number(buffer, it);\n\n let message;\n try {\n message = (buffer.byteLength > it.offset)\n ? unpack(buffer.subarray(it.offset, buffer.byteLength))\n : undefined;\n debugMessage(\"received: '%s' -> %j (roomId: %s)\", messageType, message, this.room.roomId);\n\n // custom message validation\n if (this.validators[messageType] !== undefined) {\n message = standardValidate(this.validators[messageType], message);\n }\n\n } catch (e: any) {\n debugAndPrintError(e);\n client.leave(CloseCode.WITH_ERROR);\n return;\n }\n\n if (this.events.events[messageType]) {\n this.events.emit(messageType as string, client, message, SEND_CONTEXT);\n\n } else if (this.events.events['*']) {\n this.events.emit('*', client, messageType, message);\n\n } else {\n this.#noHandler(client, messageType, message);\n }\n }\n\n /** Dispatch a `ROOM_REQUEST` frame: same handlers as ROOM_DATA, but the client\n * opted into a reply, so echo the `requestId` with the outcome. The handler's\n * reply is decided by what it returns / does to `ctx`: `ctx.reject(reason)` \u2192\n * `REJECTED(reason)`, `ctx.resolve(value)` \u2192 `OK(value)`, a thrown error or\n * missing handler \u2192 `ERROR`, else `OK(return)`. */\n onRequest(client: Client & ClientPrivate, buffer: Buffer, it: Iterator): void {\n const requestId = decode.number(buffer, it);\n\n const messageType = (decode.stringCheck(buffer, it))\n ? decode.string(buffer, it)\n : decode.number(buffer, it);\n\n let message;\n try {\n message = (buffer.byteLength > it.offset)\n ? unpack(buffer.subarray(it.offset, buffer.byteLength))\n : undefined;\n debugMessage(\"request #%d: '%s' -> %j (roomId: %s)\", requestId, messageType, message, this.room.roomId);\n\n // custom message validation (shared with the ROOM_DATA path)\n if (this.validators[messageType] !== undefined) {\n message = standardValidate(this.validators[messageType], message);\n }\n\n } catch (e: any) {\n // Reply with an error so the caller's pending request resolves instead of timing out.\n debugAndPrintError(e);\n this.#replyToRequest(client, requestId, ResponseStatus.ERROR, toResponseError(e));\n return;\n }\n\n // Answered by the FIRST handler registered for the type (emit would discard\n // returns); wildcard handlers have no response contract, so they're ineligible.\n const handler = this.events.events[messageType as string]?.[0];\n\n if (handler === undefined) {\n this.#replyToRequest(client, requestId, ResponseStatus.ERROR, {\n name: \"no_handler\",\n message: `room \"${this.room.roomName}\" has no onMessage(\"${messageType}\") handler to answer this request.`,\n });\n return;\n }\n\n const ctx = new DispatchContext(requestId);\n\n // Sync handlers reply in this tick with no promise machinery; only a thenable\n // return defers to the microtask queue. A sync throw (unwrapped handler) and an\n // async rejection both fall to the ERROR reply. With onUncaughtException set the\n // handler is wrapped (swallows its own errors), so it never throws / its promise\n // resolves undefined, and the error reports there.\n let response: any;\n try {\n response = handler(client, message, ctx);\n if (response !== null && typeof response === 'object' && typeof response.then === 'function') {\n response.then(\n (resolved: any) => this.#finalizeRequest(client, requestId, ctx, resolved),\n (e: any) => {\n debugAndPrintError(e);\n this.#replyToRequest(client, requestId, ResponseStatus.ERROR, toResponseError(e));\n },\n );\n return;\n }\n } catch (e: any) {\n debugAndPrintError(e);\n this.#replyToRequest(client, requestId, ResponseStatus.ERROR, toResponseError(e));\n return;\n }\n\n this.#finalizeRequest(client, requestId, ctx, response);\n }\n\n /** Finalize a request: project the handler's outcome onto a ROOM_RESPONSE reply \u2014\n * `ctx.reject` \u2192 REJECTED(reason), `ctx.resolve(value)` \u2192 OK(value), else OK(return). */\n #finalizeRequest(client: Client, requestId: number, ctx: DispatchContext, response: any): void {\n if (ctx._outcome === OUTCOME_REJECTED) {\n this.#replyToRequest(client, requestId, ResponseStatus.REJECTED, ctx._reason);\n } else if (ctx._outcome === OUTCOME_RESOLVED) {\n this.#replyToRequest(client, requestId, ResponseStatus.OK, ctx._value);\n } else {\n this.#replyToRequest(client, requestId, ResponseStatus.OK, response);\n }\n }\n\n /** Dispatch a `ROOM_DATA_BYTES` frame: raw bytes routed to `_$b`-prefixed handlers. */\n onDataBytes(client: Client & ClientPrivate, buffer: Buffer, it: Iterator): void {\n const messageType = (decode.stringCheck(buffer, it))\n ? decode.string(buffer, it)\n : decode.number(buffer, it);\n\n let message: any = buffer.subarray(it.offset, buffer.byteLength);\n debugMessage(\"received: '%s' -> %j (roomId: %s)\", messageType, message, this.room.roomId);\n\n const bytesMessageType = `_$b${messageType}`;\n\n // custom message validation\n try {\n if (this.validators[bytesMessageType] !== undefined) {\n message = standardValidate(this.validators[bytesMessageType], message);\n }\n } catch (e: any) {\n debugAndPrintError(e);\n client.leave(CloseCode.WITH_ERROR);\n return;\n }\n\n if (this.events.events[bytesMessageType]) {\n this.events.emit(bytesMessageType, client, message);\n\n } else if (this.events.events['*']) {\n this.events.emit('*', client, messageType, message);\n\n } else {\n this.#noHandler(client, messageType, message);\n }\n }\n\n /** Emit a ROOM_RESPONSE reply immediately. */\n #replyToRequest(client: Client, requestId: number, status: ResponseStatus, payload?: any): void {\n debugMessage(\"response #%d: status=%d -> %j (roomId: %s)\", requestId, status, payload, this.room.roomId);\n client.enqueueRaw(getMessageBytes[Protocol.ROOM_RESPONSE](requestId, status, payload));\n }\n\n /** No handler for the type: error the client in dev, drop it in production. */\n #noHandler(client: Client, messageType: string | number, _message: unknown): void {\n const errorMessage = `room onMessage for \"${messageType}\" not registered.`;\n debugMessage(`${errorMessage} (roomId: ${this.room.roomId})`);\n if (isDevMode) {\n client.error(ErrorCode.INVALID_PAYLOAD, errorMessage);\n } else {\n client.leave(CloseCode.WITH_ERROR, errorMessage);\n }\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }