@babylonjs-toolkit/server 0.0.0-stage → 9.35.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 (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,180 @@
1
+ import type { CloudBillingView, CloudDeployDetailView, CloudHistoryView, CloudLogLines, CloudLogStream, CloudLogsView, CloudPart, ColyseusCloudFeature, ColyseusCloudView } from "./AdminApiTypes.js";
2
+ import type { AdminCoreServices } from "./AdminServices.js";
3
+ /** The leader-refreshed, store-shared cache of the Colyseus Cloud API behind the console's Colyseus Cloud page (D2–D5). */
4
+ export declare class ColyseusCloudService {
5
+ /** Whether the page applies (D8). */
6
+ readonly feature: ColyseusCloudFeature;
7
+ /** The admin services this cache reads the config, store and clock from. */
8
+ private readonly services;
9
+ /** Null unless a token is configured. */
10
+ private readonly client;
11
+ /** Host name of publicUrl, to mark this console's own instance (D11). */
12
+ private readonly ownHostName;
13
+ /** Deploy output this process fetched recently, by deploy id (D5). */
14
+ private readonly deployDetails;
15
+ /**
16
+ * Creates the cache; nothing is requested until the leader calls refreshDue or a route asks.
17
+ * @param services The admin core services (config, store, clock and the refresh test hook).
18
+ * @param environment The process environment (COLYSEUS_CLOUD marks Colyseus Cloud, D8).
19
+ */
20
+ constructor(services: AdminCoreServices, environment?: NodeJS.ProcessEnv);
21
+ /** True when a Cloud API token is configured, so the leader refreshes and the routes answer. */
22
+ get isConfigured(): boolean;
23
+ /**
24
+ * One leader refresh pass (D3–D5): unless a Cloud 429 pauses the fleet, refreshes "applications" when due and then,
25
+ * once an application is chosen, every other due resource in RESOURCE_REFRESH_ORDER, stopping at the first 429.
26
+ * Always reports the pass to the colyseusCloudRefreshed hook (D17), early returns included.
27
+ */
28
+ refreshDue(): Promise<void>;
29
+ /**
30
+ * GET cloud: the feature, the application choice and the overview, load, deploys and egress parts (always answers).
31
+ * @returns The summary view.
32
+ */
33
+ readSummary(): Promise<ColyseusCloudView>;
34
+ /**
35
+ * GET cloud/history: the load history of the last 30 days, one point a day (an empty part while no application is
36
+ * chosen). The last 24 hours are the console's own Metrics page, so Colyseus's minute history is not read.
37
+ * @returns The history part.
38
+ * @throws AdminRequestError 404 when no token is configured.
39
+ */
40
+ readHistory(): Promise<CloudPart<CloudHistoryView>>;
41
+ /**
42
+ * GET cloud/billing: the billing account and this application's subscription (an empty part while no application is chosen).
43
+ * @returns The billing part.
44
+ * @throws AdminRequestError 404 when no token is configured.
45
+ */
46
+ readBilling(): Promise<CloudPart<CloudBillingView>>;
47
+ /**
48
+ * GET cloud/deploys/:deployId: one deploy with its output, fetched on request and reused by this process for
49
+ * COLYSEUS_CLOUD_DEPLOY_DETAIL_CACHE_MS (D5).
50
+ * @param deployId Colyseus's deploy id.
51
+ * @returns The deploy with its output.
52
+ * @throws AdminRequestError 404 without a token, an application or such a deploy; 503 while a Cloud 429 pauses the
53
+ * fleet (detail: the ISO time it ends); 502 when Colyseus failed.
54
+ */
55
+ readDeploy(deployId: number): Promise<CloudDeployDetailView>;
56
+ /**
57
+ * GET cloud/instances/:instanceId/logs: an instance's newest log lines, at most one Cloud request per
58
+ * COLYSEUS_CLOUD_LOG_INTERVAL_MS for the whole fleet; another asker of the same instance, stream and lines within
59
+ * COLYSEUS_CLOUD_SHARED_LOGS_MS gets that answer (D5, D10).
60
+ * @param instanceId Colyseus's instance id.
61
+ * @param stream Which output stream to read.
62
+ * @param lines How many lines to read.
63
+ * @returns The logs.
64
+ * @throws AdminRequestError 404 without a token, an application or such an instance; 409 when the instance is not
65
+ * running; 429 when the limit allows no request yet (detail: the next allowed ISO time); 502 when Colyseus failed.
66
+ */
67
+ readLogs(instanceId: number, stream: CloudLogStream, lines: CloudLogLines): Promise<CloudLogsView>;
68
+ /**
69
+ * The body of one refresh pass (§7 steps 1–5).
70
+ * @param now The clock time of the pass, in milliseconds.
71
+ */
72
+ private refreshPass;
73
+ /**
74
+ * Whether a resource should be refreshed now: never attempted, its retryAt reached, or its refresh interval since
75
+ * the last attempt elapsed (each within COLYSEUS_CLOUD_DUE_TOLERANCE_MS, so a slightly early tick still counts).
76
+ * @param resource The resource.
77
+ * @param nowMilliseconds The clock time of the pass.
78
+ * @returns True when due.
79
+ */
80
+ private isDue;
81
+ /**
82
+ * Fetches one resource and stores its snapshot: the new data on success; on a Cloud error the last good data with
83
+ * the problem and, for a 5xx or no answer, the retryAt Colyseus asked for. A 429 also pauses the fleet.
84
+ * @param resource The resource.
85
+ * @param fetchData Requests and converts the resource.
86
+ * @param nowMilliseconds The clock time of the pass.
87
+ * @returns False after a 429 (the pass must stop), else true.
88
+ * @throws Any error other than ColyseusCloudApiError (the leader logs it).
89
+ */
90
+ private refreshResource;
91
+ /**
92
+ * Pauses every refresh of the fleet after a Cloud 429 until Colyseus's Retry-After (D5).
93
+ * @param retryAfterSeconds Colyseus's wait, or null for COLYSEUS_CLOUD_DEFAULT_RETRY_SECONDS.
94
+ * @param nowMilliseconds The current clock time.
95
+ * @returns The ISO time the pause ends.
96
+ */
97
+ private writeRateLimit;
98
+ /**
99
+ * The client, for the routes that call the Cloud API on request.
100
+ * @returns The client.
101
+ * @throws AdminRequestError 404 when no token is configured.
102
+ */
103
+ private requireClient;
104
+ /**
105
+ * The application the page shows, from the stored choice.
106
+ * @returns The application.
107
+ * @throws AdminRequestError 404 while none is chosen (D9).
108
+ */
109
+ private requireApplication;
110
+ /**
111
+ * Reads a stored resource snapshot.
112
+ * @param resource The resource.
113
+ * @returns The snapshot, or null before its first attempt.
114
+ */
115
+ private readSnapshot;
116
+ /**
117
+ * Stores a resource snapshot (last writer wins: only the leader refreshes).
118
+ * @param resource The resource.
119
+ * @param snapshot The snapshot.
120
+ */
121
+ private writeSnapshot;
122
+ /**
123
+ * A per-application part (history or billing): empty while no application is chosen (D9).
124
+ * @param resource The resource.
125
+ * @returns The part.
126
+ * @throws AdminRequestError 404 when no token is configured.
127
+ */
128
+ private readApplicationPart;
129
+ /**
130
+ * GET cloud while no token is configured: the feature with empty parts.
131
+ * @param nowMilliseconds The current clock time.
132
+ * @returns The summary view.
133
+ */
134
+ private emptySummary;
135
+ /**
136
+ * The route error of a failed deploy output request: 404 for an unknown deploy, 503 after a 429 (which also pauses
137
+ * the fleet), else 502.
138
+ * @param error What the request threw.
139
+ * @param nowMilliseconds The clock time of the request.
140
+ * @returns The route error.
141
+ * @throws The error itself when it is not a ColyseusCloudApiError.
142
+ */
143
+ private deployRequestError;
144
+ /**
145
+ * Reads the fleet-wide log gate.
146
+ * @returns The gate document, or null before the first log request.
147
+ */
148
+ private readLogGate;
149
+ /**
150
+ * Claims the next log request for this process through the gate's revision, so two processes never both send one.
151
+ * @param gate The gate as read, or null when none exists yet.
152
+ * @param nowMilliseconds The current clock time.
153
+ * @returns True when claimed; false when another process claimed it first.
154
+ */
155
+ private claimLogGate;
156
+ /**
157
+ * While the gate is closed: the last log answer when it is of the same instance, stream and lines and recent enough,
158
+ * else a 429 naming when the next request is allowed.
159
+ * @param instanceId The asked instance.
160
+ * @param stream The asked stream.
161
+ * @param lines The asked number of lines.
162
+ * @param nextAllowedMilliseconds When the next log request is allowed, in clock milliseconds.
163
+ * @param nowMilliseconds The current clock time.
164
+ * @returns The shared answer.
165
+ * @throws AdminRequestError 429 when there is nothing to share.
166
+ */
167
+ private sharedLogsOrRefusal;
168
+ /**
169
+ * Sends the claimed log request; on a Cloud error, honours its Retry-After in the gate and maps it to a route error.
170
+ * @param client The Cloud API client.
171
+ * @param slug The application slug.
172
+ * @param instanceId The instance.
173
+ * @param stream The stream.
174
+ * @param lines The number of lines.
175
+ * @param nowMilliseconds The clock time of the request.
176
+ * @returns The log text.
177
+ * @throws AdminRequestError 409 when the instance is not running, 429 when Colyseus refused, else 502.
178
+ */
179
+ private fetchLogs;
180
+ }
@@ -0,0 +1,476 @@
1
+ // The cache behind the console's Colyseus Cloud page (colyseus-cloud-console D2–D5, D9, D10, D17): the fleet leader
2
+ // refreshes every Cloud API resource at Colyseus's own cadence into the admin store collection "colyseusCloud", and
3
+ // every process's routes read that collection. Only deploy output and instance logs call the Cloud API on request,
4
+ // under Colyseus's limits: deploy output is cached per process, logs are gated fleet-wide and shared.
5
+ import { AdminRequestError } from "./AdminApiRouter.js";
6
+ import { COLYSEUS_CLOUD_DEFAULT_RETRY_SECONDS, COLYSEUS_CLOUD_DEPLOY_DETAIL_CACHE_MS, COLYSEUS_CLOUD_DEPLOY_LIMIT, COLYSEUS_CLOUD_DUE_TOLERANCE_MS, COLYSEUS_CLOUD_LOG_INTERVAL_MS, COLYSEUS_CLOUD_REFRESH_SECONDS, COLYSEUS_CLOUD_SHARED_LOGS_MS, MILLISECONDS_PER_SECOND } from "./AdminConstants.js";
7
+ import { AdminConflictError } from "./AdminStore.js";
8
+ import { ColyseusCloudApiError, ColyseusCloudClient } from "./ColyseusCloudClient.js";
9
+ import { colyseusCloudFeatureOf, ownHostNameOf } from "./ColyseusCloudSettings.js";
10
+ import { partOf, resolveApplication, toBillingView, toDeployDetailView, toDeployView, toEgressView, toHistoryView, toLoadView } from "./ColyseusCloudViews.js";
11
+ /** HTTP status when there is no token, no chosen application, or no such deploy or instance. */
12
+ const HTTP_NOT_FOUND = 404;
13
+ /** HTTP status when an instance is not running. */
14
+ const HTTP_CONFLICT = 409;
15
+ /** HTTP status when Colyseus's log limit allows no request yet. */
16
+ const HTTP_TOO_MANY_REQUESTS = 429;
17
+ /** HTTP status when Colyseus failed. */
18
+ const HTTP_BAD_GATEWAY = 502;
19
+ /** HTTP status while Colyseus asked to wait. */
20
+ const HTTP_SERVICE_UNAVAILABLE = 503;
21
+ /** The store collection of every Cloud document (D3). */
22
+ const CLOUD_COLLECTION = "colyseusCloud";
23
+ /** Store id of the fleet-wide pause after a Cloud 429 (D5). */
24
+ const RATE_LIMIT_DOCUMENT_ID = "rateLimit";
25
+ /** Store id of the fleet-wide log request gate (D5). */
26
+ const LOG_GATE_DOCUMENT_ID = "logGate";
27
+ /** Store id of the last log answer, shared with later askers (D5). */
28
+ const LAST_LOGS_DOCUMENT_ID = "lastLogs";
29
+ /**
30
+ * Refresh order after "applications": the live figures first (D4). Colyseus's 24-hour minute history is not read, because
31
+ * the console's own Metrics page already covers the last 24 hours.
32
+ */
33
+ const RESOURCE_REFRESH_ORDER = ["metricsCurrent", "deploys", "egress", "metricsDay", "billing"];
34
+ /** Message of a Cloud route while no application is chosen or no token is set (D9). */
35
+ const NOT_CONFIGURED_MESSAGE = "no Colyseus Cloud application is available: set a Cloud API token and, if the team has several applications, the application slug";
36
+ /** The leader-refreshed, store-shared cache of the Colyseus Cloud API behind the console's Colyseus Cloud page (D2–D5). */
37
+ export class ColyseusCloudService {
38
+ /** Whether the page applies (D8). */
39
+ feature;
40
+ /** The admin services this cache reads the config, store and clock from. */
41
+ services;
42
+ /** Null unless a token is configured. */
43
+ client;
44
+ /** Host name of publicUrl, to mark this console's own instance (D11). */
45
+ ownHostName;
46
+ /** Deploy output this process fetched recently, by deploy id (D5). */
47
+ deployDetails = new Map();
48
+ /**
49
+ * Creates the cache; nothing is requested until the leader calls refreshDue or a route asks.
50
+ * @param services The admin core services (config, store, clock and the refresh test hook).
51
+ * @param environment The process environment (COLYSEUS_CLOUD marks Colyseus Cloud, D8).
52
+ */
53
+ constructor(services, environment = process.env) {
54
+ this.services = services;
55
+ this.feature = colyseusCloudFeatureOf(services.config, environment);
56
+ const settings = services.config.admin.colyseusCloud;
57
+ this.client = (settings.apiToken != null) ? new ColyseusCloudClient(settings.apiUrl, settings.apiToken) : null;
58
+ this.ownHostName = ownHostNameOf(services.config);
59
+ }
60
+ /** True when a Cloud API token is configured, so the leader refreshes and the routes answer. */
61
+ get isConfigured() {
62
+ return this.client != null;
63
+ }
64
+ /**
65
+ * One leader refresh pass (D3–D5): unless a Cloud 429 pauses the fleet, refreshes "applications" when due and then,
66
+ * once an application is chosen, every other due resource in RESOURCE_REFRESH_ORDER, stopping at the first 429.
67
+ * Always reports the pass to the colyseusCloudRefreshed hook (D17), early returns included.
68
+ */
69
+ async refreshDue() {
70
+ const now = this.services.clock.now();
71
+ try {
72
+ await this.refreshPass(now);
73
+ }
74
+ finally {
75
+ this.services.colyseusCloudRefreshed(now);
76
+ }
77
+ }
78
+ /**
79
+ * GET cloud: the feature, the application choice and the overview, load, deploys and egress parts (always answers).
80
+ * @returns The summary view.
81
+ */
82
+ async readSummary() {
83
+ const now = this.services.clock.now();
84
+ if (!this.isConfigured)
85
+ return this.emptySummary(now);
86
+ const choice = await this.readSnapshot("applications");
87
+ const overview = (choice != null) ? { ...choice, data: choice.data?.application ?? null } : null;
88
+ const choiceProblem = (choice?.data == null) ? choice?.problem ?? null : null;
89
+ return {
90
+ feature: this.feature,
91
+ applicationChoices: choice?.data?.choices ?? [],
92
+ setupProblem: choice?.data?.setupProblem ?? choiceProblem,
93
+ overview: partOf(overview, COLYSEUS_CLOUD_REFRESH_SECONDS.applications, now),
94
+ load: partOf(await this.readSnapshot("metricsCurrent"), COLYSEUS_CLOUD_REFRESH_SECONDS.metricsCurrent, now),
95
+ deploys: partOf(await this.readSnapshot("deploys"), COLYSEUS_CLOUD_REFRESH_SECONDS.deploys, now),
96
+ egress: partOf(await this.readSnapshot("egress"), COLYSEUS_CLOUD_REFRESH_SECONDS.egress, now)
97
+ };
98
+ }
99
+ /**
100
+ * GET cloud/history: the load history of the last 30 days, one point a day (an empty part while no application is
101
+ * chosen). The last 24 hours are the console's own Metrics page, so Colyseus's minute history is not read.
102
+ * @returns The history part.
103
+ * @throws AdminRequestError 404 when no token is configured.
104
+ */
105
+ async readHistory() {
106
+ return await this.readApplicationPart("metricsDay");
107
+ }
108
+ /**
109
+ * GET cloud/billing: the billing account and this application's subscription (an empty part while no application is chosen).
110
+ * @returns The billing part.
111
+ * @throws AdminRequestError 404 when no token is configured.
112
+ */
113
+ async readBilling() {
114
+ return await this.readApplicationPart("billing");
115
+ }
116
+ /**
117
+ * GET cloud/deploys/:deployId: one deploy with its output, fetched on request and reused by this process for
118
+ * COLYSEUS_CLOUD_DEPLOY_DETAIL_CACHE_MS (D5).
119
+ * @param deployId Colyseus's deploy id.
120
+ * @returns The deploy with its output.
121
+ * @throws AdminRequestError 404 without a token, an application or such a deploy; 503 while a Cloud 429 pauses the
122
+ * fleet (detail: the ISO time it ends); 502 when Colyseus failed.
123
+ */
124
+ async readDeploy(deployId) {
125
+ const client = this.requireClient();
126
+ const application = await this.requireApplication();
127
+ const now = this.services.clock.now();
128
+ const cached = this.deployDetails.get(deployId);
129
+ if (cached != null && now - cached.fetchedAtMilliseconds < COLYSEUS_CLOUD_DEPLOY_DETAIL_CACHE_MS)
130
+ return cached.view;
131
+ const rateLimit = await this.services.store.getDocument(CLOUD_COLLECTION, RATE_LIMIT_DOCUMENT_ID);
132
+ if (rateLimit != null && Date.parse(rateLimit.value.blockedUntil) > now)
133
+ throw rateLimitedError(rateLimit.value.blockedUntil);
134
+ try {
135
+ const view = toDeployDetailView(await client.deploy(application.slug, deployId));
136
+ this.deployDetails.set(deployId, { view: view, fetchedAtMilliseconds: now });
137
+ return view;
138
+ }
139
+ catch (error) {
140
+ throw await this.deployRequestError(error, now);
141
+ }
142
+ }
143
+ /**
144
+ * GET cloud/instances/:instanceId/logs: an instance's newest log lines, at most one Cloud request per
145
+ * COLYSEUS_CLOUD_LOG_INTERVAL_MS for the whole fleet; another asker of the same instance, stream and lines within
146
+ * COLYSEUS_CLOUD_SHARED_LOGS_MS gets that answer (D5, D10).
147
+ * @param instanceId Colyseus's instance id.
148
+ * @param stream Which output stream to read.
149
+ * @param lines How many lines to read.
150
+ * @returns The logs.
151
+ * @throws AdminRequestError 404 without a token, an application or such an instance; 409 when the instance is not
152
+ * running; 429 when the limit allows no request yet (detail: the next allowed ISO time); 502 when Colyseus failed.
153
+ */
154
+ async readLogs(instanceId, stream, lines) {
155
+ const client = this.requireClient();
156
+ const application = await this.requireApplication();
157
+ if (!hasInstance(application, instanceId))
158
+ throw new AdminRequestError(HTTP_NOT_FOUND, "no such instance in " + application.slug);
159
+ const now = this.services.clock.now();
160
+ const gate = await this.readLogGate();
161
+ const nextAllowed = (gate != null) ? Date.parse(gate.value.nextAllowedAt) : 0;
162
+ if (now < nextAllowed)
163
+ return await this.sharedLogsOrRefusal(instanceId, stream, lines, nextAllowed, now);
164
+ if (!(await this.claimLogGate(gate, now))) {
165
+ const claimedGate = await this.readLogGate();
166
+ const claimedNextAllowed = (claimedGate != null) ? Date.parse(claimedGate.value.nextAllowedAt) : now + COLYSEUS_CLOUD_LOG_INTERVAL_MS;
167
+ return await this.sharedLogsOrRefusal(instanceId, stream, lines, claimedNextAllowed, now);
168
+ }
169
+ const text = await this.fetchLogs(client, application.slug, instanceId, stream, lines, now);
170
+ const view = { instanceId: instanceId, stream: stream, lines: lines, text: text, fetchedAt: isoTimeOf(now), isShared: false, nextAllowedAt: isoTimeOf(now + COLYSEUS_CLOUD_LOG_INTERVAL_MS) };
171
+ await this.services.store.putDocument(CLOUD_COLLECTION, LAST_LOGS_DOCUMENT_ID, view, null);
172
+ return view;
173
+ }
174
+ /**
175
+ * The body of one refresh pass (§7 steps 1–5).
176
+ * @param now The clock time of the pass, in milliseconds.
177
+ */
178
+ async refreshPass(now) {
179
+ const client = this.client;
180
+ if (client == null)
181
+ return;
182
+ const rateLimit = await this.services.store.getDocument(CLOUD_COLLECTION, RATE_LIMIT_DOCUMENT_ID);
183
+ if (rateLimit != null && Date.parse(rateLimit.value.blockedUntil) > now)
184
+ return;
185
+ if (await this.isDue("applications", now)) {
186
+ const configuredSlug = this.services.config.admin.colyseusCloud.application;
187
+ const fetchChoice = async () => resolveApplication(await client.listApplications(), configuredSlug, this.ownHostName);
188
+ if (!(await this.refreshResource("applications", fetchChoice, now)))
189
+ return;
190
+ }
191
+ const choice = await this.readSnapshot("applications");
192
+ const application = choice?.data?.application ?? null;
193
+ if (application == null)
194
+ return;
195
+ for (const resource of RESOURCE_REFRESH_ORDER) {
196
+ if (!(await this.isDue(resource, now)))
197
+ continue;
198
+ const fetchData = () => fetchApplicationResource(client, resource, application.slug);
199
+ if (!(await this.refreshResource(resource, fetchData, now)))
200
+ return;
201
+ }
202
+ }
203
+ /**
204
+ * Whether a resource should be refreshed now: never attempted, its retryAt reached, or its refresh interval since
205
+ * the last attempt elapsed (each within COLYSEUS_CLOUD_DUE_TOLERANCE_MS, so a slightly early tick still counts).
206
+ * @param resource The resource.
207
+ * @param nowMilliseconds The clock time of the pass.
208
+ * @returns True when due.
209
+ */
210
+ async isDue(resource, nowMilliseconds) {
211
+ const snapshot = await this.readSnapshot(resource);
212
+ if (snapshot == null)
213
+ return true;
214
+ const toleratedNow = nowMilliseconds + COLYSEUS_CLOUD_DUE_TOLERANCE_MS;
215
+ if (snapshot.retryAt != null)
216
+ return toleratedNow >= Date.parse(snapshot.retryAt);
217
+ return toleratedNow >= Date.parse(snapshot.attemptedAt) + COLYSEUS_CLOUD_REFRESH_SECONDS[resource] * MILLISECONDS_PER_SECOND;
218
+ }
219
+ /**
220
+ * Fetches one resource and stores its snapshot: the new data on success; on a Cloud error the last good data with
221
+ * the problem and, for a 5xx or no answer, the retryAt Colyseus asked for. A 429 also pauses the fleet.
222
+ * @param resource The resource.
223
+ * @param fetchData Requests and converts the resource.
224
+ * @param nowMilliseconds The clock time of the pass.
225
+ * @returns False after a 429 (the pass must stop), else true.
226
+ * @throws Any error other than ColyseusCloudApiError (the leader logs it).
227
+ */
228
+ async refreshResource(resource, fetchData, nowMilliseconds) {
229
+ const previous = await this.readSnapshot(resource);
230
+ const attemptedAt = isoTimeOf(nowMilliseconds);
231
+ try {
232
+ const data = await fetchData();
233
+ await this.writeSnapshot(resource, { data: data, fetchedAt: attemptedAt, attemptedAt: attemptedAt, retryAt: null, problem: null });
234
+ return true;
235
+ }
236
+ catch (error) {
237
+ if (!(error instanceof ColyseusCloudApiError))
238
+ throw error;
239
+ const isRateLimited = error.status === HTTP_TOO_MANY_REQUESTS;
240
+ // A 429 pauses the whole fleet instead (rateLimit), so only a 5xx or no answer sets this resource's retryAt.
241
+ const retryAt = (!isRateLimited && error.retryAfterSeconds != null) ? isoTimeOf(nowMilliseconds + error.retryAfterSeconds * MILLISECONDS_PER_SECOND) : null;
242
+ await this.writeSnapshot(resource, { data: previous?.data ?? null, fetchedAt: previous?.fetchedAt ?? null, attemptedAt: attemptedAt, retryAt: retryAt, problem: error.toProblem() });
243
+ if (isRateLimited)
244
+ await this.writeRateLimit(error.retryAfterSeconds, nowMilliseconds);
245
+ return !isRateLimited;
246
+ }
247
+ }
248
+ /**
249
+ * Pauses every refresh of the fleet after a Cloud 429 until Colyseus's Retry-After (D5).
250
+ * @param retryAfterSeconds Colyseus's wait, or null for COLYSEUS_CLOUD_DEFAULT_RETRY_SECONDS.
251
+ * @param nowMilliseconds The current clock time.
252
+ * @returns The ISO time the pause ends.
253
+ */
254
+ async writeRateLimit(retryAfterSeconds, nowMilliseconds) {
255
+ const blockedUntil = isoTimeOf(nowMilliseconds + (retryAfterSeconds ?? COLYSEUS_CLOUD_DEFAULT_RETRY_SECONDS) * MILLISECONDS_PER_SECOND);
256
+ await this.services.store.putDocument(CLOUD_COLLECTION, RATE_LIMIT_DOCUMENT_ID, { blockedUntil: blockedUntil }, null);
257
+ return blockedUntil;
258
+ }
259
+ /**
260
+ * The client, for the routes that call the Cloud API on request.
261
+ * @returns The client.
262
+ * @throws AdminRequestError 404 when no token is configured.
263
+ */
264
+ requireClient() {
265
+ if (this.client == null)
266
+ throw new AdminRequestError(HTTP_NOT_FOUND, NOT_CONFIGURED_MESSAGE);
267
+ return this.client;
268
+ }
269
+ /**
270
+ * The application the page shows, from the stored choice.
271
+ * @returns The application.
272
+ * @throws AdminRequestError 404 while none is chosen (D9).
273
+ */
274
+ async requireApplication() {
275
+ const choice = await this.readSnapshot("applications");
276
+ const application = choice?.data?.application ?? null;
277
+ if (application == null)
278
+ throw new AdminRequestError(HTTP_NOT_FOUND, NOT_CONFIGURED_MESSAGE);
279
+ return application;
280
+ }
281
+ /**
282
+ * Reads a stored resource snapshot.
283
+ * @param resource The resource.
284
+ * @returns The snapshot, or null before its first attempt.
285
+ */
286
+ async readSnapshot(resource) {
287
+ const stored = await this.services.store.getDocument(CLOUD_COLLECTION, resource);
288
+ return stored?.value ?? null;
289
+ }
290
+ /**
291
+ * Stores a resource snapshot (last writer wins: only the leader refreshes).
292
+ * @param resource The resource.
293
+ * @param snapshot The snapshot.
294
+ */
295
+ async writeSnapshot(resource, snapshot) {
296
+ await this.services.store.putDocument(CLOUD_COLLECTION, resource, snapshot, null);
297
+ }
298
+ /**
299
+ * A per-application part (history or billing): empty while no application is chosen (D9).
300
+ * @param resource The resource.
301
+ * @returns The part.
302
+ * @throws AdminRequestError 404 when no token is configured.
303
+ */
304
+ async readApplicationPart(resource) {
305
+ this.requireClient();
306
+ const now = this.services.clock.now();
307
+ const choice = await this.readSnapshot("applications");
308
+ const snapshot = (choice?.data?.application != null) ? await this.readSnapshot(resource) : null;
309
+ return partOf(snapshot, COLYSEUS_CLOUD_REFRESH_SECONDS[resource], now);
310
+ }
311
+ /**
312
+ * GET cloud while no token is configured: the feature with empty parts.
313
+ * @param nowMilliseconds The current clock time.
314
+ * @returns The summary view.
315
+ */
316
+ emptySummary(nowMilliseconds) {
317
+ return {
318
+ feature: this.feature,
319
+ applicationChoices: [],
320
+ setupProblem: null,
321
+ overview: partOf(null, COLYSEUS_CLOUD_REFRESH_SECONDS.applications, nowMilliseconds),
322
+ load: partOf(null, COLYSEUS_CLOUD_REFRESH_SECONDS.metricsCurrent, nowMilliseconds),
323
+ deploys: partOf(null, COLYSEUS_CLOUD_REFRESH_SECONDS.deploys, nowMilliseconds),
324
+ egress: partOf(null, COLYSEUS_CLOUD_REFRESH_SECONDS.egress, nowMilliseconds)
325
+ };
326
+ }
327
+ /**
328
+ * The route error of a failed deploy output request: 404 for an unknown deploy, 503 after a 429 (which also pauses
329
+ * the fleet), else 502.
330
+ * @param error What the request threw.
331
+ * @param nowMilliseconds The clock time of the request.
332
+ * @returns The route error.
333
+ * @throws The error itself when it is not a ColyseusCloudApiError.
334
+ */
335
+ async deployRequestError(error, nowMilliseconds) {
336
+ if (!(error instanceof ColyseusCloudApiError))
337
+ throw error;
338
+ if (error.status === HTTP_NOT_FOUND)
339
+ return new AdminRequestError(HTTP_NOT_FOUND, "no such deploy");
340
+ if (error.status === HTTP_TOO_MANY_REQUESTS)
341
+ return rateLimitedError(await this.writeRateLimit(error.retryAfterSeconds, nowMilliseconds));
342
+ return new AdminRequestError(HTTP_BAD_GATEWAY, "Colyseus Cloud: " + error.message, error.code);
343
+ }
344
+ /**
345
+ * Reads the fleet-wide log gate.
346
+ * @returns The gate document, or null before the first log request.
347
+ */
348
+ async readLogGate() {
349
+ return await this.services.store.getDocument(CLOUD_COLLECTION, LOG_GATE_DOCUMENT_ID);
350
+ }
351
+ /**
352
+ * Claims the next log request for this process through the gate's revision, so two processes never both send one.
353
+ * @param gate The gate as read, or null when none exists yet.
354
+ * @param nowMilliseconds The current clock time.
355
+ * @returns True when claimed; false when another process claimed it first.
356
+ */
357
+ async claimLogGate(gate, nowMilliseconds) {
358
+ const claim = { nextAllowedAt: isoTimeOf(nowMilliseconds + COLYSEUS_CLOUD_LOG_INTERVAL_MS) };
359
+ try {
360
+ await this.services.store.putDocument(CLOUD_COLLECTION, LOG_GATE_DOCUMENT_ID, claim, gate?.revision ?? 0);
361
+ return true;
362
+ }
363
+ catch (error) {
364
+ if (error instanceof AdminConflictError)
365
+ return false;
366
+ throw error;
367
+ }
368
+ }
369
+ /**
370
+ * While the gate is closed: the last log answer when it is of the same instance, stream and lines and recent enough,
371
+ * else a 429 naming when the next request is allowed.
372
+ * @param instanceId The asked instance.
373
+ * @param stream The asked stream.
374
+ * @param lines The asked number of lines.
375
+ * @param nextAllowedMilliseconds When the next log request is allowed, in clock milliseconds.
376
+ * @param nowMilliseconds The current clock time.
377
+ * @returns The shared answer.
378
+ * @throws AdminRequestError 429 when there is nothing to share.
379
+ */
380
+ async sharedLogsOrRefusal(instanceId, stream, lines, nextAllowedMilliseconds, nowMilliseconds) {
381
+ const nextAllowedAt = isoTimeOf(nextAllowedMilliseconds);
382
+ const last = await this.services.store.getDocument(CLOUD_COLLECTION, LAST_LOGS_DOCUMENT_ID);
383
+ const isSameRequest = last != null && last.value.instanceId === instanceId && last.value.stream === stream && last.value.lines === lines;
384
+ if (last != null && isSameRequest && nowMilliseconds - Date.parse(last.value.fetchedAt) < COLYSEUS_CLOUD_SHARED_LOGS_MS) {
385
+ return { ...last.value, isShared: true, nextAllowedAt: nextAllowedAt };
386
+ }
387
+ throw new AdminRequestError(HTTP_TOO_MANY_REQUESTS, "Colyseus Cloud allows two log requests a minute. Ask again after " + nextAllowedAt + ".", nextAllowedAt);
388
+ }
389
+ /**
390
+ * Sends the claimed log request; on a Cloud error, honours its Retry-After in the gate and maps it to a route error.
391
+ * @param client The Cloud API client.
392
+ * @param slug The application slug.
393
+ * @param instanceId The instance.
394
+ * @param stream The stream.
395
+ * @param lines The number of lines.
396
+ * @param nowMilliseconds The clock time of the request.
397
+ * @returns The log text.
398
+ * @throws AdminRequestError 409 when the instance is not running, 429 when Colyseus refused, else 502.
399
+ */
400
+ async fetchLogs(client, slug, instanceId, stream, lines, nowMilliseconds) {
401
+ try {
402
+ return await client.instanceLogs(slug, instanceId, stream, lines);
403
+ }
404
+ catch (error) {
405
+ if (!(error instanceof ColyseusCloudApiError))
406
+ throw error;
407
+ if (error.retryAfterSeconds != null) {
408
+ const reopenedGate = { nextAllowedAt: isoTimeOf(nowMilliseconds + error.retryAfterSeconds * MILLISECONDS_PER_SECOND) };
409
+ await this.services.store.putDocument(CLOUD_COLLECTION, LOG_GATE_DOCUMENT_ID, reopenedGate, null);
410
+ }
411
+ throw logRequestError(error, nowMilliseconds);
412
+ }
413
+ }
414
+ }
415
+ /**
416
+ * Requests and converts one per-application resource (§7 step 5).
417
+ * @param client The Cloud API client.
418
+ * @param resource The resource.
419
+ * @param slug The application slug.
420
+ * @returns The converted data of the resource's snapshot.
421
+ * @throws ColyseusCloudApiError on a failed or missing answer.
422
+ */
423
+ async function fetchApplicationResource(client, resource, slug) {
424
+ switch (resource) {
425
+ case "metricsCurrent":
426
+ return toLoadView(await client.currentMetrics(slug));
427
+ case "deploys":
428
+ return (await client.deploys(slug, COLYSEUS_CLOUD_DEPLOY_LIMIT)).map(toDeployView);
429
+ case "egress":
430
+ return toEgressView(await client.egress(slug));
431
+ case "metricsDay":
432
+ return toHistoryView(await client.metricsHistory(slug, "day"));
433
+ case "billing":
434
+ return toBillingView(await client.billing(), slug);
435
+ }
436
+ }
437
+ /**
438
+ * Whether an application has an instance in any of its locations.
439
+ * @param application The application.
440
+ * @param instanceId Colyseus's instance id.
441
+ * @returns True when one of its locations lists the instance.
442
+ */
443
+ function hasInstance(application, instanceId) {
444
+ return application.locations.some((location) => location.instances.some((instance) => instance.id === instanceId));
445
+ }
446
+ /**
447
+ * The 503 of deploy output while a Cloud 429 pauses the fleet.
448
+ * @param blockedUntil When the pause ends (ISO).
449
+ * @returns The route error, its detail the end of the pause.
450
+ */
451
+ function rateLimitedError(blockedUntil) {
452
+ return new AdminRequestError(HTTP_SERVICE_UNAVAILABLE, "Colyseus Cloud asked to wait. Try again after " + blockedUntil + ".", blockedUntil);
453
+ }
454
+ /**
455
+ * The route error of a failed log request.
456
+ * @param error The Cloud error.
457
+ * @param nowMilliseconds The clock time of the request.
458
+ * @returns 409 when the instance is not running, 429 (detail: when to ask again) when Colyseus refused, else 502.
459
+ */
460
+ function logRequestError(error, nowMilliseconds) {
461
+ if (error.status === HTTP_CONFLICT)
462
+ return new AdminRequestError(HTTP_CONFLICT, "The instance is not running, so its logs cannot be read.", error.code);
463
+ if (error.status === HTTP_TOO_MANY_REQUESTS) {
464
+ const retryAt = isoTimeOf(nowMilliseconds + (error.retryAfterSeconds ?? COLYSEUS_CLOUD_DEFAULT_RETRY_SECONDS) * MILLISECONDS_PER_SECOND);
465
+ return new AdminRequestError(HTTP_TOO_MANY_REQUESTS, error.message, retryAt);
466
+ }
467
+ return new AdminRequestError(HTTP_BAD_GATEWAY, "Colyseus Cloud: " + error.message, error.code);
468
+ }
469
+ /**
470
+ * An ISO time from clock milliseconds.
471
+ * @param milliseconds Milliseconds since the Unix epoch.
472
+ * @returns The ISO time.
473
+ */
474
+ function isoTimeOf(milliseconds) {
475
+ return new Date(milliseconds).toISOString();
476
+ }
@@ -0,0 +1,16 @@
1
+ import { type GameServerConfig } from "../GameServerConfig.js";
2
+ import type { ColyseusCloudFeature } from "./AdminApiTypes.js";
3
+ /**
4
+ * Whether the console's Colyseus Cloud page applies to this server (colyseus-cloud-console D8): "configured" when a
5
+ * Cloud API token is set, "setupNeeded" on Colyseus Cloud without one (the page explains how to add it), else "off".
6
+ * @param config The resolved server config.
7
+ * @param environment The process environment (COLYSEUS_CLOUD is set only on Colyseus Cloud).
8
+ * @returns The feature state.
9
+ */
10
+ export declare function colyseusCloudFeatureOf(config: GameServerConfig, environment: NodeJS.ProcessEnv): ColyseusCloudFeature;
11
+ /**
12
+ * The host name players reach this server at, from publicUrl, used to mark this console's own Cloud instance (D11).
13
+ * @param config The resolved server config.
14
+ * @returns The host name, or null when publicUrl is unset or not a URL.
15
+ */
16
+ export declare function ownHostNameOf(config: GameServerConfig): string | null;