@colyseus/core 0.18.9 → 0.18.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/Server.cjs +31 -14
- package/build/Server.cjs.map +2 -2
- package/build/Server.d.ts +18 -2
- package/build/Server.mjs +31 -14
- package/build/Server.mjs.map +2 -2
- package/build/internal.cjs +5 -0
- package/build/internal.cjs.map +2 -2
- package/build/internal.d.ts +12 -0
- package/build/internal.mjs +4 -0
- package/build/internal.mjs.map +2 -2
- package/package.json +5 -5
- package/src/Server.ts +35 -13
- package/src/internal.ts +19 -0
package/build/Server.cjs
CHANGED
|
@@ -70,7 +70,7 @@ var Server = class _Server {
|
|
|
70
70
|
/**
|
|
71
71
|
* Every side effect of construction: the dev-mode flag, the transport, the
|
|
72
72
|
* matchmaker and the process signal handlers. Distinct from
|
|
73
|
-
* {@link
|
|
73
|
+
* {@link prepareServices}, which boots the services user code declared.
|
|
74
74
|
*
|
|
75
75
|
* Isolated so {@link DevServer} can opt out of all four — under
|
|
76
76
|
* `colyseus/vite` the plugin owns them, and user code still gets a real
|
|
@@ -106,7 +106,7 @@ var Server = class _Server {
|
|
|
106
106
|
* @param listeningListener
|
|
107
107
|
*/
|
|
108
108
|
async listen(port, hostname, backlog, listeningListener) {
|
|
109
|
-
await this.
|
|
109
|
+
await this._bootForListen();
|
|
110
110
|
if (process.env.COLYSEUS_CLOUD !== void 0) {
|
|
111
111
|
if (typeof hostname === "number") {
|
|
112
112
|
hostname = void 0;
|
|
@@ -168,7 +168,7 @@ var Server = class _Server {
|
|
|
168
168
|
* ```
|
|
169
169
|
*/
|
|
170
170
|
async serverless() {
|
|
171
|
-
await this.
|
|
171
|
+
await this._bootForListen();
|
|
172
172
|
await this._onTransportReady;
|
|
173
173
|
const server = this.transport.server;
|
|
174
174
|
if (!server) {
|
|
@@ -257,23 +257,40 @@ var Server = class _Server {
|
|
|
257
257
|
onBeforeShutdown(callback) {
|
|
258
258
|
this.onBeforeShutdownCallback = callback;
|
|
259
259
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
260
|
+
/**
|
|
261
|
+
* Boot the services user code declared: the `beforeListen` hook, the
|
|
262
|
+
* database, and the endpoints they contribute to the router. Returns the
|
|
263
|
+
* finalized router, so a caller can't read a stale one by mistake.
|
|
264
|
+
*
|
|
265
|
+
* Reached from outside via `@colyseus/core/internal` — `colyseus/vite` owns
|
|
266
|
+
* the HTTP server and never calls {@link listen}, but user code there still
|
|
267
|
+
* expects `database.boot()` and the auth routes.
|
|
268
|
+
*
|
|
269
|
+
* `runBeforeListen` is false when a host re-boots after an HMR reload:
|
|
270
|
+
* `beforeListen` fires once because the server listens once, while the
|
|
271
|
+
* database and the router defaults track instances the reload rebuilt.
|
|
272
|
+
*/
|
|
273
|
+
async prepareServices(runBeforeListen = true) {
|
|
274
|
+
const { beforeListen, database } = this.options;
|
|
275
|
+
if (runBeforeListen && beforeListen) {
|
|
271
276
|
await beforeListen();
|
|
272
277
|
}
|
|
273
278
|
if (database) {
|
|
274
279
|
await database.boot();
|
|
275
280
|
}
|
|
276
281
|
await this._applyRouterDefaults();
|
|
282
|
+
return this.router;
|
|
283
|
+
}
|
|
284
|
+
// Listen-time boot: the services above, then the express app. serverless()
|
|
285
|
+
// needs this too — its exported server still serves the express callback
|
|
286
|
+
// and the auth/database routes.
|
|
287
|
+
async _bootForListen() {
|
|
288
|
+
const { express } = this.options;
|
|
289
|
+
const envLatency = parseLatencyEnv();
|
|
290
|
+
if (envLatency !== void 0) {
|
|
291
|
+
this.simulateLatency(envLatency);
|
|
292
|
+
}
|
|
293
|
+
await this.prepareServices();
|
|
277
294
|
if (express) {
|
|
278
295
|
await this._onTransportReady;
|
|
279
296
|
if (this.transport.getExpressApp) {
|
package/build/Server.cjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/Server.ts"],
|
|
4
|
-
"sourcesContent": ["import { greet } from \"@colyseus/greeting-banner\";\nimport type express from 'express';\nimport type { Server as HttpServer } from 'node:http';\n\nimport { debugAndPrintError } from './Debug.ts';\nimport * as matchMaker from './MatchMaker.ts';\nimport { RegisteredHandler } from './matchmaker/RegisteredHandler.ts';\n\nimport { type OnCreateOptions, Room } from './Room.ts';\nimport { Deferred, registerGracefulShutdown, dynamicImport, type Type } from './utils/Utils.ts';\n\nimport type { Presence } from \"./presence/Presence.ts\";\n\nimport { getTransport, setTransport, Transport } from './Transport.ts';\nimport { logger, setLogger } from './Logger.ts';\nimport { setDevMode, isDevMode } from './utils/DevMode.ts';\nimport { type Router, bindRouterToTransport, createRouter } from './router/index.ts';\nimport { prereadRequestBodies } from './router/node.ts';\nimport { type SDKTypes as SharedSDKTypes } from '@colyseus/shared-types';\nimport { getDefaultRouter } from './router/default_routes.ts';\n\nexport type ServerOptions = {\n publicAddress?: string,\n presence?: Presence,\n driver?: matchMaker.MatchMakerDriver,\n transport?: Transport,\n gracefullyShutdown?: boolean,\n logger?: any;\n\n /**\n * Optional callback to execute before the server listens.\n * This is useful for example to connect into a database or other services before the server listens.\n */\n beforeListen?: () => Promise<void> | void,\n\n /**\n * Booted before `matchMaker.accept()`. Structural so `@colyseus/core` keeps\n * no runtime dep on `@colyseus/database` \u2014 any `{ boot(): Promise<void> }`\n * works. The optional `applyRouterDefaults` is invoked after boot with the\n * user's router so the database can contribute endpoints (e.g. auth routes).\n */\n database?: {\n boot(): Promise<void>;\n applyRouterDefaults?(router: Router): Router | Promise<Router>;\n },\n\n /**\n * Mount `@colyseus/auth` routes into the router. Pass an options object\n * (forwarded to `auth.endpoints(...)`) to wire it explicitly \u2014 useful when\n * `database` is not in use. `false` disables auto-mounting even if a\n * `database` is present and would have provided defaults.\n */\n auth?: false | {\n settings?: Record<string, any>;\n oauth?: boolean | { cookieSecret?: string };\n prefix?: string;\n },\n\n /**\n * Optional callback to configure Express routes.\n * When provided, the transport layer will initialize an Express-compatible app\n * and pass it to this callback for custom route configuration.\n *\n * For uWebSockets transport, this uses the uwebsockets-express module.\n */\n express?: (app: express.Application) => Promise<void> | void,\n\n /**\n * Custom function to determine which process should handle room creation.\n * Default: assign new rooms the process with least amount of rooms created\n */\n selectProcessIdToCreateRoom?: matchMaker.SelectProcessIdCallback;\n\n /**\n * Whether this process is running as a standalone match-maker or not. (default: false)\n * When enabled, this process will not spawn rooms and will only be responsible for matchmaking.\n */\n isStandaloneMatchMaker?: boolean; \n\n /**\n * If enabled, rooms are going to be restored in the server-side upon restart,\n * clients are going to automatically re-connect when server reboots.\n *\n * Beware of \"schema mismatch\" issues. When updating Schema structures and\n * reloading existing data, you may see \"schema mismatch\" errors in the\n * client-side.\n *\n * (This operation is costly and should not be used in a production\n * environment)\n */\n devMode?: boolean,\n\n /**\n * Display greeting message on server start.\n * Default: true\n */\n greet?: boolean,\n};\n\n/**\n * Exposed types for the client-side SDK.\n * Re-exported from @colyseus/shared-types with specific type constraints.\n */\nexport interface SDKTypes<\n RoomTypes extends Record<string, RegisteredHandler> = any,\n Routes extends Router = any\n> extends SharedSDKTypes<RoomTypes, Routes> {}\n\nexport class Server<\n RoomTypes extends Record<string, RegisteredHandler> = any,\n Routes extends Router = any\n> implements SDKTypes<RoomTypes, Routes> {\n '~rooms': RoomTypes;\n '~routes': Routes;\n\n public transport: Transport;\n public router: Routes;\n public options: ServerOptions;\n\n protected presence: Presence;\n protected driver: matchMaker.MatchMakerDriver;\n\n protected port: number | string;\n protected greet: boolean;\n\n protected _onTransportReady = new Deferred<Transport>();\n\n // Implicit default for callers that omit explicit Server reference \u2014 e.g.\n // `playground()` reads `Server.current.router.endpoints` at request time.\n // Last-construction wins; multi-server setups should reference instances\n // explicitly.\n static current: Server<any, any> | undefined;\n\n constructor(options: ServerOptions = {}) {\n const { greet = true } = options;\n\n this.options = options;\n this.greet = greet;\n\n (Server as { current: Server<any, any> | undefined }).current = this as Server<any, any>;\n\n if (options.logger) {\n setLogger(options.logger);\n }\n\n this.bootRuntime();\n }\n\n /**\n * Every side effect of construction: the dev-mode flag, the transport, the\n * matchmaker and the process signal handlers. Distinct from\n * {@link _bootServices}, which prepares user services once we're listening.\n *\n * Isolated so {@link DevServer} can opt out of all four \u2014 under\n * `colyseus/vite` the plugin owns them, and user code still gets a real\n * `Server` to call.\n */\n protected bootRuntime() {\n const options = this.options;\n\n setDevMode(options.devMode === true);\n\n this.attach(options);\n\n // Pass options.presence/driver through as-is (possibly undefined).\n // matchMaker.setup() falls back to getDefaultPresence/getDefaultDriver,\n // which auto-select RedisPresence/RedisDriver on Colyseus Cloud.\n matchMaker.setup(\n options.presence,\n options.driver,\n options.publicAddress,\n options.selectProcessIdToCreateRoom,\n ).then(() => {\n this.presence = matchMaker.presence;\n this.driver = matchMaker.driver;\n });\n\n if (options.gracefullyShutdown !== false) {\n registerGracefulShutdown((err) => this.gracefullyShutdown(true, err));\n }\n }\n\n public async attach(options: ServerOptions) {\n this.transport = options.transport || await this.getDefaultTransport(options);\n // `options.express` runs in `listen()` after `database.boot()` so user\n // code reading `database.auth.settings` etc. finds services instantiated.\n this._onTransportReady.resolve(this.transport);\n }\n\n /**\n * Bind the server into the port specified.\n *\n * @param port - Port number or Unix socket path\n * @param hostname\n * @param backlog\n * @param listeningListener\n */\n public async listen(port: number | string, hostname?: string, backlog?: number, listeningListener?: Function) {\n await this._bootServices();\n\n //\n // if Colyseus Cloud is detected, use @colyseus/tools to listen\n //\n if (process.env.COLYSEUS_CLOUD !== undefined ) {\n if (typeof(hostname) === \"number\") {\n //\n // workaround, @colyseus/tools calls server.listen() again with the port as a string\n //\n hostname = undefined;\n\n } else {\n try {\n return (await dynamicImport(\"@colyseus/tools\")).listen(this);\n } catch (error) {\n const err = new Error(\"Please install @colyseus/tools to be able to host on Colyseus Cloud.\");\n err.cause = error;\n throw err;\n }\n }\n }\n\n //\n // otherwise, listen on the port directly\n //\n this.port = port;\n\n //\n // Make sure matchmaker is ready before accepting connections\n // (isDevMode: matchmaker may take extra milliseconds to restore the rooms)\n //\n await matchMaker.accept(this.options.isStandaloneMatchMaker);\n\n /**\n * Greetings!\n */\n if (this.greet) {\n greet();\n }\n\n // Wait for the transport to be ready\n await this._onTransportReady;\n\n return new Promise<void>((resolve, reject) => {\n this.transport.listen(port, hostname, backlog, (err) => {\n if (this.transport.server) {\n this.transport.server.on('error', (err) => reject(err));\n }\n\n this.bindRoutes();\n\n if (listeningListener) {\n listeningListener(err);\n }\n\n if (err) {\n reject(err);\n\n } else {\n resolve();\n }\n });\n });\n }\n\n /**\n * Prepare matchmaking and HTTP routes, then return the underlying\n * `http.Server` *without* binding to a port.\n *\n * Use this on serverless platforms that consume an exported HTTP server\n * instead of a listening one. Vercel, for example, drives the request handler\n * of a default-exported server (its Express/Hono WebSocket examples use\n * `export default server`), whereas calling `listen()` there selects Vercel's\n * \"captured server\" path, which does not invoke Express-style app handlers.\n *\n * The transport must be created with an `http.Server` so it can be exported.\n * Vercel additionally needs `package.json` `\"main\"` pointing at the entrypoint:\n *\n * ```ts\n * import { createServer } from \"node:http\";\n * const httpServer = createServer();\n * const server = new Server({\n * transport: new WebSocketTransport({ server: httpServer }),\n * express: (app) => { app.get(\"/hello\", (req, res) => res.json({ ok: true })); },\n * });\n * server.define(\"my_room\", MyRoom);\n * export default await server.serverless(); // no listen()\n * ```\n */\n public async serverless(): Promise<HttpServer> {\n await this._bootServices();\n\n // transport (and its HTTP server) must be ready before binding routes\n await this._onTransportReady;\n\n const server = this.transport.server as HttpServer | undefined;\n if (!server) {\n throw new Error(\"serverless() requires a transport backed by an http.Server (pass `{ server }` to the transport).\");\n }\n\n this.bindRoutes();\n\n // When the server is consumed via `export default` (rather than listen()),\n // the request body must be available synchronously \u2014 buffer it up-front so\n // `req.body` is set before the router runs.\n prereadRequestBodies(server);\n\n await matchMaker.accept(this.options.isStandaloneMatchMaker);\n\n if (this.greet) {\n greet();\n }\n\n return server;\n }\n\n /**\n * Set the active transport globally and bind the matchmaking/HTTP routes.\n * Shared by {@link listen} and {@link serverless}.\n *\n * `setTransport()` runs here rather than before `transport.listen()`: the\n * global transport is only read by the matchmaking route handler, which can't\n * run until `bindRouterToTransport()` below registers the route.\n */\n private bindRoutes() {\n // set transport globally, to be used by the matchmaking route\n setTransport(this.transport);\n\n // default router is used if no router is provided\n if (!this.router) {\n this.router = getDefaultRouter() as unknown as Routes;\n\n } else {\n // make sure default routes are included\n // https://github.com/Bekacru/better-call/pull/67\n this.router = this.router.extend({ ...getDefaultRouter().endpoints }) as unknown as Routes;\n }\n\n bindRouterToTransport(this.transport, this.router, this.options.express !== undefined);\n }\n\n /**\n * Define a new type of room for matchmaking.\n *\n * @param name public room identifier for match-making.\n * @param roomClass Room class definition\n * @param defaultOptions default options for `onCreate`\n */\n public define<T extends Type<Room>>(\n roomClass: T,\n defaultOptions?: OnCreateOptions<T>,\n ): RegisteredHandler\n public define<T extends Type<Room>>(\n name: string,\n roomClass: T,\n defaultOptions?: OnCreateOptions<T>,\n ): RegisteredHandler\n public define<T extends Type<Room>>(\n nameOrHandler: string | T,\n handlerOrOptions: T | OnCreateOptions<T>,\n defaultOptions?: OnCreateOptions<T>,\n ): RegisteredHandler {\n const name = (typeof(nameOrHandler) === \"string\")\n ? nameOrHandler\n : nameOrHandler.name;\n\n const roomClass = (typeof(nameOrHandler) === \"string\")\n ? handlerOrOptions\n : nameOrHandler;\n\n const options = (typeof(nameOrHandler) === \"string\")\n ? defaultOptions\n : handlerOrOptions;\n\n return matchMaker.defineRoomType(name, roomClass, options);\n }\n\n /**\n * Remove a room definition from matchmaking.\n * This method does not destroy any room. It only dissallows matchmaking\n */\n public removeRoomType(name: string): void {\n matchMaker.removeRoomType(name);\n }\n\n public async gracefullyShutdown(exit: boolean = true, err?: Error) {\n if (matchMaker.state === matchMaker.MatchMakerState.SHUTTING_DOWN) {\n return;\n }\n\n try {\n // custom \"before shutdown\" method\n await this.onBeforeShutdownCallback();\n\n // this is going to lock all rooms and wait for them to be disposed\n await matchMaker.gracefullyShutdown();\n\n this.transport?.shutdown();\n this.presence?.shutdown();\n await this.driver?.shutdown();\n\n // custom \"after shutdown\" method\n await this.onShutdownCallback();\n\n } catch (e) {\n debugAndPrintError(`error during shutdown: ${e}`);\n\n } finally {\n if (exit) {\n process.exit((err && !isDevMode) ? 1 : 0);\n }\n }\n }\n\n /**\n * Add simulated latency between client and server.\n *\n * May be called at any time \u2014 before or after `listen()`. Also available\n * via the `COLYSEUS_LATENCY` environment variable (round-trip ms), which\n * overrides calls made before the server boots.\n *\n * @param milliseconds round trip latency in milliseconds.\n */\n public simulateLatency(milliseconds: number) {\n // transport may still be resolving (constructor's attach() imports the default one)\n this._onTransportReady.then(\n (transport) => applySimulatedLatency(transport, milliseconds),\n () => {/* missing-transport error already surfaces via attach()/listen() */},\n );\n }\n\n /**\n * Register a callback that is going to be executed before the server shuts down.\n * @param callback\n */\n public onShutdown(callback: () => void | Promise<any>) {\n this.onShutdownCallback = callback;\n }\n\n public onBeforeShutdown(callback: () => void | Promise<any>) {\n this.onBeforeShutdownCallback = callback;\n }\n\n // Boot sequence shared by listen() and serverless(): user `beforeListen`\n // hook, database boot, framework router defaults (auth/db endpoints), then\n // the express app. serverless() needs this too \u2014 its exported server still\n // serves the express callback and auth/database routes.\n private async _bootServices(): Promise<void> {\n const { beforeListen, database, express } = this.options;\n\n // boot runs after module evaluation, so the env var wins over\n // simulateLatency() calls made at the top level of user code\n const envLatency = parseLatencyEnv();\n if (envLatency !== undefined) { this.simulateLatency(envLatency); }\n\n if (beforeListen) { await beforeListen(); }\n if (database) { await database.boot(); }\n\n await this._applyRouterDefaults();\n\n if (express) {\n await this._onTransportReady;\n if (this.transport.getExpressApp) {\n const expressApp = await this.transport.getExpressApp();\n await express(expressApp);\n }\n }\n }\n\n // Extend the user's router with framework-contributed endpoints. An explicit\n // `auth` option wins over the database's `applyRouterDefaults` so a non-DB\n // setup can still auto-mount @colyseus/auth, and a DB user can opt out.\n private async _applyRouterDefaults(): Promise<void> {\n const { auth, database } = this.options;\n const wantsAuth = auth !== undefined && auth !== false;\n const wantsDatabase = auth === undefined && !!database?.applyRouterDefaults;\n if (!wantsAuth && !wantsDatabase) { return; }\n\n // Boot an empty router if the user didn't pass `routes` \u2014 the framework's\n // default routes get layered on later in the `transport.listen()` callback.\n this.router ??= createRouter({}) as unknown as Routes;\n\n if (wantsAuth) {\n const authMod: any = await dynamicImport('@colyseus/auth').catch(() => undefined);\n const endpointsFn = authMod?.auth?.endpoints ?? authMod?.default?.auth?.endpoints;\n if (typeof endpointsFn === 'function') {\n this.router = (this.router as Router).extend(endpointsFn(auth)) as Routes;\n }\n } else {\n const updated = await database!.applyRouterDefaults!(this.router as Router);\n if (updated) { this.router = updated as Routes; }\n }\n }\n\n protected async getDefaultTransport(options: any): Promise<Transport> {\n try {\n const module = await dynamicImport('@colyseus/ws-transport');\n const WebSocketTransport = module.WebSocketTransport;\n return new WebSocketTransport(options);\n\n } catch (error) {\n this._onTransportReady.reject(error);\n throw new Error(\"Please provide a 'transport' layer. Default transport not set.\");\n }\n }\n\n protected onShutdownCallback: () => void | Promise<any> =\n () => Promise.resolve()\n\n protected onBeforeShutdownCallback: () => void | Promise<any> =\n () => Promise.resolve()\n}\n\n// the user module re-evaluates on every HMR reload \u2014 warn once per process\nconst devWarnings = new Set<string>();\nfunction warnDevOnce(message: string) {\n if (devWarnings.has(message)) { return; }\n devWarnings.add(message);\n logger.warn(message);\n}\n\n/**\n * The `Server` handed to user code under `colyseus/vite`.\n *\n * The plugin owns the HTTP server, the transport, the matchmaker and the\n * process lifecycle, so constructing one has no side effects \u2014 it only\n * carries the user's configuration back to the plugin. Everything else is\n * inherited, so the documented `Server` API keeps working in dev mode.\n */\nclass DevServer<\n RoomTypes extends Record<string, RegisteredHandler> = any,\n Routes extends Router = any\n> extends Server<RoomTypes, Routes> {\n\n protected bootRuntime() {\n // the plugin creates the transport and runs matchMaker.setup() before\n // importing user code, so both are already available here\n this.transport = getTransport();\n this._onTransportReady.resolve(this.transport);\n this.presence = matchMaker.presence;\n this.driver = matchMaker.driver;\n\n if (this.options.transport) {\n warnDevOnce(\"\u26A0\uFE0F 'transport' is ignored in dev mode \u2014 colyseus/vite serves WebSockets over Vite's own HTTP server.\");\n }\n }\n\n /** The plugin already serves Colyseus on Vite's own HTTP server and port. */\n public async listen(_port?: number | string, _hostname?: string, _backlog?: number, listeningListener?: Function) {\n warnDevOnce(\"\u26A0\uFE0F listen() is ignored in dev mode \u2014 connect to the Vite dev server's URL instead.\");\n listeningListener?.();\n }\n}\n\nlet _originalRoomOnMessage: typeof Room.prototype['_onMessage'] | null = null;\n\n/**\n * Applies both halves of the round-trip latency simulation: delays the\n * transport's outgoing messages and the Room's incoming message handling.\n *\n * Prefer `server.simulateLatency()` \u2014 this is the shared core behind it, the\n * dev-mode `defineServer()` object and the `COLYSEUS_LATENCY` env var, where\n * no `Server` instance (or transport, yet) may exist.\n */\nexport function applySimulatedLatency(transport: Transport | undefined, milliseconds: number) {\n if (milliseconds > 0) {\n logger.warn(`\uD83D\uDCF6\uFE0F\u2757 Colyseus latency simulation enabled \u2192 ${milliseconds}ms latency for round trip.`);\n } else {\n logger.warn(`\uD83D\uDCF6\uFE0F\u2757 Colyseus latency simulation disabled.`);\n }\n\n const halfwayMS = (milliseconds / 2);\n transport?.simulateLatency(halfwayMS);\n\n if (_originalRoomOnMessage == null) {\n _originalRoomOnMessage = Room.prototype['_onMessage'];\n }\n\n const originalOnMessage = _originalRoomOnMessage;\n\n Room.prototype['_onMessage'] = milliseconds <= Number.EPSILON ? originalOnMessage : function (this: Room, client, buffer) {\n // uWebSockets.js: duplicate buffer because it is cleared at native layer before the timeout.\n const cachedBuffer = Buffer.from(buffer);\n setTimeout(() => originalOnMessage.call(this, client, cachedBuffer), halfwayMS);\n };\n}\n\n/**\n * Reads the `COLYSEUS_LATENCY` environment variable (round-trip milliseconds).\n * Returns `undefined` when absent or empty; a non-numeric value warns and is ignored.\n */\nexport function parseLatencyEnv(): number | undefined {\n const value = process.env.COLYSEUS_LATENCY;\n if (value === undefined || value === '') { return undefined; }\n\n const milliseconds = Number(value);\n if (isNaN(milliseconds)) {\n logger.warn(`\uD83D\uDCF6\uFE0F\u2757 Ignoring COLYSEUS_LATENCY: expected a number of milliseconds, got ${JSON.stringify(value)}.`);\n return undefined;\n }\n return milliseconds;\n}\n\nexport type RoomDefinitions = Record<string, RegisteredHandler | Type<Room>>;\n\nfunction isRegisteredHandler(value: RegisteredHandler | Type<Room>): value is RegisteredHandler {\n return value instanceof RegisteredHandler || (\n typeof(value) === \"object\" &&\n value !== null &&\n 'klass' in (value as object)\n );\n}\n\nexport function registerRoomDefinitions<T extends RoomDefinitions>(rooms: T): string[] {\n const roomNames: string[] = [];\n\n for (const [name, value] of Object.entries(rooms)) {\n if (isRegisteredHandler(value)) {\n value.name = name;\n matchMaker.addRoomType(value);\n\n } else {\n matchMaker.defineRoomType(name, value);\n }\n\n roomNames.push(name);\n }\n\n return roomNames;\n}\n\nexport function unregisterRoomDefinitions(roomNames: Iterable<string>) {\n for (const roomName of roomNames) {\n matchMaker.removeRoomType(roomName);\n }\n}\n\nexport type DefineServerOptions<\n T extends Record<string, RegisteredHandler>,\n R extends Router\n> = ServerOptions & {\n rooms: T,\n routes?: R,\n};\n\nexport function defineServer<\n T extends Record<string, RegisteredHandler>,\n R extends Router\n>(\n options: DefineServerOptions<T, R>,\n): Server<T, R> {\n const { rooms, routes, ...serverOptions } = options;\n\n const server = isDevMode\n ? new DevServer<T, R>(serverOptions)\n : new Server<T, R>(serverOptions);\n\n server.router = routes;\n server['~rooms'] = rooms;\n\n // in dev mode the plugin reads `~rooms` back and owns their\n // (re-)registration across HMR reloads\n if (!isDevMode) {\n registerRoomDefinitions(rooms);\n }\n\n return server;\n}\n\nexport function defineRoom<T extends Type<Room>>(\n roomKlass: T,\n defaultOptions?: Parameters<NonNullable<InstanceType<T>['onCreate']>>[0],\n): RegisteredHandler<InstanceType<T>> {\n return new RegisteredHandler(roomKlass, defaultOptions) as unknown as RegisteredHandler<InstanceType<T>>;\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6BAAsB;AAItB,mBAAmC;AACnC,iBAA4B;AAC5B,+BAAkC;AAElC,kBAA2C;AAC3C,mBAA6E;AAI7E,uBAAsD;AACtD,oBAAkC;AAClC,qBAAsC;AACtC,oBAAiE;AACjE,kBAAqC;AACrC,0BAAgD;AAChD,4BAAiC;
|
|
4
|
+
"sourcesContent": ["import { greet } from \"@colyseus/greeting-banner\";\nimport type express from 'express';\nimport type { Server as HttpServer } from 'node:http';\n\nimport { debugAndPrintError } from './Debug.ts';\nimport * as matchMaker from './MatchMaker.ts';\nimport { RegisteredHandler } from './matchmaker/RegisteredHandler.ts';\n\nimport { type OnCreateOptions, Room } from './Room.ts';\nimport { Deferred, registerGracefulShutdown, dynamicImport, type Type } from './utils/Utils.ts';\n\nimport type { Presence } from \"./presence/Presence.ts\";\n\nimport { getTransport, setTransport, Transport } from './Transport.ts';\nimport { logger, setLogger } from './Logger.ts';\nimport { setDevMode, isDevMode } from './utils/DevMode.ts';\nimport { type Router, bindRouterToTransport, createRouter } from './router/index.ts';\nimport { prereadRequestBodies } from './router/node.ts';\nimport { type SDKTypes as SharedSDKTypes } from '@colyseus/shared-types';\nimport { getDefaultRouter } from './router/default_routes.ts';\n\nexport type ServerOptions = {\n publicAddress?: string,\n presence?: Presence,\n driver?: matchMaker.MatchMakerDriver,\n transport?: Transport,\n gracefullyShutdown?: boolean,\n logger?: any;\n\n /**\n * Optional callback to execute before the server listens.\n * This is useful for example to connect into a database or other services before the server listens.\n */\n beforeListen?: () => Promise<void> | void,\n\n /**\n * Booted before `matchMaker.accept()`. Structural so `@colyseus/core` keeps\n * no runtime dep on `@colyseus/database` \u2014 any `{ boot(): Promise<void> }`\n * works. The optional `applyRouterDefaults` is invoked after boot with the\n * user's router so the database can contribute endpoints (e.g. auth routes).\n */\n database?: {\n boot(): Promise<void>;\n /** Release connections \u2014 a host that re-boots on reload disposes the instance it replaced. */\n shutdown?(): Promise<void>;\n applyRouterDefaults?(router: Router): Router | Promise<Router>;\n },\n\n /**\n * Mount `@colyseus/auth` routes into the router. Pass an options object\n * (forwarded to `auth.endpoints(...)`) to wire it explicitly \u2014 useful when\n * `database` is not in use. `false` disables auto-mounting even if a\n * `database` is present and would have provided defaults.\n */\n auth?: false | {\n settings?: Record<string, any>;\n oauth?: boolean | { cookieSecret?: string };\n prefix?: string;\n },\n\n /**\n * Optional callback to configure Express routes.\n * When provided, the transport layer will initialize an Express-compatible app\n * and pass it to this callback for custom route configuration.\n *\n * For uWebSockets transport, this uses the uwebsockets-express module.\n */\n express?: (app: express.Application) => Promise<void> | void,\n\n /**\n * Custom function to determine which process should handle room creation.\n * Default: assign new rooms the process with least amount of rooms created\n */\n selectProcessIdToCreateRoom?: matchMaker.SelectProcessIdCallback;\n\n /**\n * Whether this process is running as a standalone match-maker or not. (default: false)\n * When enabled, this process will not spawn rooms and will only be responsible for matchmaking.\n */\n isStandaloneMatchMaker?: boolean; \n\n /**\n * If enabled, rooms are going to be restored in the server-side upon restart,\n * clients are going to automatically re-connect when server reboots.\n *\n * Beware of \"schema mismatch\" issues. When updating Schema structures and\n * reloading existing data, you may see \"schema mismatch\" errors in the\n * client-side.\n *\n * (This operation is costly and should not be used in a production\n * environment)\n */\n devMode?: boolean,\n\n /**\n * Display greeting message on server start.\n * Default: true\n */\n greet?: boolean,\n};\n\n/**\n * Exposed types for the client-side SDK.\n * Re-exported from @colyseus/shared-types with specific type constraints.\n */\nexport interface SDKTypes<\n RoomTypes extends Record<string, RegisteredHandler> = any,\n Routes extends Router = any\n> extends SharedSDKTypes<RoomTypes, Routes> {}\n\nexport class Server<\n RoomTypes extends Record<string, RegisteredHandler> = any,\n Routes extends Router = any\n> implements SDKTypes<RoomTypes, Routes> {\n '~rooms': RoomTypes;\n '~routes': Routes;\n\n public transport: Transport;\n public router: Routes;\n public options: ServerOptions;\n\n protected presence: Presence;\n protected driver: matchMaker.MatchMakerDriver;\n\n protected port: number | string;\n protected greet: boolean;\n\n protected _onTransportReady = new Deferred<Transport>();\n\n // Implicit default for callers that omit explicit Server reference \u2014 e.g.\n // `playground()` reads `Server.current.router.endpoints` at request time.\n // Last-construction wins; multi-server setups should reference instances\n // explicitly.\n static current: Server<any, any> | undefined;\n\n constructor(options: ServerOptions = {}) {\n const { greet = true } = options;\n\n this.options = options;\n this.greet = greet;\n\n (Server as { current: Server<any, any> | undefined }).current = this as Server<any, any>;\n\n if (options.logger) {\n setLogger(options.logger);\n }\n\n this.bootRuntime();\n }\n\n /**\n * Every side effect of construction: the dev-mode flag, the transport, the\n * matchmaker and the process signal handlers. Distinct from\n * {@link prepareServices}, which boots the services user code declared.\n *\n * Isolated so {@link DevServer} can opt out of all four \u2014 under\n * `colyseus/vite` the plugin owns them, and user code still gets a real\n * `Server` to call.\n */\n protected bootRuntime() {\n const options = this.options;\n\n setDevMode(options.devMode === true);\n\n this.attach(options);\n\n // Pass options.presence/driver through as-is (possibly undefined).\n // matchMaker.setup() falls back to getDefaultPresence/getDefaultDriver,\n // which auto-select RedisPresence/RedisDriver on Colyseus Cloud.\n matchMaker.setup(\n options.presence,\n options.driver,\n options.publicAddress,\n options.selectProcessIdToCreateRoom,\n ).then(() => {\n this.presence = matchMaker.presence;\n this.driver = matchMaker.driver;\n });\n\n if (options.gracefullyShutdown !== false) {\n registerGracefulShutdown((err) => this.gracefullyShutdown(true, err));\n }\n }\n\n public async attach(options: ServerOptions) {\n this.transport = options.transport || await this.getDefaultTransport(options);\n // `options.express` runs in `listen()` after `database.boot()` so user\n // code reading `database.auth.settings` etc. finds services instantiated.\n this._onTransportReady.resolve(this.transport);\n }\n\n /**\n * Bind the server into the port specified.\n *\n * @param port - Port number or Unix socket path\n * @param hostname\n * @param backlog\n * @param listeningListener\n */\n public async listen(port: number | string, hostname?: string, backlog?: number, listeningListener?: Function) {\n await this._bootForListen();\n\n //\n // if Colyseus Cloud is detected, use @colyseus/tools to listen\n //\n if (process.env.COLYSEUS_CLOUD !== undefined ) {\n if (typeof(hostname) === \"number\") {\n //\n // workaround, @colyseus/tools calls server.listen() again with the port as a string\n //\n hostname = undefined;\n\n } else {\n try {\n return (await dynamicImport(\"@colyseus/tools\")).listen(this);\n } catch (error) {\n const err = new Error(\"Please install @colyseus/tools to be able to host on Colyseus Cloud.\");\n err.cause = error;\n throw err;\n }\n }\n }\n\n //\n // otherwise, listen on the port directly\n //\n this.port = port;\n\n //\n // Make sure matchmaker is ready before accepting connections\n // (isDevMode: matchmaker may take extra milliseconds to restore the rooms)\n //\n await matchMaker.accept(this.options.isStandaloneMatchMaker);\n\n /**\n * Greetings!\n */\n if (this.greet) {\n greet();\n }\n\n // Wait for the transport to be ready\n await this._onTransportReady;\n\n return new Promise<void>((resolve, reject) => {\n this.transport.listen(port, hostname, backlog, (err) => {\n if (this.transport.server) {\n this.transport.server.on('error', (err) => reject(err));\n }\n\n this.bindRoutes();\n\n if (listeningListener) {\n listeningListener(err);\n }\n\n if (err) {\n reject(err);\n\n } else {\n resolve();\n }\n });\n });\n }\n\n /**\n * Prepare matchmaking and HTTP routes, then return the underlying\n * `http.Server` *without* binding to a port.\n *\n * Use this on serverless platforms that consume an exported HTTP server\n * instead of a listening one. Vercel, for example, drives the request handler\n * of a default-exported server (its Express/Hono WebSocket examples use\n * `export default server`), whereas calling `listen()` there selects Vercel's\n * \"captured server\" path, which does not invoke Express-style app handlers.\n *\n * The transport must be created with an `http.Server` so it can be exported.\n * Vercel additionally needs `package.json` `\"main\"` pointing at the entrypoint:\n *\n * ```ts\n * import { createServer } from \"node:http\";\n * const httpServer = createServer();\n * const server = new Server({\n * transport: new WebSocketTransport({ server: httpServer }),\n * express: (app) => { app.get(\"/hello\", (req, res) => res.json({ ok: true })); },\n * });\n * server.define(\"my_room\", MyRoom);\n * export default await server.serverless(); // no listen()\n * ```\n */\n public async serverless(): Promise<HttpServer> {\n await this._bootForListen();\n\n // transport (and its HTTP server) must be ready before binding routes\n await this._onTransportReady;\n\n const server = this.transport.server as HttpServer | undefined;\n if (!server) {\n throw new Error(\"serverless() requires a transport backed by an http.Server (pass `{ server }` to the transport).\");\n }\n\n this.bindRoutes();\n\n // When the server is consumed via `export default` (rather than listen()),\n // the request body must be available synchronously \u2014 buffer it up-front so\n // `req.body` is set before the router runs.\n prereadRequestBodies(server);\n\n await matchMaker.accept(this.options.isStandaloneMatchMaker);\n\n if (this.greet) {\n greet();\n }\n\n return server;\n }\n\n /**\n * Set the active transport globally and bind the matchmaking/HTTP routes.\n * Shared by {@link listen} and {@link serverless}.\n *\n * `setTransport()` runs here rather than before `transport.listen()`: the\n * global transport is only read by the matchmaking route handler, which can't\n * run until `bindRouterToTransport()` below registers the route.\n */\n private bindRoutes() {\n // set transport globally, to be used by the matchmaking route\n setTransport(this.transport);\n\n // default router is used if no router is provided\n if (!this.router) {\n this.router = getDefaultRouter() as unknown as Routes;\n\n } else {\n // make sure default routes are included\n // https://github.com/Bekacru/better-call/pull/67\n this.router = this.router.extend({ ...getDefaultRouter().endpoints }) as unknown as Routes;\n }\n\n bindRouterToTransport(this.transport, this.router, this.options.express !== undefined);\n }\n\n /**\n * Define a new type of room for matchmaking.\n *\n * @param name public room identifier for match-making.\n * @param roomClass Room class definition\n * @param defaultOptions default options for `onCreate`\n */\n public define<T extends Type<Room>>(\n roomClass: T,\n defaultOptions?: OnCreateOptions<T>,\n ): RegisteredHandler\n public define<T extends Type<Room>>(\n name: string,\n roomClass: T,\n defaultOptions?: OnCreateOptions<T>,\n ): RegisteredHandler\n public define<T extends Type<Room>>(\n nameOrHandler: string | T,\n handlerOrOptions: T | OnCreateOptions<T>,\n defaultOptions?: OnCreateOptions<T>,\n ): RegisteredHandler {\n const name = (typeof(nameOrHandler) === \"string\")\n ? nameOrHandler\n : nameOrHandler.name;\n\n const roomClass = (typeof(nameOrHandler) === \"string\")\n ? handlerOrOptions\n : nameOrHandler;\n\n const options = (typeof(nameOrHandler) === \"string\")\n ? defaultOptions\n : handlerOrOptions;\n\n return matchMaker.defineRoomType(name, roomClass, options);\n }\n\n /**\n * Remove a room definition from matchmaking.\n * This method does not destroy any room. It only dissallows matchmaking\n */\n public removeRoomType(name: string): void {\n matchMaker.removeRoomType(name);\n }\n\n public async gracefullyShutdown(exit: boolean = true, err?: Error) {\n if (matchMaker.state === matchMaker.MatchMakerState.SHUTTING_DOWN) {\n return;\n }\n\n try {\n // custom \"before shutdown\" method\n await this.onBeforeShutdownCallback();\n\n // this is going to lock all rooms and wait for them to be disposed\n await matchMaker.gracefullyShutdown();\n\n this.transport?.shutdown();\n this.presence?.shutdown();\n await this.driver?.shutdown();\n\n // custom \"after shutdown\" method\n await this.onShutdownCallback();\n\n } catch (e) {\n debugAndPrintError(`error during shutdown: ${e}`);\n\n } finally {\n if (exit) {\n process.exit((err && !isDevMode) ? 1 : 0);\n }\n }\n }\n\n /**\n * Add simulated latency between client and server.\n *\n * May be called at any time \u2014 before or after `listen()`. Also available\n * via the `COLYSEUS_LATENCY` environment variable (round-trip ms), which\n * overrides calls made before the server boots.\n *\n * @param milliseconds round trip latency in milliseconds.\n */\n public simulateLatency(milliseconds: number) {\n // transport may still be resolving (constructor's attach() imports the default one)\n this._onTransportReady.then(\n (transport) => applySimulatedLatency(transport, milliseconds),\n () => {/* missing-transport error already surfaces via attach()/listen() */},\n );\n }\n\n /**\n * Register a callback that is going to be executed before the server shuts down.\n * @param callback\n */\n public onShutdown(callback: () => void | Promise<any>) {\n this.onShutdownCallback = callback;\n }\n\n public onBeforeShutdown(callback: () => void | Promise<any>) {\n this.onBeforeShutdownCallback = callback;\n }\n\n /**\n * Boot the services user code declared: the `beforeListen` hook, the\n * database, and the endpoints they contribute to the router. Returns the\n * finalized router, so a caller can't read a stale one by mistake.\n *\n * Reached from outside via `@colyseus/core/internal` \u2014 `colyseus/vite` owns\n * the HTTP server and never calls {@link listen}, but user code there still\n * expects `database.boot()` and the auth routes.\n *\n * `runBeforeListen` is false when a host re-boots after an HMR reload:\n * `beforeListen` fires once because the server listens once, while the\n * database and the router defaults track instances the reload rebuilt.\n */\n protected async prepareServices(runBeforeListen: boolean = true): Promise<Routes> {\n const { beforeListen, database } = this.options;\n\n if (runBeforeListen && beforeListen) { await beforeListen(); }\n if (database) { await database.boot(); }\n\n await this._applyRouterDefaults();\n\n return this.router;\n }\n\n // Listen-time boot: the services above, then the express app. serverless()\n // needs this too \u2014 its exported server still serves the express callback\n // and the auth/database routes.\n private async _bootForListen(): Promise<void> {\n const { express } = this.options;\n\n // boot runs after module evaluation, so the env var wins over\n // simulateLatency() calls made at the top level of user code\n const envLatency = parseLatencyEnv();\n if (envLatency !== undefined) { this.simulateLatency(envLatency); }\n\n await this.prepareServices();\n\n if (express) {\n await this._onTransportReady;\n if (this.transport.getExpressApp) {\n const expressApp = await this.transport.getExpressApp();\n await express(expressApp);\n }\n }\n }\n\n // Extend the user's router with framework-contributed endpoints. An explicit\n // `auth` option wins over the database's `applyRouterDefaults` so a non-DB\n // setup can still auto-mount @colyseus/auth, and a DB user can opt out.\n private async _applyRouterDefaults(): Promise<void> {\n const { auth, database } = this.options;\n const wantsAuth = auth !== undefined && auth !== false;\n const wantsDatabase = auth === undefined && !!database?.applyRouterDefaults;\n if (!wantsAuth && !wantsDatabase) { return; }\n\n // Boot an empty router if the user didn't pass `routes` \u2014 the framework's\n // default routes get layered on later in the `transport.listen()` callback.\n this.router ??= createRouter({}) as unknown as Routes;\n\n if (wantsAuth) {\n const authMod: any = await dynamicImport('@colyseus/auth').catch(() => undefined);\n const endpointsFn = authMod?.auth?.endpoints ?? authMod?.default?.auth?.endpoints;\n if (typeof endpointsFn === 'function') {\n this.router = (this.router as Router).extend(endpointsFn(auth)) as Routes;\n }\n } else {\n const updated = await database!.applyRouterDefaults!(this.router as Router);\n if (updated) { this.router = updated as Routes; }\n }\n }\n\n protected async getDefaultTransport(options: any): Promise<Transport> {\n try {\n const module = await dynamicImport('@colyseus/ws-transport');\n const WebSocketTransport = module.WebSocketTransport;\n return new WebSocketTransport(options);\n\n } catch (error) {\n this._onTransportReady.reject(error);\n throw new Error(\"Please provide a 'transport' layer. Default transport not set.\");\n }\n }\n\n protected onShutdownCallback: () => void | Promise<any> =\n () => Promise.resolve()\n\n protected onBeforeShutdownCallback: () => void | Promise<any> =\n () => Promise.resolve()\n}\n\n// the user module re-evaluates on every HMR reload \u2014 warn once per process\nconst devWarnings = new Set<string>();\nfunction warnDevOnce(message: string) {\n if (devWarnings.has(message)) { return; }\n devWarnings.add(message);\n logger.warn(message);\n}\n\n/**\n * The `Server` handed to user code under `colyseus/vite`.\n *\n * The plugin owns the HTTP server, the transport, the matchmaker and the\n * process lifecycle, so constructing one has no side effects \u2014 it only\n * carries the user's configuration back to the plugin. Everything else is\n * inherited, so the documented `Server` API keeps working in dev mode.\n */\nclass DevServer<\n RoomTypes extends Record<string, RegisteredHandler> = any,\n Routes extends Router = any\n> extends Server<RoomTypes, Routes> {\n\n protected bootRuntime() {\n // the plugin creates the transport and runs matchMaker.setup() before\n // importing user code, so both are already available here\n this.transport = getTransport();\n this._onTransportReady.resolve(this.transport);\n this.presence = matchMaker.presence;\n this.driver = matchMaker.driver;\n\n if (this.options.transport) {\n warnDevOnce(\"\u26A0\uFE0F 'transport' is ignored in dev mode \u2014 colyseus/vite serves WebSockets over Vite's own HTTP server.\");\n }\n }\n\n /** The plugin already serves Colyseus on Vite's own HTTP server and port. */\n public async listen(_port?: number | string, _hostname?: string, _backlog?: number, listeningListener?: Function) {\n warnDevOnce(\"\u26A0\uFE0F listen() is ignored in dev mode \u2014 connect to the Vite dev server's URL instead.\");\n listeningListener?.();\n }\n}\n\nlet _originalRoomOnMessage: typeof Room.prototype['_onMessage'] | null = null;\n\n/**\n * Applies both halves of the round-trip latency simulation: delays the\n * transport's outgoing messages and the Room's incoming message handling.\n *\n * Prefer `server.simulateLatency()` \u2014 this is the shared core behind it, the\n * dev-mode `defineServer()` object and the `COLYSEUS_LATENCY` env var, where\n * no `Server` instance (or transport, yet) may exist.\n */\nexport function applySimulatedLatency(transport: Transport | undefined, milliseconds: number) {\n if (milliseconds > 0) {\n logger.warn(`\uD83D\uDCF6\uFE0F\u2757 Colyseus latency simulation enabled \u2192 ${milliseconds}ms latency for round trip.`);\n } else {\n logger.warn(`\uD83D\uDCF6\uFE0F\u2757 Colyseus latency simulation disabled.`);\n }\n\n const halfwayMS = (milliseconds / 2);\n transport?.simulateLatency(halfwayMS);\n\n if (_originalRoomOnMessage == null) {\n _originalRoomOnMessage = Room.prototype['_onMessage'];\n }\n\n const originalOnMessage = _originalRoomOnMessage;\n\n Room.prototype['_onMessage'] = milliseconds <= Number.EPSILON ? originalOnMessage : function (this: Room, client, buffer) {\n // uWebSockets.js: duplicate buffer because it is cleared at native layer before the timeout.\n const cachedBuffer = Buffer.from(buffer);\n setTimeout(() => originalOnMessage.call(this, client, cachedBuffer), halfwayMS);\n };\n}\n\n/**\n * Reads the `COLYSEUS_LATENCY` environment variable (round-trip milliseconds).\n * Returns `undefined` when absent or empty; a non-numeric value warns and is ignored.\n */\nexport function parseLatencyEnv(): number | undefined {\n const value = process.env.COLYSEUS_LATENCY;\n if (value === undefined || value === '') { return undefined; }\n\n const milliseconds = Number(value);\n if (isNaN(milliseconds)) {\n logger.warn(`\uD83D\uDCF6\uFE0F\u2757 Ignoring COLYSEUS_LATENCY: expected a number of milliseconds, got ${JSON.stringify(value)}.`);\n return undefined;\n }\n return milliseconds;\n}\n\nexport type RoomDefinitions = Record<string, RegisteredHandler | Type<Room>>;\n\nfunction isRegisteredHandler(value: RegisteredHandler | Type<Room>): value is RegisteredHandler {\n return value instanceof RegisteredHandler || (\n typeof(value) === \"object\" &&\n value !== null &&\n 'klass' in (value as object)\n );\n}\n\nexport function registerRoomDefinitions<T extends RoomDefinitions>(rooms: T): string[] {\n const roomNames: string[] = [];\n\n for (const [name, value] of Object.entries(rooms)) {\n if (isRegisteredHandler(value)) {\n value.name = name;\n matchMaker.addRoomType(value);\n\n } else {\n matchMaker.defineRoomType(name, value);\n }\n\n roomNames.push(name);\n }\n\n return roomNames;\n}\n\nexport function unregisterRoomDefinitions(roomNames: Iterable<string>) {\n for (const roomName of roomNames) {\n matchMaker.removeRoomType(roomName);\n }\n}\n\nexport type DefineServerOptions<\n T extends Record<string, RegisteredHandler>,\n R extends Router\n> = ServerOptions & {\n rooms: T,\n routes?: R,\n};\n\nexport function defineServer<\n T extends Record<string, RegisteredHandler>,\n R extends Router\n>(\n options: DefineServerOptions<T, R>,\n): Server<T, R> {\n const { rooms, routes, ...serverOptions } = options;\n\n const server = isDevMode\n ? new DevServer<T, R>(serverOptions)\n : new Server<T, R>(serverOptions);\n\n server.router = routes;\n server['~rooms'] = rooms;\n\n // in dev mode the plugin reads `~rooms` back and owns their\n // (re-)registration across HMR reloads\n if (!isDevMode) {\n registerRoomDefinitions(rooms);\n }\n\n return server;\n}\n\nexport function defineRoom<T extends Type<Room>>(\n roomKlass: T,\n defaultOptions?: Parameters<NonNullable<InstanceType<T>['onCreate']>>[0],\n): RegisteredHandler<InstanceType<T>> {\n return new RegisteredHandler(roomKlass, defaultOptions) as unknown as RegisteredHandler<InstanceType<T>>;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["greet", "err", "module"]
|
|
7
7
|
}
|
package/build/Server.d.ts
CHANGED
|
@@ -28,6 +28,8 @@ export type ServerOptions = {
|
|
|
28
28
|
*/
|
|
29
29
|
database?: {
|
|
30
30
|
boot(): Promise<void>;
|
|
31
|
+
/** Release connections — a host that re-boots on reload disposes the instance it replaced. */
|
|
32
|
+
shutdown?(): Promise<void>;
|
|
31
33
|
applyRouterDefaults?(router: Router): Router | Promise<Router>;
|
|
32
34
|
};
|
|
33
35
|
/**
|
|
@@ -101,7 +103,7 @@ export declare class Server<RoomTypes extends Record<string, RegisteredHandler>
|
|
|
101
103
|
/**
|
|
102
104
|
* Every side effect of construction: the dev-mode flag, the transport, the
|
|
103
105
|
* matchmaker and the process signal handlers. Distinct from
|
|
104
|
-
* {@link
|
|
106
|
+
* {@link prepareServices}, which boots the services user code declared.
|
|
105
107
|
*
|
|
106
108
|
* Isolated so {@link DevServer} can opt out of all four — under
|
|
107
109
|
* `colyseus/vite` the plugin owns them, and user code still gets a real
|
|
@@ -183,7 +185,21 @@ export declare class Server<RoomTypes extends Record<string, RegisteredHandler>
|
|
|
183
185
|
*/
|
|
184
186
|
onShutdown(callback: () => void | Promise<any>): void;
|
|
185
187
|
onBeforeShutdown(callback: () => void | Promise<any>): void;
|
|
186
|
-
|
|
188
|
+
/**
|
|
189
|
+
* Boot the services user code declared: the `beforeListen` hook, the
|
|
190
|
+
* database, and the endpoints they contribute to the router. Returns the
|
|
191
|
+
* finalized router, so a caller can't read a stale one by mistake.
|
|
192
|
+
*
|
|
193
|
+
* Reached from outside via `@colyseus/core/internal` — `colyseus/vite` owns
|
|
194
|
+
* the HTTP server and never calls {@link listen}, but user code there still
|
|
195
|
+
* expects `database.boot()` and the auth routes.
|
|
196
|
+
*
|
|
197
|
+
* `runBeforeListen` is false when a host re-boots after an HMR reload:
|
|
198
|
+
* `beforeListen` fires once because the server listens once, while the
|
|
199
|
+
* database and the router defaults track instances the reload rebuilt.
|
|
200
|
+
*/
|
|
201
|
+
protected prepareServices(runBeforeListen?: boolean): Promise<Routes>;
|
|
202
|
+
private _bootForListen;
|
|
187
203
|
private _applyRouterDefaults;
|
|
188
204
|
protected getDefaultTransport(options: any): Promise<Transport>;
|
|
189
205
|
protected onShutdownCallback: () => void | Promise<any>;
|
package/build/Server.mjs
CHANGED
|
@@ -29,7 +29,7 @@ var Server = class _Server {
|
|
|
29
29
|
/**
|
|
30
30
|
* Every side effect of construction: the dev-mode flag, the transport, the
|
|
31
31
|
* matchmaker and the process signal handlers. Distinct from
|
|
32
|
-
* {@link
|
|
32
|
+
* {@link prepareServices}, which boots the services user code declared.
|
|
33
33
|
*
|
|
34
34
|
* Isolated so {@link DevServer} can opt out of all four — under
|
|
35
35
|
* `colyseus/vite` the plugin owns them, and user code still gets a real
|
|
@@ -65,7 +65,7 @@ var Server = class _Server {
|
|
|
65
65
|
* @param listeningListener
|
|
66
66
|
*/
|
|
67
67
|
async listen(port, hostname, backlog, listeningListener) {
|
|
68
|
-
await this.
|
|
68
|
+
await this._bootForListen();
|
|
69
69
|
if (process.env.COLYSEUS_CLOUD !== void 0) {
|
|
70
70
|
if (typeof hostname === "number") {
|
|
71
71
|
hostname = void 0;
|
|
@@ -127,7 +127,7 @@ var Server = class _Server {
|
|
|
127
127
|
* ```
|
|
128
128
|
*/
|
|
129
129
|
async serverless() {
|
|
130
|
-
await this.
|
|
130
|
+
await this._bootForListen();
|
|
131
131
|
await this._onTransportReady;
|
|
132
132
|
const server = this.transport.server;
|
|
133
133
|
if (!server) {
|
|
@@ -216,23 +216,40 @@ var Server = class _Server {
|
|
|
216
216
|
onBeforeShutdown(callback) {
|
|
217
217
|
this.onBeforeShutdownCallback = callback;
|
|
218
218
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
219
|
+
/**
|
|
220
|
+
* Boot the services user code declared: the `beforeListen` hook, the
|
|
221
|
+
* database, and the endpoints they contribute to the router. Returns the
|
|
222
|
+
* finalized router, so a caller can't read a stale one by mistake.
|
|
223
|
+
*
|
|
224
|
+
* Reached from outside via `@colyseus/core/internal` — `colyseus/vite` owns
|
|
225
|
+
* the HTTP server and never calls {@link listen}, but user code there still
|
|
226
|
+
* expects `database.boot()` and the auth routes.
|
|
227
|
+
*
|
|
228
|
+
* `runBeforeListen` is false when a host re-boots after an HMR reload:
|
|
229
|
+
* `beforeListen` fires once because the server listens once, while the
|
|
230
|
+
* database and the router defaults track instances the reload rebuilt.
|
|
231
|
+
*/
|
|
232
|
+
async prepareServices(runBeforeListen = true) {
|
|
233
|
+
const { beforeListen, database } = this.options;
|
|
234
|
+
if (runBeforeListen && beforeListen) {
|
|
230
235
|
await beforeListen();
|
|
231
236
|
}
|
|
232
237
|
if (database) {
|
|
233
238
|
await database.boot();
|
|
234
239
|
}
|
|
235
240
|
await this._applyRouterDefaults();
|
|
241
|
+
return this.router;
|
|
242
|
+
}
|
|
243
|
+
// Listen-time boot: the services above, then the express app. serverless()
|
|
244
|
+
// needs this too — its exported server still serves the express callback
|
|
245
|
+
// and the auth/database routes.
|
|
246
|
+
async _bootForListen() {
|
|
247
|
+
const { express } = this.options;
|
|
248
|
+
const envLatency = parseLatencyEnv();
|
|
249
|
+
if (envLatency !== void 0) {
|
|
250
|
+
this.simulateLatency(envLatency);
|
|
251
|
+
}
|
|
252
|
+
await this.prepareServices();
|
|
236
253
|
if (express) {
|
|
237
254
|
await this._onTransportReady;
|
|
238
255
|
if (this.transport.getExpressApp) {
|
package/build/Server.mjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/Server.ts"],
|
|
4
|
-
"sourcesContent": ["import { greet } from \"@colyseus/greeting-banner\";\nimport type express from 'express';\nimport type { Server as HttpServer } from 'node:http';\n\nimport { debugAndPrintError } from './Debug.ts';\nimport * as matchMaker from './MatchMaker.ts';\nimport { RegisteredHandler } from './matchmaker/RegisteredHandler.ts';\n\nimport { type OnCreateOptions, Room } from './Room.ts';\nimport { Deferred, registerGracefulShutdown, dynamicImport, type Type } from './utils/Utils.ts';\n\nimport type { Presence } from \"./presence/Presence.ts\";\n\nimport { getTransport, setTransport, Transport } from './Transport.ts';\nimport { logger, setLogger } from './Logger.ts';\nimport { setDevMode, isDevMode } from './utils/DevMode.ts';\nimport { type Router, bindRouterToTransport, createRouter } from './router/index.ts';\nimport { prereadRequestBodies } from './router/node.ts';\nimport { type SDKTypes as SharedSDKTypes } from '@colyseus/shared-types';\nimport { getDefaultRouter } from './router/default_routes.ts';\n\nexport type ServerOptions = {\n publicAddress?: string,\n presence?: Presence,\n driver?: matchMaker.MatchMakerDriver,\n transport?: Transport,\n gracefullyShutdown?: boolean,\n logger?: any;\n\n /**\n * Optional callback to execute before the server listens.\n * This is useful for example to connect into a database or other services before the server listens.\n */\n beforeListen?: () => Promise<void> | void,\n\n /**\n * Booted before `matchMaker.accept()`. Structural so `@colyseus/core` keeps\n * no runtime dep on `@colyseus/database` \u2014 any `{ boot(): Promise<void> }`\n * works. The optional `applyRouterDefaults` is invoked after boot with the\n * user's router so the database can contribute endpoints (e.g. auth routes).\n */\n database?: {\n boot(): Promise<void>;\n applyRouterDefaults?(router: Router): Router | Promise<Router>;\n },\n\n /**\n * Mount `@colyseus/auth` routes into the router. Pass an options object\n * (forwarded to `auth.endpoints(...)`) to wire it explicitly \u2014 useful when\n * `database` is not in use. `false` disables auto-mounting even if a\n * `database` is present and would have provided defaults.\n */\n auth?: false | {\n settings?: Record<string, any>;\n oauth?: boolean | { cookieSecret?: string };\n prefix?: string;\n },\n\n /**\n * Optional callback to configure Express routes.\n * When provided, the transport layer will initialize an Express-compatible app\n * and pass it to this callback for custom route configuration.\n *\n * For uWebSockets transport, this uses the uwebsockets-express module.\n */\n express?: (app: express.Application) => Promise<void> | void,\n\n /**\n * Custom function to determine which process should handle room creation.\n * Default: assign new rooms the process with least amount of rooms created\n */\n selectProcessIdToCreateRoom?: matchMaker.SelectProcessIdCallback;\n\n /**\n * Whether this process is running as a standalone match-maker or not. (default: false)\n * When enabled, this process will not spawn rooms and will only be responsible for matchmaking.\n */\n isStandaloneMatchMaker?: boolean; \n\n /**\n * If enabled, rooms are going to be restored in the server-side upon restart,\n * clients are going to automatically re-connect when server reboots.\n *\n * Beware of \"schema mismatch\" issues. When updating Schema structures and\n * reloading existing data, you may see \"schema mismatch\" errors in the\n * client-side.\n *\n * (This operation is costly and should not be used in a production\n * environment)\n */\n devMode?: boolean,\n\n /**\n * Display greeting message on server start.\n * Default: true\n */\n greet?: boolean,\n};\n\n/**\n * Exposed types for the client-side SDK.\n * Re-exported from @colyseus/shared-types with specific type constraints.\n */\nexport interface SDKTypes<\n RoomTypes extends Record<string, RegisteredHandler> = any,\n Routes extends Router = any\n> extends SharedSDKTypes<RoomTypes, Routes> {}\n\nexport class Server<\n RoomTypes extends Record<string, RegisteredHandler> = any,\n Routes extends Router = any\n> implements SDKTypes<RoomTypes, Routes> {\n '~rooms': RoomTypes;\n '~routes': Routes;\n\n public transport: Transport;\n public router: Routes;\n public options: ServerOptions;\n\n protected presence: Presence;\n protected driver: matchMaker.MatchMakerDriver;\n\n protected port: number | string;\n protected greet: boolean;\n\n protected _onTransportReady = new Deferred<Transport>();\n\n // Implicit default for callers that omit explicit Server reference \u2014 e.g.\n // `playground()` reads `Server.current.router.endpoints` at request time.\n // Last-construction wins; multi-server setups should reference instances\n // explicitly.\n static current: Server<any, any> | undefined;\n\n constructor(options: ServerOptions = {}) {\n const { greet = true } = options;\n\n this.options = options;\n this.greet = greet;\n\n (Server as { current: Server<any, any> | undefined }).current = this as Server<any, any>;\n\n if (options.logger) {\n setLogger(options.logger);\n }\n\n this.bootRuntime();\n }\n\n /**\n * Every side effect of construction: the dev-mode flag, the transport, the\n * matchmaker and the process signal handlers. Distinct from\n * {@link _bootServices}, which prepares user services once we're listening.\n *\n * Isolated so {@link DevServer} can opt out of all four \u2014 under\n * `colyseus/vite` the plugin owns them, and user code still gets a real\n * `Server` to call.\n */\n protected bootRuntime() {\n const options = this.options;\n\n setDevMode(options.devMode === true);\n\n this.attach(options);\n\n // Pass options.presence/driver through as-is (possibly undefined).\n // matchMaker.setup() falls back to getDefaultPresence/getDefaultDriver,\n // which auto-select RedisPresence/RedisDriver on Colyseus Cloud.\n matchMaker.setup(\n options.presence,\n options.driver,\n options.publicAddress,\n options.selectProcessIdToCreateRoom,\n ).then(() => {\n this.presence = matchMaker.presence;\n this.driver = matchMaker.driver;\n });\n\n if (options.gracefullyShutdown !== false) {\n registerGracefulShutdown((err) => this.gracefullyShutdown(true, err));\n }\n }\n\n public async attach(options: ServerOptions) {\n this.transport = options.transport || await this.getDefaultTransport(options);\n // `options.express` runs in `listen()` after `database.boot()` so user\n // code reading `database.auth.settings` etc. finds services instantiated.\n this._onTransportReady.resolve(this.transport);\n }\n\n /**\n * Bind the server into the port specified.\n *\n * @param port - Port number or Unix socket path\n * @param hostname\n * @param backlog\n * @param listeningListener\n */\n public async listen(port: number | string, hostname?: string, backlog?: number, listeningListener?: Function) {\n await this._bootServices();\n\n //\n // if Colyseus Cloud is detected, use @colyseus/tools to listen\n //\n if (process.env.COLYSEUS_CLOUD !== undefined ) {\n if (typeof(hostname) === \"number\") {\n //\n // workaround, @colyseus/tools calls server.listen() again with the port as a string\n //\n hostname = undefined;\n\n } else {\n try {\n return (await dynamicImport(\"@colyseus/tools\")).listen(this);\n } catch (error) {\n const err = new Error(\"Please install @colyseus/tools to be able to host on Colyseus Cloud.\");\n err.cause = error;\n throw err;\n }\n }\n }\n\n //\n // otherwise, listen on the port directly\n //\n this.port = port;\n\n //\n // Make sure matchmaker is ready before accepting connections\n // (isDevMode: matchmaker may take extra milliseconds to restore the rooms)\n //\n await matchMaker.accept(this.options.isStandaloneMatchMaker);\n\n /**\n * Greetings!\n */\n if (this.greet) {\n greet();\n }\n\n // Wait for the transport to be ready\n await this._onTransportReady;\n\n return new Promise<void>((resolve, reject) => {\n this.transport.listen(port, hostname, backlog, (err) => {\n if (this.transport.server) {\n this.transport.server.on('error', (err) => reject(err));\n }\n\n this.bindRoutes();\n\n if (listeningListener) {\n listeningListener(err);\n }\n\n if (err) {\n reject(err);\n\n } else {\n resolve();\n }\n });\n });\n }\n\n /**\n * Prepare matchmaking and HTTP routes, then return the underlying\n * `http.Server` *without* binding to a port.\n *\n * Use this on serverless platforms that consume an exported HTTP server\n * instead of a listening one. Vercel, for example, drives the request handler\n * of a default-exported server (its Express/Hono WebSocket examples use\n * `export default server`), whereas calling `listen()` there selects Vercel's\n * \"captured server\" path, which does not invoke Express-style app handlers.\n *\n * The transport must be created with an `http.Server` so it can be exported.\n * Vercel additionally needs `package.json` `\"main\"` pointing at the entrypoint:\n *\n * ```ts\n * import { createServer } from \"node:http\";\n * const httpServer = createServer();\n * const server = new Server({\n * transport: new WebSocketTransport({ server: httpServer }),\n * express: (app) => { app.get(\"/hello\", (req, res) => res.json({ ok: true })); },\n * });\n * server.define(\"my_room\", MyRoom);\n * export default await server.serverless(); // no listen()\n * ```\n */\n public async serverless(): Promise<HttpServer> {\n await this._bootServices();\n\n // transport (and its HTTP server) must be ready before binding routes\n await this._onTransportReady;\n\n const server = this.transport.server as HttpServer | undefined;\n if (!server) {\n throw new Error(\"serverless() requires a transport backed by an http.Server (pass `{ server }` to the transport).\");\n }\n\n this.bindRoutes();\n\n // When the server is consumed via `export default` (rather than listen()),\n // the request body must be available synchronously \u2014 buffer it up-front so\n // `req.body` is set before the router runs.\n prereadRequestBodies(server);\n\n await matchMaker.accept(this.options.isStandaloneMatchMaker);\n\n if (this.greet) {\n greet();\n }\n\n return server;\n }\n\n /**\n * Set the active transport globally and bind the matchmaking/HTTP routes.\n * Shared by {@link listen} and {@link serverless}.\n *\n * `setTransport()` runs here rather than before `transport.listen()`: the\n * global transport is only read by the matchmaking route handler, which can't\n * run until `bindRouterToTransport()` below registers the route.\n */\n private bindRoutes() {\n // set transport globally, to be used by the matchmaking route\n setTransport(this.transport);\n\n // default router is used if no router is provided\n if (!this.router) {\n this.router = getDefaultRouter() as unknown as Routes;\n\n } else {\n // make sure default routes are included\n // https://github.com/Bekacru/better-call/pull/67\n this.router = this.router.extend({ ...getDefaultRouter().endpoints }) as unknown as Routes;\n }\n\n bindRouterToTransport(this.transport, this.router, this.options.express !== undefined);\n }\n\n /**\n * Define a new type of room for matchmaking.\n *\n * @param name public room identifier for match-making.\n * @param roomClass Room class definition\n * @param defaultOptions default options for `onCreate`\n */\n public define<T extends Type<Room>>(\n roomClass: T,\n defaultOptions?: OnCreateOptions<T>,\n ): RegisteredHandler\n public define<T extends Type<Room>>(\n name: string,\n roomClass: T,\n defaultOptions?: OnCreateOptions<T>,\n ): RegisteredHandler\n public define<T extends Type<Room>>(\n nameOrHandler: string | T,\n handlerOrOptions: T | OnCreateOptions<T>,\n defaultOptions?: OnCreateOptions<T>,\n ): RegisteredHandler {\n const name = (typeof(nameOrHandler) === \"string\")\n ? nameOrHandler\n : nameOrHandler.name;\n\n const roomClass = (typeof(nameOrHandler) === \"string\")\n ? handlerOrOptions\n : nameOrHandler;\n\n const options = (typeof(nameOrHandler) === \"string\")\n ? defaultOptions\n : handlerOrOptions;\n\n return matchMaker.defineRoomType(name, roomClass, options);\n }\n\n /**\n * Remove a room definition from matchmaking.\n * This method does not destroy any room. It only dissallows matchmaking\n */\n public removeRoomType(name: string): void {\n matchMaker.removeRoomType(name);\n }\n\n public async gracefullyShutdown(exit: boolean = true, err?: Error) {\n if (matchMaker.state === matchMaker.MatchMakerState.SHUTTING_DOWN) {\n return;\n }\n\n try {\n // custom \"before shutdown\" method\n await this.onBeforeShutdownCallback();\n\n // this is going to lock all rooms and wait for them to be disposed\n await matchMaker.gracefullyShutdown();\n\n this.transport?.shutdown();\n this.presence?.shutdown();\n await this.driver?.shutdown();\n\n // custom \"after shutdown\" method\n await this.onShutdownCallback();\n\n } catch (e) {\n debugAndPrintError(`error during shutdown: ${e}`);\n\n } finally {\n if (exit) {\n process.exit((err && !isDevMode) ? 1 : 0);\n }\n }\n }\n\n /**\n * Add simulated latency between client and server.\n *\n * May be called at any time \u2014 before or after `listen()`. Also available\n * via the `COLYSEUS_LATENCY` environment variable (round-trip ms), which\n * overrides calls made before the server boots.\n *\n * @param milliseconds round trip latency in milliseconds.\n */\n public simulateLatency(milliseconds: number) {\n // transport may still be resolving (constructor's attach() imports the default one)\n this._onTransportReady.then(\n (transport) => applySimulatedLatency(transport, milliseconds),\n () => {/* missing-transport error already surfaces via attach()/listen() */},\n );\n }\n\n /**\n * Register a callback that is going to be executed before the server shuts down.\n * @param callback\n */\n public onShutdown(callback: () => void | Promise<any>) {\n this.onShutdownCallback = callback;\n }\n\n public onBeforeShutdown(callback: () => void | Promise<any>) {\n this.onBeforeShutdownCallback = callback;\n }\n\n // Boot sequence shared by listen() and serverless(): user `beforeListen`\n // hook, database boot, framework router defaults (auth/db endpoints), then\n // the express app. serverless() needs this too \u2014 its exported server still\n // serves the express callback and auth/database routes.\n private async _bootServices(): Promise<void> {\n const { beforeListen, database, express } = this.options;\n\n // boot runs after module evaluation, so the env var wins over\n // simulateLatency() calls made at the top level of user code\n const envLatency = parseLatencyEnv();\n if (envLatency !== undefined) { this.simulateLatency(envLatency); }\n\n if (beforeListen) { await beforeListen(); }\n if (database) { await database.boot(); }\n\n await this._applyRouterDefaults();\n\n if (express) {\n await this._onTransportReady;\n if (this.transport.getExpressApp) {\n const expressApp = await this.transport.getExpressApp();\n await express(expressApp);\n }\n }\n }\n\n // Extend the user's router with framework-contributed endpoints. An explicit\n // `auth` option wins over the database's `applyRouterDefaults` so a non-DB\n // setup can still auto-mount @colyseus/auth, and a DB user can opt out.\n private async _applyRouterDefaults(): Promise<void> {\n const { auth, database } = this.options;\n const wantsAuth = auth !== undefined && auth !== false;\n const wantsDatabase = auth === undefined && !!database?.applyRouterDefaults;\n if (!wantsAuth && !wantsDatabase) { return; }\n\n // Boot an empty router if the user didn't pass `routes` \u2014 the framework's\n // default routes get layered on later in the `transport.listen()` callback.\n this.router ??= createRouter({}) as unknown as Routes;\n\n if (wantsAuth) {\n const authMod: any = await dynamicImport('@colyseus/auth').catch(() => undefined);\n const endpointsFn = authMod?.auth?.endpoints ?? authMod?.default?.auth?.endpoints;\n if (typeof endpointsFn === 'function') {\n this.router = (this.router as Router).extend(endpointsFn(auth)) as Routes;\n }\n } else {\n const updated = await database!.applyRouterDefaults!(this.router as Router);\n if (updated) { this.router = updated as Routes; }\n }\n }\n\n protected async getDefaultTransport(options: any): Promise<Transport> {\n try {\n const module = await dynamicImport('@colyseus/ws-transport');\n const WebSocketTransport = module.WebSocketTransport;\n return new WebSocketTransport(options);\n\n } catch (error) {\n this._onTransportReady.reject(error);\n throw new Error(\"Please provide a 'transport' layer. Default transport not set.\");\n }\n }\n\n protected onShutdownCallback: () => void | Promise<any> =\n () => Promise.resolve()\n\n protected onBeforeShutdownCallback: () => void | Promise<any> =\n () => Promise.resolve()\n}\n\n// the user module re-evaluates on every HMR reload \u2014 warn once per process\nconst devWarnings = new Set<string>();\nfunction warnDevOnce(message: string) {\n if (devWarnings.has(message)) { return; }\n devWarnings.add(message);\n logger.warn(message);\n}\n\n/**\n * The `Server` handed to user code under `colyseus/vite`.\n *\n * The plugin owns the HTTP server, the transport, the matchmaker and the\n * process lifecycle, so constructing one has no side effects \u2014 it only\n * carries the user's configuration back to the plugin. Everything else is\n * inherited, so the documented `Server` API keeps working in dev mode.\n */\nclass DevServer<\n RoomTypes extends Record<string, RegisteredHandler> = any,\n Routes extends Router = any\n> extends Server<RoomTypes, Routes> {\n\n protected bootRuntime() {\n // the plugin creates the transport and runs matchMaker.setup() before\n // importing user code, so both are already available here\n this.transport = getTransport();\n this._onTransportReady.resolve(this.transport);\n this.presence = matchMaker.presence;\n this.driver = matchMaker.driver;\n\n if (this.options.transport) {\n warnDevOnce(\"\u26A0\uFE0F 'transport' is ignored in dev mode \u2014 colyseus/vite serves WebSockets over Vite's own HTTP server.\");\n }\n }\n\n /** The plugin already serves Colyseus on Vite's own HTTP server and port. */\n public async listen(_port?: number | string, _hostname?: string, _backlog?: number, listeningListener?: Function) {\n warnDevOnce(\"\u26A0\uFE0F listen() is ignored in dev mode \u2014 connect to the Vite dev server's URL instead.\");\n listeningListener?.();\n }\n}\n\nlet _originalRoomOnMessage: typeof Room.prototype['_onMessage'] | null = null;\n\n/**\n * Applies both halves of the round-trip latency simulation: delays the\n * transport's outgoing messages and the Room's incoming message handling.\n *\n * Prefer `server.simulateLatency()` \u2014 this is the shared core behind it, the\n * dev-mode `defineServer()` object and the `COLYSEUS_LATENCY` env var, where\n * no `Server` instance (or transport, yet) may exist.\n */\nexport function applySimulatedLatency(transport: Transport | undefined, milliseconds: number) {\n if (milliseconds > 0) {\n logger.warn(`\uD83D\uDCF6\uFE0F\u2757 Colyseus latency simulation enabled \u2192 ${milliseconds}ms latency for round trip.`);\n } else {\n logger.warn(`\uD83D\uDCF6\uFE0F\u2757 Colyseus latency simulation disabled.`);\n }\n\n const halfwayMS = (milliseconds / 2);\n transport?.simulateLatency(halfwayMS);\n\n if (_originalRoomOnMessage == null) {\n _originalRoomOnMessage = Room.prototype['_onMessage'];\n }\n\n const originalOnMessage = _originalRoomOnMessage;\n\n Room.prototype['_onMessage'] = milliseconds <= Number.EPSILON ? originalOnMessage : function (this: Room, client, buffer) {\n // uWebSockets.js: duplicate buffer because it is cleared at native layer before the timeout.\n const cachedBuffer = Buffer.from(buffer);\n setTimeout(() => originalOnMessage.call(this, client, cachedBuffer), halfwayMS);\n };\n}\n\n/**\n * Reads the `COLYSEUS_LATENCY` environment variable (round-trip milliseconds).\n * Returns `undefined` when absent or empty; a non-numeric value warns and is ignored.\n */\nexport function parseLatencyEnv(): number | undefined {\n const value = process.env.COLYSEUS_LATENCY;\n if (value === undefined || value === '') { return undefined; }\n\n const milliseconds = Number(value);\n if (isNaN(milliseconds)) {\n logger.warn(`\uD83D\uDCF6\uFE0F\u2757 Ignoring COLYSEUS_LATENCY: expected a number of milliseconds, got ${JSON.stringify(value)}.`);\n return undefined;\n }\n return milliseconds;\n}\n\nexport type RoomDefinitions = Record<string, RegisteredHandler | Type<Room>>;\n\nfunction isRegisteredHandler(value: RegisteredHandler | Type<Room>): value is RegisteredHandler {\n return value instanceof RegisteredHandler || (\n typeof(value) === \"object\" &&\n value !== null &&\n 'klass' in (value as object)\n );\n}\n\nexport function registerRoomDefinitions<T extends RoomDefinitions>(rooms: T): string[] {\n const roomNames: string[] = [];\n\n for (const [name, value] of Object.entries(rooms)) {\n if (isRegisteredHandler(value)) {\n value.name = name;\n matchMaker.addRoomType(value);\n\n } else {\n matchMaker.defineRoomType(name, value);\n }\n\n roomNames.push(name);\n }\n\n return roomNames;\n}\n\nexport function unregisterRoomDefinitions(roomNames: Iterable<string>) {\n for (const roomName of roomNames) {\n matchMaker.removeRoomType(roomName);\n }\n}\n\nexport type DefineServerOptions<\n T extends Record<string, RegisteredHandler>,\n R extends Router\n> = ServerOptions & {\n rooms: T,\n routes?: R,\n};\n\nexport function defineServer<\n T extends Record<string, RegisteredHandler>,\n R extends Router\n>(\n options: DefineServerOptions<T, R>,\n): Server<T, R> {\n const { rooms, routes, ...serverOptions } = options;\n\n const server = isDevMode\n ? new DevServer<T, R>(serverOptions)\n : new Server<T, R>(serverOptions);\n\n server.router = routes;\n server['~rooms'] = rooms;\n\n // in dev mode the plugin reads `~rooms` back and owns their\n // (re-)registration across HMR reloads\n if (!isDevMode) {\n registerRoomDefinitions(rooms);\n }\n\n return server;\n}\n\nexport function defineRoom<T extends Type<Room>>(\n roomKlass: T,\n defaultOptions?: Parameters<NonNullable<InstanceType<T>['onCreate']>>[0],\n): RegisteredHandler<InstanceType<T>> {\n return new RegisteredHandler(roomKlass, defaultOptions) as unknown as RegisteredHandler<InstanceType<T>>;\n}\n"],
|
|
5
|
-
"mappings": ";AAAA,SAAS,aAAa;AAItB,SAAS,0BAA0B;AACnC,YAAY,gBAAgB;AAC5B,SAAS,yBAAyB;AAElC,SAA+B,YAAY;AAC3C,SAAS,UAAU,0BAA0B,qBAAgC;AAI7E,SAAS,cAAc,oBAA+B;AACtD,SAAS,QAAQ,iBAAiB;AAClC,SAAS,YAAY,iBAAiB;AACtC,SAAsB,uBAAuB,oBAAoB;AACjE,SAAS,4BAA4B;AACrC,OAAgD;AAChD,SAAS,wBAAwB;
|
|
4
|
+
"sourcesContent": ["import { greet } from \"@colyseus/greeting-banner\";\nimport type express from 'express';\nimport type { Server as HttpServer } from 'node:http';\n\nimport { debugAndPrintError } from './Debug.ts';\nimport * as matchMaker from './MatchMaker.ts';\nimport { RegisteredHandler } from './matchmaker/RegisteredHandler.ts';\n\nimport { type OnCreateOptions, Room } from './Room.ts';\nimport { Deferred, registerGracefulShutdown, dynamicImport, type Type } from './utils/Utils.ts';\n\nimport type { Presence } from \"./presence/Presence.ts\";\n\nimport { getTransport, setTransport, Transport } from './Transport.ts';\nimport { logger, setLogger } from './Logger.ts';\nimport { setDevMode, isDevMode } from './utils/DevMode.ts';\nimport { type Router, bindRouterToTransport, createRouter } from './router/index.ts';\nimport { prereadRequestBodies } from './router/node.ts';\nimport { type SDKTypes as SharedSDKTypes } from '@colyseus/shared-types';\nimport { getDefaultRouter } from './router/default_routes.ts';\n\nexport type ServerOptions = {\n publicAddress?: string,\n presence?: Presence,\n driver?: matchMaker.MatchMakerDriver,\n transport?: Transport,\n gracefullyShutdown?: boolean,\n logger?: any;\n\n /**\n * Optional callback to execute before the server listens.\n * This is useful for example to connect into a database or other services before the server listens.\n */\n beforeListen?: () => Promise<void> | void,\n\n /**\n * Booted before `matchMaker.accept()`. Structural so `@colyseus/core` keeps\n * no runtime dep on `@colyseus/database` \u2014 any `{ boot(): Promise<void> }`\n * works. The optional `applyRouterDefaults` is invoked after boot with the\n * user's router so the database can contribute endpoints (e.g. auth routes).\n */\n database?: {\n boot(): Promise<void>;\n /** Release connections \u2014 a host that re-boots on reload disposes the instance it replaced. */\n shutdown?(): Promise<void>;\n applyRouterDefaults?(router: Router): Router | Promise<Router>;\n },\n\n /**\n * Mount `@colyseus/auth` routes into the router. Pass an options object\n * (forwarded to `auth.endpoints(...)`) to wire it explicitly \u2014 useful when\n * `database` is not in use. `false` disables auto-mounting even if a\n * `database` is present and would have provided defaults.\n */\n auth?: false | {\n settings?: Record<string, any>;\n oauth?: boolean | { cookieSecret?: string };\n prefix?: string;\n },\n\n /**\n * Optional callback to configure Express routes.\n * When provided, the transport layer will initialize an Express-compatible app\n * and pass it to this callback for custom route configuration.\n *\n * For uWebSockets transport, this uses the uwebsockets-express module.\n */\n express?: (app: express.Application) => Promise<void> | void,\n\n /**\n * Custom function to determine which process should handle room creation.\n * Default: assign new rooms the process with least amount of rooms created\n */\n selectProcessIdToCreateRoom?: matchMaker.SelectProcessIdCallback;\n\n /**\n * Whether this process is running as a standalone match-maker or not. (default: false)\n * When enabled, this process will not spawn rooms and will only be responsible for matchmaking.\n */\n isStandaloneMatchMaker?: boolean; \n\n /**\n * If enabled, rooms are going to be restored in the server-side upon restart,\n * clients are going to automatically re-connect when server reboots.\n *\n * Beware of \"schema mismatch\" issues. When updating Schema structures and\n * reloading existing data, you may see \"schema mismatch\" errors in the\n * client-side.\n *\n * (This operation is costly and should not be used in a production\n * environment)\n */\n devMode?: boolean,\n\n /**\n * Display greeting message on server start.\n * Default: true\n */\n greet?: boolean,\n};\n\n/**\n * Exposed types for the client-side SDK.\n * Re-exported from @colyseus/shared-types with specific type constraints.\n */\nexport interface SDKTypes<\n RoomTypes extends Record<string, RegisteredHandler> = any,\n Routes extends Router = any\n> extends SharedSDKTypes<RoomTypes, Routes> {}\n\nexport class Server<\n RoomTypes extends Record<string, RegisteredHandler> = any,\n Routes extends Router = any\n> implements SDKTypes<RoomTypes, Routes> {\n '~rooms': RoomTypes;\n '~routes': Routes;\n\n public transport: Transport;\n public router: Routes;\n public options: ServerOptions;\n\n protected presence: Presence;\n protected driver: matchMaker.MatchMakerDriver;\n\n protected port: number | string;\n protected greet: boolean;\n\n protected _onTransportReady = new Deferred<Transport>();\n\n // Implicit default for callers that omit explicit Server reference \u2014 e.g.\n // `playground()` reads `Server.current.router.endpoints` at request time.\n // Last-construction wins; multi-server setups should reference instances\n // explicitly.\n static current: Server<any, any> | undefined;\n\n constructor(options: ServerOptions = {}) {\n const { greet = true } = options;\n\n this.options = options;\n this.greet = greet;\n\n (Server as { current: Server<any, any> | undefined }).current = this as Server<any, any>;\n\n if (options.logger) {\n setLogger(options.logger);\n }\n\n this.bootRuntime();\n }\n\n /**\n * Every side effect of construction: the dev-mode flag, the transport, the\n * matchmaker and the process signal handlers. Distinct from\n * {@link prepareServices}, which boots the services user code declared.\n *\n * Isolated so {@link DevServer} can opt out of all four \u2014 under\n * `colyseus/vite` the plugin owns them, and user code still gets a real\n * `Server` to call.\n */\n protected bootRuntime() {\n const options = this.options;\n\n setDevMode(options.devMode === true);\n\n this.attach(options);\n\n // Pass options.presence/driver through as-is (possibly undefined).\n // matchMaker.setup() falls back to getDefaultPresence/getDefaultDriver,\n // which auto-select RedisPresence/RedisDriver on Colyseus Cloud.\n matchMaker.setup(\n options.presence,\n options.driver,\n options.publicAddress,\n options.selectProcessIdToCreateRoom,\n ).then(() => {\n this.presence = matchMaker.presence;\n this.driver = matchMaker.driver;\n });\n\n if (options.gracefullyShutdown !== false) {\n registerGracefulShutdown((err) => this.gracefullyShutdown(true, err));\n }\n }\n\n public async attach(options: ServerOptions) {\n this.transport = options.transport || await this.getDefaultTransport(options);\n // `options.express` runs in `listen()` after `database.boot()` so user\n // code reading `database.auth.settings` etc. finds services instantiated.\n this._onTransportReady.resolve(this.transport);\n }\n\n /**\n * Bind the server into the port specified.\n *\n * @param port - Port number or Unix socket path\n * @param hostname\n * @param backlog\n * @param listeningListener\n */\n public async listen(port: number | string, hostname?: string, backlog?: number, listeningListener?: Function) {\n await this._bootForListen();\n\n //\n // if Colyseus Cloud is detected, use @colyseus/tools to listen\n //\n if (process.env.COLYSEUS_CLOUD !== undefined ) {\n if (typeof(hostname) === \"number\") {\n //\n // workaround, @colyseus/tools calls server.listen() again with the port as a string\n //\n hostname = undefined;\n\n } else {\n try {\n return (await dynamicImport(\"@colyseus/tools\")).listen(this);\n } catch (error) {\n const err = new Error(\"Please install @colyseus/tools to be able to host on Colyseus Cloud.\");\n err.cause = error;\n throw err;\n }\n }\n }\n\n //\n // otherwise, listen on the port directly\n //\n this.port = port;\n\n //\n // Make sure matchmaker is ready before accepting connections\n // (isDevMode: matchmaker may take extra milliseconds to restore the rooms)\n //\n await matchMaker.accept(this.options.isStandaloneMatchMaker);\n\n /**\n * Greetings!\n */\n if (this.greet) {\n greet();\n }\n\n // Wait for the transport to be ready\n await this._onTransportReady;\n\n return new Promise<void>((resolve, reject) => {\n this.transport.listen(port, hostname, backlog, (err) => {\n if (this.transport.server) {\n this.transport.server.on('error', (err) => reject(err));\n }\n\n this.bindRoutes();\n\n if (listeningListener) {\n listeningListener(err);\n }\n\n if (err) {\n reject(err);\n\n } else {\n resolve();\n }\n });\n });\n }\n\n /**\n * Prepare matchmaking and HTTP routes, then return the underlying\n * `http.Server` *without* binding to a port.\n *\n * Use this on serverless platforms that consume an exported HTTP server\n * instead of a listening one. Vercel, for example, drives the request handler\n * of a default-exported server (its Express/Hono WebSocket examples use\n * `export default server`), whereas calling `listen()` there selects Vercel's\n * \"captured server\" path, which does not invoke Express-style app handlers.\n *\n * The transport must be created with an `http.Server` so it can be exported.\n * Vercel additionally needs `package.json` `\"main\"` pointing at the entrypoint:\n *\n * ```ts\n * import { createServer } from \"node:http\";\n * const httpServer = createServer();\n * const server = new Server({\n * transport: new WebSocketTransport({ server: httpServer }),\n * express: (app) => { app.get(\"/hello\", (req, res) => res.json({ ok: true })); },\n * });\n * server.define(\"my_room\", MyRoom);\n * export default await server.serverless(); // no listen()\n * ```\n */\n public async serverless(): Promise<HttpServer> {\n await this._bootForListen();\n\n // transport (and its HTTP server) must be ready before binding routes\n await this._onTransportReady;\n\n const server = this.transport.server as HttpServer | undefined;\n if (!server) {\n throw new Error(\"serverless() requires a transport backed by an http.Server (pass `{ server }` to the transport).\");\n }\n\n this.bindRoutes();\n\n // When the server is consumed via `export default` (rather than listen()),\n // the request body must be available synchronously \u2014 buffer it up-front so\n // `req.body` is set before the router runs.\n prereadRequestBodies(server);\n\n await matchMaker.accept(this.options.isStandaloneMatchMaker);\n\n if (this.greet) {\n greet();\n }\n\n return server;\n }\n\n /**\n * Set the active transport globally and bind the matchmaking/HTTP routes.\n * Shared by {@link listen} and {@link serverless}.\n *\n * `setTransport()` runs here rather than before `transport.listen()`: the\n * global transport is only read by the matchmaking route handler, which can't\n * run until `bindRouterToTransport()` below registers the route.\n */\n private bindRoutes() {\n // set transport globally, to be used by the matchmaking route\n setTransport(this.transport);\n\n // default router is used if no router is provided\n if (!this.router) {\n this.router = getDefaultRouter() as unknown as Routes;\n\n } else {\n // make sure default routes are included\n // https://github.com/Bekacru/better-call/pull/67\n this.router = this.router.extend({ ...getDefaultRouter().endpoints }) as unknown as Routes;\n }\n\n bindRouterToTransport(this.transport, this.router, this.options.express !== undefined);\n }\n\n /**\n * Define a new type of room for matchmaking.\n *\n * @param name public room identifier for match-making.\n * @param roomClass Room class definition\n * @param defaultOptions default options for `onCreate`\n */\n public define<T extends Type<Room>>(\n roomClass: T,\n defaultOptions?: OnCreateOptions<T>,\n ): RegisteredHandler\n public define<T extends Type<Room>>(\n name: string,\n roomClass: T,\n defaultOptions?: OnCreateOptions<T>,\n ): RegisteredHandler\n public define<T extends Type<Room>>(\n nameOrHandler: string | T,\n handlerOrOptions: T | OnCreateOptions<T>,\n defaultOptions?: OnCreateOptions<T>,\n ): RegisteredHandler {\n const name = (typeof(nameOrHandler) === \"string\")\n ? nameOrHandler\n : nameOrHandler.name;\n\n const roomClass = (typeof(nameOrHandler) === \"string\")\n ? handlerOrOptions\n : nameOrHandler;\n\n const options = (typeof(nameOrHandler) === \"string\")\n ? defaultOptions\n : handlerOrOptions;\n\n return matchMaker.defineRoomType(name, roomClass, options);\n }\n\n /**\n * Remove a room definition from matchmaking.\n * This method does not destroy any room. It only dissallows matchmaking\n */\n public removeRoomType(name: string): void {\n matchMaker.removeRoomType(name);\n }\n\n public async gracefullyShutdown(exit: boolean = true, err?: Error) {\n if (matchMaker.state === matchMaker.MatchMakerState.SHUTTING_DOWN) {\n return;\n }\n\n try {\n // custom \"before shutdown\" method\n await this.onBeforeShutdownCallback();\n\n // this is going to lock all rooms and wait for them to be disposed\n await matchMaker.gracefullyShutdown();\n\n this.transport?.shutdown();\n this.presence?.shutdown();\n await this.driver?.shutdown();\n\n // custom \"after shutdown\" method\n await this.onShutdownCallback();\n\n } catch (e) {\n debugAndPrintError(`error during shutdown: ${e}`);\n\n } finally {\n if (exit) {\n process.exit((err && !isDevMode) ? 1 : 0);\n }\n }\n }\n\n /**\n * Add simulated latency between client and server.\n *\n * May be called at any time \u2014 before or after `listen()`. Also available\n * via the `COLYSEUS_LATENCY` environment variable (round-trip ms), which\n * overrides calls made before the server boots.\n *\n * @param milliseconds round trip latency in milliseconds.\n */\n public simulateLatency(milliseconds: number) {\n // transport may still be resolving (constructor's attach() imports the default one)\n this._onTransportReady.then(\n (transport) => applySimulatedLatency(transport, milliseconds),\n () => {/* missing-transport error already surfaces via attach()/listen() */},\n );\n }\n\n /**\n * Register a callback that is going to be executed before the server shuts down.\n * @param callback\n */\n public onShutdown(callback: () => void | Promise<any>) {\n this.onShutdownCallback = callback;\n }\n\n public onBeforeShutdown(callback: () => void | Promise<any>) {\n this.onBeforeShutdownCallback = callback;\n }\n\n /**\n * Boot the services user code declared: the `beforeListen` hook, the\n * database, and the endpoints they contribute to the router. Returns the\n * finalized router, so a caller can't read a stale one by mistake.\n *\n * Reached from outside via `@colyseus/core/internal` \u2014 `colyseus/vite` owns\n * the HTTP server and never calls {@link listen}, but user code there still\n * expects `database.boot()` and the auth routes.\n *\n * `runBeforeListen` is false when a host re-boots after an HMR reload:\n * `beforeListen` fires once because the server listens once, while the\n * database and the router defaults track instances the reload rebuilt.\n */\n protected async prepareServices(runBeforeListen: boolean = true): Promise<Routes> {\n const { beforeListen, database } = this.options;\n\n if (runBeforeListen && beforeListen) { await beforeListen(); }\n if (database) { await database.boot(); }\n\n await this._applyRouterDefaults();\n\n return this.router;\n }\n\n // Listen-time boot: the services above, then the express app. serverless()\n // needs this too \u2014 its exported server still serves the express callback\n // and the auth/database routes.\n private async _bootForListen(): Promise<void> {\n const { express } = this.options;\n\n // boot runs after module evaluation, so the env var wins over\n // simulateLatency() calls made at the top level of user code\n const envLatency = parseLatencyEnv();\n if (envLatency !== undefined) { this.simulateLatency(envLatency); }\n\n await this.prepareServices();\n\n if (express) {\n await this._onTransportReady;\n if (this.transport.getExpressApp) {\n const expressApp = await this.transport.getExpressApp();\n await express(expressApp);\n }\n }\n }\n\n // Extend the user's router with framework-contributed endpoints. An explicit\n // `auth` option wins over the database's `applyRouterDefaults` so a non-DB\n // setup can still auto-mount @colyseus/auth, and a DB user can opt out.\n private async _applyRouterDefaults(): Promise<void> {\n const { auth, database } = this.options;\n const wantsAuth = auth !== undefined && auth !== false;\n const wantsDatabase = auth === undefined && !!database?.applyRouterDefaults;\n if (!wantsAuth && !wantsDatabase) { return; }\n\n // Boot an empty router if the user didn't pass `routes` \u2014 the framework's\n // default routes get layered on later in the `transport.listen()` callback.\n this.router ??= createRouter({}) as unknown as Routes;\n\n if (wantsAuth) {\n const authMod: any = await dynamicImport('@colyseus/auth').catch(() => undefined);\n const endpointsFn = authMod?.auth?.endpoints ?? authMod?.default?.auth?.endpoints;\n if (typeof endpointsFn === 'function') {\n this.router = (this.router as Router).extend(endpointsFn(auth)) as Routes;\n }\n } else {\n const updated = await database!.applyRouterDefaults!(this.router as Router);\n if (updated) { this.router = updated as Routes; }\n }\n }\n\n protected async getDefaultTransport(options: any): Promise<Transport> {\n try {\n const module = await dynamicImport('@colyseus/ws-transport');\n const WebSocketTransport = module.WebSocketTransport;\n return new WebSocketTransport(options);\n\n } catch (error) {\n this._onTransportReady.reject(error);\n throw new Error(\"Please provide a 'transport' layer. Default transport not set.\");\n }\n }\n\n protected onShutdownCallback: () => void | Promise<any> =\n () => Promise.resolve()\n\n protected onBeforeShutdownCallback: () => void | Promise<any> =\n () => Promise.resolve()\n}\n\n// the user module re-evaluates on every HMR reload \u2014 warn once per process\nconst devWarnings = new Set<string>();\nfunction warnDevOnce(message: string) {\n if (devWarnings.has(message)) { return; }\n devWarnings.add(message);\n logger.warn(message);\n}\n\n/**\n * The `Server` handed to user code under `colyseus/vite`.\n *\n * The plugin owns the HTTP server, the transport, the matchmaker and the\n * process lifecycle, so constructing one has no side effects \u2014 it only\n * carries the user's configuration back to the plugin. Everything else is\n * inherited, so the documented `Server` API keeps working in dev mode.\n */\nclass DevServer<\n RoomTypes extends Record<string, RegisteredHandler> = any,\n Routes extends Router = any\n> extends Server<RoomTypes, Routes> {\n\n protected bootRuntime() {\n // the plugin creates the transport and runs matchMaker.setup() before\n // importing user code, so both are already available here\n this.transport = getTransport();\n this._onTransportReady.resolve(this.transport);\n this.presence = matchMaker.presence;\n this.driver = matchMaker.driver;\n\n if (this.options.transport) {\n warnDevOnce(\"\u26A0\uFE0F 'transport' is ignored in dev mode \u2014 colyseus/vite serves WebSockets over Vite's own HTTP server.\");\n }\n }\n\n /** The plugin already serves Colyseus on Vite's own HTTP server and port. */\n public async listen(_port?: number | string, _hostname?: string, _backlog?: number, listeningListener?: Function) {\n warnDevOnce(\"\u26A0\uFE0F listen() is ignored in dev mode \u2014 connect to the Vite dev server's URL instead.\");\n listeningListener?.();\n }\n}\n\nlet _originalRoomOnMessage: typeof Room.prototype['_onMessage'] | null = null;\n\n/**\n * Applies both halves of the round-trip latency simulation: delays the\n * transport's outgoing messages and the Room's incoming message handling.\n *\n * Prefer `server.simulateLatency()` \u2014 this is the shared core behind it, the\n * dev-mode `defineServer()` object and the `COLYSEUS_LATENCY` env var, where\n * no `Server` instance (or transport, yet) may exist.\n */\nexport function applySimulatedLatency(transport: Transport | undefined, milliseconds: number) {\n if (milliseconds > 0) {\n logger.warn(`\uD83D\uDCF6\uFE0F\u2757 Colyseus latency simulation enabled \u2192 ${milliseconds}ms latency for round trip.`);\n } else {\n logger.warn(`\uD83D\uDCF6\uFE0F\u2757 Colyseus latency simulation disabled.`);\n }\n\n const halfwayMS = (milliseconds / 2);\n transport?.simulateLatency(halfwayMS);\n\n if (_originalRoomOnMessage == null) {\n _originalRoomOnMessage = Room.prototype['_onMessage'];\n }\n\n const originalOnMessage = _originalRoomOnMessage;\n\n Room.prototype['_onMessage'] = milliseconds <= Number.EPSILON ? originalOnMessage : function (this: Room, client, buffer) {\n // uWebSockets.js: duplicate buffer because it is cleared at native layer before the timeout.\n const cachedBuffer = Buffer.from(buffer);\n setTimeout(() => originalOnMessage.call(this, client, cachedBuffer), halfwayMS);\n };\n}\n\n/**\n * Reads the `COLYSEUS_LATENCY` environment variable (round-trip milliseconds).\n * Returns `undefined` when absent or empty; a non-numeric value warns and is ignored.\n */\nexport function parseLatencyEnv(): number | undefined {\n const value = process.env.COLYSEUS_LATENCY;\n if (value === undefined || value === '') { return undefined; }\n\n const milliseconds = Number(value);\n if (isNaN(milliseconds)) {\n logger.warn(`\uD83D\uDCF6\uFE0F\u2757 Ignoring COLYSEUS_LATENCY: expected a number of milliseconds, got ${JSON.stringify(value)}.`);\n return undefined;\n }\n return milliseconds;\n}\n\nexport type RoomDefinitions = Record<string, RegisteredHandler | Type<Room>>;\n\nfunction isRegisteredHandler(value: RegisteredHandler | Type<Room>): value is RegisteredHandler {\n return value instanceof RegisteredHandler || (\n typeof(value) === \"object\" &&\n value !== null &&\n 'klass' in (value as object)\n );\n}\n\nexport function registerRoomDefinitions<T extends RoomDefinitions>(rooms: T): string[] {\n const roomNames: string[] = [];\n\n for (const [name, value] of Object.entries(rooms)) {\n if (isRegisteredHandler(value)) {\n value.name = name;\n matchMaker.addRoomType(value);\n\n } else {\n matchMaker.defineRoomType(name, value);\n }\n\n roomNames.push(name);\n }\n\n return roomNames;\n}\n\nexport function unregisterRoomDefinitions(roomNames: Iterable<string>) {\n for (const roomName of roomNames) {\n matchMaker.removeRoomType(roomName);\n }\n}\n\nexport type DefineServerOptions<\n T extends Record<string, RegisteredHandler>,\n R extends Router\n> = ServerOptions & {\n rooms: T,\n routes?: R,\n};\n\nexport function defineServer<\n T extends Record<string, RegisteredHandler>,\n R extends Router\n>(\n options: DefineServerOptions<T, R>,\n): Server<T, R> {\n const { rooms, routes, ...serverOptions } = options;\n\n const server = isDevMode\n ? new DevServer<T, R>(serverOptions)\n : new Server<T, R>(serverOptions);\n\n server.router = routes;\n server['~rooms'] = rooms;\n\n // in dev mode the plugin reads `~rooms` back and owns their\n // (re-)registration across HMR reloads\n if (!isDevMode) {\n registerRoomDefinitions(rooms);\n }\n\n return server;\n}\n\nexport function defineRoom<T extends Type<Room>>(\n roomKlass: T,\n defaultOptions?: Parameters<NonNullable<InstanceType<T>['onCreate']>>[0],\n): RegisteredHandler<InstanceType<T>> {\n return new RegisteredHandler(roomKlass, defaultOptions) as unknown as RegisteredHandler<InstanceType<T>>;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["greet", "err"]
|
|
7
7
|
}
|
package/build/internal.cjs
CHANGED
|
@@ -34,6 +34,7 @@ __export(internal_exports, {
|
|
|
34
34
|
USER_ROOMS_KEY_PREFIX: () => import_UserSessionIndex2.USER_ROOMS_KEY_PREFIX,
|
|
35
35
|
listUserSessions: () => import_UserSessionIndex2.listUserSessions,
|
|
36
36
|
listUserSessionsLive: () => listUserSessionsLive,
|
|
37
|
+
prepareServices: () => prepareServices,
|
|
37
38
|
releaseRoomLeave: () => import_UserSessionIndex2.releaseRoomLeave,
|
|
38
39
|
releaseUserSession: () => import_UserSessionIndex2.releaseUserSession,
|
|
39
40
|
sweepRoomDispose: () => import_UserSessionIndex2.sweepRoomDispose,
|
|
@@ -48,11 +49,15 @@ var import_UserSessionIndex2 = require("./utils/UserSessionIndex.cjs");
|
|
|
48
49
|
function listUserSessionsLive(userId, options) {
|
|
49
50
|
return (0, import_UserSessionIndex.listUserSessions)(matchMaker.presence, matchMaker.findRoomsByIds, userId, options);
|
|
50
51
|
}
|
|
52
|
+
function prepareServices(server, runBeforeListen) {
|
|
53
|
+
return server.prepareServices(runBeforeListen);
|
|
54
|
+
}
|
|
51
55
|
// Annotate the CommonJS export names for ESM import in node:
|
|
52
56
|
0 && (module.exports = {
|
|
53
57
|
USER_ROOMS_KEY_PREFIX,
|
|
54
58
|
listUserSessions,
|
|
55
59
|
listUserSessionsLive,
|
|
60
|
+
prepareServices,
|
|
56
61
|
releaseRoomLeave,
|
|
57
62
|
releaseUserSession,
|
|
58
63
|
sweepRoomDispose,
|
package/build/internal.cjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/internal.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Framework-internal entry \u2014 symbols used by the bundled `colyseus`\n * plugins (track-user-sessions, unique-session) and the admin\n * backend, but NOT part of `@colyseus/core`'s public surface.\n *\n * Third parties should reach for `TrackUserSessionsPlugin` and its\n * `listUserSessions` static instead \u2014 the raw helpers below are\n * implementation details and can change between minor versions.\n *\n * Resolved via the `./*` wildcard in `packages/core/package.json`.\n *\n * @internal\n */\nimport * as matchMaker from './MatchMaker.ts';\nimport {\n listUserSessions,\n type ListUserSessionsOptions,\n type UserSessionInfo,\n} from './utils/UserSessionIndex.ts';\n\nexport {\n userRoomsKey,\n USER_ROOMS_KEY_PREFIX,\n trackUserSession,\n releaseUserSession,\n trackRoomJoin,\n releaseRoomLeave,\n sweepRoomDispose,\n listUserSessions,\n type UserRoomEntry,\n type UserSessionInfo,\n type ListUserSessionsOptions,\n} from './utils/UserSessionIndex.ts';\n\n/**\n * `listUserSessions` wired to the live `matchMaker` \u2014 what\n * `TrackUserSessionsPlugin.listUserSessions` and the admin backend\n * actually call. The pure `listUserSessions` is kept exported above\n * so unit tests can drive it with fake presence + findRooms.\n */\nexport function listUserSessionsLive(\n userId: string,\n options?: ListUserSessionsOptions,\n): Promise<UserSessionInfo[]> {\n return listUserSessions(matchMaker.presence, matchMaker.findRoomsByIds, userId, options);\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,iBAA4B;
|
|
4
|
+
"sourcesContent": ["/**\n * Framework-internal entry \u2014 symbols used by the bundled `colyseus`\n * plugins (track-user-sessions, unique-session) and the admin\n * backend, but NOT part of `@colyseus/core`'s public surface.\n *\n * Third parties should reach for `TrackUserSessionsPlugin` and its\n * `listUserSessions` static instead \u2014 the raw helpers below are\n * implementation details and can change between minor versions.\n *\n * Resolved via the `./*` wildcard in `packages/core/package.json`.\n *\n * @internal\n */\nimport * as matchMaker from './MatchMaker.ts';\nimport type { Server } from './Server.ts';\nimport type { Router } from './router/index.ts';\nimport {\n listUserSessions,\n type ListUserSessionsOptions,\n type UserSessionInfo,\n} from './utils/UserSessionIndex.ts';\n\nexport {\n userRoomsKey,\n USER_ROOMS_KEY_PREFIX,\n trackUserSession,\n releaseUserSession,\n trackRoomJoin,\n releaseRoomLeave,\n sweepRoomDispose,\n listUserSessions,\n type UserRoomEntry,\n type UserSessionInfo,\n type ListUserSessionsOptions,\n} from './utils/UserSessionIndex.ts';\n\n/**\n * `listUserSessions` wired to the live `matchMaker` \u2014 what\n * `TrackUserSessionsPlugin.listUserSessions` and the admin backend\n * actually call. The pure `listUserSessions` is kept exported above\n * so unit tests can drive it with fake presence + findRooms.\n */\nexport function listUserSessionsLive(\n userId: string,\n options?: ListUserSessionsOptions,\n): Promise<UserSessionInfo[]> {\n return listUserSessions(matchMaker.presence, matchMaker.findRoomsByIds, userId, options);\n}\n\n/**\n * Boot a server's services without listening \u2014 `colyseus/vite` owns the HTTP\n * server and never calls `Server.listen()`, so it runs this step itself.\n * Returns the finalized router, which the database may have extended.\n *\n * Pass `runBeforeListen: false` when re-booting after an HMR reload: the\n * `beforeListen` hook fires once because the server listens once, while the\n * database and the router defaults track the instances the reload rebuilt.\n */\nexport function prepareServices(server: Server, runBeforeListen: boolean): Promise<Router> {\n // `prepareServices` is protected \u2014 first-party hosts reach it through here\n // rather than widening the published `Server` API.\n return (server as unknown as {\n prepareServices(runBeforeListen: boolean): Promise<Router>;\n }).prepareServices(runBeforeListen);\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,iBAA4B;AAG5B,8BAIO;AAEP,IAAAA,2BAYO;AAQA,SAAS,qBACd,QACA,SAC4B;AAC5B,aAAO,0CAA4B,qBAAqB,2BAAgB,QAAQ,OAAO;AACzF;AAWO,SAAS,gBAAgB,QAAgB,iBAA2C;AAGzF,SAAQ,OAEL,gBAAgB,eAAe;AACpC;",
|
|
6
6
|
"names": ["import_UserSessionIndex"]
|
|
7
7
|
}
|
package/build/internal.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { Server } from './Server.ts';
|
|
2
|
+
import type { Router } from './router/index.ts';
|
|
1
3
|
import { type ListUserSessionsOptions, type UserSessionInfo } from './utils/UserSessionIndex.ts';
|
|
2
4
|
export { userRoomsKey, USER_ROOMS_KEY_PREFIX, trackUserSession, releaseUserSession, trackRoomJoin, releaseRoomLeave, sweepRoomDispose, listUserSessions, type UserRoomEntry, type UserSessionInfo, type ListUserSessionsOptions, } from './utils/UserSessionIndex.ts';
|
|
3
5
|
/**
|
|
@@ -7,3 +9,13 @@ export { userRoomsKey, USER_ROOMS_KEY_PREFIX, trackUserSession, releaseUserSessi
|
|
|
7
9
|
* so unit tests can drive it with fake presence + findRooms.
|
|
8
10
|
*/
|
|
9
11
|
export declare function listUserSessionsLive(userId: string, options?: ListUserSessionsOptions): Promise<UserSessionInfo[]>;
|
|
12
|
+
/**
|
|
13
|
+
* Boot a server's services without listening — `colyseus/vite` owns the HTTP
|
|
14
|
+
* server and never calls `Server.listen()`, so it runs this step itself.
|
|
15
|
+
* Returns the finalized router, which the database may have extended.
|
|
16
|
+
*
|
|
17
|
+
* Pass `runBeforeListen: false` when re-booting after an HMR reload: the
|
|
18
|
+
* `beforeListen` hook fires once because the server listens once, while the
|
|
19
|
+
* database and the router defaults track the instances the reload rebuilt.
|
|
20
|
+
*/
|
|
21
|
+
export declare function prepareServices(server: Server, runBeforeListen: boolean): Promise<Router>;
|
package/build/internal.mjs
CHANGED
|
@@ -16,10 +16,14 @@ import {
|
|
|
16
16
|
function listUserSessionsLive(userId, options) {
|
|
17
17
|
return listUserSessions(matchMaker.presence, matchMaker.findRoomsByIds, userId, options);
|
|
18
18
|
}
|
|
19
|
+
function prepareServices(server, runBeforeListen) {
|
|
20
|
+
return server.prepareServices(runBeforeListen);
|
|
21
|
+
}
|
|
19
22
|
export {
|
|
20
23
|
USER_ROOMS_KEY_PREFIX,
|
|
21
24
|
listUserSessions2 as listUserSessions,
|
|
22
25
|
listUserSessionsLive,
|
|
26
|
+
prepareServices,
|
|
23
27
|
releaseRoomLeave,
|
|
24
28
|
releaseUserSession,
|
|
25
29
|
sweepRoomDispose,
|
package/build/internal.mjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/internal.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Framework-internal entry \u2014 symbols used by the bundled `colyseus`\n * plugins (track-user-sessions, unique-session) and the admin\n * backend, but NOT part of `@colyseus/core`'s public surface.\n *\n * Third parties should reach for `TrackUserSessionsPlugin` and its\n * `listUserSessions` static instead \u2014 the raw helpers below are\n * implementation details and can change between minor versions.\n *\n * Resolved via the `./*` wildcard in `packages/core/package.json`.\n *\n * @internal\n */\nimport * as matchMaker from './MatchMaker.ts';\nimport {\n listUserSessions,\n type ListUserSessionsOptions,\n type UserSessionInfo,\n} from './utils/UserSessionIndex.ts';\n\nexport {\n userRoomsKey,\n USER_ROOMS_KEY_PREFIX,\n trackUserSession,\n releaseUserSession,\n trackRoomJoin,\n releaseRoomLeave,\n sweepRoomDispose,\n listUserSessions,\n type UserRoomEntry,\n type UserSessionInfo,\n type ListUserSessionsOptions,\n} from './utils/UserSessionIndex.ts';\n\n/**\n * `listUserSessions` wired to the live `matchMaker` \u2014 what\n * `TrackUserSessionsPlugin.listUserSessions` and the admin backend\n * actually call. The pure `listUserSessions` is kept exported above\n * so unit tests can drive it with fake presence + findRooms.\n */\nexport function listUserSessionsLive(\n userId: string,\n options?: ListUserSessionsOptions,\n): Promise<UserSessionInfo[]> {\n return listUserSessions(matchMaker.presence, matchMaker.findRoomsByIds, userId, options);\n}\n"],
|
|
5
|
-
"mappings": ";AAaA,YAAY,gBAAgB;
|
|
4
|
+
"sourcesContent": ["/**\n * Framework-internal entry \u2014 symbols used by the bundled `colyseus`\n * plugins (track-user-sessions, unique-session) and the admin\n * backend, but NOT part of `@colyseus/core`'s public surface.\n *\n * Third parties should reach for `TrackUserSessionsPlugin` and its\n * `listUserSessions` static instead \u2014 the raw helpers below are\n * implementation details and can change between minor versions.\n *\n * Resolved via the `./*` wildcard in `packages/core/package.json`.\n *\n * @internal\n */\nimport * as matchMaker from './MatchMaker.ts';\nimport type { Server } from './Server.ts';\nimport type { Router } from './router/index.ts';\nimport {\n listUserSessions,\n type ListUserSessionsOptions,\n type UserSessionInfo,\n} from './utils/UserSessionIndex.ts';\n\nexport {\n userRoomsKey,\n USER_ROOMS_KEY_PREFIX,\n trackUserSession,\n releaseUserSession,\n trackRoomJoin,\n releaseRoomLeave,\n sweepRoomDispose,\n listUserSessions,\n type UserRoomEntry,\n type UserSessionInfo,\n type ListUserSessionsOptions,\n} from './utils/UserSessionIndex.ts';\n\n/**\n * `listUserSessions` wired to the live `matchMaker` \u2014 what\n * `TrackUserSessionsPlugin.listUserSessions` and the admin backend\n * actually call. The pure `listUserSessions` is kept exported above\n * so unit tests can drive it with fake presence + findRooms.\n */\nexport function listUserSessionsLive(\n userId: string,\n options?: ListUserSessionsOptions,\n): Promise<UserSessionInfo[]> {\n return listUserSessions(matchMaker.presence, matchMaker.findRoomsByIds, userId, options);\n}\n\n/**\n * Boot a server's services without listening \u2014 `colyseus/vite` owns the HTTP\n * server and never calls `Server.listen()`, so it runs this step itself.\n * Returns the finalized router, which the database may have extended.\n *\n * Pass `runBeforeListen: false` when re-booting after an HMR reload: the\n * `beforeListen` hook fires once because the server listens once, while the\n * database and the router defaults track the instances the reload rebuilt.\n */\nexport function prepareServices(server: Server, runBeforeListen: boolean): Promise<Router> {\n // `prepareServices` is protected \u2014 first-party hosts reach it through here\n // rather than widening the published `Server` API.\n return (server as unknown as {\n prepareServices(runBeforeListen: boolean): Promise<Router>;\n }).prepareServices(runBeforeListen);\n}\n"],
|
|
5
|
+
"mappings": ";AAaA,YAAY,gBAAgB;AAG5B;AAAA,EACE;AAAA,OAGK;AAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,oBAAAA;AAAA,OAIK;AAQA,SAAS,qBACd,QACA,SAC4B;AAC5B,SAAO,iBAA4B,qBAAqB,2BAAgB,QAAQ,OAAO;AACzF;AAWO,SAAS,gBAAgB,QAAgB,iBAA2C;AAGzF,SAAQ,OAEL,gBAAgB,eAAe;AACpC;",
|
|
6
6
|
"names": ["listUserSessions"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colyseus/core",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.10",
|
|
4
4
|
"description": "Multiplayer Framework for Node.js.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"input": "./src/index.ts",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"express": "^5.0.0",
|
|
61
61
|
"vitest": "^3.1.1",
|
|
62
62
|
"@colyseus/redis-driver": "^0.18.2",
|
|
63
|
-
"@colyseus/
|
|
64
|
-
"@colyseus/
|
|
63
|
+
"@colyseus/tools": "^0.18.3",
|
|
64
|
+
"@colyseus/redis-presence": "^0.18.3"
|
|
65
65
|
},
|
|
66
66
|
"peerDependencies": {
|
|
67
67
|
"@colyseus/schema": "^5.0.8",
|
|
@@ -69,8 +69,8 @@
|
|
|
69
69
|
"express": "^4.16.0 || ^5.0.0",
|
|
70
70
|
"zod": "^4.1.12",
|
|
71
71
|
"@colyseus/auth": "^0.18.2",
|
|
72
|
-
"@colyseus/
|
|
73
|
-
"@colyseus/
|
|
72
|
+
"@colyseus/better-call": "^1.3.1",
|
|
73
|
+
"@colyseus/ws-transport": "^0.18.2"
|
|
74
74
|
},
|
|
75
75
|
"peerDependenciesMeta": {
|
|
76
76
|
"@colyseus/auth": {
|
package/src/Server.ts
CHANGED
|
@@ -41,6 +41,8 @@ export type ServerOptions = {
|
|
|
41
41
|
*/
|
|
42
42
|
database?: {
|
|
43
43
|
boot(): Promise<void>;
|
|
44
|
+
/** Release connections — a host that re-boots on reload disposes the instance it replaced. */
|
|
45
|
+
shutdown?(): Promise<void>;
|
|
44
46
|
applyRouterDefaults?(router: Router): Router | Promise<Router>;
|
|
45
47
|
},
|
|
46
48
|
|
|
@@ -149,7 +151,7 @@ export class Server<
|
|
|
149
151
|
/**
|
|
150
152
|
* Every side effect of construction: the dev-mode flag, the transport, the
|
|
151
153
|
* matchmaker and the process signal handlers. Distinct from
|
|
152
|
-
* {@link
|
|
154
|
+
* {@link prepareServices}, which boots the services user code declared.
|
|
153
155
|
*
|
|
154
156
|
* Isolated so {@link DevServer} can opt out of all four — under
|
|
155
157
|
* `colyseus/vite` the plugin owns them, and user code still gets a real
|
|
@@ -196,7 +198,7 @@ export class Server<
|
|
|
196
198
|
* @param listeningListener
|
|
197
199
|
*/
|
|
198
200
|
public async listen(port: number | string, hostname?: string, backlog?: number, listeningListener?: Function) {
|
|
199
|
-
await this.
|
|
201
|
+
await this._bootForListen();
|
|
200
202
|
|
|
201
203
|
//
|
|
202
204
|
// if Colyseus Cloud is detected, use @colyseus/tools to listen
|
|
@@ -287,7 +289,7 @@ export class Server<
|
|
|
287
289
|
* ```
|
|
288
290
|
*/
|
|
289
291
|
public async serverless(): Promise<HttpServer> {
|
|
290
|
-
await this.
|
|
292
|
+
await this._bootForListen();
|
|
291
293
|
|
|
292
294
|
// transport (and its HTTP server) must be ready before binding routes
|
|
293
295
|
await this._onTransportReady;
|
|
@@ -440,22 +442,42 @@ export class Server<
|
|
|
440
442
|
this.onBeforeShutdownCallback = callback;
|
|
441
443
|
}
|
|
442
444
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
445
|
+
/**
|
|
446
|
+
* Boot the services user code declared: the `beforeListen` hook, the
|
|
447
|
+
* database, and the endpoints they contribute to the router. Returns the
|
|
448
|
+
* finalized router, so a caller can't read a stale one by mistake.
|
|
449
|
+
*
|
|
450
|
+
* Reached from outside via `@colyseus/core/internal` — `colyseus/vite` owns
|
|
451
|
+
* the HTTP server and never calls {@link listen}, but user code there still
|
|
452
|
+
* expects `database.boot()` and the auth routes.
|
|
453
|
+
*
|
|
454
|
+
* `runBeforeListen` is false when a host re-boots after an HMR reload:
|
|
455
|
+
* `beforeListen` fires once because the server listens once, while the
|
|
456
|
+
* database and the router defaults track instances the reload rebuilt.
|
|
457
|
+
*/
|
|
458
|
+
protected async prepareServices(runBeforeListen: boolean = true): Promise<Routes> {
|
|
459
|
+
const { beforeListen, database } = this.options;
|
|
460
|
+
|
|
461
|
+
if (runBeforeListen && beforeListen) { await beforeListen(); }
|
|
462
|
+
if (database) { await database.boot(); }
|
|
463
|
+
|
|
464
|
+
await this._applyRouterDefaults();
|
|
465
|
+
|
|
466
|
+
return this.router;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Listen-time boot: the services above, then the express app. serverless()
|
|
470
|
+
// needs this too — its exported server still serves the express callback
|
|
471
|
+
// and the auth/database routes.
|
|
472
|
+
private async _bootForListen(): Promise<void> {
|
|
473
|
+
const { express } = this.options;
|
|
449
474
|
|
|
450
475
|
// boot runs after module evaluation, so the env var wins over
|
|
451
476
|
// simulateLatency() calls made at the top level of user code
|
|
452
477
|
const envLatency = parseLatencyEnv();
|
|
453
478
|
if (envLatency !== undefined) { this.simulateLatency(envLatency); }
|
|
454
479
|
|
|
455
|
-
|
|
456
|
-
if (database) { await database.boot(); }
|
|
457
|
-
|
|
458
|
-
await this._applyRouterDefaults();
|
|
480
|
+
await this.prepareServices();
|
|
459
481
|
|
|
460
482
|
if (express) {
|
|
461
483
|
await this._onTransportReady;
|
package/src/internal.ts
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* @internal
|
|
13
13
|
*/
|
|
14
14
|
import * as matchMaker from './MatchMaker.ts';
|
|
15
|
+
import type { Server } from './Server.ts';
|
|
16
|
+
import type { Router } from './router/index.ts';
|
|
15
17
|
import {
|
|
16
18
|
listUserSessions,
|
|
17
19
|
type ListUserSessionsOptions,
|
|
@@ -44,3 +46,20 @@ export function listUserSessionsLive(
|
|
|
44
46
|
): Promise<UserSessionInfo[]> {
|
|
45
47
|
return listUserSessions(matchMaker.presence, matchMaker.findRoomsByIds, userId, options);
|
|
46
48
|
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Boot a server's services without listening — `colyseus/vite` owns the HTTP
|
|
52
|
+
* server and never calls `Server.listen()`, so it runs this step itself.
|
|
53
|
+
* Returns the finalized router, which the database may have extended.
|
|
54
|
+
*
|
|
55
|
+
* Pass `runBeforeListen: false` when re-booting after an HMR reload: the
|
|
56
|
+
* `beforeListen` hook fires once because the server listens once, while the
|
|
57
|
+
* database and the router defaults track the instances the reload rebuilt.
|
|
58
|
+
*/
|
|
59
|
+
export function prepareServices(server: Server, runBeforeListen: boolean): Promise<Router> {
|
|
60
|
+
// `prepareServices` is protected — first-party hosts reach it through here
|
|
61
|
+
// rather than widening the published `Server` API.
|
|
62
|
+
return (server as unknown as {
|
|
63
|
+
prepareServices(runBeforeListen: boolean): Promise<Router>;
|
|
64
|
+
}).prepareServices(runBeforeListen);
|
|
65
|
+
}
|