@colyseus/core 0.18.8 → 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 +78 -33
- package/build/Server.cjs.map +3 -3
- package/build/Server.d.ts +27 -1
- package/build/Server.mjs +78 -33
- package/build/Server.mjs.map +3 -3
- 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 +8 -8
- package/src/Server.ts +107 -36
- package/src/internal.ts +19 -0
package/build/Server.cjs
CHANGED
|
@@ -58,14 +58,27 @@ var Server = class _Server {
|
|
|
58
58
|
this._onTransportReady = new import_Utils.Deferred();
|
|
59
59
|
this.onShutdownCallback = () => Promise.resolve();
|
|
60
60
|
this.onBeforeShutdownCallback = () => Promise.resolve();
|
|
61
|
-
const {
|
|
62
|
-
gracefullyShutdown: gracefullyShutdown2 = true,
|
|
63
|
-
greet: greet2 = true
|
|
64
|
-
} = options;
|
|
65
|
-
(0, import_DevMode.setDevMode)(options.devMode === true);
|
|
61
|
+
const { greet: greet2 = true } = options;
|
|
66
62
|
this.options = options;
|
|
67
63
|
this.greet = greet2;
|
|
68
64
|
_Server.current = this;
|
|
65
|
+
if (options.logger) {
|
|
66
|
+
(0, import_Logger.setLogger)(options.logger);
|
|
67
|
+
}
|
|
68
|
+
this.bootRuntime();
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Every side effect of construction: the dev-mode flag, the transport, the
|
|
72
|
+
* matchmaker and the process signal handlers. Distinct from
|
|
73
|
+
* {@link prepareServices}, which boots the services user code declared.
|
|
74
|
+
*
|
|
75
|
+
* Isolated so {@link DevServer} can opt out of all four — under
|
|
76
|
+
* `colyseus/vite` the plugin owns them, and user code still gets a real
|
|
77
|
+
* `Server` to call.
|
|
78
|
+
*/
|
|
79
|
+
bootRuntime() {
|
|
80
|
+
const options = this.options;
|
|
81
|
+
(0, import_DevMode.setDevMode)(options.devMode === true);
|
|
69
82
|
this.attach(options);
|
|
70
83
|
matchMaker.setup(
|
|
71
84
|
options.presence,
|
|
@@ -76,12 +89,9 @@ var Server = class _Server {
|
|
|
76
89
|
this.presence = matchMaker.presence;
|
|
77
90
|
this.driver = matchMaker.driver;
|
|
78
91
|
});
|
|
79
|
-
if (
|
|
92
|
+
if (options.gracefullyShutdown !== false) {
|
|
80
93
|
(0, import_Utils.registerGracefulShutdown)((err) => this.gracefullyShutdown(true, err));
|
|
81
94
|
}
|
|
82
|
-
if (options.logger) {
|
|
83
|
-
(0, import_Logger.setLogger)(options.logger);
|
|
84
|
-
}
|
|
85
95
|
}
|
|
86
96
|
async attach(options) {
|
|
87
97
|
this.transport = options.transport || await this.getDefaultTransport(options);
|
|
@@ -96,7 +106,7 @@ var Server = class _Server {
|
|
|
96
106
|
* @param listeningListener
|
|
97
107
|
*/
|
|
98
108
|
async listen(port, hostname, backlog, listeningListener) {
|
|
99
|
-
await this.
|
|
109
|
+
await this._bootForListen();
|
|
100
110
|
if (process.env.COLYSEUS_CLOUD !== void 0) {
|
|
101
111
|
if (typeof hostname === "number") {
|
|
102
112
|
hostname = void 0;
|
|
@@ -158,7 +168,7 @@ var Server = class _Server {
|
|
|
158
168
|
* ```
|
|
159
169
|
*/
|
|
160
170
|
async serverless() {
|
|
161
|
-
await this.
|
|
171
|
+
await this._bootForListen();
|
|
162
172
|
await this._onTransportReady;
|
|
163
173
|
const server = this.transport.server;
|
|
164
174
|
if (!server) {
|
|
@@ -247,23 +257,40 @@ var Server = class _Server {
|
|
|
247
257
|
onBeforeShutdown(callback) {
|
|
248
258
|
this.onBeforeShutdownCallback = callback;
|
|
249
259
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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) {
|
|
261
276
|
await beforeListen();
|
|
262
277
|
}
|
|
263
278
|
if (database) {
|
|
264
279
|
await database.boot();
|
|
265
280
|
}
|
|
266
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();
|
|
267
294
|
if (express) {
|
|
268
295
|
await this._onTransportReady;
|
|
269
296
|
if (this.transport.getExpressApp) {
|
|
@@ -307,6 +334,30 @@ var Server = class _Server {
|
|
|
307
334
|
}
|
|
308
335
|
}
|
|
309
336
|
};
|
|
337
|
+
var devWarnings = /* @__PURE__ */ new Set();
|
|
338
|
+
function warnDevOnce(message) {
|
|
339
|
+
if (devWarnings.has(message)) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
devWarnings.add(message);
|
|
343
|
+
import_Logger.logger.warn(message);
|
|
344
|
+
}
|
|
345
|
+
var DevServer = class extends Server {
|
|
346
|
+
bootRuntime() {
|
|
347
|
+
this.transport = (0, import_Transport.getTransport)();
|
|
348
|
+
this._onTransportReady.resolve(this.transport);
|
|
349
|
+
this.presence = matchMaker.presence;
|
|
350
|
+
this.driver = matchMaker.driver;
|
|
351
|
+
if (this.options.transport) {
|
|
352
|
+
warnDevOnce("\u26A0\uFE0F 'transport' is ignored in dev mode \u2014 colyseus/vite serves WebSockets over Vite's own HTTP server.");
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
/** The plugin already serves Colyseus on Vite's own HTTP server and port. */
|
|
356
|
+
async listen(_port, _hostname, _backlog, listeningListener) {
|
|
357
|
+
warnDevOnce("\u26A0\uFE0F listen() is ignored in dev mode \u2014 connect to the Vite dev server's URL instead.");
|
|
358
|
+
listeningListener?.();
|
|
359
|
+
}
|
|
360
|
+
};
|
|
310
361
|
var _originalRoomOnMessage = null;
|
|
311
362
|
function applySimulatedLatency(transport, milliseconds) {
|
|
312
363
|
if (milliseconds > 0) {
|
|
@@ -360,18 +411,12 @@ function unregisterRoomDefinitions(roomNames) {
|
|
|
360
411
|
}
|
|
361
412
|
function defineServer(options) {
|
|
362
413
|
const { rooms, routes, ...serverOptions } = options;
|
|
363
|
-
|
|
364
|
-
return {
|
|
365
|
-
options: serverOptions,
|
|
366
|
-
router: routes,
|
|
367
|
-
"~rooms": rooms,
|
|
368
|
-
// the vite plugin registers the transport before importing user code
|
|
369
|
-
simulateLatency: (milliseconds) => applySimulatedLatency((0, import_Transport.getTransport)(), milliseconds)
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
|
-
const server = new Server(serverOptions);
|
|
414
|
+
const server = import_DevMode.isDevMode ? new DevServer(serverOptions) : new Server(serverOptions);
|
|
373
415
|
server.router = routes;
|
|
374
|
-
|
|
416
|
+
server["~rooms"] = rooms;
|
|
417
|
+
if (!import_DevMode.isDevMode) {
|
|
418
|
+
registerRoomDefinitions(rooms);
|
|
419
|
+
}
|
|
375
420
|
return server;
|
|
376
421
|
}
|
|
377
422
|
function defineRoom(roomKlass, defaultOptions) {
|
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 {\n gracefullyShutdown = true,\n greet = true\n } = options;\n\n setDevMode(options.devMode === true);\n\n this.options = options;\n this.greet = greet;\n\n (Server as { current: Server<any, any> | undefined }).current = this as Server<any, any>;\n\n this.attach(options);\n\n // Pass options.presence/driver through as-is (possibly undefined).\n // matchMaker.setup() falls back to getDefaultPresence/getDefaultDriver,\n // which auto-select RedisPresence/RedisDriver on Colyseus Cloud.\n matchMaker.setup(\n options.presence,\n options.driver,\n options.publicAddress,\n options.selectProcessIdToCreateRoom,\n ).then(() => {\n this.presence = matchMaker.presence;\n this.driver = matchMaker.driver;\n });\n\n if (gracefullyShutdown) {\n registerGracefulShutdown((err) => this.gracefullyShutdown(true, err));\n }\n\n if (options.logger) {\n setLogger(options.logger);\n }\n }\n\n public async attach(options: ServerOptions) {\n this.transport = options.transport || await this.getDefaultTransport(options);\n // `options.express` runs in `listen()` after `database.boot()` so user\n // code reading `database.auth.settings` etc. finds services instantiated.\n this._onTransportReady.resolve(this.transport);\n }\n\n /**\n * Bind the server into the port specified.\n *\n * @param port - Port number or Unix socket path\n * @param hostname\n * @param backlog\n * @param listeningListener\n */\n public async listen(port: number | string, hostname?: string, backlog?: number, listeningListener?: Function) {\n await this._bootServices();\n\n //\n // if Colyseus Cloud is detected, use @colyseus/tools to listen\n //\n if (process.env.COLYSEUS_CLOUD !== undefined ) {\n if (typeof(hostname) === \"number\") {\n //\n // workaround, @colyseus/tools calls server.listen() again with the port as a string\n //\n hostname = undefined;\n\n } else {\n try {\n return (await dynamicImport(\"@colyseus/tools\")).listen(this);\n } catch (error) {\n const err = new Error(\"Please install @colyseus/tools to be able to host on Colyseus Cloud.\");\n err.cause = error;\n throw err;\n }\n }\n }\n\n //\n // otherwise, listen on the port directly\n //\n this.port = port;\n\n //\n // Make sure matchmaker is ready before accepting connections\n // (isDevMode: matchmaker may take extra milliseconds to restore the rooms)\n //\n await matchMaker.accept(this.options.isStandaloneMatchMaker);\n\n /**\n * Greetings!\n */\n if (this.greet) {\n greet();\n }\n\n // Wait for the transport to be ready\n await this._onTransportReady;\n\n return new Promise<void>((resolve, reject) => {\n this.transport.listen(port, hostname, backlog, (err) => {\n if (this.transport.server) {\n this.transport.server.on('error', (err) => reject(err));\n }\n\n this.bindRoutes();\n\n if (listeningListener) {\n listeningListener(err);\n }\n\n if (err) {\n reject(err);\n\n } else {\n resolve();\n }\n });\n });\n }\n\n /**\n * Prepare matchmaking and HTTP routes, then return the underlying\n * `http.Server` *without* binding to a port.\n *\n * Use this on serverless platforms that consume an exported HTTP server\n * instead of a listening one. Vercel, for example, drives the request handler\n * of a default-exported server (its Express/Hono WebSocket examples use\n * `export default server`), whereas calling `listen()` there selects Vercel's\n * \"captured server\" path, which does not invoke Express-style app handlers.\n *\n * The transport must be created with an `http.Server` so it can be exported.\n * Vercel additionally needs `package.json` `\"main\"` pointing at the entrypoint:\n *\n * ```ts\n * import { createServer } from \"node:http\";\n * const httpServer = createServer();\n * const server = new Server({\n * transport: new WebSocketTransport({ server: httpServer }),\n * express: (app) => { app.get(\"/hello\", (req, res) => res.json({ ok: true })); },\n * });\n * server.define(\"my_room\", MyRoom);\n * export default await server.serverless(); // no listen()\n * ```\n */\n public async serverless(): Promise<HttpServer> {\n await this._bootServices();\n\n // transport (and its HTTP server) must be ready before binding routes\n await this._onTransportReady;\n\n const server = this.transport.server as HttpServer | undefined;\n if (!server) {\n throw new Error(\"serverless() requires a transport backed by an http.Server (pass `{ server }` to the transport).\");\n }\n\n this.bindRoutes();\n\n // When the server is consumed via `export default` (rather than listen()),\n // the request body must be available synchronously \u2014 buffer it up-front so\n // `req.body` is set before the router runs.\n prereadRequestBodies(server);\n\n await matchMaker.accept(this.options.isStandaloneMatchMaker);\n\n if (this.greet) {\n greet();\n }\n\n return server;\n }\n\n /**\n * Set the active transport globally and bind the matchmaking/HTTP routes.\n * Shared by {@link listen} and {@link serverless}.\n *\n * `setTransport()` runs here rather than before `transport.listen()`: the\n * global transport is only read by the matchmaking route handler, which can't\n * run until `bindRouterToTransport()` below registers the route.\n */\n private bindRoutes() {\n // set transport globally, to be used by the matchmaking route\n setTransport(this.transport);\n\n // default router is used if no router is provided\n if (!this.router) {\n this.router = getDefaultRouter() as unknown as Routes;\n\n } else {\n // make sure default routes are included\n // https://github.com/Bekacru/better-call/pull/67\n this.router = this.router.extend({ ...getDefaultRouter().endpoints }) as unknown as Routes;\n }\n\n bindRouterToTransport(this.transport, this.router, this.options.express !== undefined);\n }\n\n /**\n * Define a new type of room for matchmaking.\n *\n * @param name public room identifier for match-making.\n * @param roomClass Room class definition\n * @param defaultOptions default options for `onCreate`\n */\n public define<T extends Type<Room>>(\n roomClass: T,\n defaultOptions?: OnCreateOptions<T>,\n ): RegisteredHandler\n public define<T extends Type<Room>>(\n name: string,\n roomClass: T,\n defaultOptions?: OnCreateOptions<T>,\n ): RegisteredHandler\n public define<T extends Type<Room>>(\n nameOrHandler: string | T,\n handlerOrOptions: T | OnCreateOptions<T>,\n defaultOptions?: OnCreateOptions<T>,\n ): RegisteredHandler {\n const name = (typeof(nameOrHandler) === \"string\")\n ? nameOrHandler\n : nameOrHandler.name;\n\n const roomClass = (typeof(nameOrHandler) === \"string\")\n ? handlerOrOptions\n : nameOrHandler;\n\n const options = (typeof(nameOrHandler) === \"string\")\n ? defaultOptions\n : handlerOrOptions;\n\n return matchMaker.defineRoomType(name, roomClass, options);\n }\n\n /**\n * Remove a room definition from matchmaking.\n * This method does not destroy any room. It only dissallows matchmaking\n */\n public removeRoomType(name: string): void {\n matchMaker.removeRoomType(name);\n }\n\n public async gracefullyShutdown(exit: boolean = true, err?: Error) {\n if (matchMaker.state === matchMaker.MatchMakerState.SHUTTING_DOWN) {\n return;\n }\n\n try {\n // custom \"before shutdown\" method\n await this.onBeforeShutdownCallback();\n\n // this is going to lock all rooms and wait for them to be disposed\n await matchMaker.gracefullyShutdown();\n\n this.transport?.shutdown();\n this.presence?.shutdown();\n await this.driver?.shutdown();\n\n // custom \"after shutdown\" method\n await this.onShutdownCallback();\n\n } catch (e) {\n debugAndPrintError(`error during shutdown: ${e}`);\n\n } finally {\n if (exit) {\n process.exit((err && !isDevMode) ? 1 : 0);\n }\n }\n }\n\n /**\n * Add simulated latency between client and server.\n *\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\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 if (isDevMode) {\n // In dev mode, the Vite plugin manages Server/matchMaker lifecycle.\n // Return a config-only object \u2014 no Server instance, no matchMaker.setup().\n return {\n options: serverOptions,\n router: routes,\n '~rooms': rooms,\n // the vite plugin registers the transport before importing user code\n simulateLatency: (milliseconds: number) => applySimulatedLatency(getTransport(), milliseconds),\n } as unknown as Server<T, R>;\n }\n\n const server = new Server<T, R>(serverOptions);\n server.router = routes;\n\n registerRoomDefinitions(rooms);\n\n return server;\n}\n\nexport function defineRoom<T extends Type<Room>>(\n roomKlass: T,\n defaultOptions?: Parameters<NonNullable<InstanceType<T>['onCreate']>>[0],\n): RegisteredHandler<InstanceType<T>> {\n return new RegisteredHandler(roomKlass, defaultOptions) as unknown as RegisteredHandler<InstanceType<T>>;\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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;
|
|
6
|
-
"names": ["
|
|
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
|
+
"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
|
/**
|
|
@@ -98,6 +100,16 @@ export declare class Server<RoomTypes extends Record<string, RegisteredHandler>
|
|
|
98
100
|
protected _onTransportReady: Deferred<Transport>;
|
|
99
101
|
static current: Server<any, any> | undefined;
|
|
100
102
|
constructor(options?: ServerOptions);
|
|
103
|
+
/**
|
|
104
|
+
* Every side effect of construction: the dev-mode flag, the transport, the
|
|
105
|
+
* matchmaker and the process signal handlers. Distinct from
|
|
106
|
+
* {@link prepareServices}, which boots the services user code declared.
|
|
107
|
+
*
|
|
108
|
+
* Isolated so {@link DevServer} can opt out of all four — under
|
|
109
|
+
* `colyseus/vite` the plugin owns them, and user code still gets a real
|
|
110
|
+
* `Server` to call.
|
|
111
|
+
*/
|
|
112
|
+
protected bootRuntime(): void;
|
|
101
113
|
attach(options: ServerOptions): Promise<void>;
|
|
102
114
|
/**
|
|
103
115
|
* Bind the server into the port specified.
|
|
@@ -173,7 +185,21 @@ export declare class Server<RoomTypes extends Record<string, RegisteredHandler>
|
|
|
173
185
|
*/
|
|
174
186
|
onShutdown(callback: () => void | Promise<any>): void;
|
|
175
187
|
onBeforeShutdown(callback: () => void | Promise<any>): void;
|
|
176
|
-
|
|
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;
|
|
177
203
|
private _applyRouterDefaults;
|
|
178
204
|
protected getDefaultTransport(options: any): Promise<Transport>;
|
|
179
205
|
protected onShutdownCallback: () => void | Promise<any>;
|
package/build/Server.mjs
CHANGED
|
@@ -17,14 +17,27 @@ var Server = class _Server {
|
|
|
17
17
|
this._onTransportReady = new Deferred();
|
|
18
18
|
this.onShutdownCallback = () => Promise.resolve();
|
|
19
19
|
this.onBeforeShutdownCallback = () => Promise.resolve();
|
|
20
|
-
const {
|
|
21
|
-
gracefullyShutdown: gracefullyShutdown2 = true,
|
|
22
|
-
greet: greet2 = true
|
|
23
|
-
} = options;
|
|
24
|
-
setDevMode(options.devMode === true);
|
|
20
|
+
const { greet: greet2 = true } = options;
|
|
25
21
|
this.options = options;
|
|
26
22
|
this.greet = greet2;
|
|
27
23
|
_Server.current = this;
|
|
24
|
+
if (options.logger) {
|
|
25
|
+
setLogger(options.logger);
|
|
26
|
+
}
|
|
27
|
+
this.bootRuntime();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Every side effect of construction: the dev-mode flag, the transport, the
|
|
31
|
+
* matchmaker and the process signal handlers. Distinct from
|
|
32
|
+
* {@link prepareServices}, which boots the services user code declared.
|
|
33
|
+
*
|
|
34
|
+
* Isolated so {@link DevServer} can opt out of all four — under
|
|
35
|
+
* `colyseus/vite` the plugin owns them, and user code still gets a real
|
|
36
|
+
* `Server` to call.
|
|
37
|
+
*/
|
|
38
|
+
bootRuntime() {
|
|
39
|
+
const options = this.options;
|
|
40
|
+
setDevMode(options.devMode === true);
|
|
28
41
|
this.attach(options);
|
|
29
42
|
matchMaker.setup(
|
|
30
43
|
options.presence,
|
|
@@ -35,12 +48,9 @@ var Server = class _Server {
|
|
|
35
48
|
this.presence = matchMaker.presence;
|
|
36
49
|
this.driver = matchMaker.driver;
|
|
37
50
|
});
|
|
38
|
-
if (
|
|
51
|
+
if (options.gracefullyShutdown !== false) {
|
|
39
52
|
registerGracefulShutdown((err) => this.gracefullyShutdown(true, err));
|
|
40
53
|
}
|
|
41
|
-
if (options.logger) {
|
|
42
|
-
setLogger(options.logger);
|
|
43
|
-
}
|
|
44
54
|
}
|
|
45
55
|
async attach(options) {
|
|
46
56
|
this.transport = options.transport || await this.getDefaultTransport(options);
|
|
@@ -55,7 +65,7 @@ var Server = class _Server {
|
|
|
55
65
|
* @param listeningListener
|
|
56
66
|
*/
|
|
57
67
|
async listen(port, hostname, backlog, listeningListener) {
|
|
58
|
-
await this.
|
|
68
|
+
await this._bootForListen();
|
|
59
69
|
if (process.env.COLYSEUS_CLOUD !== void 0) {
|
|
60
70
|
if (typeof hostname === "number") {
|
|
61
71
|
hostname = void 0;
|
|
@@ -117,7 +127,7 @@ var Server = class _Server {
|
|
|
117
127
|
* ```
|
|
118
128
|
*/
|
|
119
129
|
async serverless() {
|
|
120
|
-
await this.
|
|
130
|
+
await this._bootForListen();
|
|
121
131
|
await this._onTransportReady;
|
|
122
132
|
const server = this.transport.server;
|
|
123
133
|
if (!server) {
|
|
@@ -206,23 +216,40 @@ var Server = class _Server {
|
|
|
206
216
|
onBeforeShutdown(callback) {
|
|
207
217
|
this.onBeforeShutdownCallback = callback;
|
|
208
218
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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) {
|
|
220
235
|
await beforeListen();
|
|
221
236
|
}
|
|
222
237
|
if (database) {
|
|
223
238
|
await database.boot();
|
|
224
239
|
}
|
|
225
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();
|
|
226
253
|
if (express) {
|
|
227
254
|
await this._onTransportReady;
|
|
228
255
|
if (this.transport.getExpressApp) {
|
|
@@ -266,6 +293,30 @@ var Server = class _Server {
|
|
|
266
293
|
}
|
|
267
294
|
}
|
|
268
295
|
};
|
|
296
|
+
var devWarnings = /* @__PURE__ */ new Set();
|
|
297
|
+
function warnDevOnce(message) {
|
|
298
|
+
if (devWarnings.has(message)) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
devWarnings.add(message);
|
|
302
|
+
logger.warn(message);
|
|
303
|
+
}
|
|
304
|
+
var DevServer = class extends Server {
|
|
305
|
+
bootRuntime() {
|
|
306
|
+
this.transport = getTransport();
|
|
307
|
+
this._onTransportReady.resolve(this.transport);
|
|
308
|
+
this.presence = matchMaker.presence;
|
|
309
|
+
this.driver = matchMaker.driver;
|
|
310
|
+
if (this.options.transport) {
|
|
311
|
+
warnDevOnce("\u26A0\uFE0F 'transport' is ignored in dev mode \u2014 colyseus/vite serves WebSockets over Vite's own HTTP server.");
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
/** The plugin already serves Colyseus on Vite's own HTTP server and port. */
|
|
315
|
+
async listen(_port, _hostname, _backlog, listeningListener) {
|
|
316
|
+
warnDevOnce("\u26A0\uFE0F listen() is ignored in dev mode \u2014 connect to the Vite dev server's URL instead.");
|
|
317
|
+
listeningListener?.();
|
|
318
|
+
}
|
|
319
|
+
};
|
|
269
320
|
var _originalRoomOnMessage = null;
|
|
270
321
|
function applySimulatedLatency(transport, milliseconds) {
|
|
271
322
|
if (milliseconds > 0) {
|
|
@@ -319,18 +370,12 @@ function unregisterRoomDefinitions(roomNames) {
|
|
|
319
370
|
}
|
|
320
371
|
function defineServer(options) {
|
|
321
372
|
const { rooms, routes, ...serverOptions } = options;
|
|
322
|
-
|
|
323
|
-
return {
|
|
324
|
-
options: serverOptions,
|
|
325
|
-
router: routes,
|
|
326
|
-
"~rooms": rooms,
|
|
327
|
-
// the vite plugin registers the transport before importing user code
|
|
328
|
-
simulateLatency: (milliseconds) => applySimulatedLatency(getTransport(), milliseconds)
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
const server = new Server(serverOptions);
|
|
373
|
+
const server = isDevMode ? new DevServer(serverOptions) : new Server(serverOptions);
|
|
332
374
|
server.router = routes;
|
|
333
|
-
|
|
375
|
+
server["~rooms"] = rooms;
|
|
376
|
+
if (!isDevMode) {
|
|
377
|
+
registerRoomDefinitions(rooms);
|
|
378
|
+
}
|
|
334
379
|
return server;
|
|
335
380
|
}
|
|
336
381
|
function defineRoom(roomKlass, defaultOptions) {
|
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 {\n gracefullyShutdown = true,\n greet = true\n } = options;\n\n setDevMode(options.devMode === true);\n\n this.options = options;\n this.greet = greet;\n\n (Server as { current: Server<any, any> | undefined }).current = this as Server<any, any>;\n\n this.attach(options);\n\n // Pass options.presence/driver through as-is (possibly undefined).\n // matchMaker.setup() falls back to getDefaultPresence/getDefaultDriver,\n // which auto-select RedisPresence/RedisDriver on Colyseus Cloud.\n matchMaker.setup(\n options.presence,\n options.driver,\n options.publicAddress,\n options.selectProcessIdToCreateRoom,\n ).then(() => {\n this.presence = matchMaker.presence;\n this.driver = matchMaker.driver;\n });\n\n if (gracefullyShutdown) {\n registerGracefulShutdown((err) => this.gracefullyShutdown(true, err));\n }\n\n if (options.logger) {\n setLogger(options.logger);\n }\n }\n\n public async attach(options: ServerOptions) {\n this.transport = options.transport || await this.getDefaultTransport(options);\n // `options.express` runs in `listen()` after `database.boot()` so user\n // code reading `database.auth.settings` etc. finds services instantiated.\n this._onTransportReady.resolve(this.transport);\n }\n\n /**\n * Bind the server into the port specified.\n *\n * @param port - Port number or Unix socket path\n * @param hostname\n * @param backlog\n * @param listeningListener\n */\n public async listen(port: number | string, hostname?: string, backlog?: number, listeningListener?: Function) {\n await this._bootServices();\n\n //\n // if Colyseus Cloud is detected, use @colyseus/tools to listen\n //\n if (process.env.COLYSEUS_CLOUD !== undefined ) {\n if (typeof(hostname) === \"number\") {\n //\n // workaround, @colyseus/tools calls server.listen() again with the port as a string\n //\n hostname = undefined;\n\n } else {\n try {\n return (await dynamicImport(\"@colyseus/tools\")).listen(this);\n } catch (error) {\n const err = new Error(\"Please install @colyseus/tools to be able to host on Colyseus Cloud.\");\n err.cause = error;\n throw err;\n }\n }\n }\n\n //\n // otherwise, listen on the port directly\n //\n this.port = port;\n\n //\n // Make sure matchmaker is ready before accepting connections\n // (isDevMode: matchmaker may take extra milliseconds to restore the rooms)\n //\n await matchMaker.accept(this.options.isStandaloneMatchMaker);\n\n /**\n * Greetings!\n */\n if (this.greet) {\n greet();\n }\n\n // Wait for the transport to be ready\n await this._onTransportReady;\n\n return new Promise<void>((resolve, reject) => {\n this.transport.listen(port, hostname, backlog, (err) => {\n if (this.transport.server) {\n this.transport.server.on('error', (err) => reject(err));\n }\n\n this.bindRoutes();\n\n if (listeningListener) {\n listeningListener(err);\n }\n\n if (err) {\n reject(err);\n\n } else {\n resolve();\n }\n });\n });\n }\n\n /**\n * Prepare matchmaking and HTTP routes, then return the underlying\n * `http.Server` *without* binding to a port.\n *\n * Use this on serverless platforms that consume an exported HTTP server\n * instead of a listening one. Vercel, for example, drives the request handler\n * of a default-exported server (its Express/Hono WebSocket examples use\n * `export default server`), whereas calling `listen()` there selects Vercel's\n * \"captured server\" path, which does not invoke Express-style app handlers.\n *\n * The transport must be created with an `http.Server` so it can be exported.\n * Vercel additionally needs `package.json` `\"main\"` pointing at the entrypoint:\n *\n * ```ts\n * import { createServer } from \"node:http\";\n * const httpServer = createServer();\n * const server = new Server({\n * transport: new WebSocketTransport({ server: httpServer }),\n * express: (app) => { app.get(\"/hello\", (req, res) => res.json({ ok: true })); },\n * });\n * server.define(\"my_room\", MyRoom);\n * export default await server.serverless(); // no listen()\n * ```\n */\n public async serverless(): Promise<HttpServer> {\n await this._bootServices();\n\n // transport (and its HTTP server) must be ready before binding routes\n await this._onTransportReady;\n\n const server = this.transport.server as HttpServer | undefined;\n if (!server) {\n throw new Error(\"serverless() requires a transport backed by an http.Server (pass `{ server }` to the transport).\");\n }\n\n this.bindRoutes();\n\n // When the server is consumed via `export default` (rather than listen()),\n // the request body must be available synchronously \u2014 buffer it up-front so\n // `req.body` is set before the router runs.\n prereadRequestBodies(server);\n\n await matchMaker.accept(this.options.isStandaloneMatchMaker);\n\n if (this.greet) {\n greet();\n }\n\n return server;\n }\n\n /**\n * Set the active transport globally and bind the matchmaking/HTTP routes.\n * Shared by {@link listen} and {@link serverless}.\n *\n * `setTransport()` runs here rather than before `transport.listen()`: the\n * global transport is only read by the matchmaking route handler, which can't\n * run until `bindRouterToTransport()` below registers the route.\n */\n private bindRoutes() {\n // set transport globally, to be used by the matchmaking route\n setTransport(this.transport);\n\n // default router is used if no router is provided\n if (!this.router) {\n this.router = getDefaultRouter() as unknown as Routes;\n\n } else {\n // make sure default routes are included\n // https://github.com/Bekacru/better-call/pull/67\n this.router = this.router.extend({ ...getDefaultRouter().endpoints }) as unknown as Routes;\n }\n\n bindRouterToTransport(this.transport, this.router, this.options.express !== undefined);\n }\n\n /**\n * Define a new type of room for matchmaking.\n *\n * @param name public room identifier for match-making.\n * @param roomClass Room class definition\n * @param defaultOptions default options for `onCreate`\n */\n public define<T extends Type<Room>>(\n roomClass: T,\n defaultOptions?: OnCreateOptions<T>,\n ): RegisteredHandler\n public define<T extends Type<Room>>(\n name: string,\n roomClass: T,\n defaultOptions?: OnCreateOptions<T>,\n ): RegisteredHandler\n public define<T extends Type<Room>>(\n nameOrHandler: string | T,\n handlerOrOptions: T | OnCreateOptions<T>,\n defaultOptions?: OnCreateOptions<T>,\n ): RegisteredHandler {\n const name = (typeof(nameOrHandler) === \"string\")\n ? nameOrHandler\n : nameOrHandler.name;\n\n const roomClass = (typeof(nameOrHandler) === \"string\")\n ? handlerOrOptions\n : nameOrHandler;\n\n const options = (typeof(nameOrHandler) === \"string\")\n ? defaultOptions\n : handlerOrOptions;\n\n return matchMaker.defineRoomType(name, roomClass, options);\n }\n\n /**\n * Remove a room definition from matchmaking.\n * This method does not destroy any room. It only dissallows matchmaking\n */\n public removeRoomType(name: string): void {\n matchMaker.removeRoomType(name);\n }\n\n public async gracefullyShutdown(exit: boolean = true, err?: Error) {\n if (matchMaker.state === matchMaker.MatchMakerState.SHUTTING_DOWN) {\n return;\n }\n\n try {\n // custom \"before shutdown\" method\n await this.onBeforeShutdownCallback();\n\n // this is going to lock all rooms and wait for them to be disposed\n await matchMaker.gracefullyShutdown();\n\n this.transport?.shutdown();\n this.presence?.shutdown();\n await this.driver?.shutdown();\n\n // custom \"after shutdown\" method\n await this.onShutdownCallback();\n\n } catch (e) {\n debugAndPrintError(`error during shutdown: ${e}`);\n\n } finally {\n if (exit) {\n process.exit((err && !isDevMode) ? 1 : 0);\n }\n }\n }\n\n /**\n * Add simulated latency between client and server.\n *\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\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 if (isDevMode) {\n // In dev mode, the Vite plugin manages Server/matchMaker lifecycle.\n // Return a config-only object \u2014 no Server instance, no matchMaker.setup().\n return {\n options: serverOptions,\n router: routes,\n '~rooms': rooms,\n // the vite plugin registers the transport before importing user code\n simulateLatency: (milliseconds: number) => applySimulatedLatency(getTransport(), milliseconds),\n } as unknown as Server<T, R>;\n }\n\n const server = new Server<T, R>(serverOptions);\n server.router = routes;\n\n registerRoomDefinitions(rooms);\n\n return server;\n}\n\nexport function defineRoom<T extends Type<Room>>(\n roomKlass: T,\n defaultOptions?: Parameters<NonNullable<InstanceType<T>['onCreate']>>[0],\n): RegisteredHandler<InstanceType<T>> {\n return new RegisteredHandler(roomKlass, defaultOptions) as unknown as RegisteredHandler<InstanceType<T>>;\n}\n"],
|
|
5
|
-
"mappings": ";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;
|
|
6
|
-
"names": ["
|
|
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
|
+
"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",
|
|
@@ -52,25 +52,25 @@
|
|
|
52
52
|
"msgpackr": "^2.0.1",
|
|
53
53
|
"nanoid": "^3.3.11",
|
|
54
54
|
"@colyseus/better-call": "^1.3.1",
|
|
55
|
-
"@colyseus/greeting-banner": "^4.0.
|
|
56
|
-
"@colyseus/shared-types": "^0.18.
|
|
55
|
+
"@colyseus/greeting-banner": "^4.0.2",
|
|
56
|
+
"@colyseus/shared-types": "^0.18.2"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@colyseus/schema": "^5.0.8",
|
|
60
60
|
"express": "^5.0.0",
|
|
61
61
|
"vitest": "^3.1.1",
|
|
62
|
+
"@colyseus/redis-driver": "^0.18.2",
|
|
62
63
|
"@colyseus/tools": "^0.18.3",
|
|
63
|
-
"@colyseus/redis-presence": "^0.18.
|
|
64
|
-
"@colyseus/redis-driver": "^0.18.2"
|
|
64
|
+
"@colyseus/redis-presence": "^0.18.3"
|
|
65
65
|
},
|
|
66
66
|
"peerDependencies": {
|
|
67
67
|
"@colyseus/schema": "^5.0.8",
|
|
68
68
|
"@pm2/io": "^6.1.0",
|
|
69
69
|
"express": "^4.16.0 || ^5.0.0",
|
|
70
70
|
"zod": "^4.1.12",
|
|
71
|
-
"@colyseus/auth": "^0.18.
|
|
72
|
-
"@colyseus/
|
|
73
|
-
"@colyseus/
|
|
71
|
+
"@colyseus/auth": "^0.18.2",
|
|
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
|
|
|
@@ -132,18 +134,34 @@ export class Server<
|
|
|
132
134
|
static current: Server<any, any> | undefined;
|
|
133
135
|
|
|
134
136
|
constructor(options: ServerOptions = {}) {
|
|
135
|
-
const {
|
|
136
|
-
gracefullyShutdown = true,
|
|
137
|
-
greet = true
|
|
138
|
-
} = options;
|
|
139
|
-
|
|
140
|
-
setDevMode(options.devMode === true);
|
|
137
|
+
const { greet = true } = options;
|
|
141
138
|
|
|
142
139
|
this.options = options;
|
|
143
140
|
this.greet = greet;
|
|
144
141
|
|
|
145
142
|
(Server as { current: Server<any, any> | undefined }).current = this as Server<any, any>;
|
|
146
143
|
|
|
144
|
+
if (options.logger) {
|
|
145
|
+
setLogger(options.logger);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
this.bootRuntime();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Every side effect of construction: the dev-mode flag, the transport, the
|
|
153
|
+
* matchmaker and the process signal handlers. Distinct from
|
|
154
|
+
* {@link prepareServices}, which boots the services user code declared.
|
|
155
|
+
*
|
|
156
|
+
* Isolated so {@link DevServer} can opt out of all four — under
|
|
157
|
+
* `colyseus/vite` the plugin owns them, and user code still gets a real
|
|
158
|
+
* `Server` to call.
|
|
159
|
+
*/
|
|
160
|
+
protected bootRuntime() {
|
|
161
|
+
const options = this.options;
|
|
162
|
+
|
|
163
|
+
setDevMode(options.devMode === true);
|
|
164
|
+
|
|
147
165
|
this.attach(options);
|
|
148
166
|
|
|
149
167
|
// Pass options.presence/driver through as-is (possibly undefined).
|
|
@@ -159,13 +177,9 @@ export class Server<
|
|
|
159
177
|
this.driver = matchMaker.driver;
|
|
160
178
|
});
|
|
161
179
|
|
|
162
|
-
if (gracefullyShutdown) {
|
|
180
|
+
if (options.gracefullyShutdown !== false) {
|
|
163
181
|
registerGracefulShutdown((err) => this.gracefullyShutdown(true, err));
|
|
164
182
|
}
|
|
165
|
-
|
|
166
|
-
if (options.logger) {
|
|
167
|
-
setLogger(options.logger);
|
|
168
|
-
}
|
|
169
183
|
}
|
|
170
184
|
|
|
171
185
|
public async attach(options: ServerOptions) {
|
|
@@ -184,7 +198,7 @@ export class Server<
|
|
|
184
198
|
* @param listeningListener
|
|
185
199
|
*/
|
|
186
200
|
public async listen(port: number | string, hostname?: string, backlog?: number, listeningListener?: Function) {
|
|
187
|
-
await this.
|
|
201
|
+
await this._bootForListen();
|
|
188
202
|
|
|
189
203
|
//
|
|
190
204
|
// if Colyseus Cloud is detected, use @colyseus/tools to listen
|
|
@@ -275,7 +289,7 @@ export class Server<
|
|
|
275
289
|
* ```
|
|
276
290
|
*/
|
|
277
291
|
public async serverless(): Promise<HttpServer> {
|
|
278
|
-
await this.
|
|
292
|
+
await this._bootForListen();
|
|
279
293
|
|
|
280
294
|
// transport (and its HTTP server) must be ready before binding routes
|
|
281
295
|
await this._onTransportReady;
|
|
@@ -428,22 +442,42 @@ export class Server<
|
|
|
428
442
|
this.onBeforeShutdownCallback = callback;
|
|
429
443
|
}
|
|
430
444
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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;
|
|
437
474
|
|
|
438
475
|
// boot runs after module evaluation, so the env var wins over
|
|
439
476
|
// simulateLatency() calls made at the top level of user code
|
|
440
477
|
const envLatency = parseLatencyEnv();
|
|
441
478
|
if (envLatency !== undefined) { this.simulateLatency(envLatency); }
|
|
442
479
|
|
|
443
|
-
|
|
444
|
-
if (database) { await database.boot(); }
|
|
445
|
-
|
|
446
|
-
await this._applyRouterDefaults();
|
|
480
|
+
await this.prepareServices();
|
|
447
481
|
|
|
448
482
|
if (express) {
|
|
449
483
|
await this._onTransportReady;
|
|
@@ -498,6 +532,47 @@ export class Server<
|
|
|
498
532
|
() => Promise.resolve()
|
|
499
533
|
}
|
|
500
534
|
|
|
535
|
+
// the user module re-evaluates on every HMR reload — warn once per process
|
|
536
|
+
const devWarnings = new Set<string>();
|
|
537
|
+
function warnDevOnce(message: string) {
|
|
538
|
+
if (devWarnings.has(message)) { return; }
|
|
539
|
+
devWarnings.add(message);
|
|
540
|
+
logger.warn(message);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* The `Server` handed to user code under `colyseus/vite`.
|
|
545
|
+
*
|
|
546
|
+
* The plugin owns the HTTP server, the transport, the matchmaker and the
|
|
547
|
+
* process lifecycle, so constructing one has no side effects — it only
|
|
548
|
+
* carries the user's configuration back to the plugin. Everything else is
|
|
549
|
+
* inherited, so the documented `Server` API keeps working in dev mode.
|
|
550
|
+
*/
|
|
551
|
+
class DevServer<
|
|
552
|
+
RoomTypes extends Record<string, RegisteredHandler> = any,
|
|
553
|
+
Routes extends Router = any
|
|
554
|
+
> extends Server<RoomTypes, Routes> {
|
|
555
|
+
|
|
556
|
+
protected bootRuntime() {
|
|
557
|
+
// the plugin creates the transport and runs matchMaker.setup() before
|
|
558
|
+
// importing user code, so both are already available here
|
|
559
|
+
this.transport = getTransport();
|
|
560
|
+
this._onTransportReady.resolve(this.transport);
|
|
561
|
+
this.presence = matchMaker.presence;
|
|
562
|
+
this.driver = matchMaker.driver;
|
|
563
|
+
|
|
564
|
+
if (this.options.transport) {
|
|
565
|
+
warnDevOnce("⚠️ 'transport' is ignored in dev mode — colyseus/vite serves WebSockets over Vite's own HTTP server.");
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/** The plugin already serves Colyseus on Vite's own HTTP server and port. */
|
|
570
|
+
public async listen(_port?: number | string, _hostname?: string, _backlog?: number, listeningListener?: Function) {
|
|
571
|
+
warnDevOnce("⚠️ listen() is ignored in dev mode — connect to the Vite dev server's URL instead.");
|
|
572
|
+
listeningListener?.();
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
501
576
|
let _originalRoomOnMessage: typeof Room.prototype['_onMessage'] | null = null;
|
|
502
577
|
|
|
503
578
|
/**
|
|
@@ -597,22 +672,18 @@ export function defineServer<
|
|
|
597
672
|
): Server<T, R> {
|
|
598
673
|
const { rooms, routes, ...serverOptions } = options;
|
|
599
674
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
return {
|
|
604
|
-
options: serverOptions,
|
|
605
|
-
router: routes,
|
|
606
|
-
'~rooms': rooms,
|
|
607
|
-
// the vite plugin registers the transport before importing user code
|
|
608
|
-
simulateLatency: (milliseconds: number) => applySimulatedLatency(getTransport(), milliseconds),
|
|
609
|
-
} as unknown as Server<T, R>;
|
|
610
|
-
}
|
|
675
|
+
const server = isDevMode
|
|
676
|
+
? new DevServer<T, R>(serverOptions)
|
|
677
|
+
: new Server<T, R>(serverOptions);
|
|
611
678
|
|
|
612
|
-
const server = new Server<T, R>(serverOptions);
|
|
613
679
|
server.router = routes;
|
|
680
|
+
server['~rooms'] = rooms;
|
|
614
681
|
|
|
615
|
-
|
|
682
|
+
// in dev mode the plugin reads `~rooms` back and owns their
|
|
683
|
+
// (re-)registration across HMR reloads
|
|
684
|
+
if (!isDevMode) {
|
|
685
|
+
registerRoomDefinitions(rooms);
|
|
686
|
+
}
|
|
616
687
|
|
|
617
688
|
return server;
|
|
618
689
|
}
|
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
|
+
}
|