@babylonjs-toolkit/server 0.0.0-stage → 9.35.0

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 (236) hide show
  1. package/CUSTOM.md +1690 -0
  2. package/DEPLOY.md +2012 -0
  3. package/README.md +1081 -2
  4. package/lib/AbuseLimits.d.ts +64 -0
  5. package/lib/AbuseLimits.js +121 -0
  6. package/lib/Authentication.d.ts +86 -0
  7. package/lib/Authentication.js +272 -0
  8. package/lib/BootSessionOwner.d.ts +86 -0
  9. package/lib/BootSessionOwner.js +116 -0
  10. package/lib/ColyseusLoggerAdapter.d.ts +32 -0
  11. package/lib/ColyseusLoggerAdapter.js +73 -0
  12. package/lib/DedicatedSceneRegistry.d.ts +52 -0
  13. package/lib/DedicatedSceneRegistry.js +74 -0
  14. package/lib/DedicatedSession.d.ts +152 -0
  15. package/lib/DedicatedSession.js +173 -0
  16. package/lib/DualTransport.d.ts +112 -0
  17. package/lib/DualTransport.js +197 -0
  18. package/lib/GameServer.d.ts +64 -0
  19. package/lib/GameServer.js +647 -0
  20. package/lib/GameServerConfig.d.ts +387 -0
  21. package/lib/GameServerConfig.js +1059 -0
  22. package/lib/GameServerLobbyRoom.d.ts +33 -0
  23. package/lib/GameServerLobbyRoom.js +56 -0
  24. package/lib/GameServerLogger.d.ts +111 -0
  25. package/lib/GameServerLogger.js +190 -0
  26. package/lib/GameServerMetrics.d.ts +158 -0
  27. package/lib/GameServerMetrics.js +276 -0
  28. package/lib/GameServerState.d.ts +164 -0
  29. package/lib/GameServerState.js +26 -0
  30. package/lib/HeadlessRuntime.d.ts +316 -0
  31. package/lib/HeadlessRuntime.js +380 -0
  32. package/lib/JoinGate.d.ts +34 -0
  33. package/lib/JoinGate.js +221 -0
  34. package/lib/NetworkAddresses.d.ts +33 -0
  35. package/lib/NetworkAddresses.js +90 -0
  36. package/lib/PlayerTokens.d.ts +17 -0
  37. package/lib/PlayerTokens.js +24 -0
  38. package/lib/RoomServerTransport.d.ts +73 -0
  39. package/lib/RoomServerTransport.js +90 -0
  40. package/lib/ServerEndpoints.d.ts +133 -0
  41. package/lib/ServerEndpoints.js +247 -0
  42. package/lib/ServerInit.d.ts +28 -0
  43. package/lib/ServerInit.js +113 -0
  44. package/lib/SessionRoom.d.ts +391 -0
  45. package/lib/SessionRoom.js +930 -0
  46. package/lib/TlsCertificates.d.ts +35 -0
  47. package/lib/TlsCertificates.js +130 -0
  48. package/lib/admin/AccountRoutes.d.ts +8 -0
  49. package/lib/admin/AccountRoutes.js +502 -0
  50. package/lib/admin/AdminAccounts.d.ts +329 -0
  51. package/lib/admin/AdminAccounts.js +607 -0
  52. package/lib/admin/AdminApiRouter.d.ts +202 -0
  53. package/lib/admin/AdminApiRouter.js +465 -0
  54. package/lib/admin/AdminApiTypes.d.ts +1782 -0
  55. package/lib/admin/AdminApiTypes.js +4 -0
  56. package/lib/admin/AdminAudit.d.ts +113 -0
  57. package/lib/admin/AdminAudit.js +266 -0
  58. package/lib/admin/AdminCli.d.ts +7 -0
  59. package/lib/admin/AdminCli.js +351 -0
  60. package/lib/admin/AdminClock.d.ts +51 -0
  61. package/lib/admin/AdminClock.js +10 -0
  62. package/lib/admin/AdminConsole.d.ts +39 -0
  63. package/lib/admin/AdminConsole.js +325 -0
  64. package/lib/admin/AdminConstants.d.ts +147 -0
  65. package/lib/admin/AdminConstants.js +167 -0
  66. package/lib/admin/AdminCrypto.d.ts +19 -0
  67. package/lib/admin/AdminCrypto.js +29 -0
  68. package/lib/admin/AdminExtensionsHost.d.ts +176 -0
  69. package/lib/admin/AdminExtensionsHost.js +307 -0
  70. package/lib/admin/AdminPasswords.d.ts +33 -0
  71. package/lib/admin/AdminPasswords.js +92 -0
  72. package/lib/admin/AdminPermissions.d.ts +28 -0
  73. package/lib/admin/AdminPermissions.js +74 -0
  74. package/lib/admin/AdminRecords.d.ts +196 -0
  75. package/lib/admin/AdminRecords.js +3 -0
  76. package/lib/admin/AdminRequestBodies.d.ts +47 -0
  77. package/lib/admin/AdminRequestBodies.js +84 -0
  78. package/lib/admin/AdminRequestGate.d.ts +38 -0
  79. package/lib/admin/AdminRequestGate.js +59 -0
  80. package/lib/admin/AdminResponses.d.ts +103 -0
  81. package/lib/admin/AdminResponses.js +164 -0
  82. package/lib/admin/AdminServices.d.ts +99 -0
  83. package/lib/admin/AdminServices.js +4 -0
  84. package/lib/admin/AdminStaticFiles.d.ts +29 -0
  85. package/lib/admin/AdminStaticFiles.js +97 -0
  86. package/lib/admin/AdminStore.d.ts +152 -0
  87. package/lib/admin/AdminStore.js +46 -0
  88. package/lib/admin/AlertChannels.d.ts +43 -0
  89. package/lib/admin/AlertChannels.js +120 -0
  90. package/lib/admin/AlertEngine.d.ts +231 -0
  91. package/lib/admin/AlertEngine.js +681 -0
  92. package/lib/admin/AlertRoutes.d.ts +8 -0
  93. package/lib/admin/AlertRoutes.js +180 -0
  94. package/lib/admin/ApiKeyRoutes.d.ts +8 -0
  95. package/lib/admin/ApiKeyRoutes.js +53 -0
  96. package/lib/admin/ApiKeys.d.ts +63 -0
  97. package/lib/admin/ApiKeys.js +167 -0
  98. package/lib/admin/AuditRoutes.d.ts +8 -0
  99. package/lib/admin/AuditRoutes.js +106 -0
  100. package/lib/admin/AuthToolRoutes.d.ts +13 -0
  101. package/lib/admin/AuthToolRoutes.js +164 -0
  102. package/lib/admin/BanList.d.ts +87 -0
  103. package/lib/admin/BanList.js +152 -0
  104. package/lib/admin/CborDecoder.d.ts +18 -0
  105. package/lib/admin/CborDecoder.js +201 -0
  106. package/lib/admin/ColyseusCloudClient.d.ts +313 -0
  107. package/lib/admin/ColyseusCloudClient.js +272 -0
  108. package/lib/admin/ColyseusCloudRoutes.d.ts +8 -0
  109. package/lib/admin/ColyseusCloudRoutes.js +91 -0
  110. package/lib/admin/ColyseusCloudService.d.ts +180 -0
  111. package/lib/admin/ColyseusCloudService.js +476 -0
  112. package/lib/admin/ColyseusCloudSettings.d.ts +16 -0
  113. package/lib/admin/ColyseusCloudSettings.js +29 -0
  114. package/lib/admin/ColyseusCloudViews.d.ts +105 -0
  115. package/lib/admin/ColyseusCloudViews.js +371 -0
  116. package/lib/admin/CommonPasswords.d.ts +6 -0
  117. package/lib/admin/CommonPasswords.js +19 -0
  118. package/lib/admin/ConfigOverrides.d.ts +174 -0
  119. package/lib/admin/ConfigOverrides.js +406 -0
  120. package/lib/admin/ConfigRoutes.d.ts +8 -0
  121. package/lib/admin/ConfigRoutes.js +91 -0
  122. package/lib/admin/ConsoleSessions.d.ts +112 -0
  123. package/lib/admin/ConsoleSessions.js +201 -0
  124. package/lib/admin/DedicatedRoutes.d.ts +21 -0
  125. package/lib/admin/DedicatedRoutes.js +254 -0
  126. package/lib/admin/ExtensionRoutes.d.ts +8 -0
  127. package/lib/admin/ExtensionRoutes.js +145 -0
  128. package/lib/admin/FileAdminStore.d.ts +157 -0
  129. package/lib/admin/FileAdminStore.js +349 -0
  130. package/lib/admin/FleetDirectory.d.ts +323 -0
  131. package/lib/admin/FleetDirectory.js +489 -0
  132. package/lib/admin/FleetLeader.d.ts +43 -0
  133. package/lib/admin/FleetLeader.js +89 -0
  134. package/lib/admin/FleetRoutes.d.ts +55 -0
  135. package/lib/admin/FleetRoutes.js +465 -0
  136. package/lib/admin/HealthWarnings.d.ts +10 -0
  137. package/lib/admin/HealthWarnings.js +95 -0
  138. package/lib/admin/LeaderboardRoutes.d.ts +8 -0
  139. package/lib/admin/LeaderboardRoutes.js +569 -0
  140. package/lib/admin/LogBuffer.d.ts +60 -0
  141. package/lib/admin/LogBuffer.js +92 -0
  142. package/lib/admin/LogRedaction.d.ts +15 -0
  143. package/lib/admin/LogRedaction.js +72 -0
  144. package/lib/admin/LogRoutes.d.ts +45 -0
  145. package/lib/admin/LogRoutes.js +307 -0
  146. package/lib/admin/MaintenanceMode.d.ts +41 -0
  147. package/lib/admin/MaintenanceMode.js +74 -0
  148. package/lib/admin/MaintenanceWindows.d.ts +91 -0
  149. package/lib/admin/MaintenanceWindows.js +207 -0
  150. package/lib/admin/MetricsRoutes.d.ts +8 -0
  151. package/lib/admin/MetricsRoutes.js +209 -0
  152. package/lib/admin/MetricsSampler.d.ts +124 -0
  153. package/lib/admin/MetricsSampler.js +211 -0
  154. package/lib/admin/OneTimeCodes.d.ts +60 -0
  155. package/lib/admin/OneTimeCodes.js +162 -0
  156. package/lib/admin/OpenIdConnect.d.ts +146 -0
  157. package/lib/admin/OpenIdConnect.js +335 -0
  158. package/lib/admin/Passkeys.d.ts +130 -0
  159. package/lib/admin/Passkeys.js +432 -0
  160. package/lib/admin/PlayerRoutes.d.ts +20 -0
  161. package/lib/admin/PlayerRoutes.js +676 -0
  162. package/lib/admin/RedisAdminStore.d.ts +114 -0
  163. package/lib/admin/RedisAdminStore.js +258 -0
  164. package/lib/admin/RolloutCoordinator.d.ts +99 -0
  165. package/lib/admin/RolloutCoordinator.js +321 -0
  166. package/lib/admin/ServerEvents.d.ts +57 -0
  167. package/lib/admin/ServerEvents.js +89 -0
  168. package/lib/admin/SessionRoutes.d.ts +113 -0
  169. package/lib/admin/SessionRoutes.js +386 -0
  170. package/lib/admin/UserRoutes.d.ts +8 -0
  171. package/lib/admin/UserRoutes.js +214 -0
  172. package/lib/cli.d.ts +2 -0
  173. package/lib/cli.js +221 -0
  174. package/lib/console/assets/index-CI5N7c-T.js +21 -0
  175. package/lib/console/assets/index-DPitvzLp.css +1 -0
  176. package/lib/console/favicon.svg +1 -0
  177. package/lib/console/index.html +16 -0
  178. package/lib/discord/DiscordOAuth.d.ts +57 -0
  179. package/lib/discord/DiscordOAuth.js +127 -0
  180. package/lib/discord/DiscordSessions.d.ts +79 -0
  181. package/lib/discord/DiscordSessions.js +135 -0
  182. package/lib/discord/DiscordSignInConstants.d.ts +54 -0
  183. package/lib/discord/DiscordSignInConstants.js +55 -0
  184. package/lib/discord/DiscordSignInRoutes.d.ts +51 -0
  185. package/lib/discord/DiscordSignInRoutes.js +359 -0
  186. package/lib/index.d.ts +24 -0
  187. package/lib/index.js +18 -0
  188. package/lib/leaderboards/FileLeaderboardRankStore.d.ts +293 -0
  189. package/lib/leaderboards/FileLeaderboardRankStore.js +808 -0
  190. package/lib/leaderboards/LeaderboardConstants.d.ts +63 -0
  191. package/lib/leaderboards/LeaderboardConstants.js +70 -0
  192. package/lib/leaderboards/LeaderboardError.d.ts +15 -0
  193. package/lib/leaderboards/LeaderboardError.js +20 -0
  194. package/lib/leaderboards/LeaderboardIdentity.d.ts +44 -0
  195. package/lib/leaderboards/LeaderboardIdentity.js +72 -0
  196. package/lib/leaderboards/LeaderboardPublicApi.d.ts +17 -0
  197. package/lib/leaderboards/LeaderboardPublicApi.js +389 -0
  198. package/lib/leaderboards/LeaderboardRankStore.d.ts +296 -0
  199. package/lib/leaderboards/LeaderboardRankStore.js +18 -0
  200. package/lib/leaderboards/LeaderboardRules.d.ts +122 -0
  201. package/lib/leaderboards/LeaderboardRules.js +472 -0
  202. package/lib/leaderboards/LeaderboardScheduler.d.ts +43 -0
  203. package/lib/leaderboards/LeaderboardScheduler.js +103 -0
  204. package/lib/leaderboards/LeaderboardService.d.ts +669 -0
  205. package/lib/leaderboards/LeaderboardService.js +1667 -0
  206. package/lib/leaderboards/LeaderboardTransfer.d.ts +35 -0
  207. package/lib/leaderboards/LeaderboardTransfer.js +580 -0
  208. package/lib/leaderboards/RedisLeaderboardRankStore.d.ts +282 -0
  209. package/lib/leaderboards/RedisLeaderboardRankStore.js +691 -0
  210. package/package.json +64 -4
  211. package/templates/init/Caddyfile +6 -0
  212. package/templates/init/DEPLOY.md +233 -0
  213. package/templates/init/Dockerfile +12 -0
  214. package/templates/init/app.json +21 -0
  215. package/templates/init/aws/README.md +118 -0
  216. package/templates/init/aws/ecs-fargate-alb.yml +207 -0
  217. package/templates/init/aws/ecs-fargate-nlb-quic.yml +210 -0
  218. package/templates/init/babylon-game-server.config.mjs +158 -0
  219. package/templates/init/colyseus-cloud/README.md +133 -0
  220. package/templates/init/colyseus-cloud/babylon-game-server.config.mjs +36 -0
  221. package/templates/init/colyseus-cloud/ecosystem.config.cjs +35 -0
  222. package/templates/init/colyseus-cloud/gitignore +6 -0
  223. package/templates/init/colyseus-cloud/package.json +15 -0
  224. package/templates/init/digitalocean-app.yaml +30 -0
  225. package/templates/init/docker-compose.full.yml +45 -0
  226. package/templates/init/docker-compose.websocket.yml +41 -0
  227. package/templates/init/fly.toml +56 -0
  228. package/templates/init/heroku.yml +11 -0
  229. package/templates/init/kubernetes/deployment.yaml +71 -0
  230. package/templates/init/kubernetes/redis.yaml +73 -0
  231. package/templates/init/kubernetes/service.yaml +24 -0
  232. package/templates/init/monitoring/grafana-dashboard.json +539 -0
  233. package/templates/init/monitoring/prometheus-alerts.yml +51 -0
  234. package/templates/init/monitoring/prometheus-scrape.yml +17 -0
  235. package/templates/init/railway.json +13 -0
  236. package/templates/init/render.yaml +19 -0
