@colyseus/core
Version:
Multiplayer Framework for Node.js.
262 lines (261 loc) • 10 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var Server_exports = {};
__export(Server_exports, {
Server: () => Server
});
module.exports = __toCommonJS(Server_exports);
var import_greeting_banner = __toESM(require("@colyseus/greeting-banner"));
var import_Debug = require("./Debug.js");
var matchMaker = __toESM(require("./MatchMaker.js"));
var import_Room = require("./Room.js");
var import_Utils = require("./utils/Utils.js");
var import_discovery = require("./discovery/index.js");
var import_LocalPresence = require("./presence/LocalPresence.js");
var import_LocalDriver = require("./matchmaker/driver/local/LocalDriver.js");
var import_Logger = require("./Logger.js");
var import_DevMode = require("./utils/DevMode.js");
class Server {
constructor(options = {}) {
//@ts-expect-error
this._originalRoomOnMessage = null;
this.onShutdownCallback = () => Promise.resolve();
this.onBeforeShutdownCallback = () => Promise.resolve();
const { gracefullyShutdown = true, greet = true } = options;
(0, import_DevMode.setDevMode)(options.devMode === true);
this.presence = options.presence || new import_LocalPresence.LocalPresence();
this.driver = options.driver || new import_LocalDriver.LocalDriver();
this.greet = greet;
this.attach(options);
matchMaker.setup(
this.presence,
this.driver,
options.publicAddress,
options.selectProcessIdToCreateRoom
);
if (gracefullyShutdown) {
(0, import_Utils.registerGracefulShutdown)((err) => this.gracefullyShutdown(true, err));
}
if (options.logger) {
(0, import_Logger.setLogger)(options.logger);
}
}
attach(options) {
if (options.pingInterval !== void 0 || options.pingMaxRetries !== void 0 || options.server !== void 0 || options.verifyClient !== void 0) {
import_Logger.logger.warn("DEPRECATION WARNING: 'pingInterval', 'pingMaxRetries', 'server', and 'verifyClient' Server options will be permanently moved to WebSocketTransport on v0.15");
import_Logger.logger.warn(`new Server({
transport: new WebSocketTransport({
pingInterval: ...,
pingMaxRetries: ...,
server: ...,
verifyClient: ...
})
})`);
import_Logger.logger.warn("\u{1F449} Documentation: https://docs.colyseus.io/server/transport/");
}
const transport = options.transport || this.getDefaultTransport(options);
this.transport = transport;
if (this.transport.server) {
this.transport.server.once("listening", () => this.registerProcessForDiscovery());
this.attachMatchMakingRoutes(this.transport.server);
}
}
/**
* Bind the server into the port specified.
*
* @param port
* @param hostname
* @param backlog
* @param listeningListener
*/
async listen(port, hostname, backlog, listeningListener) {
this.port = port;
await matchMaker.accept();
if (this.greet) {
console.log(import_greeting_banner.default);
}
return new Promise((resolve, reject) => {
this.transport.server?.on("error", (err) => reject(err));
this.transport.listen(port, hostname, backlog, (err) => {
if (listeningListener) {
listeningListener(err);
}
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
async registerProcessForDiscovery() {
await (0, import_discovery.registerNode)(this.presence, {
port: this.port,
processId: matchMaker.processId
});
}
define(nameOrHandler, handlerOrOptions, defaultOptions) {
const name = typeof nameOrHandler === "string" ? nameOrHandler : nameOrHandler.name;
const roomClass = typeof nameOrHandler === "string" ? handlerOrOptions : nameOrHandler;
const options = typeof nameOrHandler === "string" ? defaultOptions : handlerOrOptions;
return matchMaker.defineRoomType(name, roomClass, options);
}
/**
* Remove a room definition from matchmaking.
* This method does not destroy any room. It only dissallows matchmaking
*/
removeRoomType(name) {
matchMaker.removeRoomType(name);
}
async gracefullyShutdown(exit = true, err) {
if (matchMaker.state === matchMaker.MatchMakerState.SHUTTING_DOWN) {
return;
}
await (0, import_discovery.unregisterNode)(this.presence, {
port: this.port,
processId: matchMaker.processId
});
try {
await this.onBeforeShutdownCallback();
await matchMaker.gracefullyShutdown();
this.transport.shutdown();
this.presence.shutdown();
this.driver.shutdown();
await this.onShutdownCallback();
} catch (e) {
(0, import_Debug.debugAndPrintError)(`error during shutdown: ${e}`);
} finally {
if (exit) {
process.exit(err && !import_DevMode.isDevMode ? 1 : 0);
}
}
}
/**
* Add simulated latency between client and server.
* @param milliseconds round trip latency in milliseconds.
*/
simulateLatency(milliseconds) {
if (milliseconds > 0) {
import_Logger.logger.warn(`\u{1F4F6}\uFE0F\u2757 Colyseus latency simulation enabled \u2192 ${milliseconds}ms latency for round trip.`);
} else {
import_Logger.logger.warn(`\u{1F4F6}\uFE0F\u2757 Colyseus latency simulation disabled.`);
}
const halfwayMS = milliseconds / 2;
this.transport.simulateLatency(halfwayMS);
if (this._originalRoomOnMessage == null) {
this._originalRoomOnMessage = import_Room.Room.prototype["_onMessage"];
}
const originalOnMessage = this._originalRoomOnMessage;
import_Room.Room.prototype["_onMessage"] = milliseconds <= Number.EPSILON ? originalOnMessage : function(client, buffer) {
const cachedBuffer = Buffer.from(buffer);
setTimeout(() => originalOnMessage.call(this, client, cachedBuffer), halfwayMS);
};
}
/**
* Register a callback that is going to be executed before the server shuts down.
* @param callback
*/
onShutdown(callback) {
this.onShutdownCallback = callback;
}
onBeforeShutdown(callback) {
this.onBeforeShutdownCallback = callback;
}
getDefaultTransport(_) {
throw new Error("Please provide a 'transport' layer. Default transport not set.");
}
attachMatchMakingRoutes(server) {
const listeners = server.listeners("request").slice(0);
server.removeAllListeners("request");
server.on("request", (req, res) => {
if (req.url.indexOf(`/${matchMaker.controller.matchmakeRoute}`) !== -1) {
(0, import_Debug.debugMatchMaking)("received matchmake request: %s", req.url);
this.handleMatchMakeRequest(req, res);
} else {
for (let i = 0, l = listeners.length; i < l; i++) {
listeners[i].call(server, req, res);
}
}
});
}
async handleMatchMakeRequest(req, res) {
if (matchMaker.state === matchMaker.MatchMakerState.SHUTTING_DOWN) {
res.writeHead(503, {});
res.end();
return;
}
const headers = Object.assign(
{},
matchMaker.controller.DEFAULT_CORS_HEADERS,
matchMaker.controller.getCorsHeaders.call(void 0, req)
);
if (req.method === "OPTIONS") {
res.writeHead(204, headers);
res.end();
} else if (req.method === "POST") {
const matchedParams = req.url.match(matchMaker.controller.allowedRoomNameChars);
const matchmakeIndex = matchedParams.indexOf(matchMaker.controller.matchmakeRoute);
const method = matchedParams[matchmakeIndex + 1];
const roomName = matchedParams[matchmakeIndex + 2] || "";
const data = [];
req.on("data", (chunk) => data.push(chunk));
req.on("end", async () => {
headers["Content-Type"] = "application/json";
res.writeHead(200, headers);
try {
const clientOptions = JSON.parse(Buffer.concat(data).toString());
const response = await matchMaker.controller.invokeMethod(
method,
roomName,
clientOptions,
{
token: (0, import_Utils.getBearerToken)(req.headers["authorization"]),
headers: req.headers,
ip: req.headers["x-real-ip"] ?? req.headers["x-forwarded-for"] ?? req.socket.remoteAddress,
req
}
);
if (this.transport.protocol !== void 0) {
response.protocol = this.transport.protocol;
}
res.write(JSON.stringify(response));
} catch (e) {
res.write(JSON.stringify({ code: e.code, error: e.message }));
}
res.end();
});
} else if (req.method === "GET") {
res.writeHead(404, headers);
res.end();
}
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Server
});