@colyseus/core 0.18.6 → 0.18.7

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 CHANGED
@@ -32,8 +32,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
32
32
  var Server_exports = {};
33
33
  __export(Server_exports, {
34
34
  Server: () => Server,
35
+ applySimulatedLatency: () => applySimulatedLatency,
35
36
  defineRoom: () => defineRoom,
36
37
  defineServer: () => defineServer,
38
+ parseLatencyEnv: () => parseLatencyEnv,
37
39
  registerRoomDefinitions: () => registerRoomDefinitions,
38
40
  unregisterRoomDefinitions: () => unregisterRoomDefinitions
39
41
  });
@@ -54,7 +56,6 @@ var import_default_routes = require("./router/default_routes.cjs");
54
56
  var Server = class _Server {
55
57
  constructor(options = {}) {
56
58
  this._onTransportReady = new import_Utils.Deferred();
57
- this._originalRoomOnMessage = null;
58
59
  this.onShutdownCallback = () => Promise.resolve();
59
60
  this.onBeforeShutdownCallback = () => Promise.resolve();
60
61
  const {
@@ -208,7 +209,7 @@ var Server = class _Server {
208
209
  try {
209
210
  await this.onBeforeShutdownCallback();
210
211
  await matchMaker.gracefullyShutdown();
211
- this.transport.shutdown();
212
+ this.transport?.shutdown();
212
213
  this.presence?.shutdown();
213
214
  await this.driver?.shutdown();
214
215
  await this.onShutdownCallback();
@@ -222,24 +223,19 @@ var Server = class _Server {
222
223
  }
223
224
  /**
224
225
  * Add simulated latency between client and server.
226
+ *
227
+ * May be called at any time — before or after `listen()`. Also available
228
+ * via the `COLYSEUS_LATENCY` environment variable (round-trip ms), which
229
+ * overrides calls made before the server boots.
230
+ *
225
231
  * @param milliseconds round trip latency in milliseconds.
226
232
  */
227
233
  simulateLatency(milliseconds) {
228
- if (milliseconds > 0) {
229
- import_Logger.logger.warn(`\u{1F4F6}\uFE0F\u2757 Colyseus latency simulation enabled \u2192 ${milliseconds}ms latency for round trip.`);
230
- } else {
231
- import_Logger.logger.warn(`\u{1F4F6}\uFE0F\u2757 Colyseus latency simulation disabled.`);
232
- }
233
- const halfwayMS = milliseconds / 2;
234
- this.transport.simulateLatency(halfwayMS);
235
- if (this._originalRoomOnMessage == null) {
236
- this._originalRoomOnMessage = import_Room.Room.prototype["_onMessage"];
237
- }
238
- const originalOnMessage = this._originalRoomOnMessage;
239
- import_Room.Room.prototype["_onMessage"] = milliseconds <= Number.EPSILON ? originalOnMessage : function(client, buffer) {
240
- const cachedBuffer = Buffer.from(buffer);
241
- setTimeout(() => originalOnMessage.call(this, client, cachedBuffer), halfwayMS);
242
- };
234
+ this._onTransportReady.then(
235
+ (transport) => applySimulatedLatency(transport, milliseconds),
236
+ () => {
237
+ }
238
+ );
243
239
  }
244
240
  /**
245
241
  * Register a callback that is going to be executed before the server shuts down.
@@ -257,6 +253,10 @@ var Server = class _Server {
257
253
  // serves the express callback and auth/database routes.
258
254
  async _bootServices() {
259
255
  const { beforeListen, database, express } = this.options;
256
+ const envLatency = parseLatencyEnv();
257
+ if (envLatency !== void 0) {
258
+ this.simulateLatency(envLatency);
259
+ }
260
260
  if (beforeListen) {
261
261
  await beforeListen();
262
262
  }
@@ -307,6 +307,36 @@ var Server = class _Server {
307
307
  }
308
308
  }
309
309
  };
310
+ var _originalRoomOnMessage = null;
311
+ function applySimulatedLatency(transport, milliseconds) {
312
+ if (milliseconds > 0) {
313
+ import_Logger.logger.warn(`\u{1F4F6}\uFE0F\u2757 Colyseus latency simulation enabled \u2192 ${milliseconds}ms latency for round trip.`);
314
+ } else {
315
+ import_Logger.logger.warn(`\u{1F4F6}\uFE0F\u2757 Colyseus latency simulation disabled.`);
316
+ }
317
+ const halfwayMS = milliseconds / 2;
318
+ transport?.simulateLatency(halfwayMS);
319
+ if (_originalRoomOnMessage == null) {
320
+ _originalRoomOnMessage = import_Room.Room.prototype["_onMessage"];
321
+ }
322
+ const originalOnMessage = _originalRoomOnMessage;
323
+ import_Room.Room.prototype["_onMessage"] = milliseconds <= Number.EPSILON ? originalOnMessage : function(client, buffer) {
324
+ const cachedBuffer = Buffer.from(buffer);
325
+ setTimeout(() => originalOnMessage.call(this, client, cachedBuffer), halfwayMS);
326
+ };
327
+ }
328
+ function parseLatencyEnv() {
329
+ const value = process.env.COLYSEUS_LATENCY;
330
+ if (value === void 0 || value === "") {
331
+ return void 0;
332
+ }
333
+ const milliseconds = Number(value);
334
+ if (isNaN(milliseconds)) {
335
+ import_Logger.logger.warn(`\u{1F4F6}\uFE0F\u2757 Ignoring COLYSEUS_LATENCY: expected a number of milliseconds, got ${JSON.stringify(value)}.`);
336
+ return void 0;
337
+ }
338
+ return milliseconds;
339
+ }
310
340
  function isRegisteredHandler(value) {
311
341
  return value instanceof import_RegisteredHandler.RegisteredHandler || typeof value === "object" && value !== null && "klass" in value;
312
342
  }
@@ -334,7 +364,9 @@ function defineServer(options) {
334
364
  return {
335
365
  options: serverOptions,
336
366
  router: routes,
337
- "~rooms": rooms
367
+ "~rooms": rooms,
368
+ // the vite plugin registers the transport before importing user code
369
+ simulateLatency: (milliseconds) => applySimulatedLatency((0, import_Transport.getTransport)(), milliseconds)
338
370
  };
339
371
  }
340
372
  const server = new Server(serverOptions);
@@ -348,8 +380,10 @@ function defineRoom(roomKlass, defaultOptions) {
348
380
  // Annotate the CommonJS export names for ESM import in node:
349
381
  0 && (module.exports = {
350
382
  Server,
383
+ applySimulatedLatency,
351
384
  defineRoom,
352
385
  defineServer,
386
+ parseLatencyEnv,
353
387
  registerRoomDefinitions,
354
388
  unregisterRoomDefinitions
355
389
  });
@@ -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 { 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 private _originalRoomOnMessage: typeof Room.prototype['_onMessage'] | null = null;\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 * @param milliseconds round trip latency in milliseconds.\n */\n public simulateLatency(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 this.transport.simulateLatency(halfwayMS);\n\n if (this._originalRoomOnMessage == null) {\n this._originalRoomOnMessage = Room.prototype['_onMessage'];\n }\n\n const originalOnMessage = this._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 * 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 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\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 } 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": "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",
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": "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",
6
6
  "names": ["gracefullyShutdown", "greet", "err", "module"]
7
7
  }
package/build/Server.d.ts CHANGED
@@ -96,7 +96,6 @@ export declare class Server<RoomTypes extends Record<string, RegisteredHandler>
96
96
  protected port: number | string;
97
97
  protected greet: boolean;
98
98
  protected _onTransportReady: Deferred<Transport>;
99
- private _originalRoomOnMessage;
100
99
  static current: Server<any, any> | undefined;
101
100
  constructor(options?: ServerOptions);
102
101
  attach(options: ServerOptions): Promise<void>;
@@ -160,6 +159,11 @@ export declare class Server<RoomTypes extends Record<string, RegisteredHandler>
160
159
  gracefullyShutdown(exit?: boolean, err?: Error): Promise<void>;
161
160
  /**
162
161
  * Add simulated latency between client and server.
162
+ *
163
+ * May be called at any time — before or after `listen()`. Also available
164
+ * via the `COLYSEUS_LATENCY` environment variable (round-trip ms), which
165
+ * overrides calls made before the server boots.
166
+ *
163
167
  * @param milliseconds round trip latency in milliseconds.
164
168
  */
165
169
  simulateLatency(milliseconds: number): void;
@@ -175,6 +179,20 @@ export declare class Server<RoomTypes extends Record<string, RegisteredHandler>
175
179
  protected onShutdownCallback: () => void | Promise<any>;
176
180
  protected onBeforeShutdownCallback: () => void | Promise<any>;
177
181
  }
182
+ /**
183
+ * Applies both halves of the round-trip latency simulation: delays the
184
+ * transport's outgoing messages and the Room's incoming message handling.
185
+ *
186
+ * Prefer `server.simulateLatency()` — this is the shared core behind it, the
187
+ * dev-mode `defineServer()` object and the `COLYSEUS_LATENCY` env var, where
188
+ * no `Server` instance (or transport, yet) may exist.
189
+ */
190
+ export declare function applySimulatedLatency(transport: Transport | undefined, milliseconds: number): void;
191
+ /**
192
+ * Reads the `COLYSEUS_LATENCY` environment variable (round-trip milliseconds).
193
+ * Returns `undefined` when absent or empty; a non-numeric value warns and is ignored.
194
+ */
195
+ export declare function parseLatencyEnv(): number | undefined;
178
196
  export type RoomDefinitions = Record<string, RegisteredHandler | Type<Room>>;
179
197
  export declare function registerRoomDefinitions<T extends RoomDefinitions>(rooms: T): string[];
180
198
  export declare function unregisterRoomDefinitions(roomNames: Iterable<string>): void;
package/build/Server.mjs CHANGED
@@ -5,7 +5,7 @@ import * as matchMaker from "./MatchMaker.mjs";
5
5
  import { RegisteredHandler } from "./matchmaker/RegisteredHandler.mjs";
6
6
  import { Room } from "./Room.mjs";
7
7
  import { Deferred, registerGracefulShutdown, dynamicImport } from "./utils/Utils.mjs";
8
- import { setTransport } from "./Transport.mjs";
8
+ import { getTransport, setTransport } from "./Transport.mjs";
9
9
  import { logger, setLogger } from "./Logger.mjs";
10
10
  import { setDevMode, isDevMode } from "./utils/DevMode.mjs";
11
11
  import { bindRouterToTransport, createRouter } from "./router/index.mjs";
@@ -15,7 +15,6 @@ import { getDefaultRouter } from "./router/default_routes.mjs";
15
15
  var Server = class _Server {
16
16
  constructor(options = {}) {
17
17
  this._onTransportReady = new Deferred();
18
- this._originalRoomOnMessage = null;
19
18
  this.onShutdownCallback = () => Promise.resolve();
20
19
  this.onBeforeShutdownCallback = () => Promise.resolve();
21
20
  const {
@@ -169,7 +168,7 @@ var Server = class _Server {
169
168
  try {
170
169
  await this.onBeforeShutdownCallback();
171
170
  await matchMaker.gracefullyShutdown();
172
- this.transport.shutdown();
171
+ this.transport?.shutdown();
173
172
  this.presence?.shutdown();
174
173
  await this.driver?.shutdown();
175
174
  await this.onShutdownCallback();
@@ -183,24 +182,19 @@ var Server = class _Server {
183
182
  }
184
183
  /**
185
184
  * Add simulated latency between client and server.
185
+ *
186
+ * May be called at any time — before or after `listen()`. Also available
187
+ * via the `COLYSEUS_LATENCY` environment variable (round-trip ms), which
188
+ * overrides calls made before the server boots.
189
+ *
186
190
  * @param milliseconds round trip latency in milliseconds.
187
191
  */
188
192
  simulateLatency(milliseconds) {
189
- if (milliseconds > 0) {
190
- logger.warn(`\u{1F4F6}\uFE0F\u2757 Colyseus latency simulation enabled \u2192 ${milliseconds}ms latency for round trip.`);
191
- } else {
192
- logger.warn(`\u{1F4F6}\uFE0F\u2757 Colyseus latency simulation disabled.`);
193
- }
194
- const halfwayMS = milliseconds / 2;
195
- this.transport.simulateLatency(halfwayMS);
196
- if (this._originalRoomOnMessage == null) {
197
- this._originalRoomOnMessage = Room.prototype["_onMessage"];
198
- }
199
- const originalOnMessage = this._originalRoomOnMessage;
200
- Room.prototype["_onMessage"] = milliseconds <= Number.EPSILON ? originalOnMessage : function(client, buffer) {
201
- const cachedBuffer = Buffer.from(buffer);
202
- setTimeout(() => originalOnMessage.call(this, client, cachedBuffer), halfwayMS);
203
- };
193
+ this._onTransportReady.then(
194
+ (transport) => applySimulatedLatency(transport, milliseconds),
195
+ () => {
196
+ }
197
+ );
204
198
  }
205
199
  /**
206
200
  * Register a callback that is going to be executed before the server shuts down.
@@ -218,6 +212,10 @@ var Server = class _Server {
218
212
  // serves the express callback and auth/database routes.
219
213
  async _bootServices() {
220
214
  const { beforeListen, database, express } = this.options;
215
+ const envLatency = parseLatencyEnv();
216
+ if (envLatency !== void 0) {
217
+ this.simulateLatency(envLatency);
218
+ }
221
219
  if (beforeListen) {
222
220
  await beforeListen();
223
221
  }
@@ -268,6 +266,36 @@ var Server = class _Server {
268
266
  }
269
267
  }
270
268
  };
269
+ var _originalRoomOnMessage = null;
270
+ function applySimulatedLatency(transport, milliseconds) {
271
+ if (milliseconds > 0) {
272
+ logger.warn(`\u{1F4F6}\uFE0F\u2757 Colyseus latency simulation enabled \u2192 ${milliseconds}ms latency for round trip.`);
273
+ } else {
274
+ logger.warn(`\u{1F4F6}\uFE0F\u2757 Colyseus latency simulation disabled.`);
275
+ }
276
+ const halfwayMS = milliseconds / 2;
277
+ transport?.simulateLatency(halfwayMS);
278
+ if (_originalRoomOnMessage == null) {
279
+ _originalRoomOnMessage = Room.prototype["_onMessage"];
280
+ }
281
+ const originalOnMessage = _originalRoomOnMessage;
282
+ Room.prototype["_onMessage"] = milliseconds <= Number.EPSILON ? originalOnMessage : function(client, buffer) {
283
+ const cachedBuffer = Buffer.from(buffer);
284
+ setTimeout(() => originalOnMessage.call(this, client, cachedBuffer), halfwayMS);
285
+ };
286
+ }
287
+ function parseLatencyEnv() {
288
+ const value = process.env.COLYSEUS_LATENCY;
289
+ if (value === void 0 || value === "") {
290
+ return void 0;
291
+ }
292
+ const milliseconds = Number(value);
293
+ if (isNaN(milliseconds)) {
294
+ logger.warn(`\u{1F4F6}\uFE0F\u2757 Ignoring COLYSEUS_LATENCY: expected a number of milliseconds, got ${JSON.stringify(value)}.`);
295
+ return void 0;
296
+ }
297
+ return milliseconds;
298
+ }
271
299
  function isRegisteredHandler(value) {
272
300
  return value instanceof RegisteredHandler || typeof value === "object" && value !== null && "klass" in value;
273
301
  }
@@ -295,7 +323,9 @@ function defineServer(options) {
295
323
  return {
296
324
  options: serverOptions,
297
325
  router: routes,
298
- "~rooms": rooms
326
+ "~rooms": rooms,
327
+ // the vite plugin registers the transport before importing user code
328
+ simulateLatency: (milliseconds) => applySimulatedLatency(getTransport(), milliseconds)
299
329
  };
300
330
  }
301
331
  const server = new Server(serverOptions);
@@ -308,8 +338,10 @@ function defineRoom(roomKlass, defaultOptions) {
308
338
  }
309
339
  export {
310
340
  Server,
341
+ applySimulatedLatency,
311
342
  defineRoom,
312
343
  defineServer,
344
+ parseLatencyEnv,
313
345
  registerRoomDefinitions,
314
346
  unregisterRoomDefinitions
315
347
  };
@@ -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 { 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 private _originalRoomOnMessage: typeof Room.prototype['_onMessage'] | null = null;\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 * @param milliseconds round trip latency in milliseconds.\n */\n public simulateLatency(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 this.transport.simulateLatency(halfwayMS);\n\n if (this._originalRoomOnMessage == null) {\n this._originalRoomOnMessage = Room.prototype['_onMessage'];\n }\n\n const originalOnMessage = this._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 * 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 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\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 } 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": "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",
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": "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",
6
6
  "names": ["gracefullyShutdown", "greet", "err"]
7
7
  }
package/build/index.cjs CHANGED
@@ -66,6 +66,7 @@ __export(index_exports, {
66
66
  TimedEventException: () => import_RoomExceptions.TimedEventException,
67
67
  TimestepException: () => import_RoomExceptions.TimestepException,
68
68
  Transport: () => import_Transport.Transport,
69
+ applySimulatedLatency: () => import_Server.applySimulatedLatency,
69
70
  attachToTestRoom: () => import_RoomPlugin.attachToTestRoom,
70
71
  basicAuth: () => import_router.basicAuth,
71
72
  compileSanitizer: () => import_InputBuffer.compileSanitizer,
@@ -97,6 +98,7 @@ __export(index_exports, {
97
98
  isDevMode: () => import_DevMode.isDevMode,
98
99
  logger: () => import_Logger.logger,
99
100
  matchMaker: () => matchMaker,
101
+ parseLatencyEnv: () => import_Server.parseLatencyEnv,
100
102
  registerRoomDefinitions: () => import_Server.registerRoomDefinitions,
101
103
  requestFromIPC: () => import_IPC.requestFromIPC,
102
104
  runBeforeUpgrade: () => import_Transport.runBeforeUpgrade,
@@ -177,6 +179,7 @@ var import_Logger = require("./Logger.cjs");
177
179
  TimedEventException,
178
180
  TimestepException,
179
181
  Transport,
182
+ applySimulatedLatency,
180
183
  attachToTestRoom,
181
184
  basicAuth,
182
185
  compileSanitizer,
@@ -208,6 +211,7 @@ var import_Logger = require("./Logger.cjs");
208
211
  isDevMode,
209
212
  logger,
210
213
  matchMaker,
214
+ parseLatencyEnv,
211
215
  registerRoomDefinitions,
212
216
  requestFromIPC,
213
217
  runBeforeUpgrade,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts"],
4
- "sourcesContent": ["import { ClockTimer as Clock, Delayed } from '@colyseus/timer';\n\n// Shared types - re-export from @colyseus/shared-types for convenience\nexport {\n Protocol,\n ProtocolModifier,\n ErrorCode,\n CloseCode,\n ResponseStatus,\n type InferState,\n type ExtractRoomMessages,\n type ExtractRoomClientMessages,\n type MessageContext,\n type Rejection,\n type Resolution,\n type ExtractRejectReason,\n type ExtractResponseType,\n} from '@colyseus/shared-types';\n\n// Core classes\nexport { Server, defineRoom, defineServer, registerRoomDefinitions, unregisterRoomDefinitions, type RoomDefinitions, type ServerOptions, type SDKTypes } from './Server.ts';\nexport { Room, RoomInternalState, validate, type RoomOptions, type DefineInputOptions, type SimulationCallback, type FixedTimestepCallback, type StepContext, type MessageHandlerWithFormat, type Messages, type ExtractRoomState, type ExtractRoomMetadata, type ExtractRoomClient } from './Room.ts';\nexport { InputBufferImpl, compileSanitizer } from './input/InputBuffer.ts';\nexport { type InputAccessor, type InputAPI, type NormalizedInputOptions, type ConsumeOptions, type IdleInput, type IdleContext, type SanitizeInput, type NumericFieldsOf } from './input/types.ts';\nexport { Rewind, RewindView, type RewindOptions, type RewindMode } from './Rewind.ts';\nexport {\n RoomPlugin, definePlugins, attachToTestRoom,\n type RoomPluginOrder, type PluginDependencies, type RoomPluginClass,\n} from './RoomPlugin.ts';\nexport { getMessageBytes } from './Protocol.ts';\nexport { RegisteredHandler } from './matchmaker/RegisteredHandler.ts';\nexport { ServerError } from './errors/ServerError.ts';\n\nexport {\n type RoomException,\n type RoomMethodName,\n OnCreateException,\n OnAuthException,\n OnJoinException,\n OnLeaveException,\n OnDisposeException,\n OnMessageException,\n TimestepException,\n SimulationIntervalException,\n TimedEventException,\n} from './errors/RoomExceptions.ts';\n\n// MatchMaker\nimport * as matchMaker from './MatchMaker.ts';\nexport { matchMaker };\nexport { updateLobby, subscribeLobby } from './matchmaker/Lobby.ts';\nexport { createNodeMatchmakingMiddleware } from './router/node.ts';\n\n// Driver\nexport * from './matchmaker/LocalDriver/LocalDriver.ts';\nexport { initializeRoomCache } from './matchmaker/driver.ts';\n\n// Transport\nexport { type Client, type ClientPrivate, type AuthContext, ClientState, ClientArray, Transport, type ISendOptions, type BeforeUpgradeHandler, runBeforeUpgrade, createAuthContext, connectClientToRoom, enqueueClientRaw } from './Transport.ts';\n\n// Presence\nexport { type Presence } from './presence/Presence.ts';\nexport { LocalPresence } from './presence/LocalPresence.ts';\n\n// Serializers\nexport { type Serializer } from './serializer/Serializer.ts';\nexport { SchemaSerializer } from './serializer/SchemaSerializer.ts';\n\n// Utilities\nexport { Clock, Delayed };\nexport { generateId, Deferred, spliceOne, getBearerToken, dynamicImport } from './utils/Utils.ts';\nexport { isDevMode, setDevMode } from './utils/DevMode.ts';\n\n// IPC\nexport { subscribeIPC, requestFromIPC } from './IPC.ts';\n\n// Debug\nexport {\n debugMatchMaking,\n debugMessage,\n debugPatch,\n debugError,\n debugConnection,\n debugDriver,\n debugPresence,\n debugAndPrintError,\n} from './Debug.ts';\n\n// Default rooms\nexport { LobbyRoom } from './rooms/LobbyRoom.ts';\nexport { RelayRoom } from './rooms/RelayRoom.ts';\nexport { QueueRoom, type QueueOptions, type QueueMatchGroup, type QueueMatchTeam, type QueueClientData } from './rooms/QueueRoom.ts';\n\n// Router / Endpoints\nexport {\n createEndpoint,\n createInternalContext,\n createMiddleware,\n createRouter,\n basicAuth,\n type BasicAuthOptions,\n toNodeHandler,\n dualModeEndpoints,\n type Router,\n type RouterConfig,\n type Endpoint,\n type EndpointHandler,\n type EndpointOptions,\n type EndpointContext,\n type StrictEndpoint,\n type ExpressMiddleware,\n type NodeHandler,\n type DualModeHelpers,\n} from './router/index.ts';\n\n// Abstract logging support\nexport { logger } from './Logger.ts';\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA,4BAAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA6C;AAG7C,0BAcO;AAGP,oBAA8J;AAC9J,kBAA2R;AAC3R,yBAAkD;AAClD,mBAAgL;AAChL,oBAAwE;AACxE,wBAGO;AACP,sBAAgC;AAChC,+BAAkC;AAClC,yBAA4B;AAE5B,4BAYO;AAGP,iBAA4B;AAE5B,mBAA4C;AAC5C,kBAAgD;AAGhD,0BAAc,qDAtDd;AAuDA,oBAAoC;AAGpC,uBAAiO;AAGjO,sBAA8B;AAC9B,2BAA8B;AAG9B,wBAAgC;AAChC,8BAAiC;AAIjC,mBAA+E;AAC/E,qBAAsC;AAGtC,iBAA6C;AAG7C,mBASO;AAGP,uBAA0B;AAC1B,uBAA0B;AAC1B,uBAA8G;AAG9G,oBAmBO;AAGP,oBAAuB;",
4
+ "sourcesContent": ["import { ClockTimer as Clock, Delayed } from '@colyseus/timer';\n\n// Shared types - re-export from @colyseus/shared-types for convenience\nexport {\n Protocol,\n ProtocolModifier,\n ErrorCode,\n CloseCode,\n ResponseStatus,\n type InferState,\n type ExtractRoomMessages,\n type ExtractRoomClientMessages,\n type MessageContext,\n type Rejection,\n type Resolution,\n type ExtractRejectReason,\n type ExtractResponseType,\n} from '@colyseus/shared-types';\n\n// Core classes\nexport { Server, defineRoom, defineServer, registerRoomDefinitions, unregisterRoomDefinitions, applySimulatedLatency, parseLatencyEnv, type RoomDefinitions, type ServerOptions, type SDKTypes } from './Server.ts';\nexport { Room, RoomInternalState, validate, type RoomOptions, type DefineInputOptions, type SimulationCallback, type FixedTimestepCallback, type StepContext, type MessageHandlerWithFormat, type Messages, type ExtractRoomState, type ExtractRoomMetadata, type ExtractRoomClient } from './Room.ts';\nexport { InputBufferImpl, compileSanitizer } from './input/InputBuffer.ts';\nexport { type InputAccessor, type InputAPI, type NormalizedInputOptions, type ConsumeOptions, type IdleInput, type IdleContext, type SanitizeInput, type NumericFieldsOf } from './input/types.ts';\nexport { Rewind, RewindView, type RewindOptions, type RewindMode } from './Rewind.ts';\nexport {\n RoomPlugin, definePlugins, attachToTestRoom,\n type RoomPluginOrder, type PluginDependencies, type RoomPluginClass,\n} from './RoomPlugin.ts';\nexport { getMessageBytes } from './Protocol.ts';\nexport { RegisteredHandler } from './matchmaker/RegisteredHandler.ts';\nexport { ServerError } from './errors/ServerError.ts';\n\nexport {\n type RoomException,\n type RoomMethodName,\n OnCreateException,\n OnAuthException,\n OnJoinException,\n OnLeaveException,\n OnDisposeException,\n OnMessageException,\n TimestepException,\n SimulationIntervalException,\n TimedEventException,\n} from './errors/RoomExceptions.ts';\n\n// MatchMaker\nimport * as matchMaker from './MatchMaker.ts';\nexport { matchMaker };\nexport { updateLobby, subscribeLobby } from './matchmaker/Lobby.ts';\nexport { createNodeMatchmakingMiddleware } from './router/node.ts';\n\n// Driver\nexport * from './matchmaker/LocalDriver/LocalDriver.ts';\nexport { initializeRoomCache } from './matchmaker/driver.ts';\n\n// Transport\nexport { type Client, type ClientPrivate, type AuthContext, ClientState, ClientArray, Transport, type ISendOptions, type BeforeUpgradeHandler, runBeforeUpgrade, createAuthContext, connectClientToRoom, enqueueClientRaw } from './Transport.ts';\n\n// Presence\nexport { type Presence } from './presence/Presence.ts';\nexport { LocalPresence } from './presence/LocalPresence.ts';\n\n// Serializers\nexport { type Serializer } from './serializer/Serializer.ts';\nexport { SchemaSerializer } from './serializer/SchemaSerializer.ts';\n\n// Utilities\nexport { Clock, Delayed };\nexport { generateId, Deferred, spliceOne, getBearerToken, dynamicImport } from './utils/Utils.ts';\nexport { isDevMode, setDevMode } from './utils/DevMode.ts';\n\n// IPC\nexport { subscribeIPC, requestFromIPC } from './IPC.ts';\n\n// Debug\nexport {\n debugMatchMaking,\n debugMessage,\n debugPatch,\n debugError,\n debugConnection,\n debugDriver,\n debugPresence,\n debugAndPrintError,\n} from './Debug.ts';\n\n// Default rooms\nexport { LobbyRoom } from './rooms/LobbyRoom.ts';\nexport { RelayRoom } from './rooms/RelayRoom.ts';\nexport { QueueRoom, type QueueOptions, type QueueMatchGroup, type QueueMatchTeam, type QueueClientData } from './rooms/QueueRoom.ts';\n\n// Router / Endpoints\nexport {\n createEndpoint,\n createInternalContext,\n createMiddleware,\n createRouter,\n basicAuth,\n type BasicAuthOptions,\n toNodeHandler,\n dualModeEndpoints,\n type Router,\n type RouterConfig,\n type Endpoint,\n type EndpointHandler,\n type EndpointOptions,\n type EndpointContext,\n type StrictEndpoint,\n type ExpressMiddleware,\n type NodeHandler,\n type DualModeHelpers,\n} from './router/index.ts';\n\n// Abstract logging support\nexport { logger } from './Logger.ts';\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA,4BAAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA6C;AAG7C,0BAcO;AAGP,oBAAsM;AACtM,kBAA2R;AAC3R,yBAAkD;AAClD,mBAAgL;AAChL,oBAAwE;AACxE,wBAGO;AACP,sBAAgC;AAChC,+BAAkC;AAClC,yBAA4B;AAE5B,4BAYO;AAGP,iBAA4B;AAE5B,mBAA4C;AAC5C,kBAAgD;AAGhD,0BAAc,qDAtDd;AAuDA,oBAAoC;AAGpC,uBAAiO;AAGjO,sBAA8B;AAC9B,2BAA8B;AAG9B,wBAAgC;AAChC,8BAAiC;AAIjC,mBAA+E;AAC/E,qBAAsC;AAGtC,iBAA6C;AAG7C,mBASO;AAGP,uBAA0B;AAC1B,uBAA0B;AAC1B,uBAA8G;AAG9G,oBAmBO;AAGP,oBAAuB;",
6
6
  "names": ["Clock"]
7
7
  }
package/build/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ClockTimer as Clock, Delayed } from '@colyseus/timer';
2
2
  export { Protocol, ProtocolModifier, ErrorCode, CloseCode, ResponseStatus, type InferState, type ExtractRoomMessages, type ExtractRoomClientMessages, type MessageContext, type Rejection, type Resolution, type ExtractRejectReason, type ExtractResponseType, } from '@colyseus/shared-types';
3
- export { Server, defineRoom, defineServer, registerRoomDefinitions, unregisterRoomDefinitions, type RoomDefinitions, type ServerOptions, type SDKTypes } from './Server.ts';
3
+ export { Server, defineRoom, defineServer, registerRoomDefinitions, unregisterRoomDefinitions, applySimulatedLatency, parseLatencyEnv, type RoomDefinitions, type ServerOptions, type SDKTypes } from './Server.ts';
4
4
  export { Room, RoomInternalState, validate, type RoomOptions, type DefineInputOptions, type SimulationCallback, type FixedTimestepCallback, type StepContext, type MessageHandlerWithFormat, type Messages, type ExtractRoomState, type ExtractRoomMetadata, type ExtractRoomClient } from './Room.ts';
5
5
  export { InputBufferImpl, compileSanitizer } from './input/InputBuffer.ts';
6
6
  export { type InputAccessor, type InputAPI, type NormalizedInputOptions, type ConsumeOptions, type IdleInput, type IdleContext, type SanitizeInput, type NumericFieldsOf } from './input/types.ts';
package/build/index.mjs CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  CloseCode,
8
8
  ResponseStatus
9
9
  } from "@colyseus/shared-types";
10
- import { Server, defineRoom, defineServer, registerRoomDefinitions, unregisterRoomDefinitions } from "./Server.mjs";
10
+ import { Server, defineRoom, defineServer, registerRoomDefinitions, unregisterRoomDefinitions, applySimulatedLatency, parseLatencyEnv } from "./Server.mjs";
11
11
  import { Room, RoomInternalState, validate } from "./Room.mjs";
12
12
  import { InputBufferImpl, compileSanitizer } from "./input/InputBuffer.mjs";
13
13
  import {} from "./input/types.mjs";
@@ -102,6 +102,7 @@ export {
102
102
  TimedEventException,
103
103
  TimestepException,
104
104
  Transport,
105
+ applySimulatedLatency,
105
106
  attachToTestRoom,
106
107
  basicAuth,
107
108
  compileSanitizer,
@@ -133,6 +134,7 @@ export {
133
134
  isDevMode,
134
135
  logger,
135
136
  matchMaker,
137
+ parseLatencyEnv,
136
138
  registerRoomDefinitions,
137
139
  requestFromIPC,
138
140
  runBeforeUpgrade,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts"],
4
- "sourcesContent": ["import { ClockTimer as Clock, Delayed } from '@colyseus/timer';\n\n// Shared types - re-export from @colyseus/shared-types for convenience\nexport {\n Protocol,\n ProtocolModifier,\n ErrorCode,\n CloseCode,\n ResponseStatus,\n type InferState,\n type ExtractRoomMessages,\n type ExtractRoomClientMessages,\n type MessageContext,\n type Rejection,\n type Resolution,\n type ExtractRejectReason,\n type ExtractResponseType,\n} from '@colyseus/shared-types';\n\n// Core classes\nexport { Server, defineRoom, defineServer, registerRoomDefinitions, unregisterRoomDefinitions, type RoomDefinitions, type ServerOptions, type SDKTypes } from './Server.ts';\nexport { Room, RoomInternalState, validate, type RoomOptions, type DefineInputOptions, type SimulationCallback, type FixedTimestepCallback, type StepContext, type MessageHandlerWithFormat, type Messages, type ExtractRoomState, type ExtractRoomMetadata, type ExtractRoomClient } from './Room.ts';\nexport { InputBufferImpl, compileSanitizer } from './input/InputBuffer.ts';\nexport { type InputAccessor, type InputAPI, type NormalizedInputOptions, type ConsumeOptions, type IdleInput, type IdleContext, type SanitizeInput, type NumericFieldsOf } from './input/types.ts';\nexport { Rewind, RewindView, type RewindOptions, type RewindMode } from './Rewind.ts';\nexport {\n RoomPlugin, definePlugins, attachToTestRoom,\n type RoomPluginOrder, type PluginDependencies, type RoomPluginClass,\n} from './RoomPlugin.ts';\nexport { getMessageBytes } from './Protocol.ts';\nexport { RegisteredHandler } from './matchmaker/RegisteredHandler.ts';\nexport { ServerError } from './errors/ServerError.ts';\n\nexport {\n type RoomException,\n type RoomMethodName,\n OnCreateException,\n OnAuthException,\n OnJoinException,\n OnLeaveException,\n OnDisposeException,\n OnMessageException,\n TimestepException,\n SimulationIntervalException,\n TimedEventException,\n} from './errors/RoomExceptions.ts';\n\n// MatchMaker\nimport * as matchMaker from './MatchMaker.ts';\nexport { matchMaker };\nexport { updateLobby, subscribeLobby } from './matchmaker/Lobby.ts';\nexport { createNodeMatchmakingMiddleware } from './router/node.ts';\n\n// Driver\nexport * from './matchmaker/LocalDriver/LocalDriver.ts';\nexport { initializeRoomCache } from './matchmaker/driver.ts';\n\n// Transport\nexport { type Client, type ClientPrivate, type AuthContext, ClientState, ClientArray, Transport, type ISendOptions, type BeforeUpgradeHandler, runBeforeUpgrade, createAuthContext, connectClientToRoom, enqueueClientRaw } from './Transport.ts';\n\n// Presence\nexport { type Presence } from './presence/Presence.ts';\nexport { LocalPresence } from './presence/LocalPresence.ts';\n\n// Serializers\nexport { type Serializer } from './serializer/Serializer.ts';\nexport { SchemaSerializer } from './serializer/SchemaSerializer.ts';\n\n// Utilities\nexport { Clock, Delayed };\nexport { generateId, Deferred, spliceOne, getBearerToken, dynamicImport } from './utils/Utils.ts';\nexport { isDevMode, setDevMode } from './utils/DevMode.ts';\n\n// IPC\nexport { subscribeIPC, requestFromIPC } from './IPC.ts';\n\n// Debug\nexport {\n debugMatchMaking,\n debugMessage,\n debugPatch,\n debugError,\n debugConnection,\n debugDriver,\n debugPresence,\n debugAndPrintError,\n} from './Debug.ts';\n\n// Default rooms\nexport { LobbyRoom } from './rooms/LobbyRoom.ts';\nexport { RelayRoom } from './rooms/RelayRoom.ts';\nexport { QueueRoom, type QueueOptions, type QueueMatchGroup, type QueueMatchTeam, type QueueClientData } from './rooms/QueueRoom.ts';\n\n// Router / Endpoints\nexport {\n createEndpoint,\n createInternalContext,\n createMiddleware,\n createRouter,\n basicAuth,\n type BasicAuthOptions,\n toNodeHandler,\n dualModeEndpoints,\n type Router,\n type RouterConfig,\n type Endpoint,\n type EndpointHandler,\n type EndpointOptions,\n type EndpointContext,\n type StrictEndpoint,\n type ExpressMiddleware,\n type NodeHandler,\n type DualModeHelpers,\n} from './router/index.ts';\n\n// Abstract logging support\nexport { logger } from './Logger.ts';\n"],
5
- "mappings": ";AAAA,SAAS,cAAc,OAAO,eAAe;AAG7C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OASK;AAGP,SAAS,QAAQ,YAAY,cAAc,yBAAyB,iCAA0F;AAC9J,SAAS,MAAM,mBAAmB,gBAAyP;AAC3R,SAAS,iBAAiB,wBAAwB;AAClD,eAAgL;AAChL,SAAS,QAAQ,kBAAuD;AACxE;AAAA,EACE;AAAA,EAAY;AAAA,EAAe;AAAA,OAEtB;AACP,SAAS,uBAAuB;AAChC,SAAS,yBAAyB;AAClC,SAAS,mBAAmB;AAE5B;AAAA,EAGE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,YAAY,gBAAgB;AAE5B,SAAS,aAAa,sBAAsB;AAC5C,SAAS,uCAAuC;AAGhD,cAAc;AACd,SAAS,2BAA2B;AAGpC,SAA4D,aAAa,aAAa,WAAyD,kBAAkB,mBAAmB,qBAAqB,wBAAwB;AAGjO,eAA8B;AAC9B,SAAS,qBAAqB;AAG9B,eAAgC;AAChC,SAAS,wBAAwB;AAIjC,SAAS,YAAY,UAAU,WAAW,gBAAgB,qBAAqB;AAC/E,SAAS,WAAW,kBAAkB;AAGtC,SAAS,cAAc,sBAAsB;AAG7C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,SAAS,iBAAiB;AAC1B,SAAS,iBAAiB;AAC1B,SAAS,iBAAqG;AAG9G;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,OAWK;AAGP,SAAS,cAAc;",
4
+ "sourcesContent": ["import { ClockTimer as Clock, Delayed } from '@colyseus/timer';\n\n// Shared types - re-export from @colyseus/shared-types for convenience\nexport {\n Protocol,\n ProtocolModifier,\n ErrorCode,\n CloseCode,\n ResponseStatus,\n type InferState,\n type ExtractRoomMessages,\n type ExtractRoomClientMessages,\n type MessageContext,\n type Rejection,\n type Resolution,\n type ExtractRejectReason,\n type ExtractResponseType,\n} from '@colyseus/shared-types';\n\n// Core classes\nexport { Server, defineRoom, defineServer, registerRoomDefinitions, unregisterRoomDefinitions, applySimulatedLatency, parseLatencyEnv, type RoomDefinitions, type ServerOptions, type SDKTypes } from './Server.ts';\nexport { Room, RoomInternalState, validate, type RoomOptions, type DefineInputOptions, type SimulationCallback, type FixedTimestepCallback, type StepContext, type MessageHandlerWithFormat, type Messages, type ExtractRoomState, type ExtractRoomMetadata, type ExtractRoomClient } from './Room.ts';\nexport { InputBufferImpl, compileSanitizer } from './input/InputBuffer.ts';\nexport { type InputAccessor, type InputAPI, type NormalizedInputOptions, type ConsumeOptions, type IdleInput, type IdleContext, type SanitizeInput, type NumericFieldsOf } from './input/types.ts';\nexport { Rewind, RewindView, type RewindOptions, type RewindMode } from './Rewind.ts';\nexport {\n RoomPlugin, definePlugins, attachToTestRoom,\n type RoomPluginOrder, type PluginDependencies, type RoomPluginClass,\n} from './RoomPlugin.ts';\nexport { getMessageBytes } from './Protocol.ts';\nexport { RegisteredHandler } from './matchmaker/RegisteredHandler.ts';\nexport { ServerError } from './errors/ServerError.ts';\n\nexport {\n type RoomException,\n type RoomMethodName,\n OnCreateException,\n OnAuthException,\n OnJoinException,\n OnLeaveException,\n OnDisposeException,\n OnMessageException,\n TimestepException,\n SimulationIntervalException,\n TimedEventException,\n} from './errors/RoomExceptions.ts';\n\n// MatchMaker\nimport * as matchMaker from './MatchMaker.ts';\nexport { matchMaker };\nexport { updateLobby, subscribeLobby } from './matchmaker/Lobby.ts';\nexport { createNodeMatchmakingMiddleware } from './router/node.ts';\n\n// Driver\nexport * from './matchmaker/LocalDriver/LocalDriver.ts';\nexport { initializeRoomCache } from './matchmaker/driver.ts';\n\n// Transport\nexport { type Client, type ClientPrivate, type AuthContext, ClientState, ClientArray, Transport, type ISendOptions, type BeforeUpgradeHandler, runBeforeUpgrade, createAuthContext, connectClientToRoom, enqueueClientRaw } from './Transport.ts';\n\n// Presence\nexport { type Presence } from './presence/Presence.ts';\nexport { LocalPresence } from './presence/LocalPresence.ts';\n\n// Serializers\nexport { type Serializer } from './serializer/Serializer.ts';\nexport { SchemaSerializer } from './serializer/SchemaSerializer.ts';\n\n// Utilities\nexport { Clock, Delayed };\nexport { generateId, Deferred, spliceOne, getBearerToken, dynamicImport } from './utils/Utils.ts';\nexport { isDevMode, setDevMode } from './utils/DevMode.ts';\n\n// IPC\nexport { subscribeIPC, requestFromIPC } from './IPC.ts';\n\n// Debug\nexport {\n debugMatchMaking,\n debugMessage,\n debugPatch,\n debugError,\n debugConnection,\n debugDriver,\n debugPresence,\n debugAndPrintError,\n} from './Debug.ts';\n\n// Default rooms\nexport { LobbyRoom } from './rooms/LobbyRoom.ts';\nexport { RelayRoom } from './rooms/RelayRoom.ts';\nexport { QueueRoom, type QueueOptions, type QueueMatchGroup, type QueueMatchTeam, type QueueClientData } from './rooms/QueueRoom.ts';\n\n// Router / Endpoints\nexport {\n createEndpoint,\n createInternalContext,\n createMiddleware,\n createRouter,\n basicAuth,\n type BasicAuthOptions,\n toNodeHandler,\n dualModeEndpoints,\n type Router,\n type RouterConfig,\n type Endpoint,\n type EndpointHandler,\n type EndpointOptions,\n type EndpointContext,\n type StrictEndpoint,\n type ExpressMiddleware,\n type NodeHandler,\n type DualModeHelpers,\n} from './router/index.ts';\n\n// Abstract logging support\nexport { logger } from './Logger.ts';\n"],
5
+ "mappings": ";AAAA,SAAS,cAAc,OAAO,eAAe;AAG7C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OASK;AAGP,SAAS,QAAQ,YAAY,cAAc,yBAAyB,2BAA2B,uBAAuB,uBAAgF;AACtM,SAAS,MAAM,mBAAmB,gBAAyP;AAC3R,SAAS,iBAAiB,wBAAwB;AAClD,eAAgL;AAChL,SAAS,QAAQ,kBAAuD;AACxE;AAAA,EACE;AAAA,EAAY;AAAA,EAAe;AAAA,OAEtB;AACP,SAAS,uBAAuB;AAChC,SAAS,yBAAyB;AAClC,SAAS,mBAAmB;AAE5B;AAAA,EAGE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,YAAY,gBAAgB;AAE5B,SAAS,aAAa,sBAAsB;AAC5C,SAAS,uCAAuC;AAGhD,cAAc;AACd,SAAS,2BAA2B;AAGpC,SAA4D,aAAa,aAAa,WAAyD,kBAAkB,mBAAmB,qBAAqB,wBAAwB;AAGjO,eAA8B;AAC9B,SAAS,qBAAqB;AAG9B,eAAgC;AAChC,SAAS,wBAAwB;AAIjC,SAAS,YAAY,UAAU,WAAW,gBAAgB,qBAAqB;AAC/E,SAAS,WAAW,kBAAkB;AAGtC,SAAS,cAAc,sBAAsB;AAG7C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,SAAS,iBAAiB;AAC1B,SAAS,iBAAiB;AAC1B,SAAS,iBAAqG;AAG9G;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,OAWK;AAGP,SAAS,cAAc;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colyseus/core",
3
- "version": "0.18.6",
3
+ "version": "0.18.7",
4
4
  "description": "Multiplayer Framework for Node.js.",
5
5
  "type": "module",
6
6
  "input": "./src/index.ts",
@@ -60,16 +60,16 @@
60
60
  "express": "^5.0.0",
61
61
  "vitest": "^3.1.1",
62
62
  "@colyseus/redis-driver": "^0.18.2",
63
- "@colyseus/tools": "^0.18.2",
64
- "@colyseus/redis-presence": "^0.18.2"
63
+ "@colyseus/redis-presence": "^0.18.2",
64
+ "@colyseus/tools": "^0.18.2"
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/better-call": "^1.3.1",
72
71
  "@colyseus/auth": "^0.18.1",
72
+ "@colyseus/better-call": "^1.3.1",
73
73
  "@colyseus/ws-transport": "^0.18.1"
74
74
  },
75
75
  "peerDependenciesMeta": {
package/src/Server.ts CHANGED
@@ -11,7 +11,7 @@ import { Deferred, registerGracefulShutdown, dynamicImport, type Type } from './
11
11
 
12
12
  import type { Presence } from "./presence/Presence.ts";
13
13
 
14
- import { setTransport, Transport } from './Transport.ts';
14
+ import { getTransport, setTransport, Transport } from './Transport.ts';
15
15
  import { logger, setLogger } from './Logger.ts';
16
16
  import { setDevMode, isDevMode } from './utils/DevMode.ts';
17
17
  import { type Router, bindRouterToTransport, createRouter } from './router/index.ts';
@@ -125,8 +125,6 @@ export class Server<
125
125
 
126
126
  protected _onTransportReady = new Deferred<Transport>();
127
127
 
128
- private _originalRoomOnMessage: typeof Room.prototype['_onMessage'] | null = null;
129
-
130
128
  // Implicit default for callers that omit explicit Server reference — e.g.
131
129
  // `playground()` reads `Server.current.router.endpoints` at request time.
132
130
  // Last-construction wins; multi-server setups should reference instances
@@ -384,7 +382,7 @@ export class Server<
384
382
  // this is going to lock all rooms and wait for them to be disposed
385
383
  await matchMaker.gracefullyShutdown();
386
384
 
387
- this.transport.shutdown();
385
+ this.transport?.shutdown();
388
386
  this.presence?.shutdown();
389
387
  await this.driver?.shutdown();
390
388
 
@@ -403,29 +401,19 @@ export class Server<
403
401
 
404
402
  /**
405
403
  * Add simulated latency between client and server.
404
+ *
405
+ * May be called at any time — before or after `listen()`. Also available
406
+ * via the `COLYSEUS_LATENCY` environment variable (round-trip ms), which
407
+ * overrides calls made before the server boots.
408
+ *
406
409
  * @param milliseconds round trip latency in milliseconds.
407
410
  */
408
411
  public simulateLatency(milliseconds: number) {
409
- if (milliseconds > 0) {
410
- logger.warn(`📶️❗ Colyseus latency simulation enabled → ${milliseconds}ms latency for round trip.`);
411
- } else {
412
- logger.warn(`📶️❗ Colyseus latency simulation disabled.`);
413
- }
414
-
415
- const halfwayMS = (milliseconds / 2);
416
- this.transport.simulateLatency(halfwayMS);
417
-
418
- if (this._originalRoomOnMessage == null) {
419
- this._originalRoomOnMessage = Room.prototype['_onMessage'];
420
- }
421
-
422
- const originalOnMessage = this._originalRoomOnMessage;
423
-
424
- Room.prototype['_onMessage'] = milliseconds <= Number.EPSILON ? originalOnMessage : function (this: Room, client, buffer) {
425
- // uWebSockets.js: duplicate buffer because it is cleared at native layer before the timeout.
426
- const cachedBuffer = Buffer.from(buffer);
427
- setTimeout(() => originalOnMessage.call(this, client, cachedBuffer), halfwayMS);
428
- };
412
+ // transport may still be resolving (constructor's attach() imports the default one)
413
+ this._onTransportReady.then(
414
+ (transport) => applySimulatedLatency(transport, milliseconds),
415
+ () => {/* missing-transport error already surfaces via attach()/listen() */},
416
+ );
429
417
  }
430
418
 
431
419
  /**
@@ -447,6 +435,11 @@ export class Server<
447
435
  private async _bootServices(): Promise<void> {
448
436
  const { beforeListen, database, express } = this.options;
449
437
 
438
+ // boot runs after module evaluation, so the env var wins over
439
+ // simulateLatency() calls made at the top level of user code
440
+ const envLatency = parseLatencyEnv();
441
+ if (envLatency !== undefined) { this.simulateLatency(envLatency); }
442
+
450
443
  if (beforeListen) { await beforeListen(); }
451
444
  if (database) { await database.boot(); }
452
445
 
@@ -505,6 +498,55 @@ export class Server<
505
498
  () => Promise.resolve()
506
499
  }
507
500
 
501
+ let _originalRoomOnMessage: typeof Room.prototype['_onMessage'] | null = null;
502
+
503
+ /**
504
+ * Applies both halves of the round-trip latency simulation: delays the
505
+ * transport's outgoing messages and the Room's incoming message handling.
506
+ *
507
+ * Prefer `server.simulateLatency()` — this is the shared core behind it, the
508
+ * dev-mode `defineServer()` object and the `COLYSEUS_LATENCY` env var, where
509
+ * no `Server` instance (or transport, yet) may exist.
510
+ */
511
+ export function applySimulatedLatency(transport: Transport | undefined, milliseconds: number) {
512
+ if (milliseconds > 0) {
513
+ logger.warn(`📶️❗ Colyseus latency simulation enabled → ${milliseconds}ms latency for round trip.`);
514
+ } else {
515
+ logger.warn(`📶️❗ Colyseus latency simulation disabled.`);
516
+ }
517
+
518
+ const halfwayMS = (milliseconds / 2);
519
+ transport?.simulateLatency(halfwayMS);
520
+
521
+ if (_originalRoomOnMessage == null) {
522
+ _originalRoomOnMessage = Room.prototype['_onMessage'];
523
+ }
524
+
525
+ const originalOnMessage = _originalRoomOnMessage;
526
+
527
+ Room.prototype['_onMessage'] = milliseconds <= Number.EPSILON ? originalOnMessage : function (this: Room, client, buffer) {
528
+ // uWebSockets.js: duplicate buffer because it is cleared at native layer before the timeout.
529
+ const cachedBuffer = Buffer.from(buffer);
530
+ setTimeout(() => originalOnMessage.call(this, client, cachedBuffer), halfwayMS);
531
+ };
532
+ }
533
+
534
+ /**
535
+ * Reads the `COLYSEUS_LATENCY` environment variable (round-trip milliseconds).
536
+ * Returns `undefined` when absent or empty; a non-numeric value warns and is ignored.
537
+ */
538
+ export function parseLatencyEnv(): number | undefined {
539
+ const value = process.env.COLYSEUS_LATENCY;
540
+ if (value === undefined || value === '') { return undefined; }
541
+
542
+ const milliseconds = Number(value);
543
+ if (isNaN(milliseconds)) {
544
+ logger.warn(`📶️❗ Ignoring COLYSEUS_LATENCY: expected a number of milliseconds, got ${JSON.stringify(value)}.`);
545
+ return undefined;
546
+ }
547
+ return milliseconds;
548
+ }
549
+
508
550
  export type RoomDefinitions = Record<string, RegisteredHandler | Type<Room>>;
509
551
 
510
552
  function isRegisteredHandler(value: RegisteredHandler | Type<Room>): value is RegisteredHandler {
@@ -562,6 +604,8 @@ export function defineServer<
562
604
  options: serverOptions,
563
605
  router: routes,
564
606
  '~rooms': rooms,
607
+ // the vite plugin registers the transport before importing user code
608
+ simulateLatency: (milliseconds: number) => applySimulatedLatency(getTransport(), milliseconds),
565
609
  } as unknown as Server<T, R>;
566
610
  }
567
611
 
package/src/index.ts CHANGED
@@ -18,7 +18,7 @@ export {
18
18
  } from '@colyseus/shared-types';
19
19
 
20
20
  // Core classes
21
- export { Server, defineRoom, defineServer, registerRoomDefinitions, unregisterRoomDefinitions, type RoomDefinitions, type ServerOptions, type SDKTypes } from './Server.ts';
21
+ export { Server, defineRoom, defineServer, registerRoomDefinitions, unregisterRoomDefinitions, applySimulatedLatency, parseLatencyEnv, type RoomDefinitions, type ServerOptions, type SDKTypes } from './Server.ts';
22
22
  export { Room, RoomInternalState, validate, type RoomOptions, type DefineInputOptions, type SimulationCallback, type FixedTimestepCallback, type StepContext, type MessageHandlerWithFormat, type Messages, type ExtractRoomState, type ExtractRoomMetadata, type ExtractRoomClient } from './Room.ts';
23
23
  export { InputBufferImpl, compileSanitizer } from './input/InputBuffer.ts';
24
24
  export { type InputAccessor, type InputAPI, type NormalizedInputOptions, type ConsumeOptions, type IdleInput, type IdleContext, type SanitizeInput, type NumericFieldsOf } from './input/types.ts';