@@ -0,0 +1,164 @@
1
+ // Response building of the console and its API (game-server-admin-console D5, D9, DR §7.3). The router's Node writer
2
+ // ends after the first chunk, so every answer is one in-memory buffer with content-length and connection: close.
3
+ /** Session cookie name over TLS (D10): the __Host- prefix pins it to this host, path / and Secure. */
4
+ export const SECURE_SESSION_COOKIE_NAME = "__Host-bgs-admin";
5
+ /** Session cookie name on loopback HTTP (D10). */
6
+ export const SESSION_COOKIE_NAME = "bgs-admin";
7
+ /** HTTP status of a served file. */
8
+ const HTTP_OK = 200;
9
+ /** HTTP status of an API redirect (SSO). */
10
+ const HTTP_FOUND = 302;
11
+ /** HTTP status of the console's trailing-slash redirect. */
12
+ const HTTP_PERMANENT_REDIRECT = 308;
13
+ /** Content type of every JSON answer. */
14
+ const JSON_CONTENT_TYPE = "application/json; charset=utf-8";
15
+ /** Content type of every plain-text answer. */
16
+ const TEXT_CONTENT_TYPE = "text/plain; charset=utf-8";
17
+ /** Headers on every console response (D9). */
18
+ export const SECURITY_HEADERS = {
19
+ "content-security-policy": "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self'; font-src 'self'; object-src 'none'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'",
20
+ "x-frame-options": "DENY",
21
+ "x-content-type-options": "nosniff",
22
+ "referrer-policy": "no-referrer",
23
+ "cross-origin-opener-policy": "same-origin"
24
+ };
25
+ /**
26
+ * Builds the single-buffer answer of an API call (DR §7.3): JSON, a download, or a redirect, with the D9 headers, the
27
+ * console origin as the only CORS origin (credentials off, overriding the router's CORS answer) and each cookie.
28
+ * @param result The handler's result.
29
+ * @param request The console origin of the request.
30
+ * @returns The response.
31
+ */
32
+ export function buildAdminResponse(result, request) {
33
+ const headers = new Headers();
34
+ let bytes;
35
+ let status = result.status;
36
+ if (result.redirectTo != null) {
37
+ status = HTTP_FOUND;
38
+ bytes = new Uint8Array(0);
39
+ headers.set("location", result.redirectTo);
40
+ }
41
+ else if (result.download != null) {
42
+ bytes = result.download.bytes;
43
+ headers.set("content-type", result.download.contentType);
44
+ headers.set("content-disposition", "attachment; filename=\"" + result.download.fileName.replace(/["\\\r\n]/g, "_") + "\"");
45
+ }
46
+ else {
47
+ bytes = new TextEncoder().encode(JSON.stringify(result.body));
48
+ headers.set("content-type", JSON_CONTENT_TYPE);
49
+ }
50
+ applyCommonHeaders(headers, bytes.byteLength, "no-store");
51
+ headers.set("access-control-allow-origin", request.consoleOrigin);
52
+ headers.set("access-control-allow-credentials", "false");
53
+ headers.set("vary", "Origin");
54
+ for (const cookie of result.setCookies ?? [])
55
+ headers.append("set-cookie", cookie);
56
+ return new Response(responseBody(bytes), { status: status, headers: headers });
57
+ }
58
+ /**
59
+ * A JSON answer outside the API pipeline (static-path refusals), with the same single-buffer headers.
60
+ * @param status HTTP status.
61
+ * @param body The JSON body.
62
+ * @param consoleOrigin The console origin of the request.
63
+ * @returns The response.
64
+ */
65
+ export function adminJsonResponse(status, body, consoleOrigin) {
66
+ return buildAdminResponse({ status: status, body: body }, { consoleOrigin: consoleOrigin });
67
+ }
68
+ /**
69
+ * A plain-text console answer (missing asset, console not built).
70
+ * @param status HTTP status.
71
+ * @param text The text.
72
+ * @returns The response.
73
+ */
74
+ export function adminTextResponse(status, text) {
75
+ const bytes = new TextEncoder().encode(text);
76
+ const headers = new Headers({ "content-type": TEXT_CONTENT_TYPE });
77
+ applyCommonHeaders(headers, bytes.byteLength, "no-store");
78
+ headers.set("access-control-allow-credentials", "false");
79
+ return new Response(bytes, { status: status, headers: headers });
80
+ }
81
+ /**
82
+ * A static file answer.
83
+ * @param bytes The file.
84
+ * @param contentType Its content type.
85
+ * @param cacheControl Its cache-control value.
86
+ * @returns The response.
87
+ */
88
+ export function staticFileResponse(bytes, contentType, cacheControl) {
89
+ const headers = new Headers({ "content-type": contentType });
90
+ applyCommonHeaders(headers, bytes.byteLength, cacheControl);
91
+ headers.set("access-control-allow-credentials", "false");
92
+ return new Response(responseBody(bytes), { status: HTTP_OK, headers: headers });
93
+ }
94
+ /**
95
+ * A permanent redirect (GET /game-server/admin → /game-server/admin/).
96
+ * @param location The target path.
97
+ * @returns The 308 response.
98
+ */
99
+ export function redirectResponse(location) {
100
+ const headers = new Headers({ location: location });
101
+ applyCommonHeaders(headers, 0, "no-store");
102
+ headers.set("access-control-allow-credentials", "false");
103
+ return new Response(null, { status: HTTP_PERMANENT_REDIRECT, headers: headers });
104
+ }
105
+ /**
106
+ * The name of the session cookie for a request (R9).
107
+ * @param secure True for a secure request (TLS, or HTTPS at a trusted proxy).
108
+ * @returns `__Host-bgs-admin` or `bgs-admin`.
109
+ */
110
+ export function sessionCookieName(secure) {
111
+ return secure ? SECURE_SESSION_COOKIE_NAME : SESSION_COOKIE_NAME;
112
+ }
113
+ /**
114
+ * The Set-Cookie value of a console session (D10).
115
+ * @param value The cookie value (a random session id).
116
+ * @param secure True for a secure request: adds Secure and uses the __Host- name.
117
+ * @param maxAgeSeconds How long the browser keeps it, in seconds.
118
+ * @returns The Set-Cookie value.
119
+ */
120
+ export function buildSessionCookie(value, secure, maxAgeSeconds) {
121
+ const attributes = [sessionCookieName(secure) + "=" + value, "HttpOnly", "SameSite=Strict", "Path=/", "Max-Age=" + Math.max(0, Math.floor(maxAgeSeconds))];
122
+ if (secure)
123
+ attributes.push("Secure");
124
+ return attributes.join("; ");
125
+ }
126
+ /**
127
+ * The Set-Cookie value that removes the session cookie (sign-out).
128
+ * @param secure True for a secure request.
129
+ * @returns The Set-Cookie value.
130
+ */
131
+ export function buildClearingCookie(secure) {
132
+ return buildSessionCookie("", secure, 0);
133
+ }
134
+ /**
135
+ * The console's own origin, from the Host header and the D9 scheme rule.
136
+ * @param headers The request headers.
137
+ * @param secure True for a secure request.
138
+ * @returns scheme://host.
139
+ */
140
+ export function consoleOriginOf(headers, secure) {
141
+ return (secure ? "https" : "http") + "://" + (headers.get("host") ?? "localhost");
142
+ }
143
+ /**
144
+ * The body of a single-buffer answer. The router's Node writer never ends a stream that yields no chunk, so an empty
145
+ * body must be null.
146
+ * @param bytes The body bytes.
147
+ * @returns The bytes, or null when empty.
148
+ */
149
+ function responseBody(bytes) {
150
+ return (bytes.byteLength > 0) ? bytes : null;
151
+ }
152
+ /**
153
+ * Sets the headers every console answer carries: length, connection: close, caching and the D9 headers.
154
+ * @param headers The headers to fill.
155
+ * @param byteLength The body length in bytes.
156
+ * @param cacheControl The cache-control value.
157
+ */
158
+ function applyCommonHeaders(headers, byteLength, cacheControl) {
159
+ headers.set("content-length", String(byteLength));
160
+ headers.set("connection", "close");
161
+ headers.set("cache-control", cacheControl);
162
+ for (const [name, value] of Object.entries(SECURITY_HEADERS))
163
+ headers.set(name, value);
164
+ }
@@ -0,0 +1,99 @@
1
+ import type { GameServerConfig } from "../GameServerConfig.js";
2
+ import type { GameServerStateValues } from "../GameServerState.js";
3
+ import type { GameServerRouteContext } from "../ServerEndpoints.js";
4
+ import type { AdminAccounts } from "./AdminAccounts.js";
5
+ import type { AdminAudit } from "./AdminAudit.js";
6
+ import type { ApiKeys } from "./ApiKeys.js";
7
+ import type { AdminClock } from "./AdminClock.js";
8
+ import type { AdminExtensionsHost } from "./AdminExtensionsHost.js";
9
+ import type { AdminStore } from "./AdminStore.js";
10
+ import type { ConsoleSessions } from "./ConsoleSessions.js";
11
+ import type { FleetDirectory } from "./FleetDirectory.js";
12
+ import type { MaintenanceMode } from "./MaintenanceMode.js";
13
+ import type { MetricsSampler } from "./MetricsSampler.js";
14
+ import type { TemporaryLogLevel } from "./LogRoutes.js";
15
+ import type { ConfigOverrides } from "./ConfigOverrides.js";
16
+ import type { BanList } from "./BanList.js";
17
+ import type { RoomAdminClient } from "./SessionRoutes.js";
18
+ import type { ServerEvents } from "./ServerEvents.js";
19
+ import type { FleetLeader } from "./FleetLeader.js";
20
+ import type { AlertEngine } from "./AlertEngine.js";
21
+ import type { MaintenanceWindows } from "./MaintenanceWindows.js";
22
+ import type { RolloutCoordinator } from "./RolloutCoordinator.js";
23
+ import type { Passkeys } from "./Passkeys.js";
24
+ import type { OpenIdConnectClient } from "./OpenIdConnect.js";
25
+ import type { ColyseusCloudService } from "./ColyseusCloudService.js";
26
+ import type { LeaderboardService } from "../leaderboards/LeaderboardService.js";
27
+ /** What GameServer.ts hands the console: the server's parts and the test-injectable values (R10). */
28
+ export interface AdminCoreServices {
29
+ /** The resolved config. */
30
+ config: GameServerConfig;
31
+ /** The process's game server state. */
32
+ state: GameServerStateValues;
33
+ /** The admin data store (D7). */
34
+ store: AdminStore;
35
+ /** What the legacy routes read (listening port, drain, dedicated sessions). */
36
+ routeContext: GameServerRouteContext;
37
+ /**
38
+ * This instance's process id (matchMaker.processId, R5). Call it only from start() onward: Colyseus sets it in listen.
39
+ * @returns The process id.
40
+ */
41
+ processId(): string;
42
+ /** The clock of every admin timer and every "now" (R10). */
43
+ clock: AdminClock;
44
+ /** Milliseconds between fleet heartbeats (FLEET_HEARTBEAT_INTERVAL_MS unless a test overrides it). */
45
+ heartbeatIntervalMs: number;
46
+ /** Milliseconds between metrics samples (METRICS_SAMPLE_INTERVAL_MS unless a test overrides it). */
47
+ metricsSampleIntervalMs: number;
48
+ /** Milliseconds between leader ticks (LEADER_TICK_MS unless a test overrides it). */
49
+ leaderTickMs: number;
50
+ /** The PagerDuty Events v2 URL alert channels post to. */
51
+ pagerDutyUrl: string;
52
+ /** Called after each Colyseus Cloud refresh pass (a no-op outside tests, D17). */
53
+ colyseusCloudRefreshed: (nowMilliseconds: number) => void;
54
+ /** The custom actions, panels and admin event hooks of GameServerOptions.admin (D25). */
55
+ extensions: AdminExtensionsHost;
56
+ /** The leaderboard service (game-server-leaderboards D4). */
57
+ leaderboards: LeaderboardService;
58
+ }
59
+ /** What the admin routes and services share: the core plus the services createAdminConsole builds. */
60
+ export interface AdminServices extends AdminCoreServices {
61
+ /** Console sessions (D10). */
62
+ sessions: ConsoleSessions;
63
+ /** Admin accounts, sign-in and the setup link (D8, D30). */
64
+ accounts: AdminAccounts;
65
+ /** Scoped API keys and the legacy token caller (D18). */
66
+ apiKeys: ApiKeys;
67
+ /** The hash-chained audit log (D19). */
68
+ audit: AdminAudit;
69
+ /** Heartbeats, fleet commands and change announcements (D16). */
70
+ fleet: FleetDirectory;
71
+ /** The maintenance in force on this instance (D14). */
72
+ maintenance: MaintenanceMode;
73
+ /** Every room action of the API, on whichever instance owns the room (D11, R17). */
74
+ roomClient: RoomAdminClient;
75
+ /** This instance's bans, which the join gate checks (D15); the console puts it on state.banList while it runs. */
76
+ banList: BanList;
77
+ /** This instance's metrics history (D22). */
78
+ metricsSampler: MetricsSampler;
79
+ /** This instance's temporary log level and its revert timer (D21). */
80
+ temporaryLogLevel: TemporaryLogLevel;
81
+ /** The console's live config overrides (D20). */
82
+ configOverrides: ConfigOverrides;
83
+ /** Server events of the activity feed, under the record-keeping rule (R3). */
84
+ serverEvents: ServerEvents;
85
+ /** The fleet leader lease and its tick (D24). */
86
+ leader: FleetLeader;
87
+ /** Alert rules, channels and history (D24). */
88
+ alerts: AlertEngine;
89
+ /** Scheduled maintenance windows. */
90
+ maintenanceWindows: MaintenanceWindows;
91
+ /** Rolling updates (DR §7.8). */
92
+ rollouts: RolloutCoordinator;
93
+ /** Passkey registration and sign-in (D31). */
94
+ passkeys: Passkeys;
95
+ /** OpenID Connect single sign-on (D26). */
96
+ openId: OpenIdConnectClient;
97
+ /** The Colyseus Cloud page's cache of the Cloud API (colyseus-cloud-console). */
98
+ colyseusCloud: ColyseusCloudService;
99
+ }
@@ -0,0 +1,4 @@
1
+ // The services bag handed to every admin route file (game-server-admin-console DR §6.3, §13 R5 and R10). It is the
2
+ // console's composition root: GameServer.ts fills it, AdminConsole.ts adds the services it builds.
3
+ // Each task that adds an admin service adds its field here, documented, and builds it in createAdminConsole.
4
+ export {};
@@ -0,0 +1,29 @@
1
+ /** The console app's files, held in memory. */
2
+ export declare class AdminStaticFiles {
3
+ /** The folder Vite builds into. */
4
+ private readonly rootFolder;
5
+ /** Relative URL path (forward slashes) → file bytes. */
6
+ private readonly files;
7
+ /**
8
+ * Prepares serving from a folder; call load() before respond().
9
+ * @param rootFolder The built console folder (lib/console/).
10
+ */
11
+ constructor(rootFolder: string);
12
+ /** True once index.html has been loaded. */
13
+ get isAvailable(): boolean;
14
+ /** Reads every file under the root folder into memory; a missing folder leaves the app unavailable. */
15
+ load(): Promise<void>;
16
+ /**
17
+ * Answers one console path: the file, index.html for extensionless paths (hash routing), 404 text for a missing
18
+ * file with an extension, or 503 text while the app is not built.
19
+ * @param assetPath The path after /game-server/admin/ ("" for the app itself).
20
+ * @returns The response.
21
+ */
22
+ respond(assetPath: string): Response;
23
+ /**
24
+ * The file a console path names: index.html for "" and for paths without an extension.
25
+ * @param assetPath The path after /game-server/admin/.
26
+ * @returns The relative file path.
27
+ */
28
+ private resolveFilePath;
29
+ }
@@ -0,0 +1,97 @@
1
+ // Static serving of the built console app (game-server-admin-console D1, D3). Vite writes it to lib/console/; every file
2
+ // is read into memory at startup, so each answer is one buffer (D5).
3
+ import { existsSync } from "node:fs";
4
+ import { readdir, readFile } from "node:fs/promises";
5
+ import path from "node:path";
6
+ import { adminTextResponse, staticFileResponse } from "./AdminResponses.js";
7
+ /** The app's entry page; extensionless paths serve it too (hash routing, D2). */
8
+ const INDEX_FILE = "index.html";
9
+ /** Folder of Vite's hashed assets. */
10
+ const ASSETS_FOLDER_PREFIX = "assets/";
11
+ /** Cache policy of hashed assets: their names change with their content. */
12
+ const IMMUTABLE_CACHE_CONTROL = "public, max-age=31536000, immutable";
13
+ /** Cache policy of the entry page and other files. */
14
+ const REVALIDATE_CACHE_CONTROL = "no-cache";
15
+ /** Status of a missing asset. */
16
+ const HTTP_NOT_FOUND = 404;
17
+ /** Status while the app is not built. */
18
+ const HTTP_SERVICE_UNAVAILABLE = 503;
19
+ /** The text answered while the app is not built. */
20
+ const NOT_BUILT_TEXT = "the console files are not built (run npm run build in SVR)";
21
+ /** Content type of a file whose extension is not listed. */
22
+ const DEFAULT_CONTENT_TYPE = "application/octet-stream";
23
+ /** Content types by file extension. */
24
+ const CONTENT_TYPES = {
25
+ ".html": "text/html; charset=utf-8",
26
+ ".js": "text/javascript; charset=utf-8",
27
+ ".mjs": "text/javascript; charset=utf-8",
28
+ ".css": "text/css; charset=utf-8",
29
+ ".json": "application/json; charset=utf-8",
30
+ ".map": "application/json; charset=utf-8",
31
+ ".svg": "image/svg+xml",
32
+ ".png": "image/png",
33
+ ".ico": "image/x-icon",
34
+ ".webp": "image/webp",
35
+ ".woff2": "font/woff2",
36
+ ".txt": "text/plain; charset=utf-8"
37
+ };
38
+ /** The console app's files, held in memory. */
39
+ export class AdminStaticFiles {
40
+ /** The folder Vite builds into. */
41
+ rootFolder;
42
+ /** Relative URL path (forward slashes) → file bytes. */
43
+ files = new Map();
44
+ /**
45
+ * Prepares serving from a folder; call load() before respond().
46
+ * @param rootFolder The built console folder (lib/console/).
47
+ */
48
+ constructor(rootFolder) {
49
+ this.rootFolder = rootFolder;
50
+ }
51
+ /** True once index.html has been loaded. */
52
+ get isAvailable() {
53
+ return this.files.has(INDEX_FILE);
54
+ }
55
+ /** Reads every file under the root folder into memory; a missing folder leaves the app unavailable. */
56
+ async load() {
57
+ this.files.clear();
58
+ if (!existsSync(this.rootFolder))
59
+ return;
60
+ const entries = await readdir(this.rootFolder, { recursive: true });
61
+ for (const relativePath of entries) {
62
+ const absolutePath = path.join(this.rootFolder, relativePath);
63
+ const bytes = await readFile(absolutePath).catch(() => null);
64
+ if (bytes == null)
65
+ continue;
66
+ this.files.set(relativePath.split(path.sep).join("/"), new Uint8Array(bytes));
67
+ }
68
+ }
69
+ /**
70
+ * Answers one console path: the file, index.html for extensionless paths (hash routing), 404 text for a missing
71
+ * file with an extension, or 503 text while the app is not built.
72
+ * @param assetPath The path after /game-server/admin/ ("" for the app itself).
73
+ * @returns The response.
74
+ */
75
+ respond(assetPath) {
76
+ if (!this.isAvailable)
77
+ return adminTextResponse(HTTP_SERVICE_UNAVAILABLE, NOT_BUILT_TEXT);
78
+ const filePath = this.resolveFilePath(assetPath);
79
+ const bytes = this.files.get(filePath);
80
+ if (bytes == null)
81
+ return adminTextResponse(HTTP_NOT_FOUND, "not found");
82
+ const cacheControl = filePath.startsWith(ASSETS_FOLDER_PREFIX) ? IMMUTABLE_CACHE_CONTROL : REVALIDATE_CACHE_CONTROL;
83
+ const contentType = CONTENT_TYPES[path.extname(filePath).toLowerCase()] ?? DEFAULT_CONTENT_TYPE;
84
+ return staticFileResponse(bytes, contentType, cacheControl);
85
+ }
86
+ /**
87
+ * The file a console path names: index.html for "" and for paths without an extension.
88
+ * @param assetPath The path after /game-server/admin/.
89
+ * @returns The relative file path.
90
+ */
91
+ resolveFilePath(assetPath) {
92
+ const cleanPath = assetPath.replace(/^\/+/, "");
93
+ if (cleanPath === "" || path.posix.extname(cleanPath) === "")
94
+ return INDEX_FILE;
95
+ return cleanPath;
96
+ }
97
+ }
@@ -0,0 +1,152 @@
1
+ import type { Redis } from "ioredis";
2
+ import type { GameServerConfig } from "../GameServerConfig.js";
3
+ import { type AdminClock } from "./AdminClock.js";
4
+ /** The document collections of the admin store (D7). */
5
+ export type AdminCollection = "users" | "consoleSessions" | "pendingSignIns" | "apiKeys" | "bans" | "playerNotes" | "settings" | "alertRules" | "alertChannels" | "maintenanceWindows" | "rollouts" | "passkeys" | "signInFailures" | "setupLinks" | "colyseusCloud" | "leaderboards" | "leaderboardSettings" | "discordSignIn";
6
+ /** The append-only streams of the admin store (D7). `metrics:<processId>` streams are Redis-only (D22). */
7
+ export type AdminStream = "audit" | "events" | "alertHistory" | "configHistory" | "playerHistory" | "leaderboardActivity" | `metrics:${string}`;
8
+ /** A stored document with its optimistic revision. */
9
+ export interface StoredDocument<Value> {
10
+ /** The document id within its collection. */
11
+ id: string;
12
+ /** Starts at 1 and grows by one per write. */
13
+ revision: number;
14
+ /** ISO time of the last write. */
15
+ updatedAt: string;
16
+ /** The document. */
17
+ value: Value;
18
+ }
19
+ /** A stored stream record. */
20
+ export interface StoredRecord<Value> {
21
+ /** Starts at 1 and grows by one per append, per stream. */
22
+ sequence: number;
23
+ /** ISO time of the append. */
24
+ at: string;
25
+ /** The record. */
26
+ value: Value;
27
+ }
28
+ /** A stream read. */
29
+ export interface RecordQuery {
30
+ /** Only records with a larger sequence. */
31
+ afterSequence?: number;
32
+ /** Only records with a smaller sequence. */
33
+ beforeSequence?: number;
34
+ /** Most records returned (1–5000). */
35
+ limit: number;
36
+ /** Newest first when true. */
37
+ newestFirst: boolean;
38
+ }
39
+ /** Smallest RecordQuery.limit. */
40
+ export declare const RECORD_QUERY_MIN_LIMIT: number;
41
+ /** Largest RecordQuery.limit. */
42
+ export declare const RECORD_QUERY_MAX_LIMIT: number;
43
+ /** Builds a chained record from the stream's newest record and the new record's sequence and ISO time. */
44
+ export type ChainedRecordBuilder<Value> = (previous: StoredRecord<Value> | null, sequence: number, at: string) => Value;
45
+ /** Thrown when a write's expected revision is not the stored one (spec edge case: two admins edit at once). */
46
+ export declare class AdminConflictError extends Error {
47
+ /** The document as it is now, or null when it was deleted. */
48
+ readonly current: StoredDocument<unknown> | null;
49
+ /**
50
+ * Creates the error.
51
+ * @param current The document as it is now.
52
+ */
53
+ constructor(current: StoredDocument<unknown> | null);
54
+ }
55
+ /** Persistent admin data, shared by the fleet when Redis is used (D7). */
56
+ export interface AdminStore {
57
+ /** "redis" or "file". */
58
+ readonly kind: "redis" | "file";
59
+ /** False for a file store whose folder is not on a mounted volume inside a container (see HealthWarnings). */
60
+ readonly isPersistent: boolean;
61
+ /**
62
+ * Reads one document.
63
+ * @param collection The collection.
64
+ * @param documentId The document id.
65
+ * @returns The document, or null when it does not exist.
66
+ */
67
+ getDocument<Value>(collection: AdminCollection, documentId: string): Promise<StoredDocument<Value> | null>;
68
+ /**
69
+ * Reads every document of a collection.
70
+ * @param collection The collection.
71
+ * @returns The documents, in no particular order.
72
+ */
73
+ listDocuments<Value>(collection: AdminCollection): Promise<StoredDocument<Value>[]>;
74
+ /**
75
+ * Writes a document; its revision grows by one.
76
+ * @param collection The collection.
77
+ * @param documentId The document id.
78
+ * @param value The document.
79
+ * @param expectedRevision The revision the writer read (0 for "must not exist yet"), or null for no check.
80
+ * @returns The stored document.
81
+ * @throws AdminConflictError when expectedRevision is a number that differs from the stored revision.
82
+ */
83
+ putDocument<Value>(collection: AdminCollection, documentId: string, value: Value, expectedRevision: number | null): Promise<StoredDocument<Value>>;
84
+ /**
85
+ * Deletes a document.
86
+ * @param collection The collection.
87
+ * @param documentId The document id.
88
+ * @returns True when it existed.
89
+ */
90
+ deleteDocument(collection: AdminCollection, documentId: string): Promise<boolean>;
91
+ /**
92
+ * Appends a record to a stream.
93
+ * @param stream The stream.
94
+ * @param value The record.
95
+ * @returns The stored record with its sequence.
96
+ */
97
+ appendRecord<Value>(stream: AdminStream, value: Value): Promise<StoredRecord<Value>>;
98
+ /**
99
+ * Appends a record built from the stream's newest record, atomically across the fleet (the audit hash chain, R21).
100
+ * @param stream The stream.
101
+ * @param build Builds the value from the newest record (null for an empty stream) and the new record's sequence
102
+ * and time, which the value may include (the audit hash covers them).
103
+ * @returns The stored record.
104
+ */
105
+ appendChainedRecord<Value>(stream: AdminStream, build: ChainedRecordBuilder<Value>): Promise<StoredRecord<Value>>;
106
+ /**
107
+ * Reads records of a stream.
108
+ * @param stream The stream.
109
+ * @param query The range, limit and order.
110
+ * @returns The records.
111
+ */
112
+ readRecords<Value>(stream: AdminStream, query: RecordQuery): Promise<StoredRecord<Value>[]>;
113
+ /**
114
+ * Removes records appended before the cutoff.
115
+ * @param stream The stream.
116
+ * @param cutoff Records whose `at` is earlier are removed.
117
+ * @returns How many were removed.
118
+ */
119
+ trimRecordsBefore(stream: AdminStream, cutoff: Date): Promise<number>;
120
+ /**
121
+ * Keeps only the newest records.
122
+ * @param stream The stream.
123
+ * @param keepCount How many to keep.
124
+ * @returns How many were removed.
125
+ */
126
+ trimRecordsToCount(stream: AdminStream, keepCount: number): Promise<number>;
127
+ /**
128
+ * Takes or renews a lease (the fleet leader, D24). File store: always true (one instance).
129
+ * @param leaseName The lease.
130
+ * @param holderId Who wants it (a process id).
131
+ * @param ttlMilliseconds How long it lasts without renewal.
132
+ * @returns True when this holder now holds the lease.
133
+ */
134
+ tryAcquireLease(leaseName: string, holderId: string, ttlMilliseconds: number): Promise<boolean>;
135
+ /** Releases what the store holds (the shared Redis client is left to RedisPresence, R4). */
136
+ close(): Promise<void>;
137
+ }
138
+ /**
139
+ * Opens the store the config calls for: Redis when a client is given, otherwise the file store in admin.dataDir. The file
140
+ * store creates nothing until its first write (R3).
141
+ * @param config The resolved config.
142
+ * @param redisClient The shared ioredis client, or null without Redis.
143
+ * @param clock The clock of stored times (tests pass a manual one, R10).
144
+ * @returns The open store.
145
+ */
146
+ export declare function openAdminStore(config: GameServerConfig, redisClient: Redis | null, clock?: AdminClock): Promise<AdminStore>;
147
+ /**
148
+ * Clamps a query limit to RECORD_QUERY_MIN_LIMIT … RECORD_QUERY_MAX_LIMIT.
149
+ * @param limit The requested limit.
150
+ * @returns The limit used.
151
+ */
152
+ export declare function clampRecordLimit(limit: number): number;
@@ -0,0 +1,46 @@
1
+ // The admin data store (game-server-admin-console D7, DR §6.1): Redis when redis.url is set, otherwise JSON files in
2
+ // admin.dataDir. Documents carry optimistic revisions; streams are append-only with per-stream sequences.
3
+ import { SYSTEM_CLOCK } from "./AdminClock.js";
4
+ import { FileAdminStore } from "./FileAdminStore.js";
5
+ import { RedisAdminStore } from "./RedisAdminStore.js";
6
+ /** Smallest RecordQuery.limit. */
7
+ export const RECORD_QUERY_MIN_LIMIT = 1;
8
+ /** Largest RecordQuery.limit. */
9
+ export const RECORD_QUERY_MAX_LIMIT = 5000;
10
+ /** Thrown when a write's expected revision is not the stored one (spec edge case: two admins edit at once). */
11
+ export class AdminConflictError extends Error {
12
+ /** The document as it is now, or null when it was deleted. */
13
+ current;
14
+ /**
15
+ * Creates the error.
16
+ * @param current The document as it is now.
17
+ */
18
+ constructor(current) {
19
+ super("the document changed since it was read");
20
+ this.name = "AdminConflictError";
21
+ this.current = current;
22
+ }
23
+ }
24
+ /**
25
+ * Opens the store the config calls for: Redis when a client is given, otherwise the file store in admin.dataDir. The file
26
+ * store creates nothing until its first write (R3).
27
+ * @param config The resolved config.
28
+ * @param redisClient The shared ioredis client, or null without Redis.
29
+ * @param clock The clock of stored times (tests pass a manual one, R10).
30
+ * @returns The open store.
31
+ */
32
+ export async function openAdminStore(config, redisClient, clock = SYSTEM_CLOCK) {
33
+ if (redisClient != null)
34
+ return new RedisAdminStore(redisClient, clock);
35
+ return new FileAdminStore(config.admin.dataDir, clock);
36
+ }
37
+ /**
38
+ * Clamps a query limit to RECORD_QUERY_MIN_LIMIT … RECORD_QUERY_MAX_LIMIT.
39
+ * @param limit The requested limit.
40
+ * @returns The limit used.
41
+ */
42
+ export function clampRecordLimit(limit) {
43
+ if (!Number.isFinite(limit))
44
+ return RECORD_QUERY_MAX_LIMIT;
45
+ return Math.min(RECORD_QUERY_MAX_LIMIT, Math.max(RECORD_QUERY_MIN_LIMIT, Math.floor(limit)));
46
+ }
@@ -0,0 +1,43 @@
1
+ import type { AlertChannelView, AlertView } from "./AdminApiTypes.js";
2
+ import type { AlertChannelRecord } from "./AdminRecords.js";
3
+ /** Whether an alert delivery announces a firing or a resolved alert. */
4
+ export type AlertDeliveryState = "firing" | "resolved";
5
+ /** The outcome of one delivery. */
6
+ export interface AlertDeliveryOutcome {
7
+ ok: boolean;
8
+ error: string | null;
9
+ }
10
+ /** Delivers alerts to their channels. */
11
+ export declare class AlertChannels {
12
+ /** Request timeout of a delivery, in milliseconds. */
13
+ private readonly timeoutMs;
14
+ /**
15
+ * Creates the deliverer.
16
+ * @param timeoutMs Request timeout, in milliseconds. Default ALERT_CHANNEL_TIMEOUT_MS.
17
+ */
18
+ constructor(timeoutMs?: number);
19
+ /**
20
+ * Posts an alert to a channel with its DR §8 payload. A network failure, a timeout or a non-2xx answer is an
21
+ * outcome, never a throw.
22
+ * @param channel The channel with its secrets.
23
+ * @param alert The alert.
24
+ * @param state Whether it fired or resolved.
25
+ * @param pagerDutyUrl The PagerDuty Events v2 URL (tests replace it, R10).
26
+ * @returns Whether it was delivered, and why not.
27
+ */
28
+ deliver(channel: AlertChannelRecord, alert: AlertView, state: AlertDeliveryState, pagerDutyUrl: string): Promise<AlertDeliveryOutcome>;
29
+ }
30
+ /**
31
+ * The JSON body a channel receives (DR §8).
32
+ * @param channel The channel.
33
+ * @param alert The alert.
34
+ * @param state Whether it fired or resolved.
35
+ * @returns The payload.
36
+ */
37
+ export declare function alertPayloadOf(channel: AlertChannelRecord, alert: AlertView, state: AlertDeliveryState): Record<string, unknown>;
38
+ /**
39
+ * A channel as the console sees it: the url without its path, the routing key as set / not set.
40
+ * @param channel The stored channel.
41
+ * @returns The view.
42
+ */
43
+ export declare function alertChannelView(channel: AlertChannelRecord): AlertChannelView;