@colyseus/core 0.18.3 → 0.18.5
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.
- package/build/Debug.cjs +1 -0
- package/build/Debug.cjs.map +1 -1
- package/build/IPC.cjs +1 -0
- package/build/IPC.cjs.map +1 -1
- package/build/Logger.cjs +1 -0
- package/build/Logger.cjs.map +1 -1
- package/build/MatchMaker.cjs +13 -5
- package/build/MatchMaker.cjs.map +2 -2
- package/build/MatchMaker.d.ts +10 -4
- package/build/MatchMaker.mjs +12 -5
- package/build/MatchMaker.mjs.map +2 -2
- package/build/Protocol.cjs +1 -0
- package/build/Protocol.cjs.map +1 -1
- package/build/Rewind.cjs +1 -0
- package/build/Rewind.cjs.map +1 -1
- package/build/Room.cjs +113 -3
- package/build/Room.cjs.map +2 -2
- package/build/Room.d.ts +58 -1
- package/build/Room.mjs +112 -3
- package/build/Room.mjs.map +2 -2
- package/build/RoomMessages.cjs +3 -1
- package/build/RoomMessages.cjs.map +2 -2
- package/build/RoomMessages.d.ts +3 -1
- package/build/RoomMessages.mjs +2 -1
- package/build/RoomMessages.mjs.map +2 -2
- package/build/RoomPlugin.cjs +1 -0
- package/build/RoomPlugin.cjs.map +1 -1
- package/build/Server.cjs +1 -0
- package/build/Server.cjs.map +1 -1
- package/build/Stats.cjs +1 -0
- package/build/Stats.cjs.map +1 -1
- package/build/Transport.cjs +39 -0
- package/build/Transport.cjs.map +2 -2
- package/build/Transport.d.ts +66 -1
- package/build/Transport.mjs +37 -1
- package/build/Transport.mjs.map +2 -2
- package/build/errors/RoomExceptions.cjs +1 -0
- package/build/errors/RoomExceptions.cjs.map +1 -1
- package/build/errors/SeatReservationError.cjs +1 -0
- package/build/errors/SeatReservationError.cjs.map +1 -1
- package/build/errors/ServerError.cjs +1 -0
- package/build/errors/ServerError.cjs.map +1 -1
- package/build/index.cjs +5 -0
- package/build/index.cjs.map +2 -2
- package/build/index.d.ts +1 -1
- package/build/index.mjs +3 -1
- package/build/index.mjs.map +2 -2
- package/build/input/InputBuffer.cjs +1 -0
- package/build/input/InputBuffer.cjs.map +1 -1
- package/build/input/RoomInput.cjs +64 -3
- package/build/input/RoomInput.cjs.map +2 -2
- package/build/input/RoomInput.d.ts +16 -2
- package/build/input/RoomInput.mjs +63 -3
- package/build/input/RoomInput.mjs.map +2 -2
- package/build/input/types.cjs +1 -0
- package/build/input/types.cjs.map +1 -1
- package/build/internal.cjs +1 -0
- package/build/internal.cjs.map +1 -1
- package/build/matchmaker/Lobby.cjs +1 -0
- package/build/matchmaker/Lobby.cjs.map +1 -1
- package/build/matchmaker/LocalDriver/LocalDriver.cjs +1 -0
- package/build/matchmaker/LocalDriver/LocalDriver.cjs.map +1 -1
- package/build/matchmaker/LocalDriver/Query.cjs +1 -0
- package/build/matchmaker/LocalDriver/Query.cjs.map +1 -1
- package/build/matchmaker/RegisteredHandler.cjs +1 -0
- package/build/matchmaker/RegisteredHandler.cjs.map +1 -1
- package/build/matchmaker/controller.cjs +1 -0
- package/build/matchmaker/controller.cjs.map +1 -1
- package/build/matchmaker/driver.cjs +1 -0
- package/build/matchmaker/driver.cjs.map +2 -2
- package/build/matchmaker/driver.d.ts +4 -2
- package/build/matchmaker/driver.mjs.map +1 -1
- package/build/presence/LocalPresence.cjs +1 -0
- package/build/presence/LocalPresence.cjs.map +1 -1
- package/build/presence/Presence.cjs +1 -0
- package/build/presence/Presence.cjs.map +1 -1
- package/build/rooms/LobbyRoom.cjs +1 -0
- package/build/rooms/LobbyRoom.cjs.map +1 -1
- package/build/rooms/QueueRoom.cjs +1 -0
- package/build/rooms/QueueRoom.cjs.map +1 -1
- package/build/rooms/RelayRoom.cjs +1 -0
- package/build/rooms/RelayRoom.cjs.map +1 -1
- package/build/router/default_routes.cjs +4 -8
- package/build/router/default_routes.cjs.map +2 -2
- package/build/router/default_routes.mjs +4 -9
- package/build/router/default_routes.mjs.map +2 -2
- package/build/router/index.cjs +7 -1
- package/build/router/index.cjs.map +2 -2
- package/build/router/index.mjs +6 -1
- package/build/router/index.mjs.map +2 -2
- package/build/router/node.cjs +23 -2
- package/build/router/node.cjs.map +2 -2
- package/build/router/node.mjs +22 -2
- package/build/router/node.mjs.map +2 -2
- package/build/serializer/NoneSerializer.cjs +1 -0
- package/build/serializer/NoneSerializer.cjs.map +1 -1
- package/build/serializer/SchemaSerializer.cjs +98 -0
- package/build/serializer/SchemaSerializer.cjs.map +2 -2
- package/build/serializer/SchemaSerializer.d.ts +25 -0
- package/build/serializer/SchemaSerializer.mjs +98 -1
- package/build/serializer/SchemaSerializer.mjs.map +2 -2
- package/build/serializer/Serializer.cjs +1 -0
- package/build/serializer/Serializer.cjs.map +2 -2
- package/build/serializer/Serializer.d.ts +15 -0
- package/build/utils/DevMode.cjs +1 -0
- package/build/utils/DevMode.cjs.map +1 -1
- package/build/utils/Env.cjs +1 -0
- package/build/utils/Env.cjs.map +1 -1
- package/build/utils/StandardSchema.cjs +1 -0
- package/build/utils/StandardSchema.cjs.map +1 -1
- package/build/utils/UserSessionIndex.cjs +1 -0
- package/build/utils/UserSessionIndex.cjs.map +1 -1
- package/build/utils/Utils.cjs +1 -0
- package/build/utils/Utils.cjs.map +2 -2
- package/build/utils/Utils.d.ts +12 -0
- package/build/utils/Utils.mjs.map +2 -2
- package/build/utils/nanoevents.cjs +5 -1
- package/build/utils/nanoevents.cjs.map +2 -2
- package/build/utils/nanoevents.d.ts +3 -1
- package/build/utils/nanoevents.mjs +4 -1
- package/build/utils/nanoevents.mjs.map +2 -2
- package/package.json +8 -7
- package/src/MatchMaker.ts +53 -15
- package/src/Room.ts +145 -4
- package/src/RoomMessages.ts +2 -1
- package/src/Transport.ts +132 -4
- package/src/index.ts +1 -1
- package/src/input/RoomInput.ts +76 -3
- package/src/matchmaker/driver.ts +4 -2
- package/src/router/default_routes.ts +4 -9
- package/src/router/index.ts +7 -0
- package/src/router/node.ts +26 -2
- package/src/serializer/SchemaSerializer.ts +146 -1
- package/src/serializer/Serializer.ts +17 -0
- package/src/utils/Utils.ts +18 -0
- package/src/utils/nanoevents.ts +4 -1
package/build/RoomMessages.cjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const __cjsImportMetaUrl = require('node:url').pathToFileURL(__filename).href;
|
|
1
2
|
"use strict";
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -73,7 +74,8 @@ var RoomMessages = class {
|
|
|
73
74
|
this.events = (0, import_nanoevents.createNanoEvents)();
|
|
74
75
|
/** Per-type StandardSchema validators. Public for the same reason as
|
|
75
76
|
* {@link events} (`onMessageValidators`). */
|
|
76
|
-
|
|
77
|
+
// null-prototype: keyed by client-supplied message type (colyseus/colyseus#951)
|
|
78
|
+
this.validators = /* @__PURE__ */ Object.create(null);
|
|
77
79
|
this.room = room;
|
|
78
80
|
}
|
|
79
81
|
/**
|
|
@@ -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": "
|
|
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
|
}
|
package/build/RoomMessages.d.ts
CHANGED
|
@@ -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
|
package/build/RoomMessages.mjs
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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
|
}
|
package/build/RoomPlugin.cjs
CHANGED
package/build/RoomPlugin.cjs.map
CHANGED
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/RoomPlugin.ts"],
|
|
4
4
|
"sourcesContent": ["/**\n * Room plugins.\n *\n * A plugin is a composable extension to a Room \u2014 it can declare message\n * handlers, attach lifecycle hooks, and expose public methods that the\n * room can call (`this.plugins.<key>.someMethod()`). The framework\n * instantiates one plugin per room, sets `this.room` after the room is\n * constructed, then merges the plugin's `messages` with the room's own\n * and wires its lifecycle hooks alongside the room's.\n *\n * Example: a tiny chat plugin contributing a single message handler:\n *\n * class ChatPlugin extends RoomPlugin {\n * private history: string[] = [];\n * constructor(private opts: { historyLimit: number }) { super(); }\n *\n * messages = {\n * chat: (client: Client, msg: { text: string }) => {\n * this.history.push(msg.text);\n * if (this.history.length > this.opts.historyLimit) this.history.shift();\n * this.room.broadcast('chat', { from: client.userId, text: msg.text });\n * },\n * };\n *\n * // Public \u2014 callable as `this.plugins.chat.getHistory()` from the room\n * getHistory(): readonly string[] { return this.history; }\n * }\n *\n * Use via `definePlugins`:\n *\n * class MyRoom extends Room<{ state: MyState }> {\n * plugins = definePlugins({\n * chat: new ChatPlugin({ historyLimit: 100 }),\n * });\n * }\n */\nimport type { Room } from './Room.ts';\nimport type { AuthContext, Client } from './Transport.ts';\nimport type { Messages } from '@colyseus/shared-types';\n\n/**\n * Ordering hint for a plugin's lifecycle hook relative to the room's own\n * hook. Sensible per-hook defaults are applied when omitted (see\n * `Room.__init` for the exact ordering policy).\n *\n * onCreate / onAuth / onJoin \u2192 plugins run BEFORE room (guards + setup)\n * onLeave / onDispose \u2192 plugins run AFTER room (capture final state)\n */\nexport interface RoomPluginOrder {\n onCreate?: 'before' | 'after';\n onAuth?: 'before' | 'after';\n onJoin?: 'before' | 'after';\n onLeave?: 'before' | 'after';\n onDispose?: 'before' | 'after';\n}\n\n/**\n * Base class for room plugins. Subclass to define a plugin; the framework\n * sets `this.room` after the host room is fully constructed.\n *\n * Don't access `this.room` from the plugin's constructor \u2014 it hasn't been\n * wired yet. Everything room-dependent goes in `onCreate` / `onJoin` /\n * etc. or in public methods that are called from the room post-init.\n *\n * @typeParam This - The Room subclass this plugin is attached to. Narrow\n * for schema-driven plugins that need a specific state shape, e.g.\n * `class PhysicsPlugin extends RoomPlugin<Room<{ state: PhysicsContract }>>`.\n */\nexport abstract class RoomPlugin<This extends Room = Room> {\n /**\n * Live room reference. Wired by the framework at __init, AFTER the\n * room's own construction \u2014 accessing it from the plugin's\n * constructor throws.\n */\n protected readonly room!: This;\n\n /**\n * Canonical key for the plugin when registered via `definePlugins([...])`.\n * Declared on the subclass with `as const` so the literal type flows\n * into `room.plugins.<key>`:\n *\n * class ChatPlugin extends RoomPlugin {\n * readonly pluginName = 'chat' as const;\n * }\n *\n * Optional under the keyed-record form (`definePlugins({ chat: ... })`),\n * required under the array form. Must stay `public` so `extends` /\n * `keyof` can see the literal \u2014 those are blind to protected from\n * outside the class. End-user autocomplete hides it via `Omit` in\n * `definePlugins`'s return type.\n *\n * For multi-instance use, accept the name at construction:\n *\n * readonly pluginName: string;\n * constructor(name = 'chat') { super(); this.pluginName = name; }\n */\n readonly pluginName?: string;\n\n /**\n * Declarative message handlers \u2014 merged into the room's `messages` at\n * __init. Conflict against the room's own key: room wins. Conflict\n * between two plugins: throws at __init.\n */\n protected messages?: Messages<This>;\n\n /** Optional per-hook ordering vs the room's own hook. */\n protected order?: RoomPluginOrder;\n\n // Lifecycle hooks \u2014 override any subset. `protected` so they don't\n // leak into `this.plugins.<key>.onJoin(...)` autocomplete; subclass\n // overrides must keep `protected` (TS widens to public silently\n // otherwise).\n protected onCreate?(options: any): void | Promise<void>;\n protected onAuth?(client: Client, options: any, context: AuthContext): void | Promise<void>;\n protected onJoin?(client: Client, options?: any): void | Promise<void>;\n protected onLeave?(client: Client, code?: number): void | Promise<void>;\n protected onDispose?(): void | Promise<void>;\n}\n\n/**\n * Plugin-class constructor type used by the `dependencies` static\n * declaration. Constrained to zero-arg constructors because the\n * framework auto-instantiates missing dependencies with no\n * configuration \u2014 plugins that need options can't be auto-included\n * and must be registered explicitly in `definePlugins({...})`.\n */\nexport type RoomPluginClass = new () => RoomPlugin<any>;\n\n/**\n * Static `dependencies` declaration. List other plugin classes this\n * plugin needs alongside it; the framework auto-instantiates any\n * missing ones at room construction time. Transitive deps are\n * resolved recursively. Cycles throw at class-init.\n *\n * Example:\n * class UniqueSessionPlugin extends RoomPlugin {\n * static dependencies: PluginDependencies = [TrackUserSessionsPlugin];\n * }\n */\nexport type PluginDependencies = ReadonlyArray<RoomPluginClass>;\n\n/**\n * Define a Room's plugin record. The framework wires plugins at\n * `__init` \u2014 first construct of the class computes the layout\n * (cached on the constructor) and installs hook wrappers on the\n * prototype. The `const T` modifier preserves literal types so\n * `this.plugins.<key>.method()` autocompletes against each plugin's\n * specific subclass.\n */\n// Pulls each plugin's `pluginName` literal as the record key. Subclass\n// must narrow with `as const` \u2014 plain `string` resolves to `never`\n// and the entry is dropped (callers see a TS error on\n// `plugins.<thatKey>`).\ntype ExtractPluginName<P> = P extends { pluginName: infer K }\n ? (K extends string ? K : never)\n : never;\n\n/** Hide `pluginName` from end-user autocomplete on `this.plugins.<key>`. */\ntype PluginPublicSurface<P> = Omit<P, 'pluginName'>;\n\ntype PluginsArrayToRecord<T extends readonly RoomPlugin<any>[]> = {\n [P in T[number] as ExtractPluginName<P>]: PluginPublicSurface<P>;\n};\n\n/**\n * Array form (recommended). Each plugin declares its own canonical\n * key via `readonly pluginName = '...' as const`; the framework\n * turns the array into a typed record so `plugins.<pluginName>`\n * autocompletes the right instance type.\n *\n * class GameRoom extends Room {\n * plugins = definePlugins([\n * new ChatPlugin(), // pluginName: 'chat'\n * new UniqueSessionPlugin({ max: 1 }), // pluginName: 'uniqueSession'\n * ]);\n * }\n * this.plugins.chat.send('hi');\n *\n * Throws at runtime if any plugin is missing `pluginName` or if two\n * plugins resolve to the same key.\n */\nexport function definePlugins<const T extends readonly RoomPlugin<any>[]>(\n plugins: T,\n): PluginsArrayToRecord<T>;\n\n/**\n * Record form (legacy / multi-instance escape hatch). The caller\n * chooses the key per-room. Useful when registering two instances of\n * the same plugin class without configuring `pluginName` per\n * instance \u2014 e.g. `{ adminChat: new ChatPlugin(), playerChat: new ChatPlugin() }`.\n */\nexport function definePlugins<const T extends Record<string, RoomPlugin<any>>>(\n plugins: T,\n): { [K in keyof T]: PluginPublicSurface<T[K]> };\n\nexport function definePlugins(plugins: any): any {\n if (!Array.isArray(plugins)) { return plugins; }\n const out: Record<string, RoomPlugin> = {};\n for (const p of plugins) {\n const key = p['pluginName'];\n if (typeof key !== 'string' || key.length === 0) {\n throw new Error(\n `[Room] plugin ${p.constructor.name} is missing a 'pluginName' field. ` +\n `Declare \\`readonly pluginName = '<key>' as const\\` on the class, ` +\n `or register it via the keyed-record form \\`definePlugins({ <key>: <plugin> })\\`.`,\n );\n }\n if (out[key]) {\n throw new Error(\n `[Room] two plugins resolve to pluginName \"${key}\". Configure one of ` +\n `them via constructor argument, or use the keyed-record form to disambiguate.`,\n );\n }\n out[key] = p;\n }\n return out;\n}\n\n/**\n * Lifecycle hook keys recognized by the framework \u2014 used internally to\n * separate \"framework-recognized methods\" from \"user-defined public\n * methods\" when wiring a plugin into a room. Exported for the test\n * harness; downstream code should not need it.\n */\nexport const PLUGIN_LIFECYCLE_KEYS = ['onCreate', 'onAuth', 'onJoin', 'onLeave', 'onDispose'] as const;\nexport type PluginLifecycleKey = (typeof PLUGIN_LIFECYCLE_KEYS)[number];\n\n/**\n * Test helper \u2014 attach a stub or fake room to a plugin so its methods\n * and lifecycle hooks can be exercised in isolation without spinning up\n * a real Colyseus server.\n *\n * const plugin = new ChatPlugin({ historyLimit: 5 });\n * const room = attachToTestRoom(plugin, { broadcast: sinon.spy() });\n * await plugin.messages!.chat!.call(plugin, { userId: 'u1' }, { text: 'hi' });\n *\n * The second arg is shallow-merged onto the stub so tests only declare\n * the room properties they actually exercise. Returns the room stub for\n * post-call assertions.\n */\nexport function attachToTestRoom<This extends Room, R extends Partial<This>>(\n plugin: RoomPlugin<This>,\n roomStub: R = {} as R,\n): R {\n (plugin as any).room = roomStub;\n return roomStub;\n}\n\n// ---------------------------------------------------------------------------\n// Layout machinery \u2014 used by Room.__init to set plugins up once per class.\n// Lives in this file (rather than Room.ts) so the plugin-related types and\n// helpers stay co-located. These are framework internals; callers outside\n// `@colyseus/core` should not depend on them.\n// ---------------------------------------------------------------------------\n\n/**\n * Precomputed plugin layout for a Room subclass \u2014 populated on first\n * construct, cached on the constructor. Hook wrappers are installed\n * on the prototype in the same pass (see `installPluginHookWrappers`).\n *\n * @internal\n */\nexport interface PluginLayout {\n /** Per-hook participation: which plugin keys run before/after the room's own hook. */\n hooks: Record<PluginLifecycleKey, { before: string[]; after: string[] }>;\n /** Message key \u2192 plugin key. Conflict detection ran when this was built. */\n messageOwners: Map<string, string>;\n /** Sentinel-keyed plugin classes to instantiate per room. */\n autoDeps: Array<{ key: string; ctor: RoomPluginClass }>;\n}\n\n/** Sentinel prefix for framework-instantiated deps. The colon prevents\n * collisions with any JS identifier the user could use as a key. */\nconst DEP_KEY_PREFIX = '__dep:';\n\n/**\n * Default before/after policy for lifecycle hooks vs the room's own\n * hook. Plugins can override per-hook via the `order` field on the\n * plugin instance.\n *\n * onCreate / onAuth / onJoin \u2192 plugins run BEFORE room (guards + setup)\n * onLeave / onDispose \u2192 plugins run AFTER room (capture final state)\n *\n * @internal\n */\nexport const DEFAULT_PLUGIN_ORDER: Record<PluginLifecycleKey, 'before' | 'after'> = {\n onCreate: 'before',\n onAuth: 'before',\n onJoin: 'before',\n onLeave: 'after',\n onDispose: 'after',\n};\n\n/**\n * Walk the room's plugin instances and produce the lifecycle + message\n * layout for the Room class. Called once per Room subclass \u2014 on the\n * first construct \u2014 and the result is cached on the constructor. Throws\n * on duplicate message keys (named both plugin keys) so the failure is\n * visible at class-init rather than at first message dispatch.\n *\n * Returns `null` when no plugins participate in any hook AND none\n * declare a message \u2014 distinguishes \"computed, nothing to do\" from\n * \"not computed yet\" in the `__pluginLayout` cache.\n *\n * @internal\n */\nexport function computePluginLayout(plugins: Record<string, RoomPlugin>): PluginLayout | null {\n // Resolve `static dependencies` closures over the user's record.\n // Returns an expanded list (user + auto-deps), the auto-dep\n // class table for per-room instantiation, and a unified view of\n // entries to iterate when computing hooks / message owners.\n const resolved = resolveDependencies(plugins);\n\n const hooks = {} as Record<PluginLifecycleKey, { before: string[]; after: string[] }>;\n let anyHook = false;\n\n for (const hook of PLUGIN_LIFECYCLE_KEYS) {\n const before: string[] = [];\n const after: string[] = [];\n for (const { key, plugin } of resolved.entries) {\n if (typeof plugin[hook] !== 'function') { continue; }\n // Bracket access \u2014 `order` and `messages` are protected; TS\n // skips visibility checks on indexed access.\n const order = plugin['order']?.[hook] ?? DEFAULT_PLUGIN_ORDER[hook];\n (order === 'before' ? before : after).push(key);\n anyHook = true;\n }\n hooks[hook] = { before, after };\n }\n\n const messageOwners = new Map<string, string>();\n for (const { key, plugin } of resolved.entries) {\n const messages = plugin['messages'];\n if (!messages) { continue; }\n for (const messageKey of Object.keys(messages)) {\n const prior = messageOwners.get(messageKey);\n if (prior !== undefined) {\n throw new Error(\n `[Room] message key \"${messageKey}\" declared by multiple plugins: ` +\n `\"${prior}\" and \"${key}\". Resolve by giving one of them a ` +\n `different key, or override on the room's own \\`messages\\`.`,\n );\n }\n messageOwners.set(messageKey, key);\n }\n }\n\n if (!anyHook && messageOwners.size === 0 && resolved.autoDeps.length === 0) { return null; }\n return { hooks, messageOwners, autoDeps: resolved.autoDeps };\n}\n\n/**\n * Walk every plugin's `static dependencies` recursively, instantiating\n * missing classes (zero-arg only). Throws on cycles. Auto-deps are\n * keyed `__dep:<ClassName>` in the returned entries.\n */\nfunction resolveDependencies(plugins: Record<string, RoomPlugin>): {\n entries: Array<{ key: string; plugin: RoomPlugin }>;\n autoDeps: Array<{ key: string; ctor: RoomPluginClass }>;\n} {\n const entries: Array<{ key: string; plugin: RoomPlugin }> = [];\n for (const [key, plugin] of Object.entries(plugins)) {\n entries.push({ key, plugin });\n }\n\n const present = new Set<RoomPluginClass>();\n for (const { plugin } of entries) {\n present.add(plugin.constructor as RoomPluginClass);\n }\n\n const autoDeps: Array<{ key: string; ctor: RoomPluginClass }> = [];\n const visiting = new Set<RoomPluginClass>();\n const path: string[] = [];\n\n function walk(depCtor: RoomPluginClass): void {\n if (present.has(depCtor)) { return; }\n if (visiting.has(depCtor)) {\n const cycle = [...path, depCtor.name].join(' \u2192 ');\n throw new Error(`[Room] plugin dependency cycle: ${cycle}`);\n }\n visiting.add(depCtor);\n path.push(depCtor.name);\n\n // Recurse into deeper deps first \u2192 topological order in `entries`.\n const deeper = (depCtor as any).dependencies as PluginDependencies | undefined;\n if (Array.isArray(deeper)) {\n for (const inner of deeper) { walk(inner); }\n }\n\n let instance: RoomPlugin;\n try { instance = new depCtor(); }\n catch (err: any) {\n throw new Error(\n `[Room] auto-included plugin \"${depCtor.name}\" must be ` +\n `constructible with no arguments. If it needs options, ` +\n `register it explicitly in definePlugins({...}). ` +\n `(cause: ${err?.message ?? err})`,\n );\n }\n const key = DEP_KEY_PREFIX + depCtor.name;\n entries.push({ key, plugin: instance });\n autoDeps.push({ key, ctor: depCtor });\n present.add(depCtor);\n\n visiting.delete(depCtor);\n path.pop();\n }\n\n // Snapshot before mutating \u2014 transitive deps are handled recursively\n // inside `walk()`.\n const userEntriesSnapshot = entries.slice();\n for (const { plugin } of userEntriesSnapshot) {\n const deps = (plugin.constructor as any).dependencies as PluginDependencies | undefined;\n if (!Array.isArray(deps)) { continue; }\n for (const depCtor of deps) { walk(depCtor); }\n }\n\n return { entries, autoDeps };\n}\n\n/**\n * Install one wrapper per participating hook on the Room subclass's\n * prototype. The wrapper closes over plugin KEYS (resolved to refs at\n * call time) and invokes the captured original room hook between the\n * before/after plugin runs.\n *\n * @internal\n */\nexport function installPluginHookWrappers(\n ctor: { prototype: any },\n layout: PluginLayout | null,\n): void {\n if (layout === null) { return; }\n const proto = ctor.prototype;\n for (const hook of PLUGIN_LIFECYCLE_KEYS) {\n const { before, after } = layout.hooks[hook];\n if (before.length === 0 && after.length === 0) { continue; }\n\n const original = proto[hook] as Function | undefined;\n proto[hook] = async function (\n this: { plugins?: Record<string, RoomPlugin>; _autoPlugins?: Record<string, RoomPlugin> },\n ...args: any[]\n ) {\n // User plugins live on `this.plugins`, auto-deps (sentinel-keyed)\n // on `this._autoPlugins` \u2014 kept separate so user types stay clean.\n const lookup = (k: string): RoomPlugin =>\n (k.startsWith(DEP_KEY_PREFIX) ? this._autoPlugins![k] : this.plugins![k]);\n for (const k of before) {\n const p = lookup(k);\n await (p[hook] as Function).call(p, ...args);\n }\n let result: unknown;\n if (original) { result = await original.apply(this, args); }\n for (const k of after) {\n const p = lookup(k);\n await (p[hook] as Function).call(p, ...args);\n }\n return result;\n };\n }\n}\n\n/** Structural shape used by `setupRoomPlugins` \u2014 avoids a Room import\n * so the dependency graph stays one-way (Room \u2192 RoomPlugin). */\ninterface RoomPluginHost {\n plugins?: Record<string, RoomPlugin<any>>;\n _autoPlugins?: Record<string, RoomPlugin<any>>;\n messages?: Record<string, Function> | any;\n constructor: { __pluginLayout?: PluginLayout | null; prototype: any };\n}\n\n/**\n * Wire a Room instance's plugins. Once-per-class layout (hook\n * participation, message owners, dep resolution) is cached on the\n * constructor; per-instance work attaches `room` refs, instantiates\n * auto-deps, and merges plugin messages.\n *\n * @internal\n */\nexport function setupRoomPlugins(room: RoomPluginHost): void {\n const plugins = room.plugins!;\n const layout = resolveOrComputeLayout(room.constructor, plugins);\n\n attachRoomReference(room, plugins);\n\n if (layout && layout.autoDeps.length > 0) {\n room._autoPlugins = instantiateAutoDeps(room, layout);\n }\n\n if (layout && layout.messageOwners.size > 0) {\n mergePluginMessages(room, layout);\n }\n\n Object.freeze(plugins);\n if (room._autoPlugins) { Object.freeze(room._autoPlugins); }\n}\n\n/**\n * Read cached layout for this subclass, or compute + install on\n * first construct. `hasOwnProperty` so a subclass that redeclares\n * `plugins` doesn't inherit the parent's wrapping.\n */\nfunction resolveOrComputeLayout(\n ctor: RoomPluginHost['constructor'],\n plugins: Record<string, RoomPlugin>,\n): PluginLayout | null | undefined {\n if (Object.prototype.hasOwnProperty.call(ctor, '__pluginLayout')) {\n return ctor.__pluginLayout;\n }\n const layout = computePluginLayout(plugins);\n installPluginHookWrappers(ctor, layout);\n ctor.__pluginLayout = layout;\n return layout;\n}\n\n/** Wire `plugin.room = room` on every user-explicit plugin. */\nfunction attachRoomReference(\n room: RoomPluginHost,\n plugins: Record<string, RoomPlugin>,\n): void {\n for (const plugin of Object.values(plugins)) {\n (plugin as any).room = room;\n }\n}\n\n/**\n * Build `_autoPlugins` \u2014 one fresh instance per `static dependencies`\n * entry. Kept separate from `room.plugins` so sentinel keys\n * (`__dep:<ClassName>`) don't leak into the user's typed view.\n */\nfunction instantiateAutoDeps(\n room: RoomPluginHost,\n layout: PluginLayout,\n): Record<string, RoomPlugin> {\n const auto: Record<string, RoomPlugin> = {};\n for (const { key, ctor: depCtor } of layout.autoDeps) {\n const instance = new depCtor();\n (instance as any).room = room;\n auto[key] = instance;\n }\n return auto;\n}\n\n/**\n * Copy plugin message handlers into `room.messages` (room's own key\n * wins; plugin-vs-plugin conflicts already threw at layout time).\n */\nfunction mergePluginMessages(\n room: RoomPluginHost,\n layout: PluginLayout,\n): void {\n const plugins = room.plugins!;\n for (const [messageKey, pluginKey] of layout.messageOwners) {\n if (room.messages?.[messageKey]) { continue; }\n const source = pluginKey.startsWith(DEP_KEY_PREFIX)\n ? room._autoPlugins![pluginKey]\n : plugins[pluginKey];\n const handler = source['messages']?.[messageKey];\n if (handler !== undefined) {\n (room.messages ??= {} as any)[messageKey] = handler;\n }\n }\n}\n"],
|
|
5
|
-
"mappings": "
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoEO,IAAe,aAAf,MAAoD;AAiD3D;AA8EO,SAAS,cAAc,SAAmB;AAC/C,MAAI,CAAC,MAAM,QAAQ,OAAO,GAAG;AAAE,WAAO;AAAA,EAAS;AAC/C,QAAM,MAAkC,CAAC;AACzC,aAAW,KAAK,SAAS;AACvB,UAAM,MAAM,EAAE,YAAY;AAC1B,QAAI,OAAO,QAAQ,YAAY,IAAI,WAAW,GAAG;AAC/C,YAAM,IAAI;AAAA,QACR,iBAAiB,EAAE,YAAY,IAAI;AAAA,MAGrC;AAAA,IACF;AACA,QAAI,IAAI,GAAG,GAAG;AACZ,YAAM,IAAI;AAAA,QACR,6CAA6C,GAAG;AAAA,MAElD;AAAA,IACF;AACA,QAAI,GAAG,IAAI;AAAA,EACb;AACA,SAAO;AACT;AAQO,IAAM,wBAAwB,CAAC,YAAY,UAAU,UAAU,WAAW,WAAW;AAgBrF,SAAS,iBACd,QACA,WAAc,CAAC,GACZ;AACH,EAAC,OAAe,OAAO;AACvB,SAAO;AACT;AA2BA,IAAM,iBAAiB;AAYhB,IAAM,uBAAuE;AAAA,EAClF,UAAW;AAAA,EACX,QAAW;AAAA,EACX,QAAW;AAAA,EACX,SAAW;AAAA,EACX,WAAW;AACb;AAeO,SAAS,oBAAoB,SAA0D;AAK5F,QAAM,WAAW,oBAAoB,OAAO;AAE5C,QAAM,QAAQ,CAAC;AACf,MAAI,UAAU;AAEd,aAAW,QAAQ,uBAAuB;AACxC,UAAM,SAAmB,CAAC;AAC1B,UAAM,QAAkB,CAAC;AACzB,eAAW,EAAE,KAAK,OAAO,KAAK,SAAS,SAAS;AAC9C,UAAI,OAAO,OAAO,IAAI,MAAM,YAAY;AAAE;AAAA,MAAU;AAGpD,YAAM,QAAQ,OAAO,OAAO,IAAI,IAAI,KAAK,qBAAqB,IAAI;AAClE,OAAC,UAAU,WAAW,SAAS,OAAO,KAAK,GAAG;AAC9C,gBAAU;AAAA,IACZ;AACA,UAAM,IAAI,IAAI,EAAE,QAAQ,MAAM;AAAA,EAChC;AAEA,QAAM,gBAAgB,oBAAI,IAAoB;AAC9C,aAAW,EAAE,KAAK,OAAO,KAAK,SAAS,SAAS;AAC9C,UAAM,WAAW,OAAO,UAAU;AAClC,QAAI,CAAC,UAAU;AAAE;AAAA,IAAU;AAC3B,eAAW,cAAc,OAAO,KAAK,QAAQ,GAAG;AAC9C,YAAM,QAAQ,cAAc,IAAI,UAAU;AAC1C,UAAI,UAAU,QAAW;AACvB,cAAM,IAAI;AAAA,UACR,uBAAuB,UAAU,oCAC7B,KAAK,UAAU,GAAG;AAAA,QAExB;AAAA,MACF;AACA,oBAAc,IAAI,YAAY,GAAG;AAAA,IACnC;AAAA,EACF;AAEA,MAAI,CAAC,WAAW,cAAc,SAAS,KAAK,SAAS,SAAS,WAAW,GAAG;AAAE,WAAO;AAAA,EAAM;AAC3F,SAAO,EAAE,OAAO,eAAe,UAAU,SAAS,SAAS;AAC7D;AAOA,SAAS,oBAAoB,SAG3B;AACA,QAAM,UAAsD,CAAC;AAC7D,aAAW,CAAC,KAAK,MAAM,KAAK,OAAO,QAAQ,OAAO,GAAG;AACnD,YAAQ,KAAK,EAAE,KAAK,OAAO,CAAC;AAAA,EAC9B;AAEA,QAAM,UAAU,oBAAI,IAAqB;AACzC,aAAW,EAAE,OAAO,KAAK,SAAS;AAChC,YAAQ,IAAI,OAAO,WAA8B;AAAA,EACnD;AAEA,QAAM,WAA0D,CAAC;AACjE,QAAM,WAAW,oBAAI,IAAqB;AAC1C,QAAM,OAAiB,CAAC;AAExB,WAAS,KAAK,SAAgC;AAC5C,QAAI,QAAQ,IAAI,OAAO,GAAG;AAAE;AAAA,IAAQ;AACpC,QAAI,SAAS,IAAI,OAAO,GAAG;AACzB,YAAM,QAAQ,CAAC,GAAG,MAAM,QAAQ,IAAI,EAAE,KAAK,UAAK;AAChD,YAAM,IAAI,MAAM,mCAAmC,KAAK,EAAE;AAAA,IAC5D;AACA,aAAS,IAAI,OAAO;AACpB,SAAK,KAAK,QAAQ,IAAI;AAGtB,UAAM,SAAU,QAAgB;AAChC,QAAI,MAAM,QAAQ,MAAM,GAAG;AACzB,iBAAW,SAAS,QAAQ;AAAE,aAAK,KAAK;AAAA,MAAG;AAAA,IAC7C;AAEA,QAAI;AACJ,QAAI;AAAE,iBAAW,IAAI,QAAQ;AAAA,IAAG,SACzB,KAAU;AACf,YAAM,IAAI;AAAA,QACR,gCAAgC,QAAQ,IAAI,2HAGjC,KAAK,WAAW,GAAG;AAAA,MAChC;AAAA,IACF;AACA,UAAM,MAAM,iBAAiB,QAAQ;AACrC,YAAQ,KAAK,EAAE,KAAK,QAAQ,SAAS,CAAC;AACtC,aAAS,KAAK,EAAE,KAAK,MAAM,QAAQ,CAAC;AACpC,YAAQ,IAAI,OAAO;AAEnB,aAAS,OAAO,OAAO;AACvB,SAAK,IAAI;AAAA,EACX;AAIA,QAAM,sBAAsB,QAAQ,MAAM;AAC1C,aAAW,EAAE,OAAO,KAAK,qBAAqB;AAC5C,UAAM,OAAQ,OAAO,YAAoB;AACzC,QAAI,CAAC,MAAM,QAAQ,IAAI,GAAG;AAAE;AAAA,IAAU;AACtC,eAAW,WAAW,MAAM;AAAE,WAAK,OAAO;AAAA,IAAG;AAAA,EAC/C;AAEA,SAAO,EAAE,SAAS,SAAS;AAC7B;AAUO,SAAS,0BACd,MACA,QACM;AACN,MAAI,WAAW,MAAM;AAAE;AAAA,EAAQ;AAC/B,QAAM,QAAQ,KAAK;AACnB,aAAW,QAAQ,uBAAuB;AACxC,UAAM,EAAE,QAAQ,MAAM,IAAI,OAAO,MAAM,IAAI;AAC3C,QAAI,OAAO,WAAW,KAAK,MAAM,WAAW,GAAG;AAAE;AAAA,IAAU;AAE3D,UAAM,WAAW,MAAM,IAAI;AAC3B,UAAM,IAAI,IAAI,kBAET,MACH;AAGA,YAAM,SAAS,CAAC,MACb,EAAE,WAAW,cAAc,IAAI,KAAK,aAAc,CAAC,IAAI,KAAK,QAAS,CAAC;AACzE,iBAAW,KAAK,QAAQ;AACtB,cAAM,IAAI,OAAO,CAAC;AAClB,cAAO,EAAE,IAAI,EAAe,KAAK,GAAG,GAAG,IAAI;AAAA,MAC7C;AACA,UAAI;AACJ,UAAI,UAAU;AAAE,iBAAS,MAAM,SAAS,MAAM,MAAM,IAAI;AAAA,MAAG;AAC3D,iBAAW,KAAK,OAAO;AACrB,cAAM,IAAI,OAAO,CAAC;AAClB,cAAO,EAAE,IAAI,EAAe,KAAK,GAAG,GAAG,IAAI;AAAA,MAC7C;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAmBO,SAAS,iBAAiB,MAA4B;AAC3D,QAAM,UAAU,KAAK;AACrB,QAAM,SAAS,uBAAuB,KAAK,aAAa,OAAO;AAE/D,sBAAoB,MAAM,OAAO;AAEjC,MAAI,UAAU,OAAO,SAAS,SAAS,GAAG;AACxC,SAAK,eAAe,oBAAoB,MAAM,MAAM;AAAA,EACtD;AAEA,MAAI,UAAU,OAAO,cAAc,OAAO,GAAG;AAC3C,wBAAoB,MAAM,MAAM;AAAA,EAClC;AAEA,SAAO,OAAO,OAAO;AACrB,MAAI,KAAK,cAAc;AAAE,WAAO,OAAO,KAAK,YAAY;AAAA,EAAG;AAC7D;AAOA,SAAS,uBACP,MACA,SACiC;AACjC,MAAI,OAAO,UAAU,eAAe,KAAK,MAAM,gBAAgB,GAAG;AAChE,WAAO,KAAK;AAAA,EACd;AACA,QAAM,SAAS,oBAAoB,OAAO;AAC1C,4BAA0B,MAAM,MAAM;AACtC,OAAK,iBAAiB;AACtB,SAAO;AACT;AAGA,SAAS,oBACP,MACA,SACM;AACN,aAAW,UAAU,OAAO,OAAO,OAAO,GAAG;AAC3C,IAAC,OAAe,OAAO;AAAA,EACzB;AACF;AAOA,SAAS,oBACP,MACA,QAC4B;AAC5B,QAAM,OAAmC,CAAC;AAC1C,aAAW,EAAE,KAAK,MAAM,QAAQ,KAAK,OAAO,UAAU;AACpD,UAAM,WAAW,IAAI,QAAQ;AAC7B,IAAC,SAAiB,OAAO;AACzB,SAAK,GAAG,IAAI;AAAA,EACd;AACA,SAAO;AACT;AAMA,SAAS,oBACP,MACA,QACM;AACN,QAAM,UAAU,KAAK;AACrB,aAAW,CAAC,YAAY,SAAS,KAAK,OAAO,eAAe;AAC1D,QAAI,KAAK,WAAW,UAAU,GAAG;AAAE;AAAA,IAAU;AAC7C,UAAM,SAAS,UAAU,WAAW,cAAc,IAC9C,KAAK,aAAc,SAAS,IAC5B,QAAQ,SAAS;AACrB,UAAM,UAAU,OAAO,UAAU,IAAI,UAAU;AAC/C,QAAI,YAAY,QAAW;AACzB,OAAC,KAAK,aAAa,CAAC,GAAU,UAAU,IAAI;AAAA,IAC9C;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/build/Server.cjs
CHANGED
package/build/Server.cjs.map
CHANGED
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/Server.ts"],
|
|
4
4
|
"sourcesContent": ["import { greet } from \"@colyseus/greeting-banner\";\nimport type express from 'express';\nimport type { Server as HttpServer } from 'node:http';\n\nimport { debugAndPrintError } from './Debug.ts';\nimport * as matchMaker from './MatchMaker.ts';\nimport { RegisteredHandler } from './matchmaker/RegisteredHandler.ts';\n\nimport { type OnCreateOptions, Room } from './Room.ts';\nimport { Deferred, registerGracefulShutdown, dynamicImport, type Type } from './utils/Utils.ts';\n\nimport type { Presence } from \"./presence/Presence.ts\";\n\nimport { setTransport, Transport } from './Transport.ts';\nimport { logger, setLogger } from './Logger.ts';\nimport { setDevMode, isDevMode } from './utils/DevMode.ts';\nimport { type Router, bindRouterToTransport, createRouter } from './router/index.ts';\nimport { prereadRequestBodies } from './router/node.ts';\nimport { type SDKTypes as SharedSDKTypes } from '@colyseus/shared-types';\nimport { getDefaultRouter } from './router/default_routes.ts';\n\nexport type ServerOptions = {\n publicAddress?: string,\n presence?: Presence,\n driver?: matchMaker.MatchMakerDriver,\n transport?: Transport,\n gracefullyShutdown?: boolean,\n logger?: any;\n\n /**\n * Optional callback to execute before the server listens.\n * This is useful for example to connect into a database or other services before the server listens.\n */\n beforeListen?: () => Promise<void> | void,\n\n /**\n * Booted before `matchMaker.accept()`. Structural so `@colyseus/core` keeps\n * no runtime dep on `@colyseus/database` \u2014 any `{ boot(): Promise<void> }`\n * works. The optional `applyRouterDefaults` is invoked after boot with the\n * user's router so the database can contribute endpoints (e.g. auth routes).\n */\n database?: {\n boot(): Promise<void>;\n applyRouterDefaults?(router: Router): Router | Promise<Router>;\n },\n\n /**\n * Mount `@colyseus/auth` routes into the router. Pass an options object\n * (forwarded to `auth.endpoints(...)`) to wire it explicitly \u2014 useful when\n * `database` is not in use. `false` disables auto-mounting even if a\n * `database` is present and would have provided defaults.\n */\n auth?: false | {\n settings?: Record<string, any>;\n oauth?: boolean | { cookieSecret?: string };\n prefix?: string;\n },\n\n /**\n * Optional callback to configure Express routes.\n * When provided, the transport layer will initialize an Express-compatible app\n * and pass it to this callback for custom route configuration.\n *\n * For uWebSockets transport, this uses the uwebsockets-express module.\n */\n express?: (app: express.Application) => Promise<void> | void,\n\n /**\n * Custom function to determine which process should handle room creation.\n * Default: assign new rooms the process with least amount of rooms created\n */\n selectProcessIdToCreateRoom?: matchMaker.SelectProcessIdCallback;\n\n /**\n * Whether this process is running as a standalone match-maker or not. (default: false)\n * When enabled, this process will not spawn rooms and will only be responsible for matchmaking.\n */\n isStandaloneMatchMaker?: boolean; \n\n /**\n * If enabled, rooms are going to be restored in the server-side upon restart,\n * clients are going to automatically re-connect when server reboots.\n *\n * Beware of \"schema mismatch\" issues. When updating Schema structures and\n * reloading existing data, you may see \"schema mismatch\" errors in the\n * client-side.\n *\n * (This operation is costly and should not be used in a production\n * environment)\n */\n devMode?: boolean,\n\n /**\n * Display greeting message on server start.\n * Default: true\n */\n greet?: boolean,\n};\n\n/**\n * Exposed types for the client-side SDK.\n * Re-exported from @colyseus/shared-types with specific type constraints.\n */\nexport interface SDKTypes<\n RoomTypes extends Record<string, RegisteredHandler> = any,\n Routes extends Router = any\n> extends SharedSDKTypes<RoomTypes, Routes> {}\n\nexport class Server<\n RoomTypes extends Record<string, RegisteredHandler> = any,\n Routes extends Router = any\n> implements SDKTypes<RoomTypes, Routes> {\n '~rooms': RoomTypes;\n '~routes': Routes;\n\n public transport: Transport;\n public router: Routes;\n public options: ServerOptions;\n\n protected presence: Presence;\n protected driver: matchMaker.MatchMakerDriver;\n\n protected port: number | string;\n protected greet: boolean;\n\n protected _onTransportReady = new Deferred<Transport>();\n\n private _originalRoomOnMessage: typeof Room.prototype['_onMessage'] | null = null;\n\n // Implicit default for callers that omit explicit Server reference \u2014 e.g.\n // `playground()` reads `Server.current.router.endpoints` at request time.\n // Last-construction wins; multi-server setups should reference instances\n // explicitly.\n static current: Server<any, any> | undefined;\n\n constructor(options: ServerOptions = {}) {\n const {\n gracefullyShutdown = true,\n greet = true\n } = options;\n\n setDevMode(options.devMode === true);\n\n this.options = options;\n this.greet = greet;\n\n (Server as { current: Server<any, any> | undefined }).current = this as Server<any, any>;\n\n this.attach(options);\n\n // Pass options.presence/driver through as-is (possibly undefined).\n // matchMaker.setup() falls back to getDefaultPresence/getDefaultDriver,\n // which auto-select RedisPresence/RedisDriver on Colyseus Cloud.\n matchMaker.setup(\n options.presence,\n options.driver,\n options.publicAddress,\n options.selectProcessIdToCreateRoom,\n ).then(() => {\n this.presence = matchMaker.presence;\n this.driver = matchMaker.driver;\n });\n\n if (gracefullyShutdown) {\n registerGracefulShutdown((err) => this.gracefullyShutdown(true, err));\n }\n\n if (options.logger) {\n setLogger(options.logger);\n }\n }\n\n public async attach(options: ServerOptions) {\n this.transport = options.transport || await this.getDefaultTransport(options);\n // `options.express` runs in `listen()` after `database.boot()` so user\n // code reading `database.auth.settings` etc. finds services instantiated.\n this._onTransportReady.resolve(this.transport);\n }\n\n /**\n * Bind the server into the port specified.\n *\n * @param port - Port number or Unix socket path\n * @param hostname\n * @param backlog\n * @param listeningListener\n */\n public async listen(port: number | string, hostname?: string, backlog?: number, listeningListener?: Function) {\n await this._bootServices();\n\n //\n // if Colyseus Cloud is detected, use @colyseus/tools to listen\n //\n if (process.env.COLYSEUS_CLOUD !== undefined ) {\n if (typeof(hostname) === \"number\") {\n //\n // workaround, @colyseus/tools calls server.listen() again with the port as a string\n //\n hostname = undefined;\n\n } else {\n try {\n return (await dynamicImport(\"@colyseus/tools\")).listen(this);\n } catch (error) {\n const err = new Error(\"Please install @colyseus/tools to be able to host on Colyseus Cloud.\");\n err.cause = error;\n throw err;\n }\n }\n }\n\n //\n // otherwise, listen on the port directly\n //\n this.port = port;\n\n //\n // Make sure matchmaker is ready before accepting connections\n // (isDevMode: matchmaker may take extra milliseconds to restore the rooms)\n //\n await matchMaker.accept(this.options.isStandaloneMatchMaker);\n\n /**\n * Greetings!\n */\n if (this.greet) {\n greet();\n }\n\n // Wait for the transport to be ready\n await this._onTransportReady;\n\n return new Promise<void>((resolve, reject) => {\n this.transport.listen(port, hostname, backlog, (err) => {\n if (this.transport.server) {\n this.transport.server.on('error', (err) => reject(err));\n }\n\n this.bindRoutes();\n\n if (listeningListener) {\n listeningListener(err);\n }\n\n if (err) {\n reject(err);\n\n } else {\n resolve();\n }\n });\n });\n }\n\n /**\n * Prepare matchmaking and HTTP routes, then return the underlying\n * `http.Server` *without* binding to a port.\n *\n * Use this on serverless platforms that consume an exported HTTP server\n * instead of a listening one. Vercel, for example, drives the request handler\n * of a default-exported server (its Express/Hono WebSocket examples use\n * `export default server`), whereas calling `listen()` there selects Vercel's\n * \"captured server\" path, which does not invoke Express-style app handlers.\n *\n * The transport must be created with an `http.Server` so it can be exported.\n * Vercel additionally needs `package.json` `\"main\"` pointing at the entrypoint:\n *\n * ```ts\n * import { createServer } from \"node:http\";\n * const httpServer = createServer();\n * const server = new Server({\n * transport: new WebSocketTransport({ server: httpServer }),\n * express: (app) => { app.get(\"/hello\", (req, res) => res.json({ ok: true })); },\n * });\n * server.define(\"my_room\", MyRoom);\n * export default await server.serverless(); // no listen()\n * ```\n */\n public async serverless(): Promise<HttpServer> {\n await this._bootServices();\n\n // transport (and its HTTP server) must be ready before binding routes\n await this._onTransportReady;\n\n const server = this.transport.server as HttpServer | undefined;\n if (!server) {\n throw new Error(\"serverless() requires a transport backed by an http.Server (pass `{ server }` to the transport).\");\n }\n\n this.bindRoutes();\n\n // When the server is consumed via `export default` (rather than listen()),\n // the request body must be available synchronously \u2014 buffer it up-front so\n // `req.body` is set before the router runs.\n prereadRequestBodies(server);\n\n await matchMaker.accept(this.options.isStandaloneMatchMaker);\n\n if (this.greet) {\n greet();\n }\n\n return server;\n }\n\n /**\n * Set the active transport globally and bind the matchmaking/HTTP routes.\n * Shared by {@link listen} and {@link serverless}.\n *\n * `setTransport()` runs here rather than before `transport.listen()`: the\n * global transport is only read by the matchmaking route handler, which can't\n * run until `bindRouterToTransport()` below registers the route.\n */\n private bindRoutes() {\n // set transport globally, to be used by the matchmaking route\n setTransport(this.transport);\n\n // default router is used if no router is provided\n if (!this.router) {\n this.router = getDefaultRouter() as unknown as Routes;\n\n } else {\n // make sure default routes are included\n // https://github.com/Bekacru/better-call/pull/67\n this.router = this.router.extend({ ...getDefaultRouter().endpoints }) as unknown as Routes;\n }\n\n bindRouterToTransport(this.transport, this.router, this.options.express !== undefined);\n }\n\n /**\n * Define a new type of room for matchmaking.\n *\n * @param name public room identifier for match-making.\n * @param roomClass Room class definition\n * @param defaultOptions default options for `onCreate`\n */\n public define<T extends Type<Room>>(\n roomClass: T,\n defaultOptions?: OnCreateOptions<T>,\n ): RegisteredHandler\n public define<T extends Type<Room>>(\n name: string,\n roomClass: T,\n defaultOptions?: OnCreateOptions<T>,\n ): RegisteredHandler\n public define<T extends Type<Room>>(\n nameOrHandler: string | T,\n handlerOrOptions: T | OnCreateOptions<T>,\n defaultOptions?: OnCreateOptions<T>,\n ): RegisteredHandler {\n const name = (typeof(nameOrHandler) === \"string\")\n ? nameOrHandler\n : nameOrHandler.name;\n\n const roomClass = (typeof(nameOrHandler) === \"string\")\n ? handlerOrOptions\n : nameOrHandler;\n\n const options = (typeof(nameOrHandler) === \"string\")\n ? defaultOptions\n : handlerOrOptions;\n\n return matchMaker.defineRoomType(name, roomClass, options);\n }\n\n /**\n * Remove a room definition from matchmaking.\n * This method does not destroy any room. It only dissallows matchmaking\n */\n public removeRoomType(name: string): void {\n matchMaker.removeRoomType(name);\n }\n\n public async gracefullyShutdown(exit: boolean = true, err?: Error) {\n if (matchMaker.state === matchMaker.MatchMakerState.SHUTTING_DOWN) {\n return;\n }\n\n try {\n // custom \"before shutdown\" method\n await this.onBeforeShutdownCallback();\n\n // this is going to lock all rooms and wait for them to be disposed\n await matchMaker.gracefullyShutdown();\n\n this.transport.shutdown();\n this.presence?.shutdown();\n await this.driver?.shutdown();\n\n // custom \"after shutdown\" method\n await this.onShutdownCallback();\n\n } catch (e) {\n debugAndPrintError(`error during shutdown: ${e}`);\n\n } finally {\n if (exit) {\n process.exit((err && !isDevMode) ? 1 : 0);\n }\n }\n }\n\n /**\n * Add simulated latency between client and server.\n * @param milliseconds round trip latency in milliseconds.\n */\n public simulateLatency(milliseconds: number) {\n if (milliseconds > 0) {\n logger.warn(`\uD83D\uDCF6\uFE0F\u2757 Colyseus latency simulation enabled \u2192 ${milliseconds}ms latency for round trip.`);\n } else {\n logger.warn(`\uD83D\uDCF6\uFE0F\u2757 Colyseus latency simulation disabled.`);\n }\n\n const halfwayMS = (milliseconds / 2);\n this.transport.simulateLatency(halfwayMS);\n\n if (this._originalRoomOnMessage == null) {\n this._originalRoomOnMessage = Room.prototype['_onMessage'];\n }\n\n const originalOnMessage = this._originalRoomOnMessage;\n\n Room.prototype['_onMessage'] = milliseconds <= Number.EPSILON ? originalOnMessage : function (this: Room, client, buffer) {\n // uWebSockets.js: duplicate buffer because it is cleared at native layer before the timeout.\n const cachedBuffer = Buffer.from(buffer);\n setTimeout(() => originalOnMessage.call(this, client, cachedBuffer), halfwayMS);\n };\n }\n\n /**\n * Register a callback that is going to be executed before the server shuts down.\n * @param callback\n */\n public onShutdown(callback: () => void | Promise<any>) {\n this.onShutdownCallback = callback;\n }\n\n public onBeforeShutdown(callback: () => void | Promise<any>) {\n this.onBeforeShutdownCallback = callback;\n }\n\n // Boot sequence shared by listen() and serverless(): user `beforeListen`\n // hook, database boot, framework router defaults (auth/db endpoints), then\n // the express app. serverless() needs this too \u2014 its exported server still\n // serves the express callback and auth/database routes.\n private async _bootServices(): Promise<void> {\n const { beforeListen, database, express } = this.options;\n\n if (beforeListen) { await beforeListen(); }\n if (database) { await database.boot(); }\n\n await this._applyRouterDefaults();\n\n if (express) {\n await this._onTransportReady;\n if (this.transport.getExpressApp) {\n const expressApp = await this.transport.getExpressApp();\n await express(expressApp);\n }\n }\n }\n\n // Extend the user's router with framework-contributed endpoints. An explicit\n // `auth` option wins over the database's `applyRouterDefaults` so a non-DB\n // setup can still auto-mount @colyseus/auth, and a DB user can opt out.\n private async _applyRouterDefaults(): Promise<void> {\n const { auth, database } = this.options;\n const wantsAuth = auth !== undefined && auth !== false;\n const wantsDatabase = auth === undefined && !!database?.applyRouterDefaults;\n if (!wantsAuth && !wantsDatabase) { return; }\n\n // Boot an empty router if the user didn't pass `routes` \u2014 the framework's\n // default routes get layered on later in the `transport.listen()` callback.\n this.router ??= createRouter({}) as unknown as Routes;\n\n if (wantsAuth) {\n const authMod: any = await dynamicImport('@colyseus/auth').catch(() => undefined);\n const endpointsFn = authMod?.auth?.endpoints ?? authMod?.default?.auth?.endpoints;\n if (typeof endpointsFn === 'function') {\n this.router = (this.router as Router).extend(endpointsFn(auth)) as Routes;\n }\n } else {\n const updated = await database!.applyRouterDefaults!(this.router as Router);\n if (updated) { this.router = updated as Routes; }\n }\n }\n\n protected async getDefaultTransport(options: any): Promise<Transport> {\n try {\n const module = await dynamicImport('@colyseus/ws-transport');\n const WebSocketTransport = module.WebSocketTransport;\n return new WebSocketTransport(options);\n\n } catch (error) {\n this._onTransportReady.reject(error);\n throw new Error(\"Please provide a 'transport' layer. Default transport not set.\");\n }\n }\n\n protected onShutdownCallback: () => void | Promise<any> =\n () => Promise.resolve()\n\n protected onBeforeShutdownCallback: () => void | Promise<any> =\n () => Promise.resolve()\n}\n\nexport type RoomDefinitions = Record<string, RegisteredHandler | Type<Room>>;\n\nfunction isRegisteredHandler(value: RegisteredHandler | Type<Room>): value is RegisteredHandler {\n return value instanceof RegisteredHandler || (\n typeof(value) === \"object\" &&\n value !== null &&\n 'klass' in (value as object)\n );\n}\n\nexport function registerRoomDefinitions<T extends RoomDefinitions>(rooms: T): string[] {\n const roomNames: string[] = [];\n\n for (const [name, value] of Object.entries(rooms)) {\n if (isRegisteredHandler(value)) {\n value.name = name;\n matchMaker.addRoomType(value);\n\n } else {\n matchMaker.defineRoomType(name, value);\n }\n\n roomNames.push(name);\n }\n\n return roomNames;\n}\n\nexport function unregisterRoomDefinitions(roomNames: Iterable<string>) {\n for (const roomName of roomNames) {\n matchMaker.removeRoomType(roomName);\n }\n}\n\nexport type DefineServerOptions<\n T extends Record<string, RegisteredHandler>,\n R extends Router\n> = ServerOptions & {\n rooms: T,\n routes?: R,\n};\n\nexport function defineServer<\n T extends Record<string, RegisteredHandler>,\n R extends Router\n>(\n options: DefineServerOptions<T, R>,\n): Server<T, R> {\n const { rooms, routes, ...serverOptions } = options;\n\n if (isDevMode) {\n // In dev mode, the Vite plugin manages Server/matchMaker lifecycle.\n // Return a config-only object \u2014 no Server instance, no matchMaker.setup().\n return {\n options: serverOptions,\n router: routes,\n '~rooms': rooms,\n } as unknown as Server<T, R>;\n }\n\n const server = new Server<T, R>(serverOptions);\n server.router = routes;\n\n registerRoomDefinitions(rooms);\n\n return server;\n}\n\nexport function defineRoom<T extends Type<Room>>(\n roomKlass: T,\n defaultOptions?: Parameters<NonNullable<InstanceType<T>['onCreate']>>[0],\n): RegisteredHandler<InstanceType<T>> {\n return new RegisteredHandler(roomKlass, defaultOptions) as unknown as RegisteredHandler<InstanceType<T>>;\n}\n"],
|
|
5
|
-
"mappings": "
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["gracefullyShutdown", "greet", "err", "module"]
|
|
7
7
|
}
|
package/build/Stats.cjs
CHANGED
package/build/Stats.cjs.map
CHANGED
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/Stats.ts"],
|
|
4
4
|
"sourcesContent": ["import { MatchMakerState, presence, processId, state } from './MatchMaker.ts';\n\nexport type Stats = {\n roomCount: number;\n ccu: number;\n}\n\nexport let local: Stats = {\n roomCount: 0,\n ccu: 0,\n};\n\nexport async function fetchAll() {\n // TODO: cache this value to avoid querying too often\n const allStats: Array<Stats & { processId: string }> = [];\n const allProcesses = await presence.hgetall(getRoomCountKey());\n\n for (let remoteProcessId in allProcesses) {\n if (remoteProcessId === processId) {\n allStats.push({ processId, roomCount: local.roomCount, ccu: local.ccu, });\n\n } else {\n const [roomCount, ccu] = allProcesses[remoteProcessId].split(',').map(Number);\n allStats.push({ processId: remoteProcessId, roomCount, ccu });\n }\n }\n\n return allStats;\n}\n\nlet lastPersisted = 0;\nlet persistTimeout = undefined;\nconst persistInterval = 1000;\n\nexport function persist(forceNow: boolean = false) {\n // skip if shutting down\n if (state === MatchMakerState.SHUTTING_DOWN) {\n return;\n }\n\n /**\n * Avoid persisting more than once per second.\n */\n const now = Date.now();\n\n if (forceNow || (now - lastPersisted > persistInterval)) {\n lastPersisted = now;\n return presence.hset(getRoomCountKey(), processId, `${local.roomCount},${local.ccu}`);\n\n } else {\n clearTimeout(persistTimeout);\n persistTimeout = setTimeout(persist, persistInterval);\n }\n}\n\nexport function reset(_persist: boolean = true) {\n local.roomCount = 0;\n local.ccu = 0;\n\n if (_persist) {\n lastPersisted = 0;\n clearTimeout(persistTimeout);\n persist();\n }\n\n //\n // Attach local metrics to PM2 (if available)\n //\n import('@pm2/io').then((io) => {\n io.default.metric({ id: 'app/stats/ccu', name: 'ccu', value: () => local.ccu });\n io.default.metric({ id: 'app/stats/roomcount', name: 'roomcount', value: () => local.roomCount });\n }).catch(() => { });\n}\n\nexport function excludeProcess(_processId: string) {\n return presence.hdel(getRoomCountKey(), _processId);\n}\n\nexport async function getGlobalCCU() {\n const allStats = await fetchAll();\n return allStats.reduce((prev, next) => prev + next.ccu, 0);\n}\n\n/**\n * Auto-persist every minute.\n */\nlet autoPersistInterval = undefined;\n\nexport function setAutoPersistInterval() {\n const interval = 60 * 1000;// 1 minute\n\n autoPersistInterval = setInterval(() => {\n const now = Date.now();\n\n if (now - lastPersisted > interval) {\n persist();\n }\n }, interval);\n}\n\nexport function clearAutoPersistInterval() {\n clearInterval(autoPersistInterval);\n}\n\nfunction getRoomCountKey() {\n return 'roomcount';\n}"],
|
|
5
|
-
"mappings": "
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wBAA4D;AAOrD,IAAI,QAAe;AAAA,EACxB,WAAW;AAAA,EACX,KAAK;AACP;AAEA,eAAsB,WAAW;AAE/B,QAAM,WAAiD,CAAC;AACxD,QAAM,eAAe,MAAM,2BAAS,QAAQ,gBAAgB,CAAC;AAE7D,WAAS,mBAAmB,cAAc;AACxC,QAAI,oBAAoB,6BAAW;AACjC,eAAS,KAAK,EAAE,wCAAW,WAAW,MAAM,WAAW,KAAK,MAAM,IAAK,CAAC;AAAA,IAE1E,OAAO;AACL,YAAM,CAAC,WAAW,GAAG,IAAI,aAAa,eAAe,EAAE,MAAM,GAAG,EAAE,IAAI,MAAM;AAC5E,eAAS,KAAK,EAAE,WAAW,iBAAiB,WAAW,IAAI,CAAC;AAAA,IAC9D;AAAA,EACF;AAEA,SAAO;AACT;AAEA,IAAI,gBAAgB;AACpB,IAAI,iBAAiB;AACrB,IAAM,kBAAkB;AAEjB,SAAS,QAAQ,WAAoB,OAAO;AAEjD,MAAI,4BAAU,kCAAgB,eAAe;AAC3C;AAAA,EACF;AAKA,QAAM,MAAM,KAAK,IAAI;AAErB,MAAI,YAAa,MAAM,gBAAgB,iBAAkB;AACvD,oBAAgB;AAChB,WAAO,2BAAS,KAAK,gBAAgB,GAAG,6BAAW,GAAG,MAAM,SAAS,IAAI,MAAM,GAAG,EAAE;AAAA,EAEtF,OAAO;AACL,iBAAa,cAAc;AAC3B,qBAAiB,WAAW,SAAS,eAAe;AAAA,EACtD;AACF;AAEO,SAAS,MAAM,WAAoB,MAAM;AAC9C,QAAM,YAAY;AAClB,QAAM,MAAM;AAEZ,MAAI,UAAU;AACZ,oBAAgB;AAChB,iBAAa,cAAc;AAC3B,YAAQ;AAAA,EACV;AAKA,SAAO,SAAS,EAAE,KAAK,CAAC,OAAO;AAC7B,OAAG,QAAQ,OAAO,EAAE,IAAI,iBAAiB,MAAM,OAAO,OAAO,MAAM,MAAM,IAAI,CAAC;AAC9E,OAAG,QAAQ,OAAO,EAAE,IAAI,uBAAuB,MAAM,aAAa,OAAO,MAAM,MAAM,UAAU,CAAC;AAAA,EAClG,CAAC,EAAE,MAAM,MAAM;AAAA,EAAE,CAAC;AACpB;AAEO,SAAS,eAAe,YAAoB;AACjD,SAAO,2BAAS,KAAK,gBAAgB,GAAG,UAAU;AACpD;AAEA,eAAsB,eAAe;AACnC,QAAM,WAAW,MAAM,SAAS;AAChC,SAAO,SAAS,OAAO,CAAC,MAAM,SAAS,OAAO,KAAK,KAAK,CAAC;AAC3D;AAKA,IAAI,sBAAsB;AAEnB,SAAS,yBAAyB;AACvC,QAAM,WAAW,KAAK;AAEtB,wBAAsB,YAAY,MAAM;AACtC,UAAM,MAAM,KAAK,IAAI;AAErB,QAAI,MAAM,gBAAgB,UAAU;AAClC,cAAQ;AAAA,IACV;AAAA,EACF,GAAG,QAAQ;AACb;AAEO,SAAS,2BAA2B;AACzC,gBAAc,mBAAmB;AACnC;AAEA,SAAS,kBAAkB;AACzB,SAAO;AACT;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/build/Transport.cjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const __cjsImportMetaUrl = require('node:url').pathToFileURL(__filename).href;
|
|
1
2
|
"use strict";
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -24,8 +25,10 @@ __export(Transport_exports, {
|
|
|
24
25
|
ClientState: () => ClientState,
|
|
25
26
|
Transport: () => Transport,
|
|
26
27
|
connectClientToRoom: () => connectClientToRoom,
|
|
28
|
+
createAuthContext: () => createAuthContext,
|
|
27
29
|
enqueueClientRaw: () => enqueueClientRaw,
|
|
28
30
|
getTransport: () => getTransport,
|
|
31
|
+
runBeforeUpgrade: () => runBeforeUpgrade,
|
|
29
32
|
setTransport: () => setTransport
|
|
30
33
|
});
|
|
31
34
|
module.exports = __toCommonJS(Transport_exports);
|
|
@@ -34,6 +37,7 @@ var https = require("https");
|
|
|
34
37
|
var import_shared_types = require("@colyseus/shared-types");
|
|
35
38
|
var import_schema = require("@colyseus/schema");
|
|
36
39
|
var import_events = require("events");
|
|
40
|
+
var import_Debug = require("./Debug.cjs");
|
|
37
41
|
var import_Utils = require("./utils/Utils.cjs");
|
|
38
42
|
var import_ServerError = require("./errors/ServerError.cjs");
|
|
39
43
|
var _transport;
|
|
@@ -45,6 +49,39 @@ function getTransport() {
|
|
|
45
49
|
}
|
|
46
50
|
var Transport = class {
|
|
47
51
|
};
|
|
52
|
+
async function runBeforeUpgrade(handler, url, context) {
|
|
53
|
+
let request;
|
|
54
|
+
try {
|
|
55
|
+
const host = context.headers.get("host") || "localhost";
|
|
56
|
+
request = new Request(`http://${host}${url}`, { headers: context.headers });
|
|
57
|
+
} catch (e) {
|
|
58
|
+
(0, import_Debug.debugAndPrintError)(e);
|
|
59
|
+
return new Response(null, { status: 400 });
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
return await handler(request, context) ?? void 0;
|
|
63
|
+
} catch (e) {
|
|
64
|
+
(0, import_Debug.debugAndPrintError)(e);
|
|
65
|
+
return new Response(null, { status: 500 });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
var readHeader = (headers, name) => headers instanceof Headers ? headers.get(name) : headers[name];
|
|
69
|
+
function createAuthContext(options) {
|
|
70
|
+
const source = options.headers;
|
|
71
|
+
let headers;
|
|
72
|
+
return {
|
|
73
|
+
token: options.token ?? (0, import_Utils.getBearerToken)(readHeader(source, "authorization")),
|
|
74
|
+
ip: resolveClientIp(source, options.remoteAddress),
|
|
75
|
+
req: options.req,
|
|
76
|
+
get headers() {
|
|
77
|
+
return headers ??= source instanceof Headers ? source : new Headers(source);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function resolveClientIp(headers, remoteAddress) {
|
|
82
|
+
const firstHop = (name) => readHeader(headers, name)?.split(",")[0].trim() || void 0;
|
|
83
|
+
return firstHop("x-real-ip") ?? firstHop("x-forwarded-for") ?? firstHop("x-client-ip") ?? (remoteAddress || void 0);
|
|
84
|
+
}
|
|
48
85
|
var ClientState = {
|
|
49
86
|
JOINING: 0,
|
|
50
87
|
JOINED: 1,
|
|
@@ -133,7 +170,9 @@ async function connectClientToRoom(room, client, authContext, connectionOptions)
|
|
|
133
170
|
ClientState,
|
|
134
171
|
Transport,
|
|
135
172
|
connectClientToRoom,
|
|
173
|
+
createAuthContext,
|
|
136
174
|
enqueueClientRaw,
|
|
137
175
|
getTransport,
|
|
176
|
+
runBeforeUpgrade,
|
|
138
177
|
setTransport
|
|
139
178
|
});
|