@colyseus/core 0.18.0 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/MatchMaker.cjs +3 -7
- package/build/MatchMaker.cjs.map +2 -2
- package/build/MatchMaker.mjs +4 -8
- package/build/MatchMaker.mjs.map +2 -2
- package/build/Protocol.cjs +3 -3
- package/build/Protocol.cjs.map +1 -1
- package/build/Protocol.d.ts +3 -3
- package/build/Protocol.mjs +3 -3
- package/build/Protocol.mjs.map +1 -1
- package/build/Rewind.cjs +385 -0
- package/build/Rewind.cjs.map +7 -0
- package/build/Rewind.d.ts +291 -0
- package/build/Rewind.mjs +359 -0
- package/build/Rewind.mjs.map +7 -0
- package/build/Room.cjs +280 -281
- package/build/Room.cjs.map +3 -3
- package/build/Room.d.ts +185 -81
- package/build/Room.mjs +286 -286
- package/build/Room.mjs.map +2 -2
- package/build/RoomMessages.cjs +265 -0
- package/build/RoomMessages.cjs.map +7 -0
- package/build/RoomMessages.d.ts +49 -0
- package/build/RoomMessages.mjs +240 -0
- package/build/RoomMessages.mjs.map +7 -0
- package/build/Server.cjs +88 -31
- package/build/Server.cjs.map +2 -2
- package/build/Server.d.ts +36 -0
- package/build/Server.mjs +88 -31
- package/build/Server.mjs.map +2 -2
- package/build/Transport.cjs +26 -0
- package/build/Transport.cjs.map +2 -2
- package/build/Transport.d.ts +69 -2
- package/build/Transport.mjs +25 -0
- package/build/Transport.mjs.map +2 -2
- package/build/errors/RoomExceptions.cjs +7 -4
- package/build/errors/RoomExceptions.cjs.map +2 -2
- package/build/errors/RoomExceptions.d.ts +15 -3
- package/build/errors/RoomExceptions.mjs +5 -3
- package/build/errors/RoomExceptions.mjs.map +2 -2
- package/build/index.cjs +19 -2
- package/build/index.cjs.map +2 -2
- package/build/index.d.ts +7 -4
- package/build/index.mjs +17 -4
- package/build/index.mjs.map +2 -2
- package/build/input/InputBuffer.cjs +439 -16
- package/build/input/InputBuffer.cjs.map +2 -2
- package/build/input/InputBuffer.d.ts +186 -96
- package/build/input/InputBuffer.mjs +436 -15
- package/build/input/InputBuffer.mjs.map +2 -2
- package/build/input/RoomInput.cjs +306 -0
- package/build/input/RoomInput.cjs.map +7 -0
- package/build/input/RoomInput.d.ts +91 -0
- package/build/input/RoomInput.mjs +287 -0
- package/build/input/RoomInput.mjs.map +7 -0
- package/build/input/types.cjs +18 -0
- package/build/input/types.cjs.map +7 -0
- package/build/input/types.d.ts +476 -0
- package/build/input/types.mjs +0 -0
- package/build/input/types.mjs.map +7 -0
- package/build/rooms/QueueRoom.cjs +1 -1
- package/build/rooms/QueueRoom.cjs.map +2 -2
- package/build/rooms/QueueRoom.mjs +1 -1
- package/build/rooms/QueueRoom.mjs.map +2 -2
- package/build/router/default_routes.cjs +4 -1
- package/build/router/default_routes.cjs.map +2 -2
- package/build/router/default_routes.mjs +4 -1
- package/build/router/default_routes.mjs.map +2 -2
- package/build/router/index.cjs +6 -1
- package/build/router/index.cjs.map +2 -2
- package/build/router/index.mjs +6 -1
- package/build/router/index.mjs.map +2 -2
- package/build/router/node.cjs +37 -2
- package/build/router/node.cjs.map +2 -2
- package/build/router/node.d.ts +10 -0
- package/build/router/node.mjs +35 -1
- package/build/router/node.mjs.map +2 -2
- package/build/serializer/NoneSerializer.cjs +2 -2
- package/build/serializer/NoneSerializer.cjs.map +2 -2
- package/build/serializer/NoneSerializer.d.ts +3 -3
- package/build/serializer/NoneSerializer.mjs +2 -2
- package/build/serializer/NoneSerializer.mjs.map +2 -2
- package/build/serializer/SchemaSerializer.cjs +44 -14
- package/build/serializer/SchemaSerializer.cjs.map +2 -2
- package/build/serializer/SchemaSerializer.d.ts +3 -3
- package/build/serializer/SchemaSerializer.mjs +46 -16
- package/build/serializer/SchemaSerializer.mjs.map +2 -2
- package/build/serializer/Serializer.cjs.map +1 -1
- package/build/serializer/Serializer.d.ts +12 -2
- package/build/utils/DevMode.cjs +14 -14
- package/build/utils/DevMode.cjs.map +2 -2
- package/build/utils/DevMode.d.ts +0 -2
- package/build/utils/DevMode.mjs +14 -12
- package/build/utils/DevMode.mjs.map +2 -2
- package/build/utils/Env.cjs +8 -4
- package/build/utils/Env.cjs.map +3 -3
- package/build/utils/Env.mjs +8 -4
- package/build/utils/Env.mjs.map +2 -2
- package/build/utils/Utils.cjs +3 -0
- package/build/utils/Utils.cjs.map +2 -2
- package/build/utils/Utils.d.ts +8 -0
- package/build/utils/Utils.mjs +2 -0
- package/build/utils/Utils.mjs.map +2 -2
- package/package.json +11 -11
- package/src/MatchMaker.ts +7 -9
- package/src/Protocol.ts +3 -3
- package/src/Rewind.ts +572 -0
- package/src/Room.ts +409 -448
- package/src/RoomMessages.ts +342 -0
- package/src/Server.ts +112 -38
- package/src/Transport.ts +97 -2
- package/src/errors/RoomExceptions.ts +18 -4
- package/src/index.ts +13 -2
- package/src/input/InputBuffer.ts +464 -107
- package/src/input/RoomInput.ts +337 -0
- package/src/input/types.ts +503 -0
- package/src/rooms/QueueRoom.ts +1 -1
- package/src/router/default_routes.ts +6 -1
- package/src/router/index.ts +6 -1
- package/src/router/node.ts +44 -0
- package/src/serializer/NoneSerializer.ts +3 -3
- package/src/serializer/SchemaSerializer.ts +110 -19
- package/src/serializer/Serializer.ts +13 -2
- package/src/utils/DevMode.ts +18 -13
- package/src/utils/Env.ts +12 -4
- package/src/utils/Utils.ts +9 -0
package/build/Server.cjs
CHANGED
|
@@ -43,12 +43,11 @@ var matchMaker = __toESM(require("./MatchMaker.cjs"), 1);
|
|
|
43
43
|
var import_RegisteredHandler = require("./matchmaker/RegisteredHandler.cjs");
|
|
44
44
|
var import_Room = require("./Room.cjs");
|
|
45
45
|
var import_Utils = require("./utils/Utils.cjs");
|
|
46
|
-
var import_LocalPresence = require("./presence/LocalPresence.cjs");
|
|
47
|
-
var import_LocalDriver = require("./matchmaker/LocalDriver/LocalDriver.cjs");
|
|
48
46
|
var import_Transport = require("./Transport.cjs");
|
|
49
47
|
var import_Logger = require("./Logger.cjs");
|
|
50
48
|
var import_DevMode = require("./utils/DevMode.cjs");
|
|
51
49
|
var import_router = require("./router/index.cjs");
|
|
50
|
+
var import_node = require("./router/node.cjs");
|
|
52
51
|
var import_shared_types = require("@colyseus/shared-types");
|
|
53
52
|
var import_default_routes = require("./router/default_routes.cjs");
|
|
54
53
|
var Server = class _Server {
|
|
@@ -62,18 +61,19 @@ var Server = class _Server {
|
|
|
62
61
|
greet: greet2 = true
|
|
63
62
|
} = options;
|
|
64
63
|
(0, import_DevMode.setDevMode)(options.devMode === true);
|
|
65
|
-
this.presence = options.presence || new import_LocalPresence.LocalPresence();
|
|
66
|
-
this.driver = options.driver || new import_LocalDriver.LocalDriver();
|
|
67
64
|
this.options = options;
|
|
68
65
|
this.greet = greet2;
|
|
69
66
|
_Server.current = this;
|
|
70
67
|
this.attach(options);
|
|
71
68
|
matchMaker.setup(
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
options.presence,
|
|
70
|
+
options.driver,
|
|
74
71
|
options.publicAddress,
|
|
75
72
|
options.selectProcessIdToCreateRoom
|
|
76
|
-
)
|
|
73
|
+
).then(() => {
|
|
74
|
+
this.presence = matchMaker.presence;
|
|
75
|
+
this.driver = matchMaker.driver;
|
|
76
|
+
});
|
|
77
77
|
if (gracefullyShutdown2) {
|
|
78
78
|
(0, import_Utils.registerGracefulShutdown)((err) => this.gracefullyShutdown(true, err));
|
|
79
79
|
}
|
|
@@ -94,21 +94,7 @@ var Server = class _Server {
|
|
|
94
94
|
* @param listeningListener
|
|
95
95
|
*/
|
|
96
96
|
async listen(port, hostname, backlog, listeningListener) {
|
|
97
|
-
|
|
98
|
-
if (beforeListen) {
|
|
99
|
-
await beforeListen();
|
|
100
|
-
}
|
|
101
|
-
if (database) {
|
|
102
|
-
await database.boot();
|
|
103
|
-
}
|
|
104
|
-
await this._applyRouterDefaults();
|
|
105
|
-
if (express) {
|
|
106
|
-
await this._onTransportReady;
|
|
107
|
-
if (this.transport.getExpressApp) {
|
|
108
|
-
const expressApp = await this.transport.getExpressApp();
|
|
109
|
-
await express(expressApp);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
97
|
+
await this._bootServices();
|
|
112
98
|
if (process.env.COLYSEUS_CLOUD !== void 0) {
|
|
113
99
|
if (typeof hostname === "number") {
|
|
114
100
|
hostname = void 0;
|
|
@@ -129,17 +115,11 @@ var Server = class _Server {
|
|
|
129
115
|
}
|
|
130
116
|
await this._onTransportReady;
|
|
131
117
|
return new Promise((resolve, reject) => {
|
|
132
|
-
(0, import_Transport.setTransport)(this.transport);
|
|
133
118
|
this.transport.listen(port, hostname, backlog, (err) => {
|
|
134
119
|
if (this.transport.server) {
|
|
135
120
|
this.transport.server.on("error", (err2) => reject(err2));
|
|
136
121
|
}
|
|
137
|
-
|
|
138
|
-
this.router = (0, import_default_routes.getDefaultRouter)();
|
|
139
|
-
} else {
|
|
140
|
-
this.router = this.router.extend({ ...(0, import_default_routes.getDefaultRouter)().endpoints });
|
|
141
|
-
}
|
|
142
|
-
(0, import_router.bindRouterToTransport)(this.transport, this.router, this.options.express !== void 0);
|
|
122
|
+
this.bindRoutes();
|
|
143
123
|
if (listeningListener) {
|
|
144
124
|
listeningListener(err);
|
|
145
125
|
}
|
|
@@ -151,6 +131,62 @@ var Server = class _Server {
|
|
|
151
131
|
});
|
|
152
132
|
});
|
|
153
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Prepare matchmaking and HTTP routes, then return the underlying
|
|
136
|
+
* `http.Server` *without* binding to a port.
|
|
137
|
+
*
|
|
138
|
+
* Use this on serverless platforms that consume an exported HTTP server
|
|
139
|
+
* instead of a listening one. Vercel, for example, drives the request handler
|
|
140
|
+
* of a default-exported server (its Express/Hono WebSocket examples use
|
|
141
|
+
* `export default server`), whereas calling `listen()` there selects Vercel's
|
|
142
|
+
* "captured server" path, which does not invoke Express-style app handlers.
|
|
143
|
+
*
|
|
144
|
+
* The transport must be created with an `http.Server` so it can be exported.
|
|
145
|
+
* Vercel additionally needs `package.json` `"main"` pointing at the entrypoint:
|
|
146
|
+
*
|
|
147
|
+
* ```ts
|
|
148
|
+
* import { createServer } from "node:http";
|
|
149
|
+
* const httpServer = createServer();
|
|
150
|
+
* const server = new Server({
|
|
151
|
+
* transport: new WebSocketTransport({ server: httpServer }),
|
|
152
|
+
* express: (app) => { app.get("/hello", (req, res) => res.json({ ok: true })); },
|
|
153
|
+
* });
|
|
154
|
+
* server.define("my_room", MyRoom);
|
|
155
|
+
* export default await server.serverless(); // no listen()
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
async serverless() {
|
|
159
|
+
await this._bootServices();
|
|
160
|
+
await this._onTransportReady;
|
|
161
|
+
const server = this.transport.server;
|
|
162
|
+
if (!server) {
|
|
163
|
+
throw new Error("serverless() requires a transport backed by an http.Server (pass `{ server }` to the transport).");
|
|
164
|
+
}
|
|
165
|
+
this.bindRoutes();
|
|
166
|
+
(0, import_node.prereadRequestBodies)(server);
|
|
167
|
+
await matchMaker.accept(this.options.isStandaloneMatchMaker);
|
|
168
|
+
if (this.greet) {
|
|
169
|
+
(0, import_greeting_banner.greet)();
|
|
170
|
+
}
|
|
171
|
+
return server;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Set the active transport globally and bind the matchmaking/HTTP routes.
|
|
175
|
+
* Shared by {@link listen} and {@link serverless}.
|
|
176
|
+
*
|
|
177
|
+
* `setTransport()` runs here rather than before `transport.listen()`: the
|
|
178
|
+
* global transport is only read by the matchmaking route handler, which can't
|
|
179
|
+
* run until `bindRouterToTransport()` below registers the route.
|
|
180
|
+
*/
|
|
181
|
+
bindRoutes() {
|
|
182
|
+
(0, import_Transport.setTransport)(this.transport);
|
|
183
|
+
if (!this.router) {
|
|
184
|
+
this.router = (0, import_default_routes.getDefaultRouter)();
|
|
185
|
+
} else {
|
|
186
|
+
this.router = this.router.extend({ ...(0, import_default_routes.getDefaultRouter)().endpoints });
|
|
187
|
+
}
|
|
188
|
+
(0, import_router.bindRouterToTransport)(this.transport, this.router, this.options.express !== void 0);
|
|
189
|
+
}
|
|
154
190
|
define(nameOrHandler, handlerOrOptions, defaultOptions) {
|
|
155
191
|
const name = typeof nameOrHandler === "string" ? nameOrHandler : nameOrHandler.name;
|
|
156
192
|
const roomClass = typeof nameOrHandler === "string" ? handlerOrOptions : nameOrHandler;
|
|
@@ -172,8 +208,8 @@ var Server = class _Server {
|
|
|
172
208
|
await this.onBeforeShutdownCallback();
|
|
173
209
|
await matchMaker.gracefullyShutdown();
|
|
174
210
|
this.transport.shutdown();
|
|
175
|
-
this.presence
|
|
176
|
-
await this.driver
|
|
211
|
+
this.presence?.shutdown();
|
|
212
|
+
await this.driver?.shutdown();
|
|
177
213
|
await this.onShutdownCallback();
|
|
178
214
|
} catch (e) {
|
|
179
215
|
(0, import_Debug.debugAndPrintError)(`error during shutdown: ${e}`);
|
|
@@ -214,6 +250,27 @@ var Server = class _Server {
|
|
|
214
250
|
onBeforeShutdown(callback) {
|
|
215
251
|
this.onBeforeShutdownCallback = callback;
|
|
216
252
|
}
|
|
253
|
+
// Boot sequence shared by listen() and serverless(): user `beforeListen`
|
|
254
|
+
// hook, database boot, framework router defaults (auth/db endpoints), then
|
|
255
|
+
// the express app. serverless() needs this too — its exported server still
|
|
256
|
+
// serves the express callback and auth/database routes.
|
|
257
|
+
async _bootServices() {
|
|
258
|
+
const { beforeListen, database, express } = this.options;
|
|
259
|
+
if (beforeListen) {
|
|
260
|
+
await beforeListen();
|
|
261
|
+
}
|
|
262
|
+
if (database) {
|
|
263
|
+
await database.boot();
|
|
264
|
+
}
|
|
265
|
+
await this._applyRouterDefaults();
|
|
266
|
+
if (express) {
|
|
267
|
+
await this._onTransportReady;
|
|
268
|
+
if (this.transport.getExpressApp) {
|
|
269
|
+
const expressApp = await this.transport.getExpressApp();
|
|
270
|
+
await express(expressApp);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
217
274
|
// Extend the user's router with framework-contributed endpoints. An explicit
|
|
218
275
|
// `auth` option wins over the database's `applyRouterDefaults` so a non-DB
|
|
219
276
|
// setup can still auto-mount @colyseus/auth, and a DB user can opt out.
|
package/build/Server.cjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/Server.ts"],
|
|
4
|
-
"sourcesContent": ["import { greet } from \"@colyseus/greeting-banner\";\nimport type express from 'express';\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\";\nimport { LocalPresence } from './presence/LocalPresence.ts';\nimport { LocalDriver } from './matchmaker/LocalDriver/LocalDriver.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 { 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.presence = options.presence || new LocalPresence();\n this.driver = options.driver || new LocalDriver();\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 matchMaker.setup(\n this.presence,\n this.driver,\n options.publicAddress,\n options.selectProcessIdToCreateRoom,\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 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 // 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 // TODO: refactor me!\n // set transport globally, to be used by matchmaking route\n setTransport(this.transport);\n\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 // 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 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 * 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 // 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": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6BAAsB;
|
|
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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",
|
|
6
6
|
"names": ["gracefullyShutdown", "greet", "err", "module"]
|
|
7
7
|
}
|
package/build/Server.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type express from 'express';
|
|
2
|
+
import type { Server as HttpServer } from 'node:http';
|
|
2
3
|
import * as matchMaker from './MatchMaker.ts';
|
|
3
4
|
import { RegisteredHandler } from './matchmaker/RegisteredHandler.ts';
|
|
4
5
|
import { type OnCreateOptions, Room } from './Room.ts';
|
|
@@ -108,6 +109,40 @@ export declare class Server<RoomTypes extends Record<string, RegisteredHandler>
|
|
|
108
109
|
* @param listeningListener
|
|
109
110
|
*/
|
|
110
111
|
listen(port: number | string, hostname?: string, backlog?: number, listeningListener?: Function): Promise<any>;
|
|
112
|
+
/**
|
|
113
|
+
* Prepare matchmaking and HTTP routes, then return the underlying
|
|
114
|
+
* `http.Server` *without* binding to a port.
|
|
115
|
+
*
|
|
116
|
+
* Use this on serverless platforms that consume an exported HTTP server
|
|
117
|
+
* instead of a listening one. Vercel, for example, drives the request handler
|
|
118
|
+
* of a default-exported server (its Express/Hono WebSocket examples use
|
|
119
|
+
* `export default server`), whereas calling `listen()` there selects Vercel's
|
|
120
|
+
* "captured server" path, which does not invoke Express-style app handlers.
|
|
121
|
+
*
|
|
122
|
+
* The transport must be created with an `http.Server` so it can be exported.
|
|
123
|
+
* Vercel additionally needs `package.json` `"main"` pointing at the entrypoint:
|
|
124
|
+
*
|
|
125
|
+
* ```ts
|
|
126
|
+
* import { createServer } from "node:http";
|
|
127
|
+
* const httpServer = createServer();
|
|
128
|
+
* const server = new Server({
|
|
129
|
+
* transport: new WebSocketTransport({ server: httpServer }),
|
|
130
|
+
* express: (app) => { app.get("/hello", (req, res) => res.json({ ok: true })); },
|
|
131
|
+
* });
|
|
132
|
+
* server.define("my_room", MyRoom);
|
|
133
|
+
* export default await server.serverless(); // no listen()
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
serverless(): Promise<HttpServer>;
|
|
137
|
+
/**
|
|
138
|
+
* Set the active transport globally and bind the matchmaking/HTTP routes.
|
|
139
|
+
* Shared by {@link listen} and {@link serverless}.
|
|
140
|
+
*
|
|
141
|
+
* `setTransport()` runs here rather than before `transport.listen()`: the
|
|
142
|
+
* global transport is only read by the matchmaking route handler, which can't
|
|
143
|
+
* run until `bindRouterToTransport()` below registers the route.
|
|
144
|
+
*/
|
|
145
|
+
private bindRoutes;
|
|
111
146
|
/**
|
|
112
147
|
* Define a new type of room for matchmaking.
|
|
113
148
|
*
|
|
@@ -134,6 +169,7 @@ export declare class Server<RoomTypes extends Record<string, RegisteredHandler>
|
|
|
134
169
|
*/
|
|
135
170
|
onShutdown(callback: () => void | Promise<any>): void;
|
|
136
171
|
onBeforeShutdown(callback: () => void | Promise<any>): void;
|
|
172
|
+
private _bootServices;
|
|
137
173
|
private _applyRouterDefaults;
|
|
138
174
|
protected getDefaultTransport(options: any): Promise<Transport>;
|
|
139
175
|
protected onShutdownCallback: () => void | Promise<any>;
|
package/build/Server.mjs
CHANGED
|
@@ -5,12 +5,11 @@ 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 { LocalPresence } from "./presence/LocalPresence.mjs";
|
|
9
|
-
import { LocalDriver } from "./matchmaker/LocalDriver/LocalDriver.mjs";
|
|
10
8
|
import { setTransport } from "./Transport.mjs";
|
|
11
9
|
import { logger, setLogger } from "./Logger.mjs";
|
|
12
10
|
import { setDevMode, isDevMode } from "./utils/DevMode.mjs";
|
|
13
11
|
import { bindRouterToTransport, createRouter } from "./router/index.mjs";
|
|
12
|
+
import { prereadRequestBodies } from "./router/node.mjs";
|
|
14
13
|
import "@colyseus/shared-types";
|
|
15
14
|
import { getDefaultRouter } from "./router/default_routes.mjs";
|
|
16
15
|
var Server = class _Server {
|
|
@@ -24,18 +23,19 @@ var Server = class _Server {
|
|
|
24
23
|
greet: greet2 = true
|
|
25
24
|
} = options;
|
|
26
25
|
setDevMode(options.devMode === true);
|
|
27
|
-
this.presence = options.presence || new LocalPresence();
|
|
28
|
-
this.driver = options.driver || new LocalDriver();
|
|
29
26
|
this.options = options;
|
|
30
27
|
this.greet = greet2;
|
|
31
28
|
_Server.current = this;
|
|
32
29
|
this.attach(options);
|
|
33
30
|
matchMaker.setup(
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
options.presence,
|
|
32
|
+
options.driver,
|
|
36
33
|
options.publicAddress,
|
|
37
34
|
options.selectProcessIdToCreateRoom
|
|
38
|
-
)
|
|
35
|
+
).then(() => {
|
|
36
|
+
this.presence = matchMaker.presence;
|
|
37
|
+
this.driver = matchMaker.driver;
|
|
38
|
+
});
|
|
39
39
|
if (gracefullyShutdown2) {
|
|
40
40
|
registerGracefulShutdown((err) => this.gracefullyShutdown(true, err));
|
|
41
41
|
}
|
|
@@ -56,21 +56,7 @@ var Server = class _Server {
|
|
|
56
56
|
* @param listeningListener
|
|
57
57
|
*/
|
|
58
58
|
async listen(port, hostname, backlog, listeningListener) {
|
|
59
|
-
|
|
60
|
-
if (beforeListen) {
|
|
61
|
-
await beforeListen();
|
|
62
|
-
}
|
|
63
|
-
if (database) {
|
|
64
|
-
await database.boot();
|
|
65
|
-
}
|
|
66
|
-
await this._applyRouterDefaults();
|
|
67
|
-
if (express) {
|
|
68
|
-
await this._onTransportReady;
|
|
69
|
-
if (this.transport.getExpressApp) {
|
|
70
|
-
const expressApp = await this.transport.getExpressApp();
|
|
71
|
-
await express(expressApp);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
59
|
+
await this._bootServices();
|
|
74
60
|
if (process.env.COLYSEUS_CLOUD !== void 0) {
|
|
75
61
|
if (typeof hostname === "number") {
|
|
76
62
|
hostname = void 0;
|
|
@@ -91,17 +77,11 @@ var Server = class _Server {
|
|
|
91
77
|
}
|
|
92
78
|
await this._onTransportReady;
|
|
93
79
|
return new Promise((resolve, reject) => {
|
|
94
|
-
setTransport(this.transport);
|
|
95
80
|
this.transport.listen(port, hostname, backlog, (err) => {
|
|
96
81
|
if (this.transport.server) {
|
|
97
82
|
this.transport.server.on("error", (err2) => reject(err2));
|
|
98
83
|
}
|
|
99
|
-
|
|
100
|
-
this.router = getDefaultRouter();
|
|
101
|
-
} else {
|
|
102
|
-
this.router = this.router.extend({ ...getDefaultRouter().endpoints });
|
|
103
|
-
}
|
|
104
|
-
bindRouterToTransport(this.transport, this.router, this.options.express !== void 0);
|
|
84
|
+
this.bindRoutes();
|
|
105
85
|
if (listeningListener) {
|
|
106
86
|
listeningListener(err);
|
|
107
87
|
}
|
|
@@ -113,6 +93,62 @@ var Server = class _Server {
|
|
|
113
93
|
});
|
|
114
94
|
});
|
|
115
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Prepare matchmaking and HTTP routes, then return the underlying
|
|
98
|
+
* `http.Server` *without* binding to a port.
|
|
99
|
+
*
|
|
100
|
+
* Use this on serverless platforms that consume an exported HTTP server
|
|
101
|
+
* instead of a listening one. Vercel, for example, drives the request handler
|
|
102
|
+
* of a default-exported server (its Express/Hono WebSocket examples use
|
|
103
|
+
* `export default server`), whereas calling `listen()` there selects Vercel's
|
|
104
|
+
* "captured server" path, which does not invoke Express-style app handlers.
|
|
105
|
+
*
|
|
106
|
+
* The transport must be created with an `http.Server` so it can be exported.
|
|
107
|
+
* Vercel additionally needs `package.json` `"main"` pointing at the entrypoint:
|
|
108
|
+
*
|
|
109
|
+
* ```ts
|
|
110
|
+
* import { createServer } from "node:http";
|
|
111
|
+
* const httpServer = createServer();
|
|
112
|
+
* const server = new Server({
|
|
113
|
+
* transport: new WebSocketTransport({ server: httpServer }),
|
|
114
|
+
* express: (app) => { app.get("/hello", (req, res) => res.json({ ok: true })); },
|
|
115
|
+
* });
|
|
116
|
+
* server.define("my_room", MyRoom);
|
|
117
|
+
* export default await server.serverless(); // no listen()
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
async serverless() {
|
|
121
|
+
await this._bootServices();
|
|
122
|
+
await this._onTransportReady;
|
|
123
|
+
const server = this.transport.server;
|
|
124
|
+
if (!server) {
|
|
125
|
+
throw new Error("serverless() requires a transport backed by an http.Server (pass `{ server }` to the transport).");
|
|
126
|
+
}
|
|
127
|
+
this.bindRoutes();
|
|
128
|
+
prereadRequestBodies(server);
|
|
129
|
+
await matchMaker.accept(this.options.isStandaloneMatchMaker);
|
|
130
|
+
if (this.greet) {
|
|
131
|
+
greet();
|
|
132
|
+
}
|
|
133
|
+
return server;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Set the active transport globally and bind the matchmaking/HTTP routes.
|
|
137
|
+
* Shared by {@link listen} and {@link serverless}.
|
|
138
|
+
*
|
|
139
|
+
* `setTransport()` runs here rather than before `transport.listen()`: the
|
|
140
|
+
* global transport is only read by the matchmaking route handler, which can't
|
|
141
|
+
* run until `bindRouterToTransport()` below registers the route.
|
|
142
|
+
*/
|
|
143
|
+
bindRoutes() {
|
|
144
|
+
setTransport(this.transport);
|
|
145
|
+
if (!this.router) {
|
|
146
|
+
this.router = getDefaultRouter();
|
|
147
|
+
} else {
|
|
148
|
+
this.router = this.router.extend({ ...getDefaultRouter().endpoints });
|
|
149
|
+
}
|
|
150
|
+
bindRouterToTransport(this.transport, this.router, this.options.express !== void 0);
|
|
151
|
+
}
|
|
116
152
|
define(nameOrHandler, handlerOrOptions, defaultOptions) {
|
|
117
153
|
const name = typeof nameOrHandler === "string" ? nameOrHandler : nameOrHandler.name;
|
|
118
154
|
const roomClass = typeof nameOrHandler === "string" ? handlerOrOptions : nameOrHandler;
|
|
@@ -134,8 +170,8 @@ var Server = class _Server {
|
|
|
134
170
|
await this.onBeforeShutdownCallback();
|
|
135
171
|
await matchMaker.gracefullyShutdown();
|
|
136
172
|
this.transport.shutdown();
|
|
137
|
-
this.presence
|
|
138
|
-
await this.driver
|
|
173
|
+
this.presence?.shutdown();
|
|
174
|
+
await this.driver?.shutdown();
|
|
139
175
|
await this.onShutdownCallback();
|
|
140
176
|
} catch (e) {
|
|
141
177
|
debugAndPrintError(`error during shutdown: ${e}`);
|
|
@@ -176,6 +212,27 @@ var Server = class _Server {
|
|
|
176
212
|
onBeforeShutdown(callback) {
|
|
177
213
|
this.onBeforeShutdownCallback = callback;
|
|
178
214
|
}
|
|
215
|
+
// Boot sequence shared by listen() and serverless(): user `beforeListen`
|
|
216
|
+
// hook, database boot, framework router defaults (auth/db endpoints), then
|
|
217
|
+
// the express app. serverless() needs this too — its exported server still
|
|
218
|
+
// serves the express callback and auth/database routes.
|
|
219
|
+
async _bootServices() {
|
|
220
|
+
const { beforeListen, database, express } = this.options;
|
|
221
|
+
if (beforeListen) {
|
|
222
|
+
await beforeListen();
|
|
223
|
+
}
|
|
224
|
+
if (database) {
|
|
225
|
+
await database.boot();
|
|
226
|
+
}
|
|
227
|
+
await this._applyRouterDefaults();
|
|
228
|
+
if (express) {
|
|
229
|
+
await this._onTransportReady;
|
|
230
|
+
if (this.transport.getExpressApp) {
|
|
231
|
+
const expressApp = await this.transport.getExpressApp();
|
|
232
|
+
await express(expressApp);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
179
236
|
// Extend the user's router with framework-contributed endpoints. An explicit
|
|
180
237
|
// `auth` option wins over the database's `applyRouterDefaults` so a non-DB
|
|
181
238
|
// setup can still auto-mount @colyseus/auth, and a DB user can opt out.
|