@colyseus/core 0.18.0 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/build/MatchMaker.cjs +3 -7
  2. package/build/MatchMaker.cjs.map +2 -2
  3. package/build/MatchMaker.mjs +4 -8
  4. package/build/MatchMaker.mjs.map +2 -2
  5. package/build/Protocol.cjs +3 -3
  6. package/build/Protocol.cjs.map +1 -1
  7. package/build/Protocol.d.ts +3 -3
  8. package/build/Protocol.mjs +3 -3
  9. package/build/Protocol.mjs.map +1 -1
  10. package/build/Rewind.cjs +385 -0
  11. package/build/Rewind.cjs.map +7 -0
  12. package/build/Rewind.d.ts +291 -0
  13. package/build/Rewind.mjs +359 -0
  14. package/build/Rewind.mjs.map +7 -0
  15. package/build/Room.cjs +280 -281
  16. package/build/Room.cjs.map +3 -3
  17. package/build/Room.d.ts +185 -81
  18. package/build/Room.mjs +286 -286
  19. package/build/Room.mjs.map +2 -2
  20. package/build/RoomMessages.cjs +265 -0
  21. package/build/RoomMessages.cjs.map +7 -0
  22. package/build/RoomMessages.d.ts +49 -0
  23. package/build/RoomMessages.mjs +240 -0
  24. package/build/RoomMessages.mjs.map +7 -0
  25. package/build/Server.cjs +88 -31
  26. package/build/Server.cjs.map +2 -2
  27. package/build/Server.d.ts +36 -0
  28. package/build/Server.mjs +88 -31
  29. package/build/Server.mjs.map +2 -2
  30. package/build/Transport.cjs +26 -0
  31. package/build/Transport.cjs.map +2 -2
  32. package/build/Transport.d.ts +69 -2
  33. package/build/Transport.mjs +25 -0
  34. package/build/Transport.mjs.map +2 -2
  35. package/build/errors/RoomExceptions.cjs +7 -4
  36. package/build/errors/RoomExceptions.cjs.map +2 -2
  37. package/build/errors/RoomExceptions.d.ts +15 -3
  38. package/build/errors/RoomExceptions.mjs +5 -3
  39. package/build/errors/RoomExceptions.mjs.map +2 -2
  40. package/build/index.cjs +19 -2
  41. package/build/index.cjs.map +2 -2
  42. package/build/index.d.ts +7 -4
  43. package/build/index.mjs +17 -4
  44. package/build/index.mjs.map +2 -2
  45. package/build/input/InputBuffer.cjs +439 -16
  46. package/build/input/InputBuffer.cjs.map +2 -2
  47. package/build/input/InputBuffer.d.ts +186 -96
  48. package/build/input/InputBuffer.mjs +436 -15
  49. package/build/input/InputBuffer.mjs.map +2 -2
  50. package/build/input/RoomInput.cjs +306 -0
  51. package/build/input/RoomInput.cjs.map +7 -0
  52. package/build/input/RoomInput.d.ts +91 -0
  53. package/build/input/RoomInput.mjs +287 -0
  54. package/build/input/RoomInput.mjs.map +7 -0
  55. package/build/input/types.cjs +18 -0
  56. package/build/input/types.cjs.map +7 -0
  57. package/build/input/types.d.ts +476 -0
  58. package/build/input/types.mjs +0 -0
  59. package/build/input/types.mjs.map +7 -0
  60. package/build/rooms/QueueRoom.cjs +1 -1
  61. package/build/rooms/QueueRoom.cjs.map +2 -2
  62. package/build/rooms/QueueRoom.mjs +1 -1
  63. package/build/rooms/QueueRoom.mjs.map +2 -2
  64. package/build/router/default_routes.cjs +4 -1
  65. package/build/router/default_routes.cjs.map +2 -2
  66. package/build/router/default_routes.mjs +4 -1
  67. package/build/router/default_routes.mjs.map +2 -2
  68. package/build/router/index.cjs +6 -1
  69. package/build/router/index.cjs.map +2 -2
  70. package/build/router/index.mjs +6 -1
  71. package/build/router/index.mjs.map +2 -2
  72. package/build/router/node.cjs +37 -2
  73. package/build/router/node.cjs.map +2 -2
  74. package/build/router/node.d.ts +10 -0
  75. package/build/router/node.mjs +35 -1
  76. package/build/router/node.mjs.map +2 -2
  77. package/build/serializer/NoneSerializer.cjs +2 -2
  78. package/build/serializer/NoneSerializer.cjs.map +2 -2
  79. package/build/serializer/NoneSerializer.d.ts +3 -3
  80. package/build/serializer/NoneSerializer.mjs +2 -2
  81. package/build/serializer/NoneSerializer.mjs.map +2 -2
  82. package/build/serializer/SchemaSerializer.cjs +44 -14
  83. package/build/serializer/SchemaSerializer.cjs.map +2 -2
  84. package/build/serializer/SchemaSerializer.d.ts +3 -3
  85. package/build/serializer/SchemaSerializer.mjs +46 -16
  86. package/build/serializer/SchemaSerializer.mjs.map +2 -2
  87. package/build/serializer/Serializer.cjs.map +1 -1
  88. package/build/serializer/Serializer.d.ts +12 -2
  89. package/build/utils/DevMode.cjs +14 -14
  90. package/build/utils/DevMode.cjs.map +2 -2
  91. package/build/utils/DevMode.d.ts +0 -2
  92. package/build/utils/DevMode.mjs +14 -12
  93. package/build/utils/DevMode.mjs.map +2 -2
  94. package/build/utils/Env.cjs +8 -4
  95. package/build/utils/Env.cjs.map +3 -3
  96. package/build/utils/Env.mjs +8 -4
  97. package/build/utils/Env.mjs.map +2 -2
  98. package/build/utils/Utils.cjs +3 -0
  99. package/build/utils/Utils.cjs.map +2 -2
  100. package/build/utils/Utils.d.ts +8 -0
  101. package/build/utils/Utils.mjs +2 -0
  102. package/build/utils/Utils.mjs.map +2 -2
  103. package/package.json +11 -11
  104. package/src/MatchMaker.ts +7 -9
  105. package/src/Protocol.ts +3 -3
  106. package/src/Rewind.ts +572 -0
  107. package/src/Room.ts +409 -448
  108. package/src/RoomMessages.ts +342 -0
  109. package/src/Server.ts +112 -38
  110. package/src/Transport.ts +97 -2
  111. package/src/errors/RoomExceptions.ts +18 -4
  112. package/src/index.ts +13 -2
  113. package/src/input/InputBuffer.ts +464 -107
  114. package/src/input/RoomInput.ts +337 -0
  115. package/src/input/types.ts +503 -0
  116. package/src/rooms/QueueRoom.ts +1 -1
  117. package/src/router/default_routes.ts +6 -1
  118. package/src/router/index.ts +6 -1
  119. package/src/router/node.ts +44 -0
  120. package/src/serializer/NoneSerializer.ts +3 -3
  121. package/src/serializer/SchemaSerializer.ts +110 -19
  122. package/src/serializer/Serializer.ts +13 -2
  123. package/src/utils/DevMode.ts +18 -13
  124. package/src/utils/Env.ts +12 -4
  125. package/src/utils/Utils.ts +9 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/rooms/QueueRoom.ts"],
