@colyseus/core 0.18.2 → 0.18.4

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 (45) hide show
  1. package/build/MatchMaker.cjs +2 -2
  2. package/build/MatchMaker.cjs.map +2 -2
  3. package/build/MatchMaker.d.ts +10 -4
  4. package/build/MatchMaker.mjs +2 -2
  5. package/build/MatchMaker.mjs.map +2 -2
  6. package/build/Room.cjs +11 -3
  7. package/build/Room.cjs.map +2 -2
  8. package/build/Room.d.ts +1 -1
  9. package/build/Room.mjs +11 -3
  10. package/build/Room.mjs.map +2 -2
  11. package/build/RoomMessages.cjs +2 -1
  12. package/build/RoomMessages.cjs.map +2 -2
  13. package/build/RoomMessages.d.ts +3 -1
  14. package/build/RoomMessages.mjs +2 -1
  15. package/build/RoomMessages.mjs.map +2 -2
  16. package/build/router/default_routes.cjs +2 -1
  17. package/build/router/default_routes.cjs.map +2 -2
  18. package/build/router/default_routes.mjs +2 -1
  19. package/build/router/default_routes.mjs.map +2 -2
  20. package/build/router/index.cjs +6 -1
  21. package/build/router/index.cjs.map +2 -2
  22. package/build/router/index.d.ts +1 -1
  23. package/build/router/index.mjs +6 -1
  24. package/build/router/index.mjs.map +2 -2
  25. package/build/router/node.cjs +22 -2
  26. package/build/router/node.cjs.map +2 -2
  27. package/build/router/node.mjs +22 -2
  28. package/build/router/node.mjs.map +2 -2
  29. package/build/utils/Utils.cjs.map +2 -2
  30. package/build/utils/Utils.d.ts +12 -0
  31. package/build/utils/Utils.mjs.map +2 -2
  32. package/build/utils/nanoevents.cjs +4 -1
  33. package/build/utils/nanoevents.cjs.map +2 -2
  34. package/build/utils/nanoevents.d.ts +3 -1
  35. package/build/utils/nanoevents.mjs +4 -1
  36. package/build/utils/nanoevents.mjs.map +2 -2
  37. package/package.json +9 -8
  38. package/src/MatchMaker.ts +33 -12
  39. package/src/Room.ts +23 -4
  40. package/src/RoomMessages.ts +2 -1
  41. package/src/router/default_routes.ts +2 -1
  42. package/src/router/index.ts +16 -1
  43. package/src/router/node.ts +26 -2
  44. package/src/utils/Utils.ts +18 -0
  45. package/src/utils/nanoevents.ts +4 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
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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",
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 // null-prototype: keyed by client-supplied message type (colyseus/colyseus#951)\n validators: { [type: string]: StandardSchemaV1 } = Object.create(null);\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
6
  "names": []
7
7
  }
@@ -21,7 +21,9 @@ export declare class RoomMessages {
21
21
  * @colyseus/testing handler-swapping. */
22
22
  events: {
23
23
  emit(event: string, ...args: any[]): void;
24
- events: {};
24
+ events: {
25
+ [event: string]: Array<(...args: any[]) => void>;
26
+ };
25
27
  on(event: string, cb: (...args: any[]) => void): () => void;
26
28
  };
27
29
  /** Per-type StandardSchema validators. Public for the same reason as
@@ -49,7 +49,8 @@ var RoomMessages = class {
49
49
  this.events = createNanoEvents();
50
50
  /** Per-type StandardSchema validators. Public for the same reason as
51
51
  * {@link events} (`onMessageValidators`). */
52
- this.validators = {};
52
+ // null-prototype: keyed by client-supplied message type (colyseus/colyseus#951)
53
+ this.validators = /* @__PURE__ */ Object.create(null);
53
54
  this.room = room;
54
55
  }
55
56
  /**
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
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": ";AAAA,SAAS,cAA6B;AACtC,SAAS,cAAc;AAEvB,SAAS,WAAW,WAAW,UAAU,sBAA2C;AACpF,SAAS,uBAAuB;AAChC,SAAS,wBAAwB;AACjC,SAAS,wBAA+C;AACxD,SAAS,iBAAiB;AAC1B,SAAS,cAAc,0BAA0B;AACjD,SAAS,0BAA0B;AAMnC,SAAS,gBAAgB,GAAuD;AAC9E,MAAI,aAAa,OAAO;AACtB,UAAM,OAAQ,EAAU;AACxB,WAAQ,SAAS,SACb,EAAE,MAAM,EAAE,MAAM,SAAS,EAAE,SAAS,KAAK,IACzC,EAAE,MAAM,EAAE,MAAM,SAAS,EAAE,QAAQ;AAAA,EACzC;AACA,SAAO,EAAE,MAAM,SAAS,SAAS,OAAO,CAAC,EAAE;AAC7C;AAMA,IAAM,eAAe;AAArB,IAAwB,mBAAmB;AAA3C,IAA8C,mBAAmB;AASjE,IAAM,kBAAN,MAAgD;AAAA,EAM9C,YAAY,IAAwB;AAJpC,oBAAW;AACX,mBAAe;AACf,kBAAc;AAGZ,SAAK,KAAK;AAAA,EACZ;AAAA,EAEA,OAAO,QAAmB;AACxB,SAAK,WAAW;AAChB,SAAK,UAAU;AACf,WAAO;AAAA,EACT;AAAA,EAEA,QAAQ,OAAkB;AACxB,SAAK,WAAW;AAChB,SAAK,SAAS;AACd,WAAO;AAAA,EACT;AACF;AAIA,IAAM,eAA+B,OAAO,OAAO;AAAA,EACjD,IAAI;AAAA,EACJ,QAAQ,MAAM;AAAA,EACd,SAAS,MAAM;AACjB,CAAC;AAaM,IAAM,eAAN,MAAmB;AAAA,EAYxB,YAAY,MAAiB;AAN7B;AAAA;AAAA;AAAA,kBAAS,iBAAiB;AAI1B;AAAA;AAAA,sBAAmD,CAAC;AAGlD,SAAK,OAAO;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,GACE,cACA,mBACA,WACY;AACZ,UAAM,cAAc,aAAa,SAAS;AAE1C,UAAM,mBAAoB,OAAO,cAAc,aAC3C,oBACA;AAEJ,UAAM,WAAY,qBAAqB,SACnC,oBACA;AAEJ,UAAM,iBAAiB,KAAK,OAAO,GAAG,aAAc,KAAK,KAAK,wBAAwB,SAClF,KAAK,oBAAoB,UAAU,YAAY,IAC/C,QAAQ;AAEZ,QAAI,qBAAqB,QAAW;AAClC,WAAK,WAAW,WAAW,IAAI;AAAA,IACjC;AAGA,WAAO,MAAM;AACX,qBAAe;AACf,UAAI,KAAK,OAAO,OAAO,WAAW,EAAE,WAAW,GAAG;AAChD,eAAO,KAAK,WAAW,WAAW;AAAA,MACpC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,oBAAoB,UAAmC,gBAAuC;AAC5F,UAAM,UAAU,KAAK,KAAK,oBAAqB,KAAK,KAAK,IAAI;AAC7D,UAAM,aAAc,mBAAmB;AAEvC,WAAO,IAAI,SAAqB;AAC9B,YAAM,SAAS,CAAC,MAAa;AAAA,QAC3B,IAAI;AAAA,UACF;AAAA,UAAG,EAAE;AAAA,UACL,KAAK,CAAC;AAAA;AAAA,UACN,aAAa,KAAK,CAAC,IAAI,KAAK,CAAC;AAAA;AAAA,UAC7B,aAAa,KAAK,CAAC,IAAI;AAAA;AAAA,QACzB;AAAA,QACA;AAAA,MACF;AAEA,UAAI;AACF,cAAM,SAAS,SAAS,GAAG,IAAI;AAC/B,eAAQ,OAAO,QAAQ,UAAU,aAAc,OAAO,MAAM,MAAM,IAAI;AAAA,MACxE,SAAS,GAAQ;AACf,eAAO,CAAC;AAAA,MACV;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,OAAO,QAAgC,QAAgB,IAAoB;AACzE,UAAM,cAAe,OAAO,YAAY,QAAQ,EAAE,IAC9C,OAAO,OAAO,QAAQ,EAAE,IACxB,OAAO,OAAO,QAAQ,EAAE;AAE5B,QAAI;AACJ,QAAI;AACF,gBAAW,OAAO,aAAa,GAAG,SAC9B,OAAO,OAAO,SAAS,GAAG,QAAQ,OAAO,UAAU,CAAC,IACpD;AACJ,mBAAa,qCAAqC,aAAa,SAAS,KAAK,KAAK,MAAM;AAGxF,UAAI,KAAK,WAAW,WAAW,MAAM,QAAW;AAC9C,kBAAU,iBAAiB,KAAK,WAAW,WAAW,GAAG,OAAO;AAAA,MAClE;AAAA,IAEF,SAAS,GAAQ;AACf,yBAAmB,CAAC;AACpB,aAAO,MAAM,UAAU,UAAU;AACjC;AAAA,IACF;AAEA,QAAI,KAAK,OAAO,OAAO,WAAW,GAAG;AACnC,WAAK,OAAO,KAAK,aAAuB,QAAQ,SAAS,YAAY;AAAA,IAEvE,WAAW,KAAK,OAAO,OAAO,GAAG,GAAG;AAClC,WAAK,OAAO,KAAK,KAAK,QAAQ,aAAa,OAAO;AAAA,IAEpD,OAAO;AACL,WAAK,WAAW,QAAQ,aAAa,OAAO;AAAA,IAC9C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAU,QAAgC,QAAgB,IAAoB;AAC5E,UAAM,YAAY,OAAO,OAAO,QAAQ,EAAE;AAE1C,UAAM,cAAe,OAAO,YAAY,QAAQ,EAAE,IAC9C,OAAO,OAAO,QAAQ,EAAE,IACxB,OAAO,OAAO,QAAQ,EAAE;AAE5B,QAAI;AACJ,QAAI;AACF,gBAAW,OAAO,aAAa,GAAG,SAC9B,OAAO,OAAO,SAAS,GAAG,QAAQ,OAAO,UAAU,CAAC,IACpD;AACJ,mBAAa,wCAAwC,WAAW,aAAa,SAAS,KAAK,KAAK,MAAM;AAGtG,UAAI,KAAK,WAAW,WAAW,MAAM,QAAW;AAC9C,kBAAU,iBAAiB,KAAK,WAAW,WAAW,GAAG,OAAO;AAAA,MAClE;AAAA,IAEF,SAAS,GAAQ;AAEf,yBAAmB,CAAC;AACpB,WAAK,gBAAgB,QAAQ,WAAW,eAAe,OAAO,gBAAgB,CAAC,CAAC;AAChF;AAAA,IACF;AAIA,UAAM,UAAU,KAAK,OAAO,OAAO,WAAqB,IAAI,CAAC;AAE7D,QAAI,YAAY,QAAW;AACzB,WAAK,gBAAgB,QAAQ,WAAW,eAAe,OAAO;AAAA,QAC5D,MAAM;AAAA,QACN,SAAS,SAAS,KAAK,KAAK,QAAQ,uBAAuB,WAAW;AAAA,MACxE,CAAC;AACD;AAAA,IACF;AAEA,UAAM,MAAM,IAAI,gBAAgB,SAAS;AAOzC,QAAI;AACJ,QAAI;AACF,iBAAW,QAAQ,QAAQ,SAAS,GAAG;AACvC,UAAI,aAAa,QAAQ,OAAO,aAAa,YAAY,OAAO,SAAS,SAAS,YAAY;AAC5F,iBAAS;AAAA,UACP,CAAC,aAAkB,KAAK,iBAAiB,QAAQ,WAAW,KAAK,QAAQ;AAAA,UACzE,CAAC,MAAW;AACV,+BAAmB,CAAC;AACpB,iBAAK,gBAAgB,QAAQ,WAAW,eAAe,OAAO,gBAAgB,CAAC,CAAC;AAAA,UAClF;AAAA,QACF;AACA;AAAA,MACF;AAAA,IACF,SAAS,GAAQ;AACf,yBAAmB,CAAC;AACpB,WAAK,gBAAgB,QAAQ,WAAW,eAAe,OAAO,gBAAgB,CAAC,CAAC;AAChF;AAAA,IACF;AAEA,SAAK,iBAAiB,QAAQ,WAAW,KAAK,QAAQ;AAAA,EACxD;AAAA;AAAA;AAAA,EAIA,iBAAiB,QAAgB,WAAmB,KAAsB,UAAqB;AAC7F,QAAI,IAAI,aAAa,kBAAkB;AACrC,WAAK,gBAAgB,QAAQ,WAAW,eAAe,UAAU,IAAI,OAAO;AAAA,IAC9E,WAAW,IAAI,aAAa,kBAAkB;AAC5C,WAAK,gBAAgB,QAAQ,WAAW,eAAe,IAAI,IAAI,MAAM;AAAA,IACvE,OAAO;AACL,WAAK,gBAAgB,QAAQ,WAAW,eAAe,IAAI,QAAQ;AAAA,IACrE;AAAA,EACF;AAAA;AAAA,EAGA,YAAY,QAAgC,QAAgB,IAAoB;AAC9E,UAAM,cAAe,OAAO,YAAY,QAAQ,EAAE,IAC9C,OAAO,OAAO,QAAQ,EAAE,IACxB,OAAO,OAAO,QAAQ,EAAE;AAE5B,QAAI,UAAe,OAAO,SAAS,GAAG,QAAQ,OAAO,UAAU;AAC/D,iBAAa,qCAAqC,aAAa,SAAS,KAAK,KAAK,MAAM;AAExF,UAAM,mBAAmB,MAAM,WAAW;AAG1C,QAAI;AACF,UAAI,KAAK,WAAW,gBAAgB,MAAM,QAAW;AACnD,kBAAU,iBAAiB,KAAK,WAAW,gBAAgB,GAAG,OAAO;AAAA,MACvE;AAAA,IACF,SAAS,GAAQ;AACf,yBAAmB,CAAC;AACpB,aAAO,MAAM,UAAU,UAAU;AACjC;AAAA,IACF;AAEA,QAAI,KAAK,OAAO,OAAO,gBAAgB,GAAG;AACxC,WAAK,OAAO,KAAK,kBAAkB,QAAQ,OAAO;AAAA,IAEpD,WAAW,KAAK,OAAO,OAAO,GAAG,GAAG;AAClC,WAAK,OAAO,KAAK,KAAK,QAAQ,aAAa,OAAO;AAAA,IAEpD,OAAO;AACL,WAAK,WAAW,QAAQ,aAAa,OAAO;AAAA,IAC9C;AAAA,EACF;AAAA;AAAA,EAGA,gBAAgB,QAAgB,WAAmB,QAAwB,SAAqB;AAC9F,iBAAa,8CAA8C,WAAW,QAAQ,SAAS,KAAK,KAAK,MAAM;AACvG,WAAO,WAAW,gBAAgB,SAAS,aAAa,EAAE,WAAW,QAAQ,OAAO,CAAC;AAAA,EACvF;AAAA;AAAA,EAGA,WAAW,QAAgB,aAA8B,UAAyB;AAChF,UAAM,eAAe,uBAAuB,WAAW;AACvD,iBAAa,GAAG,YAAY,aAAa,KAAK,KAAK,MAAM,GAAG;AAC5D,QAAI,WAAW;AACb,aAAO,MAAM,UAAU,iBAAiB,YAAY;AAAA,IACtD,OAAO;AACL,aAAO,MAAM,UAAU,YAAY,YAAY;AAAA,IACjD;AAAA,EACF;AACF;",
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 // null-prototype: keyed by client-supplied message type (colyseus/colyseus#951)\n validators: { [type: string]: StandardSchemaV1 } = Object.create(null);\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": ";AAAA,SAAS,cAA6B;AACtC,SAAS,cAAc;AAEvB,SAAS,WAAW,WAAW,UAAU,sBAA2C;AACpF,SAAS,uBAAuB;AAChC,SAAS,wBAAwB;AACjC,SAAS,wBAA+C;AACxD,SAAS,iBAAiB;AAC1B,SAAS,cAAc,0BAA0B;AACjD,SAAS,0BAA0B;AAMnC,SAAS,gBAAgB,GAAuD;AAC9E,MAAI,aAAa,OAAO;AACtB,UAAM,OAAQ,EAAU;AACxB,WAAQ,SAAS,SACb,EAAE,MAAM,EAAE,MAAM,SAAS,EAAE,SAAS,KAAK,IACzC,EAAE,MAAM,EAAE,MAAM,SAAS,EAAE,QAAQ;AAAA,EACzC;AACA,SAAO,EAAE,MAAM,SAAS,SAAS,OAAO,CAAC,EAAE;AAC7C;AAMA,IAAM,eAAe;AAArB,IAAwB,mBAAmB;AAA3C,IAA8C,mBAAmB;AASjE,IAAM,kBAAN,MAAgD;AAAA,EAM9C,YAAY,IAAwB;AAJpC,oBAAW;AACX,mBAAe;AACf,kBAAc;AAGZ,SAAK,KAAK;AAAA,EACZ;AAAA,EAEA,OAAO,QAAmB;AACxB,SAAK,WAAW;AAChB,SAAK,UAAU;AACf,WAAO;AAAA,EACT;AAAA,EAEA,QAAQ,OAAkB;AACxB,SAAK,WAAW;AAChB,SAAK,SAAS;AACd,WAAO;AAAA,EACT;AACF;AAIA,IAAM,eAA+B,OAAO,OAAO;AAAA,EACjD,IAAI;AAAA,EACJ,QAAQ,MAAM;AAAA,EACd,SAAS,MAAM;AACjB,CAAC;AAaM,IAAM,eAAN,MAAmB;AAAA,EAaxB,YAAY,MAAiB;AAP7B;AAAA;AAAA;AAAA,kBAAS,iBAAiB;AAK1B;AAAA;AAAA;AAAA,sBAAmD,uBAAO,OAAO,IAAI;AAGnE,SAAK,OAAO;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,GACE,cACA,mBACA,WACY;AACZ,UAAM,cAAc,aAAa,SAAS;AAE1C,UAAM,mBAAoB,OAAO,cAAc,aAC3C,oBACA;AAEJ,UAAM,WAAY,qBAAqB,SACnC,oBACA;AAEJ,UAAM,iBAAiB,KAAK,OAAO,GAAG,aAAc,KAAK,KAAK,wBAAwB,SAClF,KAAK,oBAAoB,UAAU,YAAY,IAC/C,QAAQ;AAEZ,QAAI,qBAAqB,QAAW;AAClC,WAAK,WAAW,WAAW,IAAI;AAAA,IACjC;AAGA,WAAO,MAAM;AACX,qBAAe;AACf,UAAI,KAAK,OAAO,OAAO,WAAW,EAAE,WAAW,GAAG;AAChD,eAAO,KAAK,WAAW,WAAW;AAAA,MACpC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,oBAAoB,UAAmC,gBAAuC;AAC5F,UAAM,UAAU,KAAK,KAAK,oBAAqB,KAAK,KAAK,IAAI;AAC7D,UAAM,aAAc,mBAAmB;AAEvC,WAAO,IAAI,SAAqB;AAC9B,YAAM,SAAS,CAAC,MAAa;AAAA,QAC3B,IAAI;AAAA,UACF;AAAA,UAAG,EAAE;AAAA,UACL,KAAK,CAAC;AAAA;AAAA,UACN,aAAa,KAAK,CAAC,IAAI,KAAK,CAAC;AAAA;AAAA,UAC7B,aAAa,KAAK,CAAC,IAAI;AAAA;AAAA,QACzB;AAAA,QACA;AAAA,MACF;AAEA,UAAI;AACF,cAAM,SAAS,SAAS,GAAG,IAAI;AAC/B,eAAQ,OAAO,QAAQ,UAAU,aAAc,OAAO,MAAM,MAAM,IAAI;AAAA,MACxE,SAAS,GAAQ;AACf,eAAO,CAAC;AAAA,MACV;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,OAAO,QAAgC,QAAgB,IAAoB;AACzE,UAAM,cAAe,OAAO,YAAY,QAAQ,EAAE,IAC9C,OAAO,OAAO,QAAQ,EAAE,IACxB,OAAO,OAAO,QAAQ,EAAE;AAE5B,QAAI;AACJ,QAAI;AACF,gBAAW,OAAO,aAAa,GAAG,SAC9B,OAAO,OAAO,SAAS,GAAG,QAAQ,OAAO,UAAU,CAAC,IACpD;AACJ,mBAAa,qCAAqC,aAAa,SAAS,KAAK,KAAK,MAAM;AAGxF,UAAI,KAAK,WAAW,WAAW,MAAM,QAAW;AAC9C,kBAAU,iBAAiB,KAAK,WAAW,WAAW,GAAG,OAAO;AAAA,MAClE;AAAA,IAEF,SAAS,GAAQ;AACf,yBAAmB,CAAC;AACpB,aAAO,MAAM,UAAU,UAAU;AACjC;AAAA,IACF;AAEA,QAAI,KAAK,OAAO,OAAO,WAAW,GAAG;AACnC,WAAK,OAAO,KAAK,aAAuB,QAAQ,SAAS,YAAY;AAAA,IAEvE,WAAW,KAAK,OAAO,OAAO,GAAG,GAAG;AAClC,WAAK,OAAO,KAAK,KAAK,QAAQ,aAAa,OAAO;AAAA,IAEpD,OAAO;AACL,WAAK,WAAW,QAAQ,aAAa,OAAO;AAAA,IAC9C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAU,QAAgC,QAAgB,IAAoB;AAC5E,UAAM,YAAY,OAAO,OAAO,QAAQ,EAAE;AAE1C,UAAM,cAAe,OAAO,YAAY,QAAQ,EAAE,IAC9C,OAAO,OAAO,QAAQ,EAAE,IACxB,OAAO,OAAO,QAAQ,EAAE;AAE5B,QAAI;AACJ,QAAI;AACF,gBAAW,OAAO,aAAa,GAAG,SAC9B,OAAO,OAAO,SAAS,GAAG,QAAQ,OAAO,UAAU,CAAC,IACpD;AACJ,mBAAa,wCAAwC,WAAW,aAAa,SAAS,KAAK,KAAK,MAAM;AAGtG,UAAI,KAAK,WAAW,WAAW,MAAM,QAAW;AAC9C,kBAAU,iBAAiB,KAAK,WAAW,WAAW,GAAG,OAAO;AAAA,MAClE;AAAA,IAEF,SAAS,GAAQ;AAEf,yBAAmB,CAAC;AACpB,WAAK,gBAAgB,QAAQ,WAAW,eAAe,OAAO,gBAAgB,CAAC,CAAC;AAChF;AAAA,IACF;AAIA,UAAM,UAAU,KAAK,OAAO,OAAO,WAAqB,IAAI,CAAC;AAE7D,QAAI,YAAY,QAAW;AACzB,WAAK,gBAAgB,QAAQ,WAAW,eAAe,OAAO;AAAA,QAC5D,MAAM;AAAA,QACN,SAAS,SAAS,KAAK,KAAK,QAAQ,uBAAuB,WAAW;AAAA,MACxE,CAAC;AACD;AAAA,IACF;AAEA,UAAM,MAAM,IAAI,gBAAgB,SAAS;AAOzC,QAAI;AACJ,QAAI;AACF,iBAAW,QAAQ,QAAQ,SAAS,GAAG;AACvC,UAAI,aAAa,QAAQ,OAAO,aAAa,YAAY,OAAO,SAAS,SAAS,YAAY;AAC5F,iBAAS;AAAA,UACP,CAAC,aAAkB,KAAK,iBAAiB,QAAQ,WAAW,KAAK,QAAQ;AAAA,UACzE,CAAC,MAAW;AACV,+BAAmB,CAAC;AACpB,iBAAK,gBAAgB,QAAQ,WAAW,eAAe,OAAO,gBAAgB,CAAC,CAAC;AAAA,UAClF;AAAA,QACF;AACA;AAAA,MACF;AAAA,IACF,SAAS,GAAQ;AACf,yBAAmB,CAAC;AACpB,WAAK,gBAAgB,QAAQ,WAAW,eAAe,OAAO,gBAAgB,CAAC,CAAC;AAChF;AAAA,IACF;AAEA,SAAK,iBAAiB,QAAQ,WAAW,KAAK,QAAQ;AAAA,EACxD;AAAA;AAAA;AAAA,EAIA,iBAAiB,QAAgB,WAAmB,KAAsB,UAAqB;AAC7F,QAAI,IAAI,aAAa,kBAAkB;AACrC,WAAK,gBAAgB,QAAQ,WAAW,eAAe,UAAU,IAAI,OAAO;AAAA,IAC9E,WAAW,IAAI,aAAa,kBAAkB;AAC5C,WAAK,gBAAgB,QAAQ,WAAW,eAAe,IAAI,IAAI,MAAM;AAAA,IACvE,OAAO;AACL,WAAK,gBAAgB,QAAQ,WAAW,eAAe,IAAI,QAAQ;AAAA,IACrE;AAAA,EACF;AAAA;AAAA,EAGA,YAAY,QAAgC,QAAgB,IAAoB;AAC9E,UAAM,cAAe,OAAO,YAAY,QAAQ,EAAE,IAC9C,OAAO,OAAO,QAAQ,EAAE,IACxB,OAAO,OAAO,QAAQ,EAAE;AAE5B,QAAI,UAAe,OAAO,SAAS,GAAG,QAAQ,OAAO,UAAU;AAC/D,iBAAa,qCAAqC,aAAa,SAAS,KAAK,KAAK,MAAM;AAExF,UAAM,mBAAmB,MAAM,WAAW;AAG1C,QAAI;AACF,UAAI,KAAK,WAAW,gBAAgB,MAAM,QAAW;AACnD,kBAAU,iBAAiB,KAAK,WAAW,gBAAgB,GAAG,OAAO;AAAA,MACvE;AAAA,IACF,SAAS,GAAQ;AACf,yBAAmB,CAAC;AACpB,aAAO,MAAM,UAAU,UAAU;AACjC;AAAA,IACF;AAEA,QAAI,KAAK,OAAO,OAAO,gBAAgB,GAAG;AACxC,WAAK,OAAO,KAAK,kBAAkB,QAAQ,OAAO;AAAA,IAEpD,WAAW,KAAK,OAAO,OAAO,GAAG,GAAG;AAClC,WAAK,OAAO,KAAK,KAAK,QAAQ,aAAa,OAAO;AAAA,IAEpD,OAAO;AACL,WAAK,WAAW,QAAQ,aAAa,OAAO;AAAA,IAC9C;AAAA,EACF;AAAA;AAAA,EAGA,gBAAgB,QAAgB,WAAmB,QAAwB,SAAqB;AAC9F,iBAAa,8CAA8C,WAAW,QAAQ,SAAS,KAAK,KAAK,MAAM;AACvG,WAAO,WAAW,gBAAgB,SAAS,aAAa,EAAE,WAAW,QAAQ,OAAO,CAAC;AAAA,EACvF;AAAA;AAAA,EAGA,WAAW,QAAgB,aAA8B,UAAyB;AAChF,UAAM,eAAe,uBAAuB,WAAW;AACvD,iBAAa,GAAG,YAAY,aAAa,KAAK,KAAK,MAAM,GAAG;AAC5D,QAAI,WAAW;AACb,aAAO,MAAM,UAAU,iBAAiB,YAAY;AAAA,IACtD,OAAO;AACL,aAAO,MAAM,UAAU,YAAY,YAAY;AAAA,IACjD;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -35,6 +35,7 @@ __export(default_routes_exports, {
35
35
  });
36
36
  module.exports = __toCommonJS(default_routes_exports);
37
37
  var import_better_call = require("@colyseus/better-call");
38
+ var import_index = require("./index.cjs");
38
39
  var matchMaker = __toESM(require("../MatchMaker.cjs"), 1);
39
40
  var import_Utils = require("../utils/Utils.cjs");
40
41
  var import_Transport = require("../Transport.cjs");
@@ -81,7 +82,7 @@ var postMatchmakeMethod = (0, import_better_call.createEndpoint)("/matchmake/:me
81
82
  }
82
83
  });
83
84
  function getDefaultRouter() {
84
- return (0, import_better_call.createRouter)({ postMatchmakeMethod }, { openapi: { disabled: true } });
85
+ return (0, import_index.createRouter)({ postMatchmakeMethod }, { openapi: { disabled: true } });
85
86
  }
86
87
  // Annotate the CommonJS export names for ESM import in node:
87
88
  0 && (module.exports = {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/router/default_routes.ts"],
4
- "sourcesContent": ["import { createEndpoint, createRouter } from \"@colyseus/better-call\";\nimport * as matchMaker from \"../MatchMaker.ts\";\nimport { getBearerToken } from \"../utils/Utils.ts\";\nimport { getTransport } from \"../Transport.ts\";\n\nexport const postMatchmakeMethod = createEndpoint(\"/matchmake/:method/:roomName\", { method: \"POST\" }, async (ctx) => {\n // do not accept matchmaking requests if already shutting down\n if (matchMaker.state === matchMaker.MatchMakerState.SHUTTING_DOWN) {\n throw ctx.error(503);\n }\n\n const requestHeaders = ctx.request.headers;\n\n const method = ctx.params.method;\n const roomName = ctx.params.roomName;\n\n try {\n const clientOptions = ctx.body;\n const response = await matchMaker.controller.invokeMethod(\n method,\n roomName,\n clientOptions,\n {\n token: getBearerToken(ctx.request.headers.get('authorization')),\n headers: ctx.request.headers,\n ip: requestHeaders.get('x-forwarded-for') ?? requestHeaders.get('x-client-ip') ?? requestHeaders.get('x-real-ip'),\n req: ctx.request as any,\n },\n );\n\n //\n // TODO: refactor here.\n // expose protocol, if available.\n //\n const transport = getTransport();\n if (transport.protocol !== undefined) {\n response.protocol = transport.protocol;\n }\n // WebTransport (h3): client pins the self-signed cert via serverCertificateHashes.\n if (transport.fingerprint !== undefined) {\n response.fingerprint = transport.fingerprint;\n }\n\n const json = JSON.stringify(response);\n\n return new Response(json, {\n headers: {\n 'content-type': 'application/json',\n //\n // Set content length manually to avoid \"chunked\" transfer-encoding header\n // See https://github.com/haxetink/tink_http/issues/27\n //\n 'content-length': json.length.toString(),\n },\n }) as any;\n\n } catch (e: any) {\n throw ctx.error(e.code, { code: e.code, error: e.message, });\n }\n\n});\n\nexport function getDefaultRouter() {\n // no public docs page \u2014 the docs surface is playground's gated /__apidocs\n return createRouter({ postMatchmakeMethod }, { openapi: { disabled: true } });\n}"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAA6C;AAC7C,iBAA4B;AAC5B,mBAA+B;AAC/B,uBAA6B;AAEtB,IAAM,0BAAsB,mCAAe,gCAAgC,EAAE,QAAQ,OAAO,GAAG,OAAO,QAAQ;AAEnH,MAAe,qBAAqB,2BAAgB,eAAe;AACjE,UAAM,IAAI,MAAM,GAAG;AAAA,EACrB;AAEA,QAAM,iBAAiB,IAAI,QAAQ;AAEnC,QAAM,SAAS,IAAI,OAAO;AAC1B,QAAM,WAAW,IAAI,OAAO;AAE5B,MAAI;AACF,UAAM,gBAAgB,IAAI;AAC1B,UAAM,WAAW,MAAiB,sBAAW;AAAA,MAC3C;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,QACE,WAAO,6BAAe,IAAI,QAAQ,QAAQ,IAAI,eAAe,CAAC;AAAA,QAC9D,SAAS,IAAI,QAAQ;AAAA,QACrB,IAAI,eAAe,IAAI,iBAAiB,KAAK,eAAe,IAAI,aAAa,KAAK,eAAe,IAAI,WAAW;AAAA,QAChH,KAAK,IAAI;AAAA,MACX;AAAA,IACF;AAMA,UAAM,gBAAY,+BAAa;AAC/B,QAAI,UAAU,aAAa,QAAW;AACpC,eAAS,WAAW,UAAU;AAAA,IAChC;AAEA,QAAI,UAAU,gBAAgB,QAAW;AACvC,eAAS,cAAc,UAAU;AAAA,IACnC;AAEA,UAAM,OAAO,KAAK,UAAU,QAAQ;AAEpC,WAAO,IAAI,SAAS,MAAM;AAAA,MACxB,SAAS;AAAA,QACP,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA,QAKhB,kBAAkB,KAAK,OAAO,SAAS;AAAA,MACzC;AAAA,IACF,CAAC;AAAA,EAEH,SAAS,GAAQ;AACf,UAAM,IAAI,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,OAAO,EAAE,QAAS,CAAC;AAAA,EAC7D;AAEF,CAAC;AAEM,SAAS,mBAAmB;AAEjC,aAAO,iCAAa,EAAE,oBAAoB,GAAG,EAAE,SAAS,EAAE,UAAU,KAAK,EAAE,CAAC;AAC9E;",
4
+ "sourcesContent": ["import { createEndpoint } from \"@colyseus/better-call\";\nimport { createRouter } from \"./index.ts\";\nimport * as matchMaker from \"../MatchMaker.ts\";\nimport { getBearerToken } from \"../utils/Utils.ts\";\nimport { getTransport } from \"../Transport.ts\";\n\nexport const postMatchmakeMethod = createEndpoint(\"/matchmake/:method/:roomName\", { method: \"POST\" }, async (ctx) => {\n // do not accept matchmaking requests if already shutting down\n if (matchMaker.state === matchMaker.MatchMakerState.SHUTTING_DOWN) {\n throw ctx.error(503);\n }\n\n const requestHeaders = ctx.request.headers;\n\n const method = ctx.params.method;\n const roomName = ctx.params.roomName;\n\n try {\n const clientOptions = ctx.body;\n const response = await matchMaker.controller.invokeMethod(\n method,\n roomName,\n clientOptions,\n {\n token: getBearerToken(ctx.request.headers.get('authorization')),\n headers: ctx.request.headers,\n ip: requestHeaders.get('x-forwarded-for') ?? requestHeaders.get('x-client-ip') ?? requestHeaders.get('x-real-ip'),\n req: ctx.request as any,\n },\n );\n\n //\n // TODO: refactor here.\n // expose protocol, if available.\n //\n const transport = getTransport();\n if (transport.protocol !== undefined) {\n response.protocol = transport.protocol;\n }\n // WebTransport (h3): client pins the self-signed cert via serverCertificateHashes.\n if (transport.fingerprint !== undefined) {\n response.fingerprint = transport.fingerprint;\n }\n\n const json = JSON.stringify(response);\n\n return new Response(json, {\n headers: {\n 'content-type': 'application/json',\n //\n // Set content length manually to avoid \"chunked\" transfer-encoding header\n // See https://github.com/haxetink/tink_http/issues/27\n //\n 'content-length': json.length.toString(),\n },\n }) as any;\n\n } catch (e: any) {\n throw ctx.error(e.code, { code: e.code, error: e.message, });\n }\n\n});\n\nexport function getDefaultRouter() {\n // no public docs page \u2014 the docs surface is playground's gated /__apidocs\n return createRouter({ postMatchmakeMethod }, { openapi: { disabled: true } });\n}"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAA+B;AAC/B,mBAA6B;AAC7B,iBAA4B;AAC5B,mBAA+B;AAC/B,uBAA6B;AAEtB,IAAM,0BAAsB,mCAAe,gCAAgC,EAAE,QAAQ,OAAO,GAAG,OAAO,QAAQ;AAEnH,MAAe,qBAAqB,2BAAgB,eAAe;AACjE,UAAM,IAAI,MAAM,GAAG;AAAA,EACrB;AAEA,QAAM,iBAAiB,IAAI,QAAQ;AAEnC,QAAM,SAAS,IAAI,OAAO;AAC1B,QAAM,WAAW,IAAI,OAAO;AAE5B,MAAI;AACF,UAAM,gBAAgB,IAAI;AAC1B,UAAM,WAAW,MAAiB,sBAAW;AAAA,MAC3C;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,QACE,WAAO,6BAAe,IAAI,QAAQ,QAAQ,IAAI,eAAe,CAAC;AAAA,QAC9D,SAAS,IAAI,QAAQ;AAAA,QACrB,IAAI,eAAe,IAAI,iBAAiB,KAAK,eAAe,IAAI,aAAa,KAAK,eAAe,IAAI,WAAW;AAAA,QAChH,KAAK,IAAI;AAAA,MACX;AAAA,IACF;AAMA,UAAM,gBAAY,+BAAa;AAC/B,QAAI,UAAU,aAAa,QAAW;AACpC,eAAS,WAAW,UAAU;AAAA,IAChC;AAEA,QAAI,UAAU,gBAAgB,QAAW;AACvC,eAAS,cAAc,UAAU;AAAA,IACnC;AAEA,UAAM,OAAO,KAAK,UAAU,QAAQ;AAEpC,WAAO,IAAI,SAAS,MAAM;AAAA,MACxB,SAAS;AAAA,QACP,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA,QAKhB,kBAAkB,KAAK,OAAO,SAAS;AAAA,MACzC;AAAA,IACF,CAAC;AAAA,EAEH,SAAS,GAAQ;AACf,UAAM,IAAI,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,OAAO,EAAE,QAAS,CAAC;AAAA,EAC7D;AAEF,CAAC;AAEM,SAAS,mBAAmB;AAEjC,aAAO,2BAAa,EAAE,oBAAoB,GAAG,EAAE,SAAS,EAAE,UAAU,KAAK,EAAE,CAAC;AAC9E;",
6
6
  "names": []
7
7
  }
@@ -1,5 +1,6 @@
1
1
  // packages/core/src/router/default_routes.ts
2
- import { createEndpoint, createRouter } from "@colyseus/better-call";
2
+ import { createEndpoint } from "@colyseus/better-call";
3
+ import { createRouter } from "./index.mjs";
3
4
  import * as matchMaker from "../MatchMaker.mjs";
4
5
  import { getBearerToken } from "../utils/Utils.mjs";
5
6
  import { getTransport } from "../Transport.mjs";
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/router/default_routes.ts"],
4
- "sourcesContent": ["import { createEndpoint, createRouter } from \"@colyseus/better-call\";\nimport * as matchMaker from \"../MatchMaker.ts\";\nimport { getBearerToken } from \"../utils/Utils.ts\";\nimport { getTransport } from \"../Transport.ts\";\n\nexport const postMatchmakeMethod = createEndpoint(\"/matchmake/:method/:roomName\", { method: \"POST\" }, async (ctx) => {\n // do not accept matchmaking requests if already shutting down\n if (matchMaker.state === matchMaker.MatchMakerState.SHUTTING_DOWN) {\n throw ctx.error(503);\n }\n\n const requestHeaders = ctx.request.headers;\n\n const method = ctx.params.method;\n const roomName = ctx.params.roomName;\n\n try {\n const clientOptions = ctx.body;\n const response = await matchMaker.controller.invokeMethod(\n method,\n roomName,\n clientOptions,\n {\n token: getBearerToken(ctx.request.headers.get('authorization')),\n headers: ctx.request.headers,\n ip: requestHeaders.get('x-forwarded-for') ?? requestHeaders.get('x-client-ip') ?? requestHeaders.get('x-real-ip'),\n req: ctx.request as any,\n },\n );\n\n //\n // TODO: refactor here.\n // expose protocol, if available.\n //\n const transport = getTransport();\n if (transport.protocol !== undefined) {\n response.protocol = transport.protocol;\n }\n // WebTransport (h3): client pins the self-signed cert via serverCertificateHashes.\n if (transport.fingerprint !== undefined) {\n response.fingerprint = transport.fingerprint;\n }\n\n const json = JSON.stringify(response);\n\n return new Response(json, {\n headers: {\n 'content-type': 'application/json',\n //\n // Set content length manually to avoid \"chunked\" transfer-encoding header\n // See https://github.com/haxetink/tink_http/issues/27\n //\n 'content-length': json.length.toString(),\n },\n }) as any;\n\n } catch (e: any) {\n throw ctx.error(e.code, { code: e.code, error: e.message, });\n }\n\n});\n\nexport function getDefaultRouter() {\n // no public docs page \u2014 the docs surface is playground's gated /__apidocs\n return createRouter({ postMatchmakeMethod }, { openapi: { disabled: true } });\n}"],
5
- "mappings": ";AAAA,SAAS,gBAAgB,oBAAoB;AAC7C,YAAY,gBAAgB;AAC5B,SAAS,sBAAsB;AAC/B,SAAS,oBAAoB;AAEtB,IAAM,sBAAsB,eAAe,gCAAgC,EAAE,QAAQ,OAAO,GAAG,OAAO,QAAQ;AAEnH,MAAe,qBAAqB,2BAAgB,eAAe;AACjE,UAAM,IAAI,MAAM,GAAG;AAAA,EACrB;AAEA,QAAM,iBAAiB,IAAI,QAAQ;AAEnC,QAAM,SAAS,IAAI,OAAO;AAC1B,QAAM,WAAW,IAAI,OAAO;AAE5B,MAAI;AACF,UAAM,gBAAgB,IAAI;AAC1B,UAAM,WAAW,MAAiB,sBAAW;AAAA,MAC3C;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,QACE,OAAO,eAAe,IAAI,QAAQ,QAAQ,IAAI,eAAe,CAAC;AAAA,QAC9D,SAAS,IAAI,QAAQ;AAAA,QACrB,IAAI,eAAe,IAAI,iBAAiB,KAAK,eAAe,IAAI,aAAa,KAAK,eAAe,IAAI,WAAW;AAAA,QAChH,KAAK,IAAI;AAAA,MACX;AAAA,IACF;AAMA,UAAM,YAAY,aAAa;AAC/B,QAAI,UAAU,aAAa,QAAW;AACpC,eAAS,WAAW,UAAU;AAAA,IAChC;AAEA,QAAI,UAAU,gBAAgB,QAAW;AACvC,eAAS,cAAc,UAAU;AAAA,IACnC;AAEA,UAAM,OAAO,KAAK,UAAU,QAAQ;AAEpC,WAAO,IAAI,SAAS,MAAM;AAAA,MACxB,SAAS;AAAA,QACP,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA,QAKhB,kBAAkB,KAAK,OAAO,SAAS;AAAA,MACzC;AAAA,IACF,CAAC;AAAA,EAEH,SAAS,GAAQ;AACf,UAAM,IAAI,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,OAAO,EAAE,QAAS,CAAC;AAAA,EAC7D;AAEF,CAAC;AAEM,SAAS,mBAAmB;AAEjC,SAAO,aAAa,EAAE,oBAAoB,GAAG,EAAE,SAAS,EAAE,UAAU,KAAK,EAAE,CAAC;AAC9E;",
4
+ "sourcesContent": ["import { createEndpoint } from \"@colyseus/better-call\";\nimport { createRouter } from \"./index.ts\";\nimport * as matchMaker from \"../MatchMaker.ts\";\nimport { getBearerToken } from \"../utils/Utils.ts\";\nimport { getTransport } from \"../Transport.ts\";\n\nexport const postMatchmakeMethod = createEndpoint(\"/matchmake/:method/:roomName\", { method: \"POST\" }, async (ctx) => {\n // do not accept matchmaking requests if already shutting down\n if (matchMaker.state === matchMaker.MatchMakerState.SHUTTING_DOWN) {\n throw ctx.error(503);\n }\n\n const requestHeaders = ctx.request.headers;\n\n const method = ctx.params.method;\n const roomName = ctx.params.roomName;\n\n try {\n const clientOptions = ctx.body;\n const response = await matchMaker.controller.invokeMethod(\n method,\n roomName,\n clientOptions,\n {\n token: getBearerToken(ctx.request.headers.get('authorization')),\n headers: ctx.request.headers,\n ip: requestHeaders.get('x-forwarded-for') ?? requestHeaders.get('x-client-ip') ?? requestHeaders.get('x-real-ip'),\n req: ctx.request as any,\n },\n );\n\n //\n // TODO: refactor here.\n // expose protocol, if available.\n //\n const transport = getTransport();\n if (transport.protocol !== undefined) {\n response.protocol = transport.protocol;\n }\n // WebTransport (h3): client pins the self-signed cert via serverCertificateHashes.\n if (transport.fingerprint !== undefined) {\n response.fingerprint = transport.fingerprint;\n }\n\n const json = JSON.stringify(response);\n\n return new Response(json, {\n headers: {\n 'content-type': 'application/json',\n //\n // Set content length manually to avoid \"chunked\" transfer-encoding header\n // See https://github.com/haxetink/tink_http/issues/27\n //\n 'content-length': json.length.toString(),\n },\n }) as any;\n\n } catch (e: any) {\n throw ctx.error(e.code, { code: e.code, error: e.message, });\n }\n\n});\n\nexport function getDefaultRouter() {\n // no public docs page \u2014 the docs surface is playground's gated /__apidocs\n return createRouter({ postMatchmakeMethod }, { openapi: { disabled: true } });\n}"],
5
+ "mappings": ";AAAA,SAAS,sBAAsB;AAC/B,SAAS,oBAAoB;AAC7B,YAAY,gBAAgB;AAC5B,SAAS,sBAAsB;AAC/B,SAAS,oBAAoB;AAEtB,IAAM,sBAAsB,eAAe,gCAAgC,EAAE,QAAQ,OAAO,GAAG,OAAO,QAAQ;AAEnH,MAAe,qBAAqB,2BAAgB,eAAe;AACjE,UAAM,IAAI,MAAM,GAAG;AAAA,EACrB;AAEA,QAAM,iBAAiB,IAAI,QAAQ;AAEnC,QAAM,SAAS,IAAI,OAAO;AAC1B,QAAM,WAAW,IAAI,OAAO;AAE5B,MAAI;AACF,UAAM,gBAAgB,IAAI;AAC1B,UAAM,WAAW,MAAiB,sBAAW;AAAA,MAC3C;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,QACE,OAAO,eAAe,IAAI,QAAQ,QAAQ,IAAI,eAAe,CAAC;AAAA,QAC9D,SAAS,IAAI,QAAQ;AAAA,QACrB,IAAI,eAAe,IAAI,iBAAiB,KAAK,eAAe,IAAI,aAAa,KAAK,eAAe,IAAI,WAAW;AAAA,QAChH,KAAK,IAAI;AAAA,MACX;AAAA,IACF;AAMA,UAAM,YAAY,aAAa;AAC/B,QAAI,UAAU,aAAa,QAAW;AACpC,eAAS,WAAW,UAAU;AAAA,IAChC;AAEA,QAAI,UAAU,gBAAgB,QAAW;AACvC,eAAS,cAAc,UAAU;AAAA,IACnC;AAEA,UAAM,OAAO,KAAK,UAAU,QAAQ;AAEpC,WAAO,IAAI,SAAS,MAAM;AAAA,MACxB,SAAS;AAAA,QACP,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA,QAKhB,kBAAkB,KAAK,OAAO,SAAS;AAAA,MACzC;AAAA,IACF,CAAC;AAAA,EAEH,SAAS,GAAQ;AACf,UAAM,IAAI,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,OAAO,EAAE,QAAS,CAAC;AAAA,EAC7D;AAEF,CAAC;AAEM,SAAS,mBAAmB;AAEjC,SAAO,aAAa,EAAE,oBAAoB,GAAG,EAAE,SAAS,EAAE,UAAU,KAAK,EAAE,CAAC;AAC9E;",
6
6
  "names": []
7
7
  }
@@ -114,11 +114,16 @@ function expressRouterStack(expressApp) {
114
114
  }
115
115
  }
116
116
  function createRouter(endpoints, config = {}) {
117
+ const onError = config?.onError;
117
118
  return (0, import_better_call.createRouter)({ ...endpoints }, {
118
119
  // better-call's /api/reference page dumps the full API surface
119
120
  // unauthenticated — opt back in by passing `openapi` explicitly.
120
121
  openapi: { disabled: true },
121
- ...config
122
+ ...config,
123
+ // Otherwise a malformed body is a 500 plus a stack trace on stderr: log
124
+ // noise any anonymous client can trigger at will. Matched on the message
125
+ // because `onError` receives no request context to test against.
126
+ onError: async (error) => error instanceof SyntaxError && error.message.includes("JSON") ? Response.json({ error: "malformed request body" }, { status: 400 }) : await onError?.(error)
122
127
  });
123
128
  }
124
129
  function basicAuth(opts) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/router/index.ts"],
4
- "sourcesContent": ["import type express from \"express\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\nimport { createHash, timingSafeEqual } from \"node:crypto\";\nimport { type Endpoint, type Router, type RouterConfig, createRouter as createBetterCallRouter, createEndpoint, createMiddleware, APIError } from \"@colyseus/better-call\";\nimport { toNodeHandler, getRequest, setResponse } from \"@colyseus/better-call/node\";\nimport { Transport } from \"../Transport.ts\";\nimport { controller } from \"../matchmaker/controller.ts\";\nimport pkg from \"../../package.json\" with { type: \"json\" };\n\nexport {\n createEndpoint,\n createMiddleware,\n createInternalContext,\n\n // Re-export types needed for declaration emit\n type Router,\n type RouterConfig,\n type Endpoint,\n type EndpointHandler,\n type EndpointOptions,\n type EndpointContext,\n type StrictEndpoint,\n} from \"@colyseus/better-call\";\n\nexport { toNodeHandler };\n\nexport function bindRouterToTransport(transport: Transport, router: Router, useExpress: boolean) {\n // add default \"/__healthcheck\" endpoint\n router.addEndpoint(createEndpoint(\"/__healthcheck\", { method: \"GET\" }, async (ctx) => {\n return new Response(\"OK\", { status: 200 });\n }));\n\n const server = transport.server;\n\n // check if the server is bound to an express app\n const expressApp: express.Application = (useExpress)\n ? transport.getExpressApp() as express.Application\n // fallback searching for express app in server listeners\n : server?.listeners('request').find((listener: Function) => listener.name === \"app\" && listener['mountpath'] === '/') as express.Application;\n\n // add default \"/\" route, if not provided.\n const hasRootRoute = (\n // check if express app has a root route\n (expressApp && hasExpressRootRoute(expressApp)) ||\n\n // check if router has a root route\n Object.values(router.endpoints).some(endpoint => endpoint.path === \"/\")\n );\n\n if (!hasRootRoute) {\n router.addEndpoint(createEndpoint(\"/\", { method: \"GET\" }, async (ctx) => {\n return new Response(`Colyseus ${pkg.version}`, { status: 200 });\n }));\n }\n\n // use custom bindRouter method if provided\n if (!server && transport.bindRouter) {\n transport.bindRouter(router);\n return;\n }\n\n // which route handler to use\n // (router + fallback to express, or just router)\n let next: any;\n\n if (expressApp) {\n server.removeListener('request', expressApp);\n\n next = async (req: IncomingMessage, res: ServerResponse) => {\n // check if the route is defined in the router\n // if so, use the router handler, otherwise fallback to express\n if (router.findRoute(req.method, req.url.split('?')[0]) !== undefined) {\n const protocol = req.headers[\"x-forwarded-proto\"] || ((req.socket as any).encrypted ? \"https\" : \"http\");\n const base = `${protocol}://${req.headers[\":authority\"] || req.headers.host}`;\n const response = await router.handler(getRequest({ base, request: req }));\n return setResponse(res, response);\n\n } else {\n return expressApp['handle'](req, res);\n }\n };\n\n } else {\n next = toNodeHandler(router.handler);\n }\n\n // handle cors headers for all requests by default\n server.prependListener('request', (req: IncomingMessage, res: ServerResponse) => {\n const corsHeaders = {\n ...controller.DEFAULT_CORS_HEADERS,\n ...controller.getCorsHeaders(new Headers(req.headers as any)),\n };\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204, corsHeaders);\n res.end();\n return;\n }\n\n Object.entries(corsHeaders).forEach(([key, value]) => {\n res.setHeader(key, value);\n });\n\n next(req, res);\n });\n}\n\n/**\n * Whether the express app already handles the root route, in which case\n * Colyseus must not register its own default \"/\" endpoint over it.\n */\nfunction hasExpressRootRoute(expressApp: express.Application) {\n return expressRouterStack(expressApp).some((layer: any) =>\n layer.match('/') && !['query', 'expressInit'].includes(layer.name));\n}\n\n/**\n * The app's router stack, or an empty stack if it has no routes yet.\n *\n * express v5 exposes the router as `app.router`; v4 exposes it as `app._router`\n * and only creates it once the first route/middleware is registered.\n */\nfunction expressRouterStack(expressApp: express.Application): any[] {\n const app = expressApp as any;\n\n if (app?._router?.stack) {\n return app._router.stack;\n }\n\n try {\n return app?.router?.stack ?? [];\n } catch (e) {\n return []; // express v4 throws on `app.router` \u2014 no routes registered\n }\n}\n\nexport function createRouter<\n E extends Record<string, Endpoint>,\n Config extends RouterConfig\n>(endpoints: E, config: Config = {} as Config) {\n return createBetterCallRouter({ ...endpoints }, {\n // better-call's /api/reference page dumps the full API surface\n // unauthenticated \u2014 opt back in by passing `openapi` explicitly.\n openapi: { disabled: true },\n ...config,\n });\n}\n\nexport interface BasicAuthOptions {\n /** username \u2192 password. The common static case. */\n users?: Record<string, string>;\n /** Custom validator (e.g. DB-backed). Takes precedence over `users`. */\n validate?: (username: string, password: string) => boolean | Promise<boolean>;\n /** Realm shown in the browser prompt. Default 'Restricted'. */\n realm?: string;\n}\n\n/**\n * HTTP Basic Auth middleware. Drop into any endpoint's `use:` slot to gate\n * it behind a browser credentials prompt:\n *\n * playground({ use: [basicAuth({ users: { admin: 's3cret' } })] })\n */\nexport function basicAuth(opts: BasicAuthOptions) {\n const { users, validate } = opts;\n if (!users && !validate) {\n throw new Error('[basicAuth] provide `users` or `validate`');\n }\n // Realm is interpolated into a header \u2014 strip `\"` so it can't break out.\n const challenge = `Basic realm=\"${(opts.realm ?? 'Restricted').replace(/\"/g, '')}\", charset=\"UTF-8\"`;\n\n return createMiddleware(async (ctx) => {\n const creds = parseBasicHeader(ctx.getHeader('authorization'));\n const ok = !!creds && (validate\n ? await validate(creds.username, creds.password)\n : staticCheck(users!, creds.username, creds.password));\n if (!ok) {\n throw new APIError(401, { message: 'authentication required' }, { 'WWW-Authenticate': challenge });\n }\n });\n}\n\nfunction parseBasicHeader(header: string | null | undefined) {\n if (!header) { return null; }\n const sep = header.indexOf(' ');\n if (sep < 0 || header.slice(0, sep).toLowerCase() !== 'basic') { return null; }\n let decoded: string;\n try { decoded = Buffer.from(header.slice(sep + 1), 'base64').toString('utf8'); } catch { return null; }\n const colon = decoded.indexOf(':');\n if (colon < 0) { return null; }\n return { username: decoded.slice(0, colon), password: decoded.slice(colon + 1) };\n}\n\nfunction staticCheck(users: Record<string, string>, username: string, password: string): boolean {\n const expected = Object.prototype.hasOwnProperty.call(users, username) ? users[username] : undefined;\n // Compare even for an unknown user so reject timing doesn't reveal which\n // usernames exist.\n return safeEqual(password, expected ?? '\\0') && expected !== undefined;\n}\n\n// Hash both sides first: equalizes length (timingSafeEqual throws on a\n// length mismatch, which would itself leak the secret's length).\nfunction safeEqual(a: string, b: string): boolean {\n return timingSafeEqual(\n createHash('sha256').update(a).digest(),\n createHash('sha256').update(b).digest(),\n );\n}\n\n// ---------------------------------------------------------------------------\n// dualModeEndpoints \u2014 shared express-compat layer for @colyseus/admin,\n// @colyseus/monitor, @colyseus/playground. Builds the two local routers\n// (specific = no catch-all, full = everything) and the matching node\n// handlers, then packages the express middleware so the return value works\n// both as `{...spread}` into createRouter AND as `app.use(\"/\", x)` middleware.\n// ---------------------------------------------------------------------------\n\nexport type ExpressMiddleware = (\n req: IncomingMessage,\n res: ServerResponse,\n next: (err?: any) => void,\n) => void;\n\nexport type NodeHandler = (req: IncomingMessage, res: ServerResponse) => Promise<void>;\n\nexport interface DualModeHelpers {\n specificRouter: Router;\n specificHandler: NodeHandler;\n fullRouter: Router;\n fullHandler: NodeHandler;\n}\n\nexport function dualModeEndpoints<E extends Record<string, Endpoint>>(\n endpoints: E,\n opts: {\n /** Key in `endpoints` whose path is a catch-all. Excluded from `specificRouter` so it doesn't eat fall-through decisions. */\n catchAllKey?: keyof E;\n /** Build the express middleware given the pre-built routers + node handlers. */\n buildMiddleware: (helpers: DualModeHelpers) => ExpressMiddleware;\n },\n): ExpressMiddleware & E {\n const fullRouter = createRouter(endpoints);\n const fullHandler = toNodeHandler(fullRouter.handler) as NodeHandler;\n\n const specificEndpoints = opts.catchAllKey\n ? Object.fromEntries(\n Object.entries(endpoints).filter(([k]) => k !== opts.catchAllKey),\n ) as Partial<E>\n : endpoints;\n const specificRouter = createRouter(specificEndpoints as E);\n const specificHandler = toNodeHandler(specificRouter.handler) as NodeHandler;\n\n const middleware = opts.buildMiddleware({\n specificRouter, specificHandler, fullRouter, fullHandler,\n });\n return Object.assign(middleware, endpoints) as ExpressMiddleware & E;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type express from \"express\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\nimport { createHash, timingSafeEqual } from \"node:crypto\";\nimport { type Endpoint, type Router, type RouterConfig, createRouter as createBetterCallRouter, createEndpoint, createMiddleware, APIError } from \"@colyseus/better-call\";\nimport { toNodeHandler, getRequest, setResponse } from \"@colyseus/better-call/node\";\nimport { Transport } from \"../Transport.ts\";\nimport { controller } from \"../matchmaker/controller.ts\";\nimport pkg from \"../../package.json\" with { type: \"json\" };\n\nexport {\n createEndpoint,\n createMiddleware,\n createInternalContext,\n\n // Re-export every type reachable from an inferred type below \u2014 consumers\n // depend on @colyseus/core, not @colyseus/better-call, and cannot name it\n // under pnpm's isolated node_modules (TS2742/TS2883).\n type Router,\n type RouterConfig,\n type Endpoint,\n type EndpointHandler,\n type EndpointOptions,\n type EndpointContext,\n type StrictEndpoint,\n type StandardSchemaV1,\n type MiddlewareOptions,\n type MiddlewareInputContext,\n type CookieOptions,\n type CookiePrefixOptions,\n type Status,\n} from \"@colyseus/better-call\";\n\nexport { toNodeHandler };\n\nexport function bindRouterToTransport(transport: Transport, router: Router, useExpress: boolean) {\n // add default \"/__healthcheck\" endpoint\n router.addEndpoint(createEndpoint(\"/__healthcheck\", { method: \"GET\" }, async (ctx) => {\n return new Response(\"OK\", { status: 200 });\n }));\n\n const server = transport.server;\n\n // check if the server is bound to an express app\n const expressApp: express.Application = (useExpress)\n ? transport.getExpressApp() as express.Application\n // fallback searching for express app in server listeners\n : server?.listeners('request').find((listener: Function) => listener.name === \"app\" && listener['mountpath'] === '/') as express.Application;\n\n // add default \"/\" route, if not provided.\n const hasRootRoute = (\n // check if express app has a root route\n (expressApp && hasExpressRootRoute(expressApp)) ||\n\n // check if router has a root route\n Object.values(router.endpoints).some(endpoint => endpoint.path === \"/\")\n );\n\n if (!hasRootRoute) {\n router.addEndpoint(createEndpoint(\"/\", { method: \"GET\" }, async (ctx) => {\n return new Response(`Colyseus ${pkg.version}`, { status: 200 });\n }));\n }\n\n // use custom bindRouter method if provided\n if (!server && transport.bindRouter) {\n transport.bindRouter(router);\n return;\n }\n\n // which route handler to use\n // (router + fallback to express, or just router)\n let next: any;\n\n if (expressApp) {\n server.removeListener('request', expressApp);\n\n next = async (req: IncomingMessage, res: ServerResponse) => {\n // check if the route is defined in the router\n // if so, use the router handler, otherwise fallback to express\n if (router.findRoute(req.method, req.url.split('?')[0]) !== undefined) {\n const protocol = req.headers[\"x-forwarded-proto\"] || ((req.socket as any).encrypted ? \"https\" : \"http\");\n const base = `${protocol}://${req.headers[\":authority\"] || req.headers.host}`;\n const response = await router.handler(getRequest({ base, request: req }));\n return setResponse(res, response);\n\n } else {\n return expressApp['handle'](req, res);\n }\n };\n\n } else {\n next = toNodeHandler(router.handler);\n }\n\n // handle cors headers for all requests by default\n server.prependListener('request', (req: IncomingMessage, res: ServerResponse) => {\n const corsHeaders = {\n ...controller.DEFAULT_CORS_HEADERS,\n ...controller.getCorsHeaders(new Headers(req.headers as any)),\n };\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204, corsHeaders);\n res.end();\n return;\n }\n\n Object.entries(corsHeaders).forEach(([key, value]) => {\n res.setHeader(key, value);\n });\n\n next(req, res);\n });\n}\n\n/**\n * Whether the express app already handles the root route, in which case\n * Colyseus must not register its own default \"/\" endpoint over it.\n */\nfunction hasExpressRootRoute(expressApp: express.Application) {\n return expressRouterStack(expressApp).some((layer: any) =>\n layer.match('/') && !['query', 'expressInit'].includes(layer.name));\n}\n\n/**\n * The app's router stack, or an empty stack if it has no routes yet.\n *\n * express v5 exposes the router as `app.router`; v4 exposes it as `app._router`\n * and only creates it once the first route/middleware is registered.\n */\nfunction expressRouterStack(expressApp: express.Application): any[] {\n const app = expressApp as any;\n\n if (app?._router?.stack) {\n return app._router.stack;\n }\n\n try {\n return app?.router?.stack ?? [];\n } catch (e) {\n return []; // express v4 throws on `app.router` \u2014 no routes registered\n }\n}\n\nexport function createRouter<\n E extends Record<string, Endpoint>,\n Config extends RouterConfig\n>(endpoints: E, config: Config = {} as Config) {\n const onError = config?.onError;\n return createBetterCallRouter({ ...endpoints }, {\n // better-call's /api/reference page dumps the full API surface\n // unauthenticated \u2014 opt back in by passing `openapi` explicitly.\n openapi: { disabled: true },\n ...config,\n // Otherwise a malformed body is a 500 plus a stack trace on stderr: log\n // noise any anonymous client can trigger at will. Matched on the message\n // because `onError` receives no request context to test against.\n onError: async (error: unknown) => (error instanceof SyntaxError && error.message.includes('JSON'))\n ? Response.json({ error: 'malformed request body' }, { status: 400 })\n : await onError?.(error),\n });\n}\n\nexport interface BasicAuthOptions {\n /** username \u2192 password. The common static case. */\n users?: Record<string, string>;\n /** Custom validator (e.g. DB-backed). Takes precedence over `users`. */\n validate?: (username: string, password: string) => boolean | Promise<boolean>;\n /** Realm shown in the browser prompt. Default 'Restricted'. */\n realm?: string;\n}\n\n/**\n * HTTP Basic Auth middleware. Drop into any endpoint's `use:` slot to gate\n * it behind a browser credentials prompt:\n *\n * playground({ use: [basicAuth({ users: { admin: 's3cret' } })] })\n */\nexport function basicAuth(opts: BasicAuthOptions) {\n const { users, validate } = opts;\n if (!users && !validate) {\n throw new Error('[basicAuth] provide `users` or `validate`');\n }\n // Realm is interpolated into a header \u2014 strip `\"` so it can't break out.\n const challenge = `Basic realm=\"${(opts.realm ?? 'Restricted').replace(/\"/g, '')}\", charset=\"UTF-8\"`;\n\n return createMiddleware(async (ctx) => {\n const creds = parseBasicHeader(ctx.getHeader('authorization'));\n const ok = !!creds && (validate\n ? await validate(creds.username, creds.password)\n : staticCheck(users!, creds.username, creds.password));\n if (!ok) {\n throw new APIError(401, { message: 'authentication required' }, { 'WWW-Authenticate': challenge });\n }\n });\n}\n\nfunction parseBasicHeader(header: string | null | undefined) {\n if (!header) { return null; }\n const sep = header.indexOf(' ');\n if (sep < 0 || header.slice(0, sep).toLowerCase() !== 'basic') { return null; }\n let decoded: string;\n try { decoded = Buffer.from(header.slice(sep + 1), 'base64').toString('utf8'); } catch { return null; }\n const colon = decoded.indexOf(':');\n if (colon < 0) { return null; }\n return { username: decoded.slice(0, colon), password: decoded.slice(colon + 1) };\n}\n\nfunction staticCheck(users: Record<string, string>, username: string, password: string): boolean {\n const expected = Object.prototype.hasOwnProperty.call(users, username) ? users[username] : undefined;\n // Compare even for an unknown user so reject timing doesn't reveal which\n // usernames exist.\n return safeEqual(password, expected ?? '\\0') && expected !== undefined;\n}\n\n// Hash both sides first: equalizes length (timingSafeEqual throws on a\n// length mismatch, which would itself leak the secret's length).\nfunction safeEqual(a: string, b: string): boolean {\n return timingSafeEqual(\n createHash('sha256').update(a).digest(),\n createHash('sha256').update(b).digest(),\n );\n}\n\n// ---------------------------------------------------------------------------\n// dualModeEndpoints \u2014 shared express-compat layer for @colyseus/admin,\n// @colyseus/monitor, @colyseus/playground. Builds the two local routers\n// (specific = no catch-all, full = everything) and the matching node\n// handlers, then packages the express middleware so the return value works\n// both as `{...spread}` into createRouter AND as `app.use(\"/\", x)` middleware.\n// ---------------------------------------------------------------------------\n\nexport type ExpressMiddleware = (\n req: IncomingMessage,\n res: ServerResponse,\n next: (err?: any) => void,\n) => void;\n\nexport type NodeHandler = (req: IncomingMessage, res: ServerResponse) => Promise<void>;\n\nexport interface DualModeHelpers {\n specificRouter: Router;\n specificHandler: NodeHandler;\n fullRouter: Router;\n fullHandler: NodeHandler;\n}\n\nexport function dualModeEndpoints<E extends Record<string, Endpoint>>(\n endpoints: E,\n opts: {\n /** Key in `endpoints` whose path is a catch-all. Excluded from `specificRouter` so it doesn't eat fall-through decisions. */\n catchAllKey?: keyof E;\n /** Build the express middleware given the pre-built routers + node handlers. */\n buildMiddleware: (helpers: DualModeHelpers) => ExpressMiddleware;\n },\n): ExpressMiddleware & E {\n const fullRouter = createRouter(endpoints);\n const fullHandler = toNodeHandler(fullRouter.handler) as NodeHandler;\n\n const specificEndpoints = opts.catchAllKey\n ? Object.fromEntries(\n Object.entries(endpoints).filter(([k]) => k !== opts.catchAllKey),\n ) as Partial<E>\n : endpoints;\n const specificRouter = createRouter(specificEndpoints as E);\n const specificHandler = toNodeHandler(specificRouter.handler) as NodeHandler;\n\n const middleware = opts.buildMiddleware({\n specificRouter, specificHandler, fullRouter, fullHandler,\n });\n return Object.assign(middleware, endpoints) as ExpressMiddleware & E;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["import_better_call", "pkg", "createBetterCallRouter"]
7
7
  }
@@ -2,7 +2,7 @@ import type { IncomingMessage, ServerResponse } from "http";
2
2
  import { type Endpoint, type Router, type RouterConfig } from "@colyseus/better-call";
3
3
  import { toNodeHandler } from "@colyseus/better-call/node";
4
4
  import { Transport } from "../Transport.ts";
5
- export { createEndpoint, createMiddleware, createInternalContext, type Router, type RouterConfig, type Endpoint, type EndpointHandler, type EndpointOptions, type EndpointContext, type StrictEndpoint, } from "@colyseus/better-call";
5
+ export { createEndpoint, createMiddleware, createInternalContext, type Router, type RouterConfig, type Endpoint, type EndpointHandler, type EndpointOptions, type EndpointContext, type StrictEndpoint, type StandardSchemaV1, type MiddlewareOptions, type MiddlewareInputContext, type CookieOptions, type CookiePrefixOptions, type Status, } from "@colyseus/better-call";
6
6
  export { toNodeHandler };
7
7
  export declare function bindRouterToTransport(transport: Transport, router: Router, useExpress: boolean): void;
8
8
  export declare function createRouter<E extends Record<string, Endpoint>, Config extends RouterConfig>(endpoints: E, config?: Config): {
@@ -77,11 +77,16 @@ function expressRouterStack(expressApp) {
77
77
  }
78
78
  }
79
79
  function createRouter(endpoints, config = {}) {
80
+ const onError = config?.onError;
80
81
  return createBetterCallRouter({ ...endpoints }, {
81
82
  // better-call's /api/reference page dumps the full API surface
82
83
  // unauthenticated — opt back in by passing `openapi` explicitly.
83
84
  openapi: { disabled: true },
84
- ...config
85
+ ...config,
86
+ // Otherwise a malformed body is a 500 plus a stack trace on stderr: log
87
+ // noise any anonymous client can trigger at will. Matched on the message
88
+ // because `onError` receives no request context to test against.
89
+ onError: async (error) => error instanceof SyntaxError && error.message.includes("JSON") ? Response.json({ error: "malformed request body" }, { status: 400 }) : await onError?.(error)
85
90
  });
86
91
  }
87
92
  function basicAuth(opts) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/router/index.ts"],
4
- "sourcesContent": ["import type express from \"express\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\nimport { createHash, timingSafeEqual } from \"node:crypto\";\nimport { type Endpoint, type Router, type RouterConfig, createRouter as createBetterCallRouter, createEndpoint, createMiddleware, APIError } from \"@colyseus/better-call\";\nimport { toNodeHandler, getRequest, setResponse } from \"@colyseus/better-call/node\";\nimport { Transport } from \"../Transport.ts\";\nimport { controller } from \"../matchmaker/controller.ts\";\nimport pkg from \"../../package.json\" with { type: \"json\" };\n\nexport {\n createEndpoint,\n createMiddleware,\n createInternalContext,\n\n // Re-export types needed for declaration emit\n type Router,\n type RouterConfig,\n type Endpoint,\n type EndpointHandler,\n type EndpointOptions,\n type EndpointContext,\n type StrictEndpoint,\n} from \"@colyseus/better-call\";\n\nexport { toNodeHandler };\n\nexport function bindRouterToTransport(transport: Transport, router: Router, useExpress: boolean) {\n // add default \"/__healthcheck\" endpoint\n router.addEndpoint(createEndpoint(\"/__healthcheck\", { method: \"GET\" }, async (ctx) => {\n return new Response(\"OK\", { status: 200 });\n }));\n\n const server = transport.server;\n\n // check if the server is bound to an express app\n const expressApp: express.Application = (useExpress)\n ? transport.getExpressApp() as express.Application\n // fallback searching for express app in server listeners\n : server?.listeners('request').find((listener: Function) => listener.name === \"app\" && listener['mountpath'] === '/') as express.Application;\n\n // add default \"/\" route, if not provided.\n const hasRootRoute = (\n // check if express app has a root route\n (expressApp && hasExpressRootRoute(expressApp)) ||\n\n // check if router has a root route\n Object.values(router.endpoints).some(endpoint => endpoint.path === \"/\")\n );\n\n if (!hasRootRoute) {\n router.addEndpoint(createEndpoint(\"/\", { method: \"GET\" }, async (ctx) => {\n return new Response(`Colyseus ${pkg.version}`, { status: 200 });\n }));\n }\n\n // use custom bindRouter method if provided\n if (!server && transport.bindRouter) {\n transport.bindRouter(router);\n return;\n }\n\n // which route handler to use\n // (router + fallback to express, or just router)\n let next: any;\n\n if (expressApp) {\n server.removeListener('request', expressApp);\n\n next = async (req: IncomingMessage, res: ServerResponse) => {\n // check if the route is defined in the router\n // if so, use the router handler, otherwise fallback to express\n if (router.findRoute(req.method, req.url.split('?')[0]) !== undefined) {\n const protocol = req.headers[\"x-forwarded-proto\"] || ((req.socket as any).encrypted ? \"https\" : \"http\");\n const base = `${protocol}://${req.headers[\":authority\"] || req.headers.host}`;\n const response = await router.handler(getRequest({ base, request: req }));\n return setResponse(res, response);\n\n } else {\n return expressApp['handle'](req, res);\n }\n };\n\n } else {\n next = toNodeHandler(router.handler);\n }\n\n // handle cors headers for all requests by default\n server.prependListener('request', (req: IncomingMessage, res: ServerResponse) => {\n const corsHeaders = {\n ...controller.DEFAULT_CORS_HEADERS,\n ...controller.getCorsHeaders(new Headers(req.headers as any)),\n };\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204, corsHeaders);\n res.end();\n return;\n }\n\n Object.entries(corsHeaders).forEach(([key, value]) => {\n res.setHeader(key, value);\n });\n\n next(req, res);\n });\n}\n\n/**\n * Whether the express app already handles the root route, in which case\n * Colyseus must not register its own default \"/\" endpoint over it.\n */\nfunction hasExpressRootRoute(expressApp: express.Application) {\n return expressRouterStack(expressApp).some((layer: any) =>\n layer.match('/') && !['query', 'expressInit'].includes(layer.name));\n}\n\n/**\n * The app's router stack, or an empty stack if it has no routes yet.\n *\n * express v5 exposes the router as `app.router`; v4 exposes it as `app._router`\n * and only creates it once the first route/middleware is registered.\n */\nfunction expressRouterStack(expressApp: express.Application): any[] {\n const app = expressApp as any;\n\n if (app?._router?.stack) {\n return app._router.stack;\n }\n\n try {\n return app?.router?.stack ?? [];\n } catch (e) {\n return []; // express v4 throws on `app.router` \u2014 no routes registered\n }\n}\n\nexport function createRouter<\n E extends Record<string, Endpoint>,\n Config extends RouterConfig\n>(endpoints: E, config: Config = {} as Config) {\n return createBetterCallRouter({ ...endpoints }, {\n // better-call's /api/reference page dumps the full API surface\n // unauthenticated \u2014 opt back in by passing `openapi` explicitly.\n openapi: { disabled: true },\n ...config,\n });\n}\n\nexport interface BasicAuthOptions {\n /** username \u2192 password. The common static case. */\n users?: Record<string, string>;\n /** Custom validator (e.g. DB-backed). Takes precedence over `users`. */\n validate?: (username: string, password: string) => boolean | Promise<boolean>;\n /** Realm shown in the browser prompt. Default 'Restricted'. */\n realm?: string;\n}\n\n/**\n * HTTP Basic Auth middleware. Drop into any endpoint's `use:` slot to gate\n * it behind a browser credentials prompt:\n *\n * playground({ use: [basicAuth({ users: { admin: 's3cret' } })] })\n */\nexport function basicAuth(opts: BasicAuthOptions) {\n const { users, validate } = opts;\n if (!users && !validate) {\n throw new Error('[basicAuth] provide `users` or `validate`');\n }\n // Realm is interpolated into a header \u2014 strip `\"` so it can't break out.\n const challenge = `Basic realm=\"${(opts.realm ?? 'Restricted').replace(/\"/g, '')}\", charset=\"UTF-8\"`;\n\n return createMiddleware(async (ctx) => {\n const creds = parseBasicHeader(ctx.getHeader('authorization'));\n const ok = !!creds && (validate\n ? await validate(creds.username, creds.password)\n : staticCheck(users!, creds.username, creds.password));\n if (!ok) {\n throw new APIError(401, { message: 'authentication required' }, { 'WWW-Authenticate': challenge });\n }\n });\n}\n\nfunction parseBasicHeader(header: string | null | undefined) {\n if (!header) { return null; }\n const sep = header.indexOf(' ');\n if (sep < 0 || header.slice(0, sep).toLowerCase() !== 'basic') { return null; }\n let decoded: string;\n try { decoded = Buffer.from(header.slice(sep + 1), 'base64').toString('utf8'); } catch { return null; }\n const colon = decoded.indexOf(':');\n if (colon < 0) { return null; }\n return { username: decoded.slice(0, colon), password: decoded.slice(colon + 1) };\n}\n\nfunction staticCheck(users: Record<string, string>, username: string, password: string): boolean {\n const expected = Object.prototype.hasOwnProperty.call(users, username) ? users[username] : undefined;\n // Compare even for an unknown user so reject timing doesn't reveal which\n // usernames exist.\n return safeEqual(password, expected ?? '\\0') && expected !== undefined;\n}\n\n// Hash both sides first: equalizes length (timingSafeEqual throws on a\n// length mismatch, which would itself leak the secret's length).\nfunction safeEqual(a: string, b: string): boolean {\n return timingSafeEqual(\n createHash('sha256').update(a).digest(),\n createHash('sha256').update(b).digest(),\n );\n}\n\n// ---------------------------------------------------------------------------\n// dualModeEndpoints \u2014 shared express-compat layer for @colyseus/admin,\n// @colyseus/monitor, @colyseus/playground. Builds the two local routers\n// (specific = no catch-all, full = everything) and the matching node\n// handlers, then packages the express middleware so the return value works\n// both as `{...spread}` into createRouter AND as `app.use(\"/\", x)` middleware.\n// ---------------------------------------------------------------------------\n\nexport type ExpressMiddleware = (\n req: IncomingMessage,\n res: ServerResponse,\n next: (err?: any) => void,\n) => void;\n\nexport type NodeHandler = (req: IncomingMessage, res: ServerResponse) => Promise<void>;\n\nexport interface DualModeHelpers {\n specificRouter: Router;\n specificHandler: NodeHandler;\n fullRouter: Router;\n fullHandler: NodeHandler;\n}\n\nexport function dualModeEndpoints<E extends Record<string, Endpoint>>(\n endpoints: E,\n opts: {\n /** Key in `endpoints` whose path is a catch-all. Excluded from `specificRouter` so it doesn't eat fall-through decisions. */\n catchAllKey?: keyof E;\n /** Build the express middleware given the pre-built routers + node handlers. */\n buildMiddleware: (helpers: DualModeHelpers) => ExpressMiddleware;\n },\n): ExpressMiddleware & E {\n const fullRouter = createRouter(endpoints);\n const fullHandler = toNodeHandler(fullRouter.handler) as NodeHandler;\n\n const specificEndpoints = opts.catchAllKey\n ? Object.fromEntries(\n Object.entries(endpoints).filter(([k]) => k !== opts.catchAllKey),\n ) as Partial<E>\n : endpoints;\n const specificRouter = createRouter(specificEndpoints as E);\n const specificHandler = toNodeHandler(specificRouter.handler) as NodeHandler;\n\n const middleware = opts.buildMiddleware({\n specificRouter, specificHandler, fullRouter, fullHandler,\n });\n return Object.assign(middleware, endpoints) as ExpressMiddleware & E;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type express from \"express\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\nimport { createHash, timingSafeEqual } from \"node:crypto\";\nimport { type Endpoint, type Router, type RouterConfig, createRouter as createBetterCallRouter, createEndpoint, createMiddleware, APIError } from \"@colyseus/better-call\";\nimport { toNodeHandler, getRequest, setResponse } from \"@colyseus/better-call/node\";\nimport { Transport } from \"../Transport.ts\";\nimport { controller } from \"../matchmaker/controller.ts\";\nimport pkg from \"../../package.json\" with { type: \"json\" };\n\nexport {\n createEndpoint,\n createMiddleware,\n createInternalContext,\n\n // Re-export every type reachable from an inferred type below \u2014 consumers\n // depend on @colyseus/core, not @colyseus/better-call, and cannot name it\n // under pnpm's isolated node_modules (TS2742/TS2883).\n type Router,\n type RouterConfig,\n type Endpoint,\n type EndpointHandler,\n type EndpointOptions,\n type EndpointContext,\n type StrictEndpoint,\n type StandardSchemaV1,\n type MiddlewareOptions,\n type MiddlewareInputContext,\n type CookieOptions,\n type CookiePrefixOptions,\n type Status,\n} from \"@colyseus/better-call\";\n\nexport { toNodeHandler };\n\nexport function bindRouterToTransport(transport: Transport, router: Router, useExpress: boolean) {\n // add default \"/__healthcheck\" endpoint\n router.addEndpoint(createEndpoint(\"/__healthcheck\", { method: \"GET\" }, async (ctx) => {\n return new Response(\"OK\", { status: 200 });\n }));\n\n const server = transport.server;\n\n // check if the server is bound to an express app\n const expressApp: express.Application = (useExpress)\n ? transport.getExpressApp() as express.Application\n // fallback searching for express app in server listeners\n : server?.listeners('request').find((listener: Function) => listener.name === \"app\" && listener['mountpath'] === '/') as express.Application;\n\n // add default \"/\" route, if not provided.\n const hasRootRoute = (\n // check if express app has a root route\n (expressApp && hasExpressRootRoute(expressApp)) ||\n\n // check if router has a root route\n Object.values(router.endpoints).some(endpoint => endpoint.path === \"/\")\n );\n\n if (!hasRootRoute) {\n router.addEndpoint(createEndpoint(\"/\", { method: \"GET\" }, async (ctx) => {\n return new Response(`Colyseus ${pkg.version}`, { status: 200 });\n }));\n }\n\n // use custom bindRouter method if provided\n if (!server && transport.bindRouter) {\n transport.bindRouter(router);\n return;\n }\n\n // which route handler to use\n // (router + fallback to express, or just router)\n let next: any;\n\n if (expressApp) {\n server.removeListener('request', expressApp);\n\n next = async (req: IncomingMessage, res: ServerResponse) => {\n // check if the route is defined in the router\n // if so, use the router handler, otherwise fallback to express\n if (router.findRoute(req.method, req.url.split('?')[0]) !== undefined) {\n const protocol = req.headers[\"x-forwarded-proto\"] || ((req.socket as any).encrypted ? \"https\" : \"http\");\n const base = `${protocol}://${req.headers[\":authority\"] || req.headers.host}`;\n const response = await router.handler(getRequest({ base, request: req }));\n return setResponse(res, response);\n\n } else {\n return expressApp['handle'](req, res);\n }\n };\n\n } else {\n next = toNodeHandler(router.handler);\n }\n\n // handle cors headers for all requests by default\n server.prependListener('request', (req: IncomingMessage, res: ServerResponse) => {\n const corsHeaders = {\n ...controller.DEFAULT_CORS_HEADERS,\n ...controller.getCorsHeaders(new Headers(req.headers as any)),\n };\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204, corsHeaders);\n res.end();\n return;\n }\n\n Object.entries(corsHeaders).forEach(([key, value]) => {\n res.setHeader(key, value);\n });\n\n next(req, res);\n });\n}\n\n/**\n * Whether the express app already handles the root route, in which case\n * Colyseus must not register its own default \"/\" endpoint over it.\n */\nfunction hasExpressRootRoute(expressApp: express.Application) {\n return expressRouterStack(expressApp).some((layer: any) =>\n layer.match('/') && !['query', 'expressInit'].includes(layer.name));\n}\n\n/**\n * The app's router stack, or an empty stack if it has no routes yet.\n *\n * express v5 exposes the router as `app.router`; v4 exposes it as `app._router`\n * and only creates it once the first route/middleware is registered.\n */\nfunction expressRouterStack(expressApp: express.Application): any[] {\n const app = expressApp as any;\n\n if (app?._router?.stack) {\n return app._router.stack;\n }\n\n try {\n return app?.router?.stack ?? [];\n } catch (e) {\n return []; // express v4 throws on `app.router` \u2014 no routes registered\n }\n}\n\nexport function createRouter<\n E extends Record<string, Endpoint>,\n Config extends RouterConfig\n>(endpoints: E, config: Config = {} as Config) {\n const onError = config?.onError;\n return createBetterCallRouter({ ...endpoints }, {\n // better-call's /api/reference page dumps the full API surface\n // unauthenticated \u2014 opt back in by passing `openapi` explicitly.\n openapi: { disabled: true },\n ...config,\n // Otherwise a malformed body is a 500 plus a stack trace on stderr: log\n // noise any anonymous client can trigger at will. Matched on the message\n // because `onError` receives no request context to test against.\n onError: async (error: unknown) => (error instanceof SyntaxError && error.message.includes('JSON'))\n ? Response.json({ error: 'malformed request body' }, { status: 400 })\n : await onError?.(error),\n });\n}\n\nexport interface BasicAuthOptions {\n /** username \u2192 password. The common static case. */\n users?: Record<string, string>;\n /** Custom validator (e.g. DB-backed). Takes precedence over `users`. */\n validate?: (username: string, password: string) => boolean | Promise<boolean>;\n /** Realm shown in the browser prompt. Default 'Restricted'. */\n realm?: string;\n}\n\n/**\n * HTTP Basic Auth middleware. Drop into any endpoint's `use:` slot to gate\n * it behind a browser credentials prompt:\n *\n * playground({ use: [basicAuth({ users: { admin: 's3cret' } })] })\n */\nexport function basicAuth(opts: BasicAuthOptions) {\n const { users, validate } = opts;\n if (!users && !validate) {\n throw new Error('[basicAuth] provide `users` or `validate`');\n }\n // Realm is interpolated into a header \u2014 strip `\"` so it can't break out.\n const challenge = `Basic realm=\"${(opts.realm ?? 'Restricted').replace(/\"/g, '')}\", charset=\"UTF-8\"`;\n\n return createMiddleware(async (ctx) => {\n const creds = parseBasicHeader(ctx.getHeader('authorization'));\n const ok = !!creds && (validate\n ? await validate(creds.username, creds.password)\n : staticCheck(users!, creds.username, creds.password));\n if (!ok) {\n throw new APIError(401, { message: 'authentication required' }, { 'WWW-Authenticate': challenge });\n }\n });\n}\n\nfunction parseBasicHeader(header: string | null | undefined) {\n if (!header) { return null; }\n const sep = header.indexOf(' ');\n if (sep < 0 || header.slice(0, sep).toLowerCase() !== 'basic') { return null; }\n let decoded: string;\n try { decoded = Buffer.from(header.slice(sep + 1), 'base64').toString('utf8'); } catch { return null; }\n const colon = decoded.indexOf(':');\n if (colon < 0) { return null; }\n return { username: decoded.slice(0, colon), password: decoded.slice(colon + 1) };\n}\n\nfunction staticCheck(users: Record<string, string>, username: string, password: string): boolean {\n const expected = Object.prototype.hasOwnProperty.call(users, username) ? users[username] : undefined;\n // Compare even for an unknown user so reject timing doesn't reveal which\n // usernames exist.\n return safeEqual(password, expected ?? '\\0') && expected !== undefined;\n}\n\n// Hash both sides first: equalizes length (timingSafeEqual throws on a\n// length mismatch, which would itself leak the secret's length).\nfunction safeEqual(a: string, b: string): boolean {\n return timingSafeEqual(\n createHash('sha256').update(a).digest(),\n createHash('sha256').update(b).digest(),\n );\n}\n\n// ---------------------------------------------------------------------------\n// dualModeEndpoints \u2014 shared express-compat layer for @colyseus/admin,\n// @colyseus/monitor, @colyseus/playground. Builds the two local routers\n// (specific = no catch-all, full = everything) and the matching node\n// handlers, then packages the express middleware so the return value works\n// both as `{...spread}` into createRouter AND as `app.use(\"/\", x)` middleware.\n// ---------------------------------------------------------------------------\n\nexport type ExpressMiddleware = (\n req: IncomingMessage,\n res: ServerResponse,\n next: (err?: any) => void,\n) => void;\n\nexport type NodeHandler = (req: IncomingMessage, res: ServerResponse) => Promise<void>;\n\nexport interface DualModeHelpers {\n specificRouter: Router;\n specificHandler: NodeHandler;\n fullRouter: Router;\n fullHandler: NodeHandler;\n}\n\nexport function dualModeEndpoints<E extends Record<string, Endpoint>>(\n endpoints: E,\n opts: {\n /** Key in `endpoints` whose path is a catch-all. Excluded from `specificRouter` so it doesn't eat fall-through decisions. */\n catchAllKey?: keyof E;\n /** Build the express middleware given the pre-built routers + node handlers. */\n buildMiddleware: (helpers: DualModeHelpers) => ExpressMiddleware;\n },\n): ExpressMiddleware & E {\n const fullRouter = createRouter(endpoints);\n const fullHandler = toNodeHandler(fullRouter.handler) as NodeHandler;\n\n const specificEndpoints = opts.catchAllKey\n ? Object.fromEntries(\n Object.entries(endpoints).filter(([k]) => k !== opts.catchAllKey),\n ) as Partial<E>\n : endpoints;\n const specificRouter = createRouter(specificEndpoints as E);\n const specificHandler = toNodeHandler(specificRouter.handler) as NodeHandler;\n\n const middleware = opts.buildMiddleware({\n specificRouter, specificHandler, fullRouter, fullHandler,\n });\n return Object.assign(middleware, endpoints) as ExpressMiddleware & E;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["createEndpoint", "createMiddleware"]
7
7
  }
@@ -38,13 +38,25 @@ var import_url = require("url");
38
38
  var matchMaker = __toESM(require("../MatchMaker.cjs"), 1);
39
39
  var import_node = require("@colyseus/better-call/node");
40
40
  var import_default_routes = require("./default_routes.cjs");
41
+ var MAX_BODY_SIZE = 1024 * 1024;
42
+ var badRequest = (status, message) => Object.assign(new Error(message), { status });
41
43
  function readBody(req) {
42
44
  return new Promise((resolve, reject) => {
43
45
  let data = "";
44
46
  req.on("data", (chunk) => {
45
47
  data += chunk.toString();
48
+ if (data.length > MAX_BODY_SIZE) {
49
+ reject(badRequest(413, "request body too large"));
50
+ req.destroy();
51
+ }
52
+ });
53
+ req.on("end", () => {
54
+ try {
55
+ resolve(data ? JSON.parse(data) : {});
56
+ } catch {
57
+ reject(badRequest(400, "malformed JSON body"));
58
+ }
46
59
  });
47
- req.on("end", () => resolve(data ? JSON.parse(data) : {}));
48
60
  req.on("error", reject);
49
61
  });
50
62
  }
@@ -112,10 +124,18 @@ function createNodeMatchmakingMiddleware() {
112
124
  return;
113
125
  }
114
126
  const [, method, roomName] = match;
127
+ let body;
128
+ try {
129
+ body = await readBody(req);
130
+ } catch (e) {
131
+ res.writeHead(e.status ?? 400, { ...corsHeaders, "content-type": "application/json" });
132
+ res.end(JSON.stringify({ error: e.message }));
133
+ return;
134
+ }
115
135
  try {
116
136
  const response = await (0, import_default_routes.postMatchmakeMethod)({
117
137
  params: { method, roomName },
118
- body: await readBody(req),
138
+ body,
119
139
  headers: req.headers,
120
140
  request: { headers },
121
141
  asResponse: true