4
- "sourcesContent": ["import { Room } from '../Room.ts';\nimport type { Client } from '../Transport.ts';\nimport type { IRoomCache } from '../matchmaker/driver.ts';\nimport * as matchMaker from '../MatchMaker.ts';\nimport { debugMatchMaking } from '../Debug.ts';\nimport { ServerError } from '../errors/ServerError.ts';\nimport { CloseCode, ErrorCode } from '@colyseus/shared-types';\n\nexport interface QueueOptions {\n /**\n * number of players on each match\n */\n maxPlayers?: number;\n\n /**\n * name of the room to create\n */\n matchRoomName: string;\n\n /**\n * after these cycles, create a match with a bot\n */\n maxWaitingCycles?: number;\n\n /**\n * after this time, try to fit this client with a not-so-compatible group\n */\n maxWaitingCyclesForPriority?: number;\n\n /**\n * If set, teams must have the same size to be matched together\n */\n maxTeamSize?: number;\n\n /**\n * If `allowIncompleteGroups` is true, players inside an unmatched group (that\n * did not reached `maxPlayers`, and `maxWaitingCycles` has been\n * reached) will be matched together. Your room should fill the remaining\n * spots with \"bots\" on this case.\n */\n allowIncompleteGroups?: boolean;\n\n /**\n * Comparison function for matching clients to groups\n * Returns true if the client is compatible with the group\n */\n compare?: (client: QueueClientData, matchGroup: QueueMatchGroup) => boolean;\n\n /**\n *\n * When onGroupReady is set, the \"roomNameToCreate\" option is ignored.\n */\n onGroupReady?: (this: QueueRoom, group: QueueMatchGroup) => Promise<IRoomCache>;\n}\n\nexport interface QueueMatchGroup {\n averageRank: number;\n clients: Array<Client<{ userData: QueueClientData }>>,\n ready?: boolean;\n confirmed?: number;\n}\n\nexport interface QueueMatchTeam {\n averageRank: number;\n clients: Array<Client<{ userData: QueueClientData }>>,\n teamId: string | symbol;\n}\n\nexport interface QueueClientData {\n /**\n * Rank of the client\n */\n rank: number;\n\n /**\n * Timestamp of when the client entered the queue\n */\n currentCycle?: number;\n\n /**\n * Optional: if matching with a team, the team ID\n */\n teamId?: string;\n\n /**\n * Additional options passed by the client when joining the room\n */\n options?: any;\n\n /**\n * Match group the client is currently in\n */\n group?: QueueMatchGroup;\n\n /**\n * Whether the client has confirmed the connection to the room\n */\n confirmed?: boolean;\n\n /**\n * Whether the client should be prioritized in the queue\n * (e.g. for players that are waiting for a long time)\n */\n highPriority?: boolean;\n\n /**\n * The last number of clients in the queue sent to the client\n */\n lastQueueClientCount?: number;\n}\n\n//\n// Optional: strongly-typed client messages\n// (This is optional, but recommended for better type safety and code generation for native SDKs)\n//\ntype QueueClient = Client<{\n userData: QueueClientData;\n messages: {\n clients: number;\n seat: matchMaker.ISeatReservation;\n }\n}>;\n\nconst DEFAULT_TEAM = Symbol(\"$default_team\");\nconst DEFAULT_COMPARE = (client: QueueClientData, matchGroup: QueueMatchGroup) => {\n const diff = Math.abs(client.rank - matchGroup.averageRank);\n const diffRatio = (diff / matchGroup.averageRank);\n // If diff ratio is too high, create a new match group\n return (diff < 10 || diffRatio <= 2);\n}\n\nexport class QueueRoom extends Room {\n maxPlayers = 4;\n maxTeamSize: number;\n allowIncompleteGroups: boolean = false;\n\n maxWaitingCycles = 15;\n maxWaitingCyclesForPriority?: number = 10;\n\n /**\n * Evaluate groups for each client at interval\n */\n cycleTickInterval = 1000;\n\n /**\n * Groups of players per iteration\n */\n groups: QueueMatchGroup[] = [];\n highPriorityGroups: QueueMatchGroup[] = [];\n\n matchRoomName: string;\n\n protected compare = DEFAULT_COMPARE;\n protected onGroupReady = (group: QueueMatchGroup) => matchMaker.createRoom(this.matchRoomName, {});\n\n messages = {\n confirm: (client: Client, _: unknown) => {\n const queueData = client.userData;\n\n if (queueData && queueData.group && typeof (queueData.group.confirmed) === \"number\") {\n queueData.confirmed = true;\n queueData.group.confirmed++;\n client.leave(CloseCode.NORMAL_CLOSURE);\n }\n },\n }\n\n onCreate(options: QueueOptions) {\n if (typeof(options.maxWaitingCycles) === \"number\") {\n this.maxWaitingCycles = options.maxWaitingCycles;\n }\n\n if (typeof(options.maxPlayers) === \"number\") {\n this.maxPlayers = options.maxPlayers;\n }\n\n if (typeof(options.maxTeamSize) === \"number\") {\n this.maxTeamSize = options.maxTeamSize;\n }\n\n if (typeof(options.allowIncompleteGroups) !== \"undefined\") {\n this.allowIncompleteGroups = options.allowIncompleteGroups;\n }\n\n if (typeof(options.compare) === \"function\") {\n this.compare = options.compare;\n }\n\n if (typeof(options.onGroupReady) === \"function\") {\n this.onGroupReady = options.onGroupReady;\n }\n\n if (options.matchRoomName) {\n this.matchRoomName = options.matchRoomName;\n\n } else {\n throw new ServerError(ErrorCode.APPLICATION_ERROR, \"QueueRoom: 'matchRoomName' option is required.\");\n }\n\n debugMatchMaking(\"QueueRoom#onCreate() maxPlayers: %d, maxWaitingCycles: %d, maxTeamSize: %d, allowIncompleteGroups: %d, roomNameToCreate: %s\", this.maxPlayers, this.maxWaitingCycles, this.maxTeamSize, this.allowIncompleteGroups, this.matchRoomName);\n\n /**\n * Redistribute clients into groups at every interval\n */\n this.setSimulationInterval(() => this.reassignMatchGroups(), this.cycleTickInterval);\n }\n\n onJoin(client: QueueClient, options: any, auth?: unknown) {\n this.addToQueue(client, {\n rank: options.rank,\n teamId: options.teamId,\n options,\n });\n }\n\n addToQueue(client: QueueClient, queueData: QueueClientData) {\n if (queueData.currentCycle === undefined) {\n queueData.currentCycle = 0;\n }\n client.userData = queueData;\n\n // FIXME: reassign groups upon joining [?] (without incrementing cycle count)\n client.send(\"clients\", 1);\n }\n\n createMatchGroup() {\n const group: QueueMatchGroup = { clients: [], averageRank: 0 };\n this.groups.push(group);\n return group;\n }\n\n reassignMatchGroups() {\n // Re-set all groups\n this.groups.length = 0;\n this.highPriorityGroups.length = 0;\n\n const sortedClients = (this.clients)\n .filter((client) => {\n // Filter out:\n // - clients that are not in the queue\n // - clients that are already in a \"ready\" group\n return (\n client.userData &&\n client.userData.group?.ready !== true\n );\n })\n .sort((a, b) => {\n //\n // Sort by rank ascending\n //\n return a.userData.rank - b.userData.rank;\n });\n\n //\n // The room either distribute by teams or by clients\n //\n if (typeof(this.maxTeamSize) === \"number\") {\n this.redistributeTeams(sortedClients);\n\n } else {\n this.redistributeClients(sortedClients);\n }\n\n this.evaluateHighPriorityGroups();\n this.processGroupsReady();\n }\n\n redistributeTeams(sortedClients: Client<{ userData: QueueClientData }>[]) {\n const teamsByID: { [teamId: string | symbol]: QueueMatchTeam } = {};\n\n sortedClients.forEach((client) => {\n const teamId = client.userData.teamId || DEFAULT_TEAM;\n\n // Create a new team if it doesn't exist\n if (!teamsByID[teamId]) {\n teamsByID[teamId] = { teamId: teamId, clients: [], averageRank: 0, };\n }\n\n teamsByID[teamId].averageRank += client.userData.rank;\n teamsByID[teamId].clients.push(client);\n });\n\n // Calculate average rank for each team\n let teams = Object.values(teamsByID).map((team) => {\n team.averageRank /= team.clients.length;\n return team;\n }).sort((a, b) => {\n // Sort by average rank ascending\n return a.averageRank - b.averageRank;\n });\n\n // Iterate over teams multiple times until all clients are assigned to a group\n do {\n let currentGroup: QueueMatchGroup = this.createMatchGroup();\n teams = teams.filter((team) => {\n // Remove clients from the team and add them to the current group\n const totalRank = team.averageRank * team.clients.length;\n\n // currentGroup.averageRank = (currentGroup.averageRank === undefined)\n // ? team.averageRank\n // : (currentGroup.averageRank + team.averageRank) / ;\n currentGroup = this.redistributeClients(team.clients.splice(0, this.maxTeamSize), currentGroup, totalRank);\n\n if (team.clients.length >= this.maxTeamSize) {\n // team still has enough clients to form a group\n return true;\n }\n\n // increment cycle count for all clients in the team\n team.clients.forEach((client) => client.userData.currentCycle++);\n\n return false;\n });\n } while (teams.length >= 2);\n }\n\n redistributeClients(\n sortedClients: Client<{ userData: QueueClientData }>[],\n currentGroup: QueueMatchGroup = this.createMatchGroup(),\n totalRank: number = 0,\n ) {\n for (let i = 0, l = sortedClients.length; i < l; i++) {\n const client = sortedClients[i];\n const userData = client.userData;\n const currentCycle = userData.currentCycle++;\n\n if (currentGroup.averageRank > 0) {\n if (\n !this.compare(userData, currentGroup) &&\n !userData.highPriority\n ) {\n currentGroup = this.createMatchGroup();\n totalRank = 0;\n }\n }\n\n userData.group = currentGroup;\n currentGroup.clients.push(client);\n\n totalRank += userData.rank;\n currentGroup.averageRank = totalRank / currentGroup.clients.length;\n\n // Enough players in the group, mark it as ready!\n if (currentGroup.clients.length === this.maxPlayers) {\n currentGroup.ready = true;\n currentGroup = this.createMatchGroup();\n totalRank = 0;\n continue;\n }\n\n if (currentCycle >= this.maxWaitingCycles && this.allowIncompleteGroups) {\n /**\n * Match long-waiting clients with bots\n */\n if (this.highPriorityGroups.indexOf(currentGroup) === -1) {\n this.highPriorityGroups.push(currentGroup);\n }\n\n } else if (\n this.maxWaitingCyclesForPriority !== undefined &&\n currentCycle >= this.maxWaitingCyclesForPriority\n ) {\n /**\n * Force this client to join a group, even if rank is incompatible\n */\n userData.highPriority = true;\n }\n }\n\n return currentGroup;\n }\n\n evaluateHighPriorityGroups() {\n /**\n * Evaluate groups with high priority clients\n */\n this.highPriorityGroups.forEach((group) => {\n group.ready = group.clients.every((c) => {\n // Give new clients another chance to join a group that is not \"high priority\"\n return c.userData?.currentCycle > 1;\n // return c.userData?.currentCycle >= this.maxWaitingCycles;\n });\n });\n }\n\n processGroupsReady() {\n this.groups.forEach(async (group) => {\n if (group.ready) {\n group.confirmed = 0;\n\n try {\n /**\n * Create room instance in the server.\n */\n const room = await this.onGroupReady.call(this, group);\n\n /**\n * Reserve a seat for each client in the group.\n * (If one fails, force all clients to leave, re-queueing is up to the client-side logic)\n */\n await matchMaker.reserveMultipleSeatsFor(\n room,\n group.clients.map((client) => ({\n sessionId: client.sessionId,\n options: client.userData.options,\n auth: client.auth,\n })),\n );\n\n /**\n * Send room data for new WebSocket connection!\n */\n group.clients.forEach((client, i) => {\n client.send(\"seat\", matchMaker.buildSeatReservation(room, client.sessionId));\n });\n\n } catch (e: any) {\n //\n // If creating a room, or reserving a seat failed - fail all clients\n // Whether the clients retry or not is up to the client-side logic\n //\n group.clients.forEach(client => client.leave(1011, e.message));\n }\n\n } else {\n /**\n * Notify clients within the group on how many players are in the queue\n */\n group.clients.forEach((client) => {\n //\n // avoid sending the same number of clients to the client if it hasn't changed\n //\n const queueClientCount = group.clients.length;\n if (client.userData.lastQueueClientCount !== queueClientCount) {\n client.userData.lastQueueClientCount = queueClientCount;\n client.send(\"clients\", queueClientCount);\n }\n });\n }\n });\n }\n\n}"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { Room } from '../Room.ts';\nimport type { Client } from '../Transport.ts';\nimport type { IRoomCache } from '../matchmaker/driver.ts';\nimport * as matchMaker from '../MatchMaker.ts';\nimport { debugMatchMaking } from '../Debug.ts';\nimport { ServerError } from '../errors/ServerError.ts';\nimport { CloseCode, ErrorCode } from '@colyseus/shared-types';\n\nexport interface QueueOptions {\n /**\n * number of players on each match\n */\n maxPlayers?: number;\n\n /**\n * name of the room to create\n */\n matchRoomName: string;\n\n /**\n * after these cycles, create a match with a bot\n */\n maxWaitingCycles?: number;\n\n /**\n * after this time, try to fit this client with a not-so-compatible group\n */\n maxWaitingCyclesForPriority?: number;\n\n /**\n * If set, teams must have the same size to be matched together\n */\n maxTeamSize?: number;\n\n /**\n * If `allowIncompleteGroups` is true, players inside an unmatched group (that\n * did not reached `maxPlayers`, and `maxWaitingCycles` has been\n * reached) will be matched together. Your room should fill the remaining\n * spots with \"bots\" on this case.\n */\n allowIncompleteGroups?: boolean;\n\n /**\n * Comparison function for matching clients to groups\n * Returns true if the client is compatible with the group\n */\n compare?: (client: QueueClientData, matchGroup: QueueMatchGroup) => boolean;\n\n /**\n *\n * When onGroupReady is set, the \"roomNameToCreate\" option is ignored.\n */\n onGroupReady?: (this: QueueRoom, group: QueueMatchGroup) => Promise<IRoomCache>;\n}\n\nexport interface QueueMatchGroup {\n averageRank: number;\n clients: Array<Client<{ userData: QueueClientData }>>,\n ready?: boolean;\n confirmed?: number;\n}\n\nexport interface QueueMatchTeam {\n averageRank: number;\n clients: Array<Client<{ userData: QueueClientData }>>,\n teamId: string | symbol;\n}\n\nexport interface QueueClientData {\n /**\n * Rank of the client\n */\n rank: number;\n\n /**\n * Timestamp of when the client entered the queue\n */\n currentCycle?: number;\n\n /**\n * Optional: if matching with a team, the team ID\n */\n teamId?: string;\n\n /**\n * Additional options passed by the client when joining the room\n */\n options?: any;\n\n /**\n * Match group the client is currently in\n */\n group?: QueueMatchGroup;\n\n /**\n * Whether the client has confirmed the connection to the room\n */\n confirmed?: boolean;\n\n /**\n * Whether the client should be prioritized in the queue\n * (e.g. for players that are waiting for a long time)\n */\n highPriority?: boolean;\n\n /**\n * The last number of clients in the queue sent to the client\n */\n lastQueueClientCount?: number;\n}\n\n//\n// Optional: strongly-typed client messages\n// (This is optional, but recommended for better type safety and code generation for native SDKs)\n//\ntype QueueClient = Client<{\n userData: QueueClientData;\n messages: {\n clients: number;\n seat: matchMaker.ISeatReservation;\n }\n}>;\n\nconst DEFAULT_TEAM = Symbol(\"$default_team\");\nconst DEFAULT_COMPARE = (client: QueueClientData, matchGroup: QueueMatchGroup) => {\n const diff = Math.abs(client.rank - matchGroup.averageRank);\n const diffRatio = (diff / matchGroup.averageRank);\n // If diff ratio is too high, create a new match group\n return (diff < 10 || diffRatio <= 2);\n}\n\nexport class QueueRoom extends Room {\n maxPlayers = 4;\n maxTeamSize: number;\n allowIncompleteGroups: boolean = false;\n\n maxWaitingCycles = 15;\n maxWaitingCyclesForPriority?: number = 10;\n\n /**\n * Evaluate groups for each client at interval\n */\n cycleTickInterval = 1000;\n\n /**\n * Groups of players per iteration\n */\n groups: QueueMatchGroup[] = [];\n highPriorityGroups: QueueMatchGroup[] = [];\n\n matchRoomName: string;\n\n protected compare = DEFAULT_COMPARE;\n protected onGroupReady = (group: QueueMatchGroup) => matchMaker.createRoom(this.matchRoomName, {});\n\n messages = {\n confirm: (client: Client, _: unknown) => {\n const queueData = client.userData;\n\n if (queueData && queueData.group && typeof (queueData.group.confirmed) === \"number\") {\n queueData.confirmed = true;\n queueData.group.confirmed++;\n client.leave(CloseCode.NORMAL_CLOSURE);\n }\n },\n }\n\n onCreate(options: QueueOptions) {\n if (typeof(options.maxWaitingCycles) === \"number\") {\n this.maxWaitingCycles = options.maxWaitingCycles;\n }\n\n if (typeof(options.maxPlayers) === \"number\") {\n this.maxPlayers = options.maxPlayers;\n }\n\n if (typeof(options.maxTeamSize) === \"number\") {\n this.maxTeamSize = options.maxTeamSize;\n }\n\n if (typeof(options.allowIncompleteGroups) !== \"undefined\") {\n this.allowIncompleteGroups = options.allowIncompleteGroups;\n }\n\n if (typeof(options.compare) === \"function\") {\n this.compare = options.compare;\n }\n\n if (typeof(options.onGroupReady) === \"function\") {\n this.onGroupReady = options.onGroupReady;\n }\n\n if (options.matchRoomName) {\n this.matchRoomName = options.matchRoomName;\n\n } else {\n throw new ServerError(ErrorCode.APPLICATION_ERROR, \"QueueRoom: 'matchRoomName' option is required.\");\n }\n\n debugMatchMaking(\"QueueRoom#onCreate() maxPlayers: %d, maxWaitingCycles: %d, maxTeamSize: %d, allowIncompleteGroups: %d, roomNameToCreate: %s\", this.maxPlayers, this.maxWaitingCycles, this.maxTeamSize, this.allowIncompleteGroups, this.matchRoomName);\n\n /**\n * Redistribute clients into groups at every interval\n */\n this.setTimestep(() => this.reassignMatchGroups(), this.cycleTickInterval);\n }\n\n onJoin(client: QueueClient, options: any, auth?: unknown) {\n this.addToQueue(client, {\n rank: options.rank,\n teamId: options.teamId,\n options,\n });\n }\n\n addToQueue(client: QueueClient, queueData: QueueClientData) {\n if (queueData.currentCycle === undefined) {\n queueData.currentCycle = 0;\n }\n client.userData = queueData;\n\n // FIXME: reassign groups upon joining [?] (without incrementing cycle count)\n client.send(\"clients\", 1);\n }\n\n createMatchGroup() {\n const group: QueueMatchGroup = { clients: [], averageRank: 0 };\n this.groups.push(group);\n return group;\n }\n\n reassignMatchGroups() {\n // Re-set all groups\n this.groups.length = 0;\n this.highPriorityGroups.length = 0;\n\n const sortedClients = (this.clients)\n .filter((client) => {\n // Filter out:\n // - clients that are not in the queue\n // - clients that are already in a \"ready\" group\n return (\n client.userData &&\n client.userData.group?.ready !== true\n );\n })\n .sort((a, b) => {\n //\n // Sort by rank ascending\n //\n return a.userData.rank - b.userData.rank;\n });\n\n //\n // The room either distribute by teams or by clients\n //\n if (typeof(this.maxTeamSize) === \"number\") {\n this.redistributeTeams(sortedClients);\n\n } else {\n this.redistributeClients(sortedClients);\n }\n\n this.evaluateHighPriorityGroups();\n this.processGroupsReady();\n }\n\n redistributeTeams(sortedClients: Client<{ userData: QueueClientData }>[]) {\n const teamsByID: { [teamId: string | symbol]: QueueMatchTeam } = {};\n\n sortedClients.forEach((client) => {\n const teamId = client.userData.teamId || DEFAULT_TEAM;\n\n // Create a new team if it doesn't exist\n if (!teamsByID[teamId]) {\n teamsByID[teamId] = { teamId: teamId, clients: [], averageRank: 0, };\n }\n\n teamsByID[teamId].averageRank += client.userData.rank;\n teamsByID[teamId].clients.push(client);\n });\n\n // Calculate average rank for each team\n let teams = Object.values(teamsByID).map((team) => {\n team.averageRank /= team.clients.length;\n return team;\n }).sort((a, b) => {\n // Sort by average rank ascending\n return a.averageRank - b.averageRank;\n });\n\n // Iterate over teams multiple times until all clients are assigned to a group\n do {\n let currentGroup: QueueMatchGroup = this.createMatchGroup();\n teams = teams.filter((team) => {\n // Remove clients from the team and add them to the current group\n const totalRank = team.averageRank * team.clients.length;\n\n // currentGroup.averageRank = (currentGroup.averageRank === undefined)\n // ? team.averageRank\n // : (currentGroup.averageRank + team.averageRank) / ;\n currentGroup = this.redistributeClients(team.clients.splice(0, this.maxTeamSize), currentGroup, totalRank);\n\n if (team.clients.length >= this.maxTeamSize) {\n // team still has enough clients to form a group\n return true;\n }\n\n // increment cycle count for all clients in the team\n team.clients.forEach((client) => client.userData.currentCycle++);\n\n return false;\n });\n } while (teams.length >= 2);\n }\n\n redistributeClients(\n sortedClients: Client<{ userData: QueueClientData }>[],\n currentGroup: QueueMatchGroup = this.createMatchGroup(),\n totalRank: number = 0,\n ) {\n for (let i = 0, l = sortedClients.length; i < l; i++) {\n const client = sortedClients[i];\n const userData = client.userData;\n const currentCycle = userData.currentCycle++;\n\n if (currentGroup.averageRank > 0) {\n if (\n !this.compare(userData, currentGroup) &&\n !userData.highPriority\n ) {\n currentGroup = this.createMatchGroup();\n totalRank = 0;\n }\n }\n\n userData.group = currentGroup;\n currentGroup.clients.push(client);\n\n totalRank += userData.rank;\n currentGroup.averageRank = totalRank / currentGroup.clients.length;\n\n // Enough players in the group, mark it as ready!\n if (currentGroup.clients.length === this.maxPlayers) {\n currentGroup.ready = true;\n currentGroup = this.createMatchGroup();\n totalRank = 0;\n continue;\n }\n\n if (currentCycle >= this.maxWaitingCycles && this.allowIncompleteGroups) {\n /**\n * Match long-waiting clients with bots\n */\n if (this.highPriorityGroups.indexOf(currentGroup) === -1) {\n this.highPriorityGroups.push(currentGroup);\n }\n\n } else if (\n this.maxWaitingCyclesForPriority !== undefined &&\n currentCycle >= this.maxWaitingCyclesForPriority\n ) {\n /**\n * Force this client to join a group, even if rank is incompatible\n */\n userData.highPriority = true;\n }\n }\n\n return currentGroup;\n }\n\n evaluateHighPriorityGroups() {\n /**\n * Evaluate groups with high priority clients\n */\n this.highPriorityGroups.forEach((group) => {\n group.ready = group.clients.every((c) => {\n // Give new clients another chance to join a group that is not \"high priority\"\n return c.userData?.currentCycle > 1;\n // return c.userData?.currentCycle >= this.maxWaitingCycles;\n });\n });\n }\n\n processGroupsReady() {\n this.groups.forEach(async (group) => {\n if (group.ready) {\n group.confirmed = 0;\n\n try {\n /**\n * Create room instance in the server.\n */\n const room = await this.onGroupReady.call(this, group);\n\n /**\n * Reserve a seat for each client in the group.\n * (If one fails, force all clients to leave, re-queueing is up to the client-side logic)\n */\n await matchMaker.reserveMultipleSeatsFor(\n room,\n group.clients.map((client) => ({\n sessionId: client.sessionId,\n options: client.userData.options,\n auth: client.auth,\n })),\n );\n\n /**\n * Send room data for new WebSocket connection!\n */\n group.clients.forEach((client, i) => {\n client.send(\"seat\", matchMaker.buildSeatReservation(room, client.sessionId));\n });\n\n } catch (e: any) {\n //\n // If creating a room, or reserving a seat failed - fail all clients\n // Whether the clients retry or not is up to the client-side logic\n //\n group.clients.forEach(client => client.leave(1011, e.message));\n }\n\n } else {\n /**\n * Notify clients within the group on how many players are in the queue\n */\n group.clients.forEach((client) => {\n //\n // avoid sending the same number of clients to the client if it hasn't changed\n //\n const queueClientCount = group.clients.length;\n if (client.userData.lastQueueClientCount !== queueClientCount) {\n client.userData.lastQueueClientCount = queueClientCount;\n client.send(\"clients\", queueClientCount);\n }\n });\n }\n });\n }\n\n}"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -62,6 +62,9 @@ var postMatchmakeMethod = (0, import_better_call.createEndpoint)("/matchmake/:me
62
62
  if (transport.protocol !== void 0) {
63
63
  response.protocol = transport.protocol;
64
64
  }
65
+ if (transport.fingerprint !== void 0) {
66
+ response.fingerprint = transport.fingerprint;
67
+ }
65
68
  const json = JSON.stringify(response);
66
69
  return new Response(json, {
67
70
  headers: {
@@ -78,7 +81,7 @@ var postMatchmakeMethod = (0, import_better_call.createEndpoint)("/matchmake/:me
78
81
  }
79
82
  });
80
83
  function getDefaultRouter() {
81
- return (0, import_better_call.createRouter)({ postMatchmakeMethod });
84
+ return (0, import_better_call.createRouter)({ postMatchmakeMethod }, { openapi: { disabled: true } });
82
85
  }
83
86
  // Annotate the CommonJS export names for ESM import in node:
84
87
  0 && (module.exports = {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/router/default_routes.ts"],
4
- "sourcesContent": ["import { createEndpoint, createRouter } from \"@colyseus/better-call\";\nimport * as matchMaker from \"../MatchMaker.ts\";\nimport { getBearerToken } from \"../utils/Utils.ts\";\nimport { getTransport } from \"../Transport.ts\";\n\nexport const postMatchmakeMethod = createEndpoint(\"/matchmake/:method/:roomName\", { method: \"POST\" }, async (ctx) => {\n // do not accept matchmaking requests if already shutting down\n if (matchMaker.state === matchMaker.MatchMakerState.SHUTTING_DOWN) {\n throw ctx.error(503);\n }\n\n const requestHeaders = ctx.request.headers;\n\n const method = ctx.params.method;\n const roomName = ctx.params.roomName;\n\n try {\n const clientOptions = ctx.body;\n const response = await matchMaker.controller.invokeMethod(\n method,\n roomName,\n clientOptions,\n {\n token: getBearerToken(ctx.request.headers.get('authorization')),\n headers: ctx.request.headers,\n ip: requestHeaders.get('x-forwarded-for') ?? requestHeaders.get('x-client-ip') ?? requestHeaders.get('x-real-ip'),\n req: ctx.request as any,\n },\n );\n\n //\n // TODO: refactor here.\n // expose protocol, if available.\n //\n const transport = getTransport();\n if (transport.protocol !== undefined) {\n response.protocol = transport.protocol;\n }\n\n const json = JSON.stringify(response);\n\n return new Response(json, {\n headers: {\n 'content-type': 'application/json',\n //\n // Set content length manually to avoid \"chunked\" transfer-encoding header\n // See https://github.com/haxetink/tink_http/issues/27\n //\n 'content-length': json.length.toString(),\n },\n }) as any;\n\n } catch (e: any) {\n throw ctx.error(e.code, { code: e.code, error: e.message, });\n }\n\n});\n\nexport function getDefaultRouter() {\n return createRouter({ postMatchmakeMethod });\n}"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAA6C;AAC7C,iBAA4B;AAC5B,mBAA+B;AAC/B,uBAA6B;AAEtB,IAAM,0BAAsB,mCAAe,gCAAgC,EAAE,QAAQ,OAAO,GAAG,OAAO,QAAQ;AAEnH,MAAe,qBAAqB,2BAAgB,eAAe;AACjE,UAAM,IAAI,MAAM,GAAG;AAAA,EACrB;AAEA,QAAM,iBAAiB,IAAI,QAAQ;AAEnC,QAAM,SAAS,IAAI,OAAO;AAC1B,QAAM,WAAW,IAAI,OAAO;AAE5B,MAAI;AACF,UAAM,gBAAgB,IAAI;AAC1B,UAAM,WAAW,MAAiB,sBAAW;AAAA,MAC3C;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,QACE,WAAO,6BAAe,IAAI,QAAQ,QAAQ,IAAI,eAAe,CAAC;AAAA,QAC9D,SAAS,IAAI,QAAQ;AAAA,QACrB,IAAI,eAAe,IAAI,iBAAiB,KAAK,eAAe,IAAI,aAAa,KAAK,eAAe,IAAI,WAAW;AAAA,QAChH,KAAK,IAAI;AAAA,MACX;AAAA,IACF;AAMA,UAAM,gBAAY,+BAAa;AAC/B,QAAI,UAAU,aAAa,QAAW;AACpC,eAAS,WAAW,UAAU;AAAA,IAChC;AAEA,UAAM,OAAO,KAAK,UAAU,QAAQ;AAEpC,WAAO,IAAI,SAAS,MAAM;AAAA,MACxB,SAAS;AAAA,QACP,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA,QAKhB,kBAAkB,KAAK,OAAO,SAAS;AAAA,MACzC;AAAA,IACF,CAAC;AAAA,EAEH,SAAS,GAAQ;AACf,UAAM,IAAI,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,OAAO,EAAE,QAAS,CAAC;AAAA,EAC7D;AAEF,CAAC;AAEM,SAAS,mBAAmB;AACjC,aAAO,iCAAa,EAAE,oBAAoB,CAAC;AAC7C;",
4
+ "sourcesContent": ["import { createEndpoint, createRouter } from \"@colyseus/better-call\";\nimport * as matchMaker from \"../MatchMaker.ts\";\nimport { getBearerToken } from \"../utils/Utils.ts\";\nimport { getTransport } from \"../Transport.ts\";\n\nexport const postMatchmakeMethod = createEndpoint(\"/matchmake/:method/:roomName\", { method: \"POST\" }, async (ctx) => {\n // do not accept matchmaking requests if already shutting down\n if (matchMaker.state === matchMaker.MatchMakerState.SHUTTING_DOWN) {\n throw ctx.error(503);\n }\n\n const requestHeaders = ctx.request.headers;\n\n const method = ctx.params.method;\n const roomName = ctx.params.roomName;\n\n try {\n const clientOptions = ctx.body;\n const response = await matchMaker.controller.invokeMethod(\n method,\n roomName,\n clientOptions,\n {\n token: getBearerToken(ctx.request.headers.get('authorization')),\n headers: ctx.request.headers,\n ip: requestHeaders.get('x-forwarded-for') ?? requestHeaders.get('x-client-ip') ?? requestHeaders.get('x-real-ip'),\n req: ctx.request as any,\n },\n );\n\n //\n // TODO: refactor here.\n // expose protocol, if available.\n //\n const transport = getTransport();\n if (transport.protocol !== undefined) {\n response.protocol = transport.protocol;\n }\n // WebTransport (h3): client pins the self-signed cert via serverCertificateHashes.\n if (transport.fingerprint !== undefined) {\n response.fingerprint = transport.fingerprint;\n }\n\n const json = JSON.stringify(response);\n\n return new Response(json, {\n headers: {\n 'content-type': 'application/json',\n //\n // Set content length manually to avoid \"chunked\" transfer-encoding header\n // See https://github.com/haxetink/tink_http/issues/27\n //\n 'content-length': json.length.toString(),\n },\n }) as any;\n\n } catch (e: any) {\n throw ctx.error(e.code, { code: e.code, error: e.message, });\n }\n\n});\n\nexport function getDefaultRouter() {\n // no public docs page \u2014 the docs surface is playground's gated /__apidocs\n return createRouter({ postMatchmakeMethod }, { openapi: { disabled: true } });\n}"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAA6C;AAC7C,iBAA4B;AAC5B,mBAA+B;AAC/B,uBAA6B;AAEtB,IAAM,0BAAsB,mCAAe,gCAAgC,EAAE,QAAQ,OAAO,GAAG,OAAO,QAAQ;AAEnH,MAAe,qBAAqB,2BAAgB,eAAe;AACjE,UAAM,IAAI,MAAM,GAAG;AAAA,EACrB;AAEA,QAAM,iBAAiB,IAAI,QAAQ;AAEnC,QAAM,SAAS,IAAI,OAAO;AAC1B,QAAM,WAAW,IAAI,OAAO;AAE5B,MAAI;AACF,UAAM,gBAAgB,IAAI;AAC1B,UAAM,WAAW,MAAiB,sBAAW;AAAA,MAC3C;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,QACE,WAAO,6BAAe,IAAI,QAAQ,QAAQ,IAAI,eAAe,CAAC;AAAA,QAC9D,SAAS,IAAI,QAAQ;AAAA,QACrB,IAAI,eAAe,IAAI,iBAAiB,KAAK,eAAe,IAAI,aAAa,KAAK,eAAe,IAAI,WAAW;AAAA,QAChH,KAAK,IAAI;AAAA,MACX;AAAA,IACF;AAMA,UAAM,gBAAY,+BAAa;AAC/B,QAAI,UAAU,aAAa,QAAW;AACpC,eAAS,WAAW,UAAU;AAAA,IAChC;AAEA,QAAI,UAAU,gBAAgB,QAAW;AACvC,eAAS,cAAc,UAAU;AAAA,IACnC;AAEA,UAAM,OAAO,KAAK,UAAU,QAAQ;AAEpC,WAAO,IAAI,SAAS,MAAM;AAAA,MACxB,SAAS;AAAA,QACP,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA,QAKhB,kBAAkB,KAAK,OAAO,SAAS;AAAA,MACzC;AAAA,IACF,CAAC;AAAA,EAEH,SAAS,GAAQ;AACf,UAAM,IAAI,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,OAAO,EAAE,QAAS,CAAC;AAAA,EAC7D;AAEF,CAAC;AAEM,SAAS,mBAAmB;AAEjC,aAAO,iCAAa,EAAE,oBAAoB,GAAG,EAAE,SAAS,EAAE,UAAU,KAAK,EAAE,CAAC;AAC9E;",
6
6
  "names": []
7
7
  }
@@ -27,6 +27,9 @@ var postMatchmakeMethod = createEndpoint("/matchmake/:method/:roomName", { metho
27
27
  if (transport.protocol !== void 0) {
28
28
  response.protocol = transport.protocol;
29
29
  }
30
+ if (transport.fingerprint !== void 0) {
31
+ response.fingerprint = transport.fingerprint;
32
+ }
30
33
  const json = JSON.stringify(response);
31
34
  return new Response(json, {
32
35
  headers: {
@@ -43,7 +46,7 @@ var postMatchmakeMethod = createEndpoint("/matchmake/:method/:roomName", { metho
43
46
  }
44
47
  });
45
48
  function getDefaultRouter() {
46
- return createRouter({ postMatchmakeMethod });
49
+ return createRouter({ postMatchmakeMethod }, { openapi: { disabled: true } });
47
50
  }
48
51
  export {
49
52
  getDefaultRouter,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/router/default_routes.ts"],
4
- "sourcesContent": ["import { createEndpoint, createRouter } from \"@colyseus/better-call\";\nimport * as matchMaker from \"../MatchMaker.ts\";\nimport { getBearerToken } from \"../utils/Utils.ts\";\nimport { getTransport } from \"../Transport.ts\";\n\nexport const postMatchmakeMethod = createEndpoint(\"/matchmake/:method/:roomName\", { method: \"POST\" }, async (ctx) => {\n // do not accept matchmaking requests if already shutting down\n if (matchMaker.state === matchMaker.MatchMakerState.SHUTTING_DOWN) {\n throw ctx.error(503);\n }\n\n const requestHeaders = ctx.request.headers;\n\n const method = ctx.params.method;\n const roomName = ctx.params.roomName;\n\n try {\n const clientOptions = ctx.body;\n const response = await matchMaker.controller.invokeMethod(\n method,\n roomName,\n clientOptions,\n {\n token: getBearerToken(ctx.request.headers.get('authorization')),\n headers: ctx.request.headers,\n ip: requestHeaders.get('x-forwarded-for') ?? requestHeaders.get('x-client-ip') ?? requestHeaders.get('x-real-ip'),\n req: ctx.request as any,\n },\n );\n\n //\n // TODO: refactor here.\n // expose protocol, if available.\n //\n const transport = getTransport();\n if (transport.protocol !== undefined) {\n response.protocol = transport.protocol;\n }\n\n const json = JSON.stringify(response);\n\n return new Response(json, {\n headers: {\n 'content-type': 'application/json',\n //\n // Set content length manually to avoid \"chunked\" transfer-encoding header\n // See https://github.com/haxetink/tink_http/issues/27\n //\n 'content-length': json.length.toString(),\n },\n }) as any;\n\n } catch (e: any) {\n throw ctx.error(e.code, { code: e.code, error: e.message, });\n }\n\n});\n\nexport function getDefaultRouter() {\n return createRouter({ postMatchmakeMethod });\n}"],
5
- "mappings": ";AAAA,SAAS,gBAAgB,oBAAoB;AAC7C,YAAY,gBAAgB;AAC5B,SAAS,sBAAsB;AAC/B,SAAS,oBAAoB;AAEtB,IAAM,sBAAsB,eAAe,gCAAgC,EAAE,QAAQ,OAAO,GAAG,OAAO,QAAQ;AAEnH,MAAe,qBAAqB,2BAAgB,eAAe;AACjE,UAAM,IAAI,MAAM,GAAG;AAAA,EACrB;AAEA,QAAM,iBAAiB,IAAI,QAAQ;AAEnC,QAAM,SAAS,IAAI,OAAO;AAC1B,QAAM,WAAW,IAAI,OAAO;AAE5B,MAAI;AACF,UAAM,gBAAgB,IAAI;AAC1B,UAAM,WAAW,MAAiB,sBAAW;AAAA,MAC3C;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,QACE,OAAO,eAAe,IAAI,QAAQ,QAAQ,IAAI,eAAe,CAAC;AAAA,QAC9D,SAAS,IAAI,QAAQ;AAAA,QACrB,IAAI,eAAe,IAAI,iBAAiB,KAAK,eAAe,IAAI,aAAa,KAAK,eAAe,IAAI,WAAW;AAAA,QAChH,KAAK,IAAI;AAAA,MACX;AAAA,IACF;AAMA,UAAM,YAAY,aAAa;AAC/B,QAAI,UAAU,aAAa,QAAW;AACpC,eAAS,WAAW,UAAU;AAAA,IAChC;AAEA,UAAM,OAAO,KAAK,UAAU,QAAQ;AAEpC,WAAO,IAAI,SAAS,MAAM;AAAA,MACxB,SAAS;AAAA,QACP,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA,QAKhB,kBAAkB,KAAK,OAAO,SAAS;AAAA,MACzC;AAAA,IACF,CAAC;AAAA,EAEH,SAAS,GAAQ;AACf,UAAM,IAAI,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,OAAO,EAAE,QAAS,CAAC;AAAA,EAC7D;AAEF,CAAC;AAEM,SAAS,mBAAmB;AACjC,SAAO,aAAa,EAAE,oBAAoB,CAAC;AAC7C;",
4
+ "sourcesContent": ["import { createEndpoint, createRouter } from \"@colyseus/better-call\";\nimport * as matchMaker from \"../MatchMaker.ts\";\nimport { getBearerToken } from \"../utils/Utils.ts\";\nimport { getTransport } from \"../Transport.ts\";\n\nexport const postMatchmakeMethod = createEndpoint(\"/matchmake/:method/:roomName\", { method: \"POST\" }, async (ctx) => {\n // do not accept matchmaking requests if already shutting down\n if (matchMaker.state === matchMaker.MatchMakerState.SHUTTING_DOWN) {\n throw ctx.error(503);\n }\n\n const requestHeaders = ctx.request.headers;\n\n const method = ctx.params.method;\n const roomName = ctx.params.roomName;\n\n try {\n const clientOptions = ctx.body;\n const response = await matchMaker.controller.invokeMethod(\n method,\n roomName,\n clientOptions,\n {\n token: getBearerToken(ctx.request.headers.get('authorization')),\n headers: ctx.request.headers,\n ip: requestHeaders.get('x-forwarded-for') ?? requestHeaders.get('x-client-ip') ?? requestHeaders.get('x-real-ip'),\n req: ctx.request as any,\n },\n );\n\n //\n // TODO: refactor here.\n // expose protocol, if available.\n //\n const transport = getTransport();\n if (transport.protocol !== undefined) {\n response.protocol = transport.protocol;\n }\n // WebTransport (h3): client pins the self-signed cert via serverCertificateHashes.\n if (transport.fingerprint !== undefined) {\n response.fingerprint = transport.fingerprint;\n }\n\n const json = JSON.stringify(response);\n\n return new Response(json, {\n headers: {\n 'content-type': 'application/json',\n //\n // Set content length manually to avoid \"chunked\" transfer-encoding header\n // See https://github.com/haxetink/tink_http/issues/27\n //\n 'content-length': json.length.toString(),\n },\n }) as any;\n\n } catch (e: any) {\n throw ctx.error(e.code, { code: e.code, error: e.message, });\n }\n\n});\n\nexport function getDefaultRouter() {\n // no public docs page \u2014 the docs surface is playground's gated /__apidocs\n return createRouter({ postMatchmakeMethod }, { openapi: { disabled: true } });\n}"],
5
+ "mappings": ";AAAA,SAAS,gBAAgB,oBAAoB;AAC7C,YAAY,gBAAgB;AAC5B,SAAS,sBAAsB;AAC/B,SAAS,oBAAoB;AAEtB,IAAM,sBAAsB,eAAe,gCAAgC,EAAE,QAAQ,OAAO,GAAG,OAAO,QAAQ;AAEnH,MAAe,qBAAqB,2BAAgB,eAAe;AACjE,UAAM,IAAI,MAAM,GAAG;AAAA,EACrB;AAEA,QAAM,iBAAiB,IAAI,QAAQ;AAEnC,QAAM,SAAS,IAAI,OAAO;AAC1B,QAAM,WAAW,IAAI,OAAO;AAE5B,MAAI;AACF,UAAM,gBAAgB,IAAI;AAC1B,UAAM,WAAW,MAAiB,sBAAW;AAAA,MAC3C;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,QACE,OAAO,eAAe,IAAI,QAAQ,QAAQ,IAAI,eAAe,CAAC;AAAA,QAC9D,SAAS,IAAI,QAAQ;AAAA,QACrB,IAAI,eAAe,IAAI,iBAAiB,KAAK,eAAe,IAAI,aAAa,KAAK,eAAe,IAAI,WAAW;AAAA,QAChH,KAAK,IAAI;AAAA,MACX;AAAA,IACF;AAMA,UAAM,YAAY,aAAa;AAC/B,QAAI,UAAU,aAAa,QAAW;AACpC,eAAS,WAAW,UAAU;AAAA,IAChC;AAEA,QAAI,UAAU,gBAAgB,QAAW;AACvC,eAAS,cAAc,UAAU;AAAA,IACnC;AAEA,UAAM,OAAO,KAAK,UAAU,QAAQ;AAEpC,WAAO,IAAI,SAAS,MAAM;AAAA,MACxB,SAAS;AAAA,QACP,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA,QAKhB,kBAAkB,KAAK,OAAO,SAAS;AAAA,MACzC;AAAA,IACF,CAAC;AAAA,EAEH,SAAS,GAAQ;AACf,UAAM,IAAI,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,OAAO,EAAE,QAAS,CAAC;AAAA,EAC7D;AAEF,CAAC;AAEM,SAAS,mBAAmB;AAEjC,SAAO,aAAa,EAAE,oBAAoB,GAAG,EAAE,SAAS,EAAE,UAAU,KAAK,EAAE,CAAC;AAC9E;",
6
6
  "names": []
7
7
  }
@@ -107,7 +107,12 @@ function expressRootRoute(expressApp) {
107
107
  return stack.find((layer) => layer.match("/") && !["query", "expressInit"].includes(layer.name));
108
108
  }
109
109
  function createRouter(endpoints, config = {}) {
110
- return (0, import_better_call.createRouter)({ ...endpoints }, config);
110
+ return (0, import_better_call.createRouter)({ ...endpoints }, {
111
+ // better-call's /api/reference page dumps the full API surface
112
+ // unauthenticated — opt back in by passing `openapi` explicitly.
113
+ openapi: { disabled: true },
114
+ ...config
115
+ });
111
116
  }
112
117
  function basicAuth(opts) {
113
118
  const { users, validate } = opts;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/router/index.ts"],
4
- "sourcesContent": ["import type express from \"express\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\nimport { createHash, timingSafeEqual } from \"node:crypto\";\nimport { type Endpoint, type Router, type RouterConfig, createRouter as createBetterCallRouter, createEndpoint, createMiddleware, APIError } from \"@colyseus/better-call\";\nimport { toNodeHandler, getRequest, setResponse } from \"@colyseus/better-call/node\";\nimport { Transport } from \"../Transport.ts\";\nimport { controller } from \"../matchmaker/controller.ts\";\nimport pkg from \"../../package.json\" with { type: \"json\" };\n\nexport {\n createEndpoint,\n createMiddleware,\n createInternalContext,\n\n // Re-export types needed for declaration emit\n type Router,\n type RouterConfig,\n type Endpoint,\n type EndpointHandler,\n type EndpointOptions,\n type EndpointContext,\n type StrictEndpoint,\n} from \"@colyseus/better-call\";\n\nexport { toNodeHandler };\n\nexport function bindRouterToTransport(transport: Transport, router: Router, useExpress: boolean) {\n // add default \"/__healthcheck\" endpoint\n router.addEndpoint(createEndpoint(\"/__healthcheck\", { method: \"GET\" }, async (ctx) => {\n return new Response(\"OK\", { status: 200 });\n }));\n\n const server = transport.server;\n\n // check if the server is bound to an express app\n const expressApp: express.Application = (useExpress)\n ? transport.getExpressApp() as express.Application\n // fallback searching for express app in server listeners\n : server?.listeners('request').find((listener: Function) => listener.name === \"app\" && listener['mountpath'] === '/') as express.Application;\n\n // add default \"/\" route, if not provided.\n const hasRootRoute = (\n // check if express app has a root route\n (expressApp && expressRootRoute(expressApp) !== undefined) ||\n\n // check if router has a root route\n Object.values(router.endpoints).some(endpoint => endpoint.path === \"/\")\n );\n\n if (!hasRootRoute) {\n router.addEndpoint(createEndpoint(\"/\", { method: \"GET\" }, async (ctx) => {\n return new Response(`Colyseus ${pkg.version}`, { status: 200 });\n }));\n }\n\n // use custom bindRouter method if provided\n if (!server && transport.bindRouter) {\n transport.bindRouter(router);\n return;\n }\n\n // which route handler to use\n // (router + fallback to express, or just router)\n let next: any;\n\n if (expressApp) {\n server.removeListener('request', expressApp);\n\n next = async (req: IncomingMessage, res: ServerResponse) => {\n // check if the route is defined in the router\n // if so, use the router handler, otherwise fallback to express\n if (router.findRoute(req.method, req.url.split('?')[0]) !== undefined) {\n const protocol = req.headers[\"x-forwarded-proto\"] || ((req.socket as any).encrypted ? \"https\" : \"http\");\n const base = `${protocol}://${req.headers[\":authority\"] || req.headers.host}`;\n const response = await router.handler(getRequest({ base, request: req }));\n return setResponse(res, response);\n\n } else {\n return expressApp['handle'](req, res);\n }\n };\n\n } else {\n next = toNodeHandler(router.handler);\n }\n\n // handle cors headers for all requests by default\n server.prependListener('request', (req: IncomingMessage, res: ServerResponse) => {\n const corsHeaders = {\n ...controller.DEFAULT_CORS_HEADERS,\n ...controller.getCorsHeaders(new Headers(req.headers as any)),\n };\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204, corsHeaders);\n res.end();\n return;\n }\n\n Object.entries(corsHeaders).forEach(([key, value]) => {\n res.setHeader(key, value);\n });\n\n next(req, res);\n });\n}\n\nfunction expressRootRoute(expressApp: express.Application) {\n //\n // express v5 uses `app.router`, express v4 uses `app._router`\n // check for `app._router` first, then `app.router`\n //\n // (express v4 will show a warning if `app.router` is used)\n //\n const stack = (expressApp as any)?._router?.stack ?? (expressApp as any)?.router?.stack;\n\n if (!stack) {\n return false;\n }\n\n return stack.find((layer: any) => layer.match('/') && !['query', 'expressInit'].includes(layer.name));\n}\n\nexport function createRouter<\n E extends Record<string, Endpoint>,\n Config extends RouterConfig\n>(endpoints: E, config: Config = {} as Config) {\n return createBetterCallRouter({ ...endpoints }, config);\n}\n\nexport interface BasicAuthOptions {\n /** username \u2192 password. The common static case. */\n users?: Record<string, string>;\n /** Custom validator (e.g. DB-backed). Takes precedence over `users`. */\n validate?: (username: string, password: string) => boolean | Promise<boolean>;\n /** Realm shown in the browser prompt. Default 'Restricted'. */\n realm?: string;\n}\n\n/**\n * HTTP Basic Auth middleware. Drop into any endpoint's `use:` slot to gate\n * it behind a browser credentials prompt:\n *\n * playground({ use: [basicAuth({ users: { admin: 's3cret' } })] })\n */\nexport function basicAuth(opts: BasicAuthOptions) {\n const { users, validate } = opts;\n if (!users && !validate) {\n throw new Error('[basicAuth] provide `users` or `validate`');\n }\n // Realm is interpolated into a header \u2014 strip `\"` so it can't break out.\n const challenge = `Basic realm=\"${(opts.realm ?? 'Restricted').replace(/\"/g, '')}\", charset=\"UTF-8\"`;\n\n return createMiddleware(async (ctx) => {\n const creds = parseBasicHeader(ctx.getHeader('authorization'));\n const ok = !!creds && (validate\n ? await validate(creds.username, creds.password)\n : staticCheck(users!, creds.username, creds.password));\n if (!ok) {\n throw new APIError(401, { message: 'authentication required' }, { 'WWW-Authenticate': challenge });\n }\n });\n}\n\nfunction parseBasicHeader(header: string | null | undefined) {\n if (!header) { return null; }\n const sep = header.indexOf(' ');\n if (sep < 0 || header.slice(0, sep).toLowerCase() !== 'basic') { return null; }\n let decoded: string;\n try { decoded = Buffer.from(header.slice(sep + 1), 'base64').toString('utf8'); } catch { return null; }\n const colon = decoded.indexOf(':');\n if (colon < 0) { return null; }\n return { username: decoded.slice(0, colon), password: decoded.slice(colon + 1) };\n}\n\nfunction staticCheck(users: Record<string, string>, username: string, password: string): boolean {\n const expected = Object.prototype.hasOwnProperty.call(users, username) ? users[username] : undefined;\n // Compare even for an unknown user so reject timing doesn't reveal which\n // usernames exist.\n return safeEqual(password, expected ?? '\\0') && expected !== undefined;\n}\n\n// Hash both sides first: equalizes length (timingSafeEqual throws on a\n// length mismatch, which would itself leak the secret's length).\nfunction safeEqual(a: string, b: string): boolean {\n return timingSafeEqual(\n createHash('sha256').update(a).digest(),\n createHash('sha256').update(b).digest(),\n );\n}\n\n// ---------------------------------------------------------------------------\n// dualModeEndpoints \u2014 shared express-compat layer for @colyseus/admin,\n// @colyseus/monitor, @colyseus/playground. Builds the two local routers\n// (specific = no catch-all, full = everything) and the matching node\n// handlers, then packages the express middleware so the return value works\n// both as `{...spread}` into createRouter AND as `app.use(\"/\", x)` middleware.\n// ---------------------------------------------------------------------------\n\nexport type ExpressMiddleware = (\n req: IncomingMessage,\n res: ServerResponse,\n next: (err?: any) => void,\n) => void;\n\nexport type NodeHandler = (req: IncomingMessage, res: ServerResponse) => Promise<void>;\n\nexport interface DualModeHelpers {\n specificRouter: Router;\n specificHandler: NodeHandler;\n fullRouter: Router;\n fullHandler: NodeHandler;\n}\n\nexport function dualModeEndpoints<E extends Record<string, Endpoint>>(\n endpoints: E,\n opts: {\n /** Key in `endpoints` whose path is a catch-all. Excluded from `specificRouter` so it doesn't eat fall-through decisions. */\n catchAllKey?: keyof E;\n /** Build the express middleware given the pre-built routers + node handlers. */\n buildMiddleware: (helpers: DualModeHelpers) => ExpressMiddleware;\n },\n): ExpressMiddleware & E {\n const fullRouter = createRouter(endpoints);\n const fullHandler = toNodeHandler(fullRouter.handler) as NodeHandler;\n\n const specificEndpoints = opts.catchAllKey\n ? Object.fromEntries(\n Object.entries(endpoints).filter(([k]) => k !== opts.catchAllKey),\n ) as Partial<E>\n : endpoints;\n const specificRouter = createRouter(specificEndpoints as E);\n const specificHandler = toNodeHandler(specificRouter.handler) as NodeHandler;\n\n const middleware = opts.buildMiddleware({\n specificRouter, specificHandler, fullRouter, fullHandler,\n });\n return Object.assign(middleware, endpoints) as ExpressMiddleware & E;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type express from \"express\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\nimport { createHash, timingSafeEqual } from \"node:crypto\";\nimport { type Endpoint, type Router, type RouterConfig, createRouter as createBetterCallRouter, createEndpoint, createMiddleware, APIError } from \"@colyseus/better-call\";\nimport { toNodeHandler, getRequest, setResponse } from \"@colyseus/better-call/node\";\nimport { Transport } from \"../Transport.ts\";\nimport { controller } from \"../matchmaker/controller.ts\";\nimport pkg from \"../../package.json\" with { type: \"json\" };\n\nexport {\n createEndpoint,\n createMiddleware,\n createInternalContext,\n\n // Re-export types needed for declaration emit\n type Router,\n type RouterConfig,\n type Endpoint,\n type EndpointHandler,\n type EndpointOptions,\n type EndpointContext,\n type StrictEndpoint,\n} from \"@colyseus/better-call\";\n\nexport { toNodeHandler };\n\nexport function bindRouterToTransport(transport: Transport, router: Router, useExpress: boolean) {\n // add default \"/__healthcheck\" endpoint\n router.addEndpoint(createEndpoint(\"/__healthcheck\", { method: \"GET\" }, async (ctx) => {\n return new Response(\"OK\", { status: 200 });\n }));\n\n const server = transport.server;\n\n // check if the server is bound to an express app\n const expressApp: express.Application = (useExpress)\n ? transport.getExpressApp() as express.Application\n // fallback searching for express app in server listeners\n : server?.listeners('request').find((listener: Function) => listener.name === \"app\" && listener['mountpath'] === '/') as express.Application;\n\n // add default \"/\" route, if not provided.\n const hasRootRoute = (\n // check if express app has a root route\n (expressApp && expressRootRoute(expressApp) !== undefined) ||\n\n // check if router has a root route\n Object.values(router.endpoints).some(endpoint => endpoint.path === \"/\")\n );\n\n if (!hasRootRoute) {\n router.addEndpoint(createEndpoint(\"/\", { method: \"GET\" }, async (ctx) => {\n return new Response(`Colyseus ${pkg.version}`, { status: 200 });\n }));\n }\n\n // use custom bindRouter method if provided\n if (!server && transport.bindRouter) {\n transport.bindRouter(router);\n return;\n }\n\n // which route handler to use\n // (router + fallback to express, or just router)\n let next: any;\n\n if (expressApp) {\n server.removeListener('request', expressApp);\n\n next = async (req: IncomingMessage, res: ServerResponse) => {\n // check if the route is defined in the router\n // if so, use the router handler, otherwise fallback to express\n if (router.findRoute(req.method, req.url.split('?')[0]) !== undefined) {\n const protocol = req.headers[\"x-forwarded-proto\"] || ((req.socket as any).encrypted ? \"https\" : \"http\");\n const base = `${protocol}://${req.headers[\":authority\"] || req.headers.host}`;\n const response = await router.handler(getRequest({ base, request: req }));\n return setResponse(res, response);\n\n } else {\n return expressApp['handle'](req, res);\n }\n };\n\n } else {\n next = toNodeHandler(router.handler);\n }\n\n // handle cors headers for all requests by default\n server.prependListener('request', (req: IncomingMessage, res: ServerResponse) => {\n const corsHeaders = {\n ...controller.DEFAULT_CORS_HEADERS,\n ...controller.getCorsHeaders(new Headers(req.headers as any)),\n };\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204, corsHeaders);\n res.end();\n return;\n }\n\n Object.entries(corsHeaders).forEach(([key, value]) => {\n res.setHeader(key, value);\n });\n\n next(req, res);\n });\n}\n\nfunction expressRootRoute(expressApp: express.Application) {\n //\n // express v5 uses `app.router`, express v4 uses `app._router`\n // check for `app._router` first, then `app.router`\n //\n // (express v4 will show a warning if `app.router` is used)\n //\n const stack = (expressApp as any)?._router?.stack ?? (expressApp as any)?.router?.stack;\n\n if (!stack) {\n return false;\n }\n\n return stack.find((layer: any) => layer.match('/') && !['query', 'expressInit'].includes(layer.name));\n}\n\nexport function createRouter<\n E extends Record<string, Endpoint>,\n Config extends RouterConfig\n>(endpoints: E, config: Config = {} as Config) {\n return createBetterCallRouter({ ...endpoints }, {\n // better-call's /api/reference page dumps the full API surface\n // unauthenticated \u2014 opt back in by passing `openapi` explicitly.\n openapi: { disabled: true },\n ...config,\n });\n}\n\nexport interface BasicAuthOptions {\n /** username \u2192 password. The common static case. */\n users?: Record<string, string>;\n /** Custom validator (e.g. DB-backed). Takes precedence over `users`. */\n validate?: (username: string, password: string) => boolean | Promise<boolean>;\n /** Realm shown in the browser prompt. Default 'Restricted'. */\n realm?: string;\n}\n\n/**\n * HTTP Basic Auth middleware. Drop into any endpoint's `use:` slot to gate\n * it behind a browser credentials prompt:\n *\n * playground({ use: [basicAuth({ users: { admin: 's3cret' } })] })\n */\nexport function basicAuth(opts: BasicAuthOptions) {\n const { users, validate } = opts;\n if (!users && !validate) {\n throw new Error('[basicAuth] provide `users` or `validate`');\n }\n // Realm is interpolated into a header \u2014 strip `\"` so it can't break out.\n const challenge = `Basic realm=\"${(opts.realm ?? 'Restricted').replace(/\"/g, '')}\", charset=\"UTF-8\"`;\n\n return createMiddleware(async (ctx) => {\n const creds = parseBasicHeader(ctx.getHeader('authorization'));\n const ok = !!creds && (validate\n ? await validate(creds.username, creds.password)\n : staticCheck(users!, creds.username, creds.password));\n if (!ok) {\n throw new APIError(401, { message: 'authentication required' }, { 'WWW-Authenticate': challenge });\n }\n });\n}\n\nfunction parseBasicHeader(header: string | null | undefined) {\n if (!header) { return null; }\n const sep = header.indexOf(' ');\n if (sep < 0 || header.slice(0, sep).toLowerCase() !== 'basic') { return null; }\n let decoded: string;\n try { decoded = Buffer.from(header.slice(sep + 1), 'base64').toString('utf8'); } catch { return null; }\n const colon = decoded.indexOf(':');\n if (colon < 0) { return null; }\n return { username: decoded.slice(0, colon), password: decoded.slice(colon + 1) };\n}\n\nfunction staticCheck(users: Record<string, string>, username: string, password: string): boolean {\n const expected = Object.prototype.hasOwnProperty.call(users, username) ? users[username] : undefined;\n // Compare even for an unknown user so reject timing doesn't reveal which\n // usernames exist.\n return safeEqual(password, expected ?? '\\0') && expected !== undefined;\n}\n\n// Hash both sides first: equalizes length (timingSafeEqual throws on a\n// length mismatch, which would itself leak the secret's length).\nfunction safeEqual(a: string, b: string): boolean {\n return timingSafeEqual(\n createHash('sha256').update(a).digest(),\n createHash('sha256').update(b).digest(),\n );\n}\n\n// ---------------------------------------------------------------------------\n// dualModeEndpoints \u2014 shared express-compat layer for @colyseus/admin,\n// @colyseus/monitor, @colyseus/playground. Builds the two local routers\n// (specific = no catch-all, full = everything) and the matching node\n// handlers, then packages the express middleware so the return value works\n// both as `{...spread}` into createRouter AND as `app.use(\"/\", x)` middleware.\n// ---------------------------------------------------------------------------\n\nexport type ExpressMiddleware = (\n req: IncomingMessage,\n res: ServerResponse,\n next: (err?: any) => void,\n) => void;\n\nexport type NodeHandler = (req: IncomingMessage, res: ServerResponse) => Promise<void>;\n\nexport interface DualModeHelpers {\n specificRouter: Router;\n specificHandler: NodeHandler;\n fullRouter: Router;\n fullHandler: NodeHandler;\n}\n\nexport function dualModeEndpoints<E extends Record<string, Endpoint>>(\n endpoints: E,\n opts: {\n /** Key in `endpoints` whose path is a catch-all. Excluded from `specificRouter` so it doesn't eat fall-through decisions. */\n catchAllKey?: keyof E;\n /** Build the express middleware given the pre-built routers + node handlers. */\n buildMiddleware: (helpers: DualModeHelpers) => ExpressMiddleware;\n },\n): ExpressMiddleware & E {\n const fullRouter = createRouter(endpoints);\n const fullHandler = toNodeHandler(fullRouter.handler) as NodeHandler;\n\n const specificEndpoints = opts.catchAllKey\n ? Object.fromEntries(\n Object.entries(endpoints).filter(([k]) => k !== opts.catchAllKey),\n ) as Partial<E>\n : endpoints;\n const specificRouter = createRouter(specificEndpoints as E);\n const specificHandler = toNodeHandler(specificRouter.handler) as NodeHandler;\n\n const middleware = opts.buildMiddleware({\n specificRouter, specificHandler, fullRouter, fullHandler,\n });\n return Object.assign(middleware, endpoints) as ExpressMiddleware & E;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["import_better_call", "pkg", "createBetterCallRouter"]
7
7
  }
@@ -70,7 +70,12 @@ function expressRootRoute(expressApp) {
70
70
  return stack.find((layer) => layer.match("/") && !["query", "expressInit"].includes(layer.name));
71
71
  }
72
72
  function createRouter(endpoints, config = {}) {
73
- return createBetterCallRouter({ ...endpoints }, config);
73
+ return createBetterCallRouter({ ...endpoints }, {
74
+ // better-call's /api/reference page dumps the full API surface
75
+ // unauthenticated — opt back in by passing `openapi` explicitly.
76
+ openapi: { disabled: true },
77
+ ...config
78
+ });
74
79
  }
75
80
  function basicAuth(opts) {
76
81
  const { users, validate } = opts;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/router/index.ts"],
4
- "sourcesContent": ["import type express from \"express\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\nimport { createHash, timingSafeEqual } from \"node:crypto\";\nimport { type Endpoint, type Router, type RouterConfig, createRouter as createBetterCallRouter, createEndpoint, createMiddleware, APIError } from \"@colyseus/better-call\";\nimport { toNodeHandler, getRequest, setResponse } from \"@colyseus/better-call/node\";\nimport { Transport } from \"../Transport.ts\";\nimport { controller } from \"../matchmaker/controller.ts\";\nimport pkg from \"../../package.json\" with { type: \"json\" };\n\nexport {\n createEndpoint,\n createMiddleware,\n createInternalContext,\n\n // Re-export types needed for declaration emit\n type Router,\n type RouterConfig,\n type Endpoint,\n type EndpointHandler,\n type EndpointOptions,\n type EndpointContext,\n type StrictEndpoint,\n} from \"@colyseus/better-call\";\n\nexport { toNodeHandler };\n\nexport function bindRouterToTransport(transport: Transport, router: Router, useExpress: boolean) {\n // add default \"/__healthcheck\" endpoint\n router.addEndpoint(createEndpoint(\"/__healthcheck\", { method: \"GET\" }, async (ctx) => {\n return new Response(\"OK\", { status: 200 });\n }));\n\n const server = transport.server;\n\n // check if the server is bound to an express app\n const expressApp: express.Application = (useExpress)\n ? transport.getExpressApp() as express.Application\n // fallback searching for express app in server listeners\n : server?.listeners('request').find((listener: Function) => listener.name === \"app\" && listener['mountpath'] === '/') as express.Application;\n\n // add default \"/\" route, if not provided.\n const hasRootRoute = (\n // check if express app has a root route\n (expressApp && expressRootRoute(expressApp) !== undefined) ||\n\n // check if router has a root route\n Object.values(router.endpoints).some(endpoint => endpoint.path === \"/\")\n );\n\n if (!hasRootRoute) {\n router.addEndpoint(createEndpoint(\"/\", { method: \"GET\" }, async (ctx) => {\n return new Response(`Colyseus ${pkg.version}`, { status: 200 });\n }));\n }\n\n // use custom bindRouter method if provided\n if (!server && transport.bindRouter) {\n transport.bindRouter(router);\n return;\n }\n\n // which route handler to use\n // (router + fallback to express, or just router)\n let next: any;\n\n if (expressApp) {\n server.removeListener('request', expressApp);\n\n next = async (req: IncomingMessage, res: ServerResponse) => {\n // check if the route is defined in the router\n // if so, use the router handler, otherwise fallback to express\n if (router.findRoute(req.method, req.url.split('?')[0]) !== undefined) {\n const protocol = req.headers[\"x-forwarded-proto\"] || ((req.socket as any).encrypted ? \"https\" : \"http\");\n const base = `${protocol}://${req.headers[\":authority\"] || req.headers.host}`;\n const response = await router.handler(getRequest({ base, request: req }));\n return setResponse(res, response);\n\n } else {\n return expressApp['handle'](req, res);\n }\n };\n\n } else {\n next = toNodeHandler(router.handler);\n }\n\n // handle cors headers for all requests by default\n server.prependListener('request', (req: IncomingMessage, res: ServerResponse) => {\n const corsHeaders = {\n ...controller.DEFAULT_CORS_HEADERS,\n ...controller.getCorsHeaders(new Headers(req.headers as any)),\n };\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204, corsHeaders);\n res.end();\n return;\n }\n\n Object.entries(corsHeaders).forEach(([key, value]) => {\n res.setHeader(key, value);\n });\n\n next(req, res);\n });\n}\n\nfunction expressRootRoute(expressApp: express.Application) {\n //\n // express v5 uses `app.router`, express v4 uses `app._router`\n // check for `app._router` first, then `app.router`\n //\n // (express v4 will show a warning if `app.router` is used)\n //\n const stack = (expressApp as any)?._router?.stack ?? (expressApp as any)?.router?.stack;\n\n if (!stack) {\n return false;\n }\n\n return stack.find((layer: any) => layer.match('/') && !['query', 'expressInit'].includes(layer.name));\n}\n\nexport function createRouter<\n E extends Record<string, Endpoint>,\n Config extends RouterConfig\n>(endpoints: E, config: Config = {} as Config) {\n return createBetterCallRouter({ ...endpoints }, config);\n}\n\nexport interface BasicAuthOptions {\n /** username \u2192 password. The common static case. */\n users?: Record<string, string>;\n /** Custom validator (e.g. DB-backed). Takes precedence over `users`. */\n validate?: (username: string, password: string) => boolean | Promise<boolean>;\n /** Realm shown in the browser prompt. Default 'Restricted'. */\n realm?: string;\n}\n\n/**\n * HTTP Basic Auth middleware. Drop into any endpoint's `use:` slot to gate\n * it behind a browser credentials prompt:\n *\n * playground({ use: [basicAuth({ users: { admin: 's3cret' } })] })\n */\nexport function basicAuth(opts: BasicAuthOptions) {\n const { users, validate } = opts;\n if (!users && !validate) {\n throw new Error('[basicAuth] provide `users` or `validate`');\n }\n // Realm is interpolated into a header \u2014 strip `\"` so it can't break out.\n const challenge = `Basic realm=\"${(opts.realm ?? 'Restricted').replace(/\"/g, '')}\", charset=\"UTF-8\"`;\n\n return createMiddleware(async (ctx) => {\n const creds = parseBasicHeader(ctx.getHeader('authorization'));\n const ok = !!creds && (validate\n ? await validate(creds.username, creds.password)\n : staticCheck(users!, creds.username, creds.password));\n if (!ok) {\n throw new APIError(401, { message: 'authentication required' }, { 'WWW-Authenticate': challenge });\n }\n });\n}\n\nfunction parseBasicHeader(header: string | null | undefined) {\n if (!header) { return null; }\n const sep = header.indexOf(' ');\n if (sep < 0 || header.slice(0, sep).toLowerCase() !== 'basic') { return null; }\n let decoded: string;\n try { decoded = Buffer.from(header.slice(sep + 1), 'base64').toString('utf8'); } catch { return null; }\n const colon = decoded.indexOf(':');\n if (colon < 0) { return null; }\n return { username: decoded.slice(0, colon), password: decoded.slice(colon + 1) };\n}\n\nfunction staticCheck(users: Record<string, string>, username: string, password: string): boolean {\n const expected = Object.prototype.hasOwnProperty.call(users, username) ? users[username] : undefined;\n // Compare even for an unknown user so reject timing doesn't reveal which\n // usernames exist.\n return safeEqual(password, expected ?? '\\0') && expected !== undefined;\n}\n\n// Hash both sides first: equalizes length (timingSafeEqual throws on a\n// length mismatch, which would itself leak the secret's length).\nfunction safeEqual(a: string, b: string): boolean {\n return timingSafeEqual(\n createHash('sha256').update(a).digest(),\n createHash('sha256').update(b).digest(),\n );\n}\n\n// ---------------------------------------------------------------------------\n// dualModeEndpoints \u2014 shared express-compat layer for @colyseus/admin,\n// @colyseus/monitor, @colyseus/playground. Builds the two local routers\n// (specific = no catch-all, full = everything) and the matching node\n// handlers, then packages the express middleware so the return value works\n// both as `{...spread}` into createRouter AND as `app.use(\"/\", x)` middleware.\n// ---------------------------------------------------------------------------\n\nexport type ExpressMiddleware = (\n req: IncomingMessage,\n res: ServerResponse,\n next: (err?: any) => void,\n) => void;\n\nexport type NodeHandler = (req: IncomingMessage, res: ServerResponse) => Promise<void>;\n\nexport interface DualModeHelpers {\n specificRouter: Router;\n specificHandler: NodeHandler;\n fullRouter: Router;\n fullHandler: NodeHandler;\n}\n\nexport function dualModeEndpoints<E extends Record<string, Endpoint>>(\n endpoints: E,\n opts: {\n /** Key in `endpoints` whose path is a catch-all. Excluded from `specificRouter` so it doesn't eat fall-through decisions. */\n catchAllKey?: keyof E;\n /** Build the express middleware given the pre-built routers + node handlers. */\n buildMiddleware: (helpers: DualModeHelpers) => ExpressMiddleware;\n },\n): ExpressMiddleware & E {\n const fullRouter = createRouter(endpoints);\n const fullHandler = toNodeHandler(fullRouter.handler) as NodeHandler;\n\n const specificEndpoints = opts.catchAllKey\n ? Object.fromEntries(\n Object.entries(endpoints).filter(([k]) => k !== opts.catchAllKey),\n ) as Partial<E>\n : endpoints;\n const specificRouter = createRouter(specificEndpoints as E);\n const specificHandler = toNodeHandler(specificRouter.handler) as NodeHandler;\n\n const middleware = opts.buildMiddleware({\n specificRouter, specificHandler, fullRouter, fullHandler,\n });\n return Object.assign(middleware, endpoints) as ExpressMiddleware & E;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type express from \"express\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\nimport { createHash, timingSafeEqual } from \"node:crypto\";\nimport { type Endpoint, type Router, type RouterConfig, createRouter as createBetterCallRouter, createEndpoint, createMiddleware, APIError } from \"@colyseus/better-call\";\nimport { toNodeHandler, getRequest, setResponse } from \"@colyseus/better-call/node\";\nimport { Transport } from \"../Transport.ts\";\nimport { controller } from \"../matchmaker/controller.ts\";\nimport pkg from \"../../package.json\" with { type: \"json\" };\n\nexport {\n createEndpoint,\n createMiddleware,\n createInternalContext,\n\n // Re-export types needed for declaration emit\n type Router,\n type RouterConfig,\n type Endpoint,\n type EndpointHandler,\n type EndpointOptions,\n type EndpointContext,\n type StrictEndpoint,\n} from \"@colyseus/better-call\";\n\nexport { toNodeHandler };\n\nexport function bindRouterToTransport(transport: Transport, router: Router, useExpress: boolean) {\n // add default \"/__healthcheck\" endpoint\n router.addEndpoint(createEndpoint(\"/__healthcheck\", { method: \"GET\" }, async (ctx) => {\n return new Response(\"OK\", { status: 200 });\n }));\n\n const server = transport.server;\n\n // check if the server is bound to an express app\n const expressApp: express.Application = (useExpress)\n ? transport.getExpressApp() as express.Application\n // fallback searching for express app in server listeners\n : server?.listeners('request').find((listener: Function) => listener.name === \"app\" && listener['mountpath'] === '/') as express.Application;\n\n // add default \"/\" route, if not provided.\n const hasRootRoute = (\n // check if express app has a root route\n (expressApp && expressRootRoute(expressApp) !== undefined) ||\n\n // check if router has a root route\n Object.values(router.endpoints).some(endpoint => endpoint.path === \"/\")\n );\n\n if (!hasRootRoute) {\n router.addEndpoint(createEndpoint(\"/\", { method: \"GET\" }, async (ctx) => {\n return new Response(`Colyseus ${pkg.version}`, { status: 200 });\n }));\n }\n\n // use custom bindRouter method if provided\n if (!server && transport.bindRouter) {\n transport.bindRouter(router);\n return;\n }\n\n // which route handler to use\n // (router + fallback to express, or just router)\n let next: any;\n\n if (expressApp) {\n server.removeListener('request', expressApp);\n\n next = async (req: IncomingMessage, res: ServerResponse) => {\n // check if the route is defined in the router\n // if so, use the router handler, otherwise fallback to express\n if (router.findRoute(req.method, req.url.split('?')[0]) !== undefined) {\n const protocol = req.headers[\"x-forwarded-proto\"] || ((req.socket as any).encrypted ? \"https\" : \"http\");\n const base = `${protocol}://${req.headers[\":authority\"] || req.headers.host}`;\n const response = await router.handler(getRequest({ base, request: req }));\n return setResponse(res, response);\n\n } else {\n return expressApp['handle'](req, res);\n }\n };\n\n } else {\n next = toNodeHandler(router.handler);\n }\n\n // handle cors headers for all requests by default\n server.prependListener('request', (req: IncomingMessage, res: ServerResponse) => {\n const corsHeaders = {\n ...controller.DEFAULT_CORS_HEADERS,\n ...controller.getCorsHeaders(new Headers(req.headers as any)),\n };\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204, corsHeaders);\n res.end();\n return;\n }\n\n Object.entries(corsHeaders).forEach(([key, value]) => {\n res.setHeader(key, value);\n });\n\n next(req, res);\n });\n}\n\nfunction expressRootRoute(expressApp: express.Application) {\n //\n // express v5 uses `app.router`, express v4 uses `app._router`\n // check for `app._router` first, then `app.router`\n //\n // (express v4 will show a warning if `app.router` is used)\n //\n const stack = (expressApp as any)?._router?.stack ?? (expressApp as any)?.router?.stack;\n\n if (!stack) {\n return false;\n }\n\n return stack.find((layer: any) => layer.match('/') && !['query', 'expressInit'].includes(layer.name));\n}\n\nexport function createRouter<\n E extends Record<string, Endpoint>,\n Config extends RouterConfig\n>(endpoints: E, config: Config = {} as Config) {\n return createBetterCallRouter({ ...endpoints }, {\n // better-call's /api/reference page dumps the full API surface\n // unauthenticated \u2014 opt back in by passing `openapi` explicitly.\n openapi: { disabled: true },\n ...config,\n });\n}\n\nexport interface BasicAuthOptions {\n /** username \u2192 password. The common static case. */\n users?: Record<string, string>;\n /** Custom validator (e.g. DB-backed). Takes precedence over `users`. */\n validate?: (username: string, password: string) => boolean | Promise<boolean>;\n /** Realm shown in the browser prompt. Default 'Restricted'. */\n realm?: string;\n}\n\n/**\n * HTTP Basic Auth middleware. Drop into any endpoint's `use:` slot to gate\n * it behind a browser credentials prompt:\n *\n * playground({ use: [basicAuth({ users: { admin: 's3cret' } })] })\n */\nexport function basicAuth(opts: BasicAuthOptions) {\n const { users, validate } = opts;\n if (!users && !validate) {\n throw new Error('[basicAuth] provide `users` or `validate`');\n }\n // Realm is interpolated into a header \u2014 strip `\"` so it can't break out.\n const challenge = `Basic realm=\"${(opts.realm ?? 'Restricted').replace(/\"/g, '')}\", charset=\"UTF-8\"`;\n\n return createMiddleware(async (ctx) => {\n const creds = parseBasicHeader(ctx.getHeader('authorization'));\n const ok = !!creds && (validate\n ? await validate(creds.username, creds.password)\n : staticCheck(users!, creds.username, creds.password));\n if (!ok) {\n throw new APIError(401, { message: 'authentication required' }, { 'WWW-Authenticate': challenge });\n }\n });\n}\n\nfunction parseBasicHeader(header: string | null | undefined) {\n if (!header) { return null; }\n const sep = header.indexOf(' ');\n if (sep < 0 || header.slice(0, sep).toLowerCase() !== 'basic') { return null; }\n let decoded: string;\n try { decoded = Buffer.from(header.slice(sep + 1), 'base64').toString('utf8'); } catch { return null; }\n const colon = decoded.indexOf(':');\n if (colon < 0) { return null; }\n return { username: decoded.slice(0, colon), password: decoded.slice(colon + 1) };\n}\n\nfunction staticCheck(users: Record<string, string>, username: string, password: string): boolean {\n const expected = Object.prototype.hasOwnProperty.call(users, username) ? users[username] : undefined;\n // Compare even for an unknown user so reject timing doesn't reveal which\n // usernames exist.\n return safeEqual(password, expected ?? '\\0') && expected !== undefined;\n}\n\n// Hash both sides first: equalizes length (timingSafeEqual throws on a\n// length mismatch, which would itself leak the secret's length).\nfunction safeEqual(a: string, b: string): boolean {\n return timingSafeEqual(\n createHash('sha256').update(a).digest(),\n createHash('sha256').update(b).digest(),\n );\n}\n\n// ---------------------------------------------------------------------------\n// dualModeEndpoints \u2014 shared express-compat layer for @colyseus/admin,\n// @colyseus/monitor, @colyseus/playground. Builds the two local routers\n// (specific = no catch-all, full = everything) and the matching node\n// handlers, then packages the express middleware so the return value works\n// both as `{...spread}` into createRouter AND as `app.use(\"/\", x)` middleware.\n// ---------------------------------------------------------------------------\n\nexport type ExpressMiddleware = (\n req: IncomingMessage,\n res: ServerResponse,\n next: (err?: any) => void,\n) => void;\n\nexport type NodeHandler = (req: IncomingMessage, res: ServerResponse) => Promise<void>;\n\nexport interface DualModeHelpers {\n specificRouter: Router;\n specificHandler: NodeHandler;\n fullRouter: Router;\n fullHandler: NodeHandler;\n}\n\nexport function dualModeEndpoints<E extends Record<string, Endpoint>>(\n endpoints: E,\n opts: {\n /** Key in `endpoints` whose path is a catch-all. Excluded from `specificRouter` so it doesn't eat fall-through decisions. */\n catchAllKey?: keyof E;\n /** Build the express middleware given the pre-built routers + node handlers. */\n buildMiddleware: (helpers: DualModeHelpers) => ExpressMiddleware;\n },\n): ExpressMiddleware & E {\n const fullRouter = createRouter(endpoints);\n const fullHandler = toNodeHandler(fullRouter.handler) as NodeHandler;\n\n const specificEndpoints = opts.catchAllKey\n ? Object.fromEntries(\n Object.entries(endpoints).filter(([k]) => k !== opts.catchAllKey),\n ) as Partial<E>\n : endpoints;\n const specificRouter = createRouter(specificEndpoints as E);\n const specificHandler = toNodeHandler(specificRouter.handler) as NodeHandler;\n\n const middleware = opts.buildMiddleware({\n specificRouter, specificHandler, fullRouter, fullHandler,\n });\n return Object.assign(middleware, endpoints) as ExpressMiddleware & E;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["createEndpoint", "createMiddleware"]
7
7
  }
@@ -30,7 +30,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // packages/core/src/router/node.ts
31
31
  var node_exports = {};
32
32
  __export(node_exports, {
33
- createNodeMatchmakingMiddleware: () => createNodeMatchmakingMiddleware
33
+ createNodeMatchmakingMiddleware: () => createNodeMatchmakingMiddleware,
34
+ prereadRequestBodies: () => prereadRequestBodies
34
35
  });
35
36
  module.exports = __toCommonJS(node_exports);
36
37
  var import_url = require("url");
@@ -47,6 +48,39 @@ function readBody(req) {
47
48
  req.on("error", reject);
48
49
  });
49
50
  }
51
+ function prereadRequestBodies(server) {
52
+ const listeners = server.listeners("request");
53
+ const run = (req, res) => {
54
+ for (const listener of listeners) {
55
+ listener.call(server, req, res);
56
+ }
57
+ };
58
+ server.removeAllListeners("request");
59
+ server.on("request", (req, res) => {
60
+ const method = req.method ?? "GET";
61
+ const needsBody = method !== "GET" && method !== "HEAD" && method !== "OPTIONS" && req.body === void 0 && Number(req.headers["content-length"]) > 0;
62
+ if (!needsBody) {
63
+ run(req, res);
64
+ return;
65
+ }
66
+ const chunks = [];
67
+ req.on("data", (chunk) => chunks.push(chunk));
68
+ req.on("end", () => {
69
+ const raw = Buffer.concat(chunks).toString("utf8");
70
+ if ((req.headers["content-type"] || "").includes("application/json")) {
71
+ try {
72
+ req.body = JSON.parse(raw || "{}");
73
+ } catch {
74
+ req.body = raw;
75
+ }
76
+ } else {
77
+ req.body = raw;
78
+ }
79
+ run(req, res);
80
+ });
81
+ req.on("error", () => run(req, res));
82
+ });
83
+ }
50
84
  function getCorsHeaders(req, headers) {
51
85
  return {
52
86
  ...matchMaker.controller.DEFAULT_CORS_HEADERS,
@@ -94,5 +128,6 @@ function createNodeMatchmakingMiddleware() {
94
128
  }
95
129
  // Annotate the CommonJS export names for ESM import in node:
96
130
  0 && (module.exports = {
97
- createNodeMatchmakingMiddleware
131
+ createNodeMatchmakingMiddleware,
132
+ prereadRequestBodies
98
133
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/router/node.ts"],
4
- "sourcesContent": ["/**\n * Raw Node.js adapter for Colyseus matchmaking routes used by `colyseus/vite`.\n *\n * This file exists specifically so the Vite plugin can share Vite's dev HTTP\n * server while still exposing the Colyseus `/matchmake/*` endpoints.\n *\n * Keep the matchmaking behavior itself in `router/default_routes.ts` and use\n * this file only as the thin raw Node/Express adapter around it.\n */\nimport type http from 'http';\nimport { URL } from 'url';\nimport * as matchMaker from '../MatchMaker.ts';\nimport { setResponse } from '@colyseus/better-call/node';\nimport { postMatchmakeMethod } from './default_routes.ts';\n\nfunction readBody(req: http.IncomingMessage): Promise<any> {\n return new Promise((resolve, reject) => {\n let data = '';\n\n req.on('data', (chunk: Buffer | string) => {\n data += chunk.toString();\n });\n req.on('end', () => resolve(data ? JSON.parse(data) : {}));\n req.on('error', reject);\n });\n}\n\nfunction getCorsHeaders(req: http.IncomingMessage, headers?: Headers): Record<string, string> {\n return {\n ...matchMaker.controller.DEFAULT_CORS_HEADERS,\n ...matchMaker.controller.getCorsHeaders(headers),\n };\n}\n\nexport function createNodeMatchmakingMiddleware() {\n return async (\n req: http.IncomingMessage,\n res: http.ServerResponse,\n next: () => void,\n ) => {\n const url = new URL(req.url || '/', 'http://localhost');\n const isMatchmakeRoute = url.pathname.startsWith(`/${matchMaker.controller.matchmakeRoute}/`);\n\n if (!isMatchmakeRoute) {\n next();\n return;\n }\n\n const headers = new Headers(req.headers as Record<string, string>);\n const corsHeaders = getCorsHeaders(req, headers);\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204, corsHeaders);\n res.end();\n return;\n }\n\n if (req.method !== 'POST') {\n next();\n return;\n }\n\n const match = url.pathname.match(/^\\/matchmake\\/(\\w+)\\/(.+)/);\n if (!match) {\n next();\n return;\n }\n\n const [, method, roomName] = match;\n\n try {\n const response = await postMatchmakeMethod({\n params: { method, roomName },\n body: await readBody(req),\n headers: req.headers as Record<string, string>,\n request: { headers } as any,\n asResponse: true,\n });\n\n await setResponse(res, response);\n\n } catch {\n // Endpoint-level failures are returned as Response when `asResponse` is true.\n // Any thrown error here is unexpected, so let the next middleware decide.\n next();\n }\n };\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAUA,iBAAoB;AACpB,iBAA4B;AAC5B,kBAA4B;AAC5B,4BAAoC;AAEpC,SAAS,SAAS,KAAyC;AACzD,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,QAAI,OAAO;AAEX,QAAI,GAAG,QAAQ,CAAC,UAA2B;AACzC,cAAQ,MAAM,SAAS;AAAA,IACzB,CAAC;AACD,QAAI,GAAG,OAAO,MAAM,QAAQ,OAAO,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC;AACzD,QAAI,GAAG,SAAS,MAAM;AAAA,EACxB,CAAC;AACH;AAEA,SAAS,eAAe,KAA2B,SAA2C;AAC5F,SAAO;AAAA,IACL,GAAc,sBAAW;AAAA,IACzB,GAAc,sBAAW,eAAe,OAAO;AAAA,EACjD;AACF;AAEO,SAAS,kCAAkC;AAChD,SAAO,OACL,KACA,KACA,SACG;AACH,UAAM,MAAM,IAAI,eAAI,IAAI,OAAO,KAAK,kBAAkB;AACtD,UAAM,mBAAmB,IAAI,SAAS,WAAW,IAAe,sBAAW,cAAc,GAAG;AAE5F,QAAI,CAAC,kBAAkB;AACrB,WAAK;AACL;AAAA,IACF;AAEA,UAAM,UAAU,IAAI,QAAQ,IAAI,OAAiC;AACjE,UAAM,cAAc,eAAe,KAAK,OAAO;AAE/C,QAAI,IAAI,WAAW,WAAW;AAC5B,UAAI,UAAU,KAAK,WAAW;AAC9B,UAAI,IAAI;AACR;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,QAAQ;AACzB,WAAK;AACL;AAAA,IACF;AAEA,UAAM,QAAQ,IAAI,SAAS,MAAM,2BAA2B;AAC5D,QAAI,CAAC,OAAO;AACV,WAAK;AACL;AAAA,IACF;AAEA,UAAM,CAAC,EAAE,QAAQ,QAAQ,IAAI;AAE7B,QAAI;AACF,YAAM,WAAW,UAAM,2CAAoB;AAAA,QACzC,QAAQ,EAAE,QAAQ,SAAS;AAAA,QAC3B,MAAM,MAAM,SAAS,GAAG;AAAA,QACxB,SAAS,IAAI;AAAA,QACb,SAAS,EAAE,QAAQ;AAAA,QACnB,YAAY;AAAA,MACd,CAAC;AAED,gBAAM,yBAAY,KAAK,QAAQ;AAAA,IAEjC,QAAQ;AAGN,WAAK;AAAA,IACP;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["/**\n * Raw Node.js adapter for Colyseus matchmaking routes used by `colyseus/vite`.\n *\n * This file exists specifically so the Vite plugin can share Vite's dev HTTP\n * server while still exposing the Colyseus `/matchmake/*` endpoints.\n *\n * Keep the matchmaking behavior itself in `router/default_routes.ts` and use\n * this file only as the thin raw Node/Express adapter around it.\n */\nimport type http from 'http';\nimport { URL } from 'url';\nimport * as matchMaker from '../MatchMaker.ts';\nimport { setResponse } from '@colyseus/better-call/node';\nimport { postMatchmakeMethod } from './default_routes.ts';\n\nfunction readBody(req: http.IncomingMessage): Promise<any> {\n return new Promise((resolve, reject) => {\n let data = '';\n\n req.on('data', (chunk: Buffer | string) => {\n data += chunk.toString();\n });\n req.on('end', () => resolve(data ? JSON.parse(data) : {}));\n req.on('error', reject);\n });\n}\n\n/**\n * Buffer incoming request bodies and expose them as `req.body` before the\n * server's existing \"request\" listeners run.\n *\n * Needed when the HTTP server is consumed via `export default` (e.g. on Vercel)\n * rather than `listen()`: the matchmaking router reads the body from `req.body`\n * when present, otherwise from a lazy request stream that does not drain in that\n * mode \u2014 which would stall matchmaking POSTs.\n */\nexport function prereadRequestBodies(server: http.Server) {\n type WithBody = http.IncomingMessage & { body?: unknown };\n const listeners = server.listeners('request') as Array<(req: http.IncomingMessage, res: http.ServerResponse) => void>;\n const run = (req: http.IncomingMessage, res: http.ServerResponse) => {\n for (const listener of listeners) { listener.call(server, req, res); }\n };\n\n server.removeAllListeners('request');\n server.on('request', (req: WithBody, res) => {\n const method = req.method ?? 'GET';\n const needsBody =\n method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS' &&\n req.body === undefined &&\n Number(req.headers['content-length']) > 0;\n\n if (!needsBody) {\n run(req, res);\n return;\n }\n\n const chunks: Buffer[] = [];\n req.on('data', (chunk: Buffer) => chunks.push(chunk));\n req.on('end', () => {\n const raw = Buffer.concat(chunks).toString('utf8');\n if ((req.headers['content-type'] || '').includes('application/json')) {\n try { req.body = JSON.parse(raw || '{}'); } catch { req.body = raw; }\n } else {\n req.body = raw;\n }\n run(req, res);\n });\n req.on('error', () => run(req, res));\n });\n}\n\nfunction getCorsHeaders(req: http.IncomingMessage, headers?: Headers): Record<string, string> {\n return {\n ...matchMaker.controller.DEFAULT_CORS_HEADERS,\n ...matchMaker.controller.getCorsHeaders(headers),\n };\n}\n\nexport function createNodeMatchmakingMiddleware() {\n return async (\n req: http.IncomingMessage,\n res: http.ServerResponse,\n next: () => void,\n ) => {\n const url = new URL(req.url || '/', 'http://localhost');\n const isMatchmakeRoute = url.pathname.startsWith(`/${matchMaker.controller.matchmakeRoute}/`);\n\n if (!isMatchmakeRoute) {\n next();\n return;\n }\n\n const headers = new Headers(req.headers as Record<string, string>);\n const corsHeaders = getCorsHeaders(req, headers);\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204, corsHeaders);\n res.end();\n return;\n }\n\n if (req.method !== 'POST') {\n next();\n return;\n }\n\n const match = url.pathname.match(/^\\/matchmake\\/(\\w+)\\/(.+)/);\n if (!match) {\n next();\n return;\n }\n\n const [, method, roomName] = match;\n\n try {\n const response = await postMatchmakeMethod({\n params: { method, roomName },\n body: await readBody(req),\n headers: req.headers as Record<string, string>,\n request: { headers } as any,\n asResponse: true,\n });\n\n await setResponse(res, response);\n\n } catch {\n // Endpoint-level failures are returned as Response when `asResponse` is true.\n // Any thrown error here is unexpected, so let the next middleware decide.\n next();\n }\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -8,4 +8,14 @@
8
8
  * this file only as the thin raw Node/Express adapter around it.
9
9
  */
10
10
  import type http from 'http';
11
+ /**
12
+ * Buffer incoming request bodies and expose them as `req.body` before the
13
+ * server's existing "request" listeners run.
14
+ *
15
+ * Needed when the HTTP server is consumed via `export default` (e.g. on Vercel)
16
+ * rather than `listen()`: the matchmaking router reads the body from `req.body`
17
+ * when present, otherwise from a lazy request stream that does not drain in that
18
+ * mode — which would stall matchmaking POSTs.
19
+ */
20
+ export declare function prereadRequestBodies(server: http.Server): void;
11
21
  export declare function createNodeMatchmakingMiddleware(): (req: http.IncomingMessage, res: http.ServerResponse, next: () => void) => Promise<void>;
@@ -13,6 +13,39 @@ function readBody(req) {
13
13
  req.on("error", reject);
14
14
  });
15
15
  }
16
+ function prereadRequestBodies(server) {
17
+ const listeners = server.listeners("request");
18
+ const run = (req, res) => {
19
+ for (const listener of listeners) {
20
+ listener.call(server, req, res);
21
+ }
22
+ };
23
+ server.removeAllListeners("request");
24
+ server.on("request", (req, res) => {
25
+ const method = req.method ?? "GET";
26
+ const needsBody = method !== "GET" && method !== "HEAD" && method !== "OPTIONS" && req.body === void 0 && Number(req.headers["content-length"]) > 0;
27
+ if (!needsBody) {
28
+ run(req, res);
29
+ return;
30
+ }
31
+ const chunks = [];
32
+ req.on("data", (chunk) => chunks.push(chunk));
33
+ req.on("end", () => {
34
+ const raw = Buffer.concat(chunks).toString("utf8");
35
+ if ((req.headers["content-type"] || "").includes("application/json")) {
36
+ try {
37
+ req.body = JSON.parse(raw || "{}");
38
+ } catch {
39
+ req.body = raw;
40
+ }
41
+ } else {
42
+ req.body = raw;
43
+ }
44
+ run(req, res);
45
+ });
46
+ req.on("error", () => run(req, res));
47
+ });
48
+ }
16
49
  function getCorsHeaders(req, headers) {
17
50
  return {
18
51
  ...matchMaker.controller.DEFAULT_CORS_HEADERS,
@@ -59,5 +92,6 @@ function createNodeMatchmakingMiddleware() {
59
92
  };
60
93
  }
61
94
  export {
62
- createNodeMatchmakingMiddleware
95
+ createNodeMatchmakingMiddleware,
96
+ prereadRequestBodies
63
97
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/router/node.ts"],
4
- "sourcesContent": ["/**\n * Raw Node.js adapter for Colyseus matchmaking routes used by `colyseus/vite`.\n *\n * This file exists specifically so the Vite plugin can share Vite's dev HTTP\n * server while still exposing the Colyseus `/matchmake/*` endpoints.\n *\n * Keep the matchmaking behavior itself in `router/default_routes.ts` and use\n * this file only as the thin raw Node/Express adapter around it.\n */\nimport type http from 'http';\nimport { URL } from 'url';\nimport * as matchMaker from '../MatchMaker.ts';\nimport { setResponse } from '@colyseus/better-call/node';\nimport { postMatchmakeMethod } from './default_routes.ts';\n\nfunction readBody(req: http.IncomingMessage): Promise<any> {\n return new Promise((resolve, reject) => {\n let data = '';\n\n req.on('data', (chunk: Buffer | string) => {\n data += chunk.toString();\n });\n req.on('end', () => resolve(data ? JSON.parse(data) : {}));\n req.on('error', reject);\n });\n}\n\nfunction getCorsHeaders(req: http.IncomingMessage, headers?: Headers): Record<string, string> {\n return {\n ...matchMaker.controller.DEFAULT_CORS_HEADERS,\n ...matchMaker.controller.getCorsHeaders(headers),\n };\n}\n\nexport function createNodeMatchmakingMiddleware() {\n return async (\n req: http.IncomingMessage,\n res: http.ServerResponse,\n next: () => void,\n ) => {\n const url = new URL(req.url || '/', 'http://localhost');\n const isMatchmakeRoute = url.pathname.startsWith(`/${matchMaker.controller.matchmakeRoute}/`);\n\n if (!isMatchmakeRoute) {\n next();\n return;\n }\n\n const headers = new Headers(req.headers as Record<string, string>);\n const corsHeaders = getCorsHeaders(req, headers);\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204, corsHeaders);\n res.end();\n return;\n }\n\n if (req.method !== 'POST') {\n next();\n return;\n }\n\n const match = url.pathname.match(/^\\/matchmake\\/(\\w+)\\/(.+)/);\n if (!match) {\n next();\n return;\n }\n\n const [, method, roomName] = match;\n\n try {\n const response = await postMatchmakeMethod({\n params: { method, roomName },\n body: await readBody(req),\n headers: req.headers as Record<string, string>,\n request: { headers } as any,\n asResponse: true,\n });\n\n await setResponse(res, response);\n\n } catch {\n // Endpoint-level failures are returned as Response when `asResponse` is true.\n // Any thrown error here is unexpected, so let the next middleware decide.\n next();\n }\n };\n}\n"],
5
- "mappings": ";AAUA,SAAS,WAAW;AACpB,YAAY,gBAAgB;AAC5B,SAAS,mBAAmB;AAC5B,SAAS,2BAA2B;AAEpC,SAAS,SAAS,KAAyC;AACzD,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,QAAI,OAAO;AAEX,QAAI,GAAG,QAAQ,CAAC,UAA2B;AACzC,cAAQ,MAAM,SAAS;AAAA,IACzB,CAAC;AACD,QAAI,GAAG,OAAO,MAAM,QAAQ,OAAO,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC;AACzD,QAAI,GAAG,SAAS,MAAM;AAAA,EACxB,CAAC;AACH;AAEA,SAAS,eAAe,KAA2B,SAA2C;AAC5F,SAAO;AAAA,IACL,GAAc,sBAAW;AAAA,IACzB,GAAc,sBAAW,eAAe,OAAO;AAAA,EACjD;AACF;AAEO,SAAS,kCAAkC;AAChD,SAAO,OACL,KACA,KACA,SACG;AACH,UAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,kBAAkB;AACtD,UAAM,mBAAmB,IAAI,SAAS,WAAW,IAAe,sBAAW,cAAc,GAAG;AAE5F,QAAI,CAAC,kBAAkB;AACrB,WAAK;AACL;AAAA,IACF;AAEA,UAAM,UAAU,IAAI,QAAQ,IAAI,OAAiC;AACjE,UAAM,cAAc,eAAe,KAAK,OAAO;AAE/C,QAAI,IAAI,WAAW,WAAW;AAC5B,UAAI,UAAU,KAAK,WAAW;AAC9B,UAAI,IAAI;AACR;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,QAAQ;AACzB,WAAK;AACL;AAAA,IACF;AAEA,UAAM,QAAQ,IAAI,SAAS,MAAM,2BAA2B;AAC5D,QAAI,CAAC,OAAO;AACV,WAAK;AACL;AAAA,IACF;AAEA,UAAM,CAAC,EAAE,QAAQ,QAAQ,IAAI;AAE7B,QAAI;AACF,YAAM,WAAW,MAAM,oBAAoB;AAAA,QACzC,QAAQ,EAAE,QAAQ,SAAS;AAAA,QAC3B,MAAM,MAAM,SAAS,GAAG;AAAA,QACxB,SAAS,IAAI;AAAA,QACb,SAAS,EAAE,QAAQ;AAAA,QACnB,YAAY;AAAA,MACd,CAAC;AAED,YAAM,YAAY,KAAK,QAAQ;AAAA,IAEjC,QAAQ;AAGN,WAAK;AAAA,IACP;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["/**\n * Raw Node.js adapter for Colyseus matchmaking routes used by `colyseus/vite`.\n *\n * This file exists specifically so the Vite plugin can share Vite's dev HTTP\n * server while still exposing the Colyseus `/matchmake/*` endpoints.\n *\n * Keep the matchmaking behavior itself in `router/default_routes.ts` and use\n * this file only as the thin raw Node/Express adapter around it.\n */\nimport type http from 'http';\nimport { URL } from 'url';\nimport * as matchMaker from '../MatchMaker.ts';\nimport { setResponse } from '@colyseus/better-call/node';\nimport { postMatchmakeMethod } from './default_routes.ts';\n\nfunction readBody(req: http.IncomingMessage): Promise<any> {\n return new Promise((resolve, reject) => {\n let data = '';\n\n req.on('data', (chunk: Buffer | string) => {\n data += chunk.toString();\n });\n req.on('end', () => resolve(data ? JSON.parse(data) : {}));\n req.on('error', reject);\n });\n}\n\n/**\n * Buffer incoming request bodies and expose them as `req.body` before the\n * server's existing \"request\" listeners run.\n *\n * Needed when the HTTP server is consumed via `export default` (e.g. on Vercel)\n * rather than `listen()`: the matchmaking router reads the body from `req.body`\n * when present, otherwise from a lazy request stream that does not drain in that\n * mode \u2014 which would stall matchmaking POSTs.\n */\nexport function prereadRequestBodies(server: http.Server) {\n type WithBody = http.IncomingMessage & { body?: unknown };\n const listeners = server.listeners('request') as Array<(req: http.IncomingMessage, res: http.ServerResponse) => void>;\n const run = (req: http.IncomingMessage, res: http.ServerResponse) => {\n for (const listener of listeners) { listener.call(server, req, res); }\n };\n\n server.removeAllListeners('request');\n server.on('request', (req: WithBody, res) => {\n const method = req.method ?? 'GET';\n const needsBody =\n method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS' &&\n req.body === undefined &&\n Number(req.headers['content-length']) > 0;\n\n if (!needsBody) {\n run(req, res);\n return;\n }\n\n const chunks: Buffer[] = [];\n req.on('data', (chunk: Buffer) => chunks.push(chunk));\n req.on('end', () => {\n const raw = Buffer.concat(chunks).toString('utf8');\n if ((req.headers['content-type'] || '').includes('application/json')) {\n try { req.body = JSON.parse(raw || '{}'); } catch { req.body = raw; }\n } else {\n req.body = raw;\n }\n run(req, res);\n });\n req.on('error', () => run(req, res));\n });\n}\n\nfunction getCorsHeaders(req: http.IncomingMessage, headers?: Headers): Record<string, string> {\n return {\n ...matchMaker.controller.DEFAULT_CORS_HEADERS,\n ...matchMaker.controller.getCorsHeaders(headers),\n };\n}\n\nexport function createNodeMatchmakingMiddleware() {\n return async (\n req: http.IncomingMessage,\n res: http.ServerResponse,\n next: () => void,\n ) => {\n const url = new URL(req.url || '/', 'http://localhost');\n const isMatchmakeRoute = url.pathname.startsWith(`/${matchMaker.controller.matchmakeRoute}/`);\n\n if (!isMatchmakeRoute) {\n next();\n return;\n }\n\n const headers = new Headers(req.headers as Record<string, string>);\n const corsHeaders = getCorsHeaders(req, headers);\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204, corsHeaders);\n res.end();\n return;\n }\n\n if (req.method !== 'POST') {\n next();\n return;\n }\n\n const match = url.pathname.match(/^\\/matchmake\\/(\\w+)\\/(.+)/);\n if (!match) {\n next();\n return;\n }\n\n const [, method, roomName] = match;\n\n try {\n const response = await postMatchmakeMethod({\n params: { method, roomName },\n body: await readBody(req),\n headers: req.headers as Record<string, string>,\n request: { headers } as any,\n asResponse: true,\n });\n\n await setResponse(res, response);\n\n } catch {\n // Endpoint-level failures are returned as Response when `asResponse` is true.\n // Any thrown error here is unexpected, so let the next middleware decide.\n next();\n }\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -29,10 +29,10 @@ var NoneSerializer = class {
29
29
  }
30
30
  reset(data) {
31
31
  }
32
- getFullState(client) {
32
+ getFullState(_client, _timing) {
33
33
  return null;
34
34
  }
35
- applyPatches(clients, state) {
35
+ applyPatches(_clients, _state, _timing) {
36
36
  return false;
37
37
  }
38
38
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/serializer/NoneSerializer.ts"],
4
- "sourcesContent": ["import type { Client } from '../Transport.ts';\nimport type { Serializer } from './Serializer.ts';\n\nexport class NoneSerializer<T= any> implements Serializer<T> {\n public id: string = 'none';\n\n public reset(data: any) {}\n\n public getFullState(client?: Client) {\n return null;\n }\n\n public applyPatches(clients: Client[], state: T): boolean {\n return false;\n }\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGO,IAAM,iBAAN,MAAsD;AAAA,EAAtD;AACL,SAAO,KAAa;AAAA;AAAA,EAEb,MAAM,MAAW;AAAA,EAAC;AAAA,EAElB,aAAa,QAAiB;AACnC,WAAO;AAAA,EACT;AAAA,EAEO,aAAa,SAAmB,OAAmB;AACxD,WAAO;AAAA,EACT;AACF;",
4
+ "sourcesContent": ["import type { Client } from '../Transport.ts';\nimport type { PatchTimingContext, Serializer } from './Serializer.ts';\n\nexport class NoneSerializer<T= any> implements Serializer<T> {\n public id: string = 'none';\n\n public reset(data: any) {}\n\n public getFullState(_client?: Client, _timing?: PatchTimingContext) {\n return null;\n }\n\n public applyPatches(_clients: Client[], _state: T, _timing?: PatchTimingContext): boolean {\n return false;\n }\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGO,IAAM,iBAAN,MAAsD;AAAA,EAAtD;AACL,SAAO,KAAa;AAAA;AAAA,EAEb,MAAM,MAAW;AAAA,EAAC;AAAA,EAElB,aAAa,SAAkB,SAA8B;AAClE,WAAO;AAAA,EACT;AAAA,EAEO,aAAa,UAAoB,QAAW,SAAuC;AACxF,WAAO;AAAA,EACT;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,8 +1,8 @@
1
1
  import type { Client } from '../Transport.ts';
2
- import type { Serializer } from './Serializer.ts';
2
+ import type { PatchTimingContext, Serializer } from './Serializer.ts';
3
3
  export declare class NoneSerializer<T = any> implements Serializer<T> {
4
4
  id: string;
5
5
  reset(data: any): void;
6
- getFullState(client?: Client): any;
7
- applyPatches(clients: Client[], state: T): boolean;
6
+ getFullState(_client?: Client, _timing?: PatchTimingContext): any;
7
+ applyPatches(_clients: Client[], _state: T, _timing?: PatchTimingContext): boolean;
8
8
  }
@@ -5,10 +5,10 @@ var NoneSerializer = class {
5
5
  }
6
6
  reset(data) {
7
7
  }
8
- getFullState(client) {
8
+ getFullState(_client, _timing) {
9
9
  return null;
10
10
  }
11
- applyPatches(clients, state) {
11
+ applyPatches(_clients, _state, _timing) {
12
12
  return false;
13
13
  }
14
14
  };