@jsprismarine/raknet
Version:
Basic RakNet implementation written in TypeScript
125 lines (124 loc) • 15.5 kB
JavaScript
"use strict";
Object.defineProperties(exports, {
__esModule: { value: true },
[Symbol.toStringTag]: { value: "Module" }
});
const require_runtime = require("./_virtual/_rolldown/runtime.cjs.cjs");
const require_protocol_BitFlags = require("./protocol/BitFlags.cjs.cjs");
require("./Constants.cjs.cjs");
const require_Session = require("./Session.cjs.cjs");
const require_protocol_OfflineHandler = require("./protocol/OfflineHandler.cjs.cjs");
let events = require("events");
let node_dgram = require("node:dgram");
node_dgram = require_runtime.__toESM(node_dgram, 1);
//#region src/ServerSocket.ts
var ServerSocket = class extends events.EventEmitter {
maxConnections;
onlineMode;
serverName;
logger;
socket;
guid;
sessions = /* @__PURE__ */ new Set();
ticker;
offlineHandler = new require_protocol_OfflineHandler.OfflineHandler(this);
constructor(maxConnections, onlineMode, serverName, logger) {
super();
this.maxConnections = maxConnections;
this.onlineMode = onlineMode;
this.serverName = serverName;
this.logger = logger;
this.socket = node_dgram.default.createSocket("udp4").unref();
this.guid = Buffer.allocUnsafe(8).readBigInt64BE();
if (this.onlineMode) this.logger.warn("Online mode is currently not supported.", "RakNet/ServerSocket");
}
start(address, port) {
try {
this.socket.bind(port, address);
} catch (error) {
if (error instanceof Error) {
this.logger.error("Failed to bind socket, error message=%s", "RakNet/ServerSocket/Start");
this.logger.error(error, "RakNet/ServerSocket/Start");
}
}
const tick = () => setTimeout(() => {
for (const session of this.sessions.values()) session.update(Date.now());
tick();
}, 10);
this.ticker = tick();
this.ticker.unref();
this.socket.on("message", this.handleMessage.bind(this));
}
handleMessage(msg, rinfo) {
if ((msg[0] & require_protocol_BitFlags.BitFlags.VALID) === 0) this.offlineHandler.process(msg, rinfo);
else {
let session;
if ((session = this.getSessionByAddress(rinfo)) !== null) session.handle(msg);
else this.logger.debug(`Cannot handle Datagram for unconnected client=${rinfo.address}:${rinfo.port}, buffer=${msg}`, "RakNet/ServerSocket/handleMessage");
}
}
kill() {
for (const session of this.getSessions()) session.sendFrameQueue();
clearTimeout(this.ticker);
this.removeAllListeners();
this.socket.close();
}
/**
* Used to retrieve if we are overflowing the maximum
* connections we can allow, value given in the constructor.
* @returns {boolean} if we can hold new connections.
*/
allowIncomingConnections() {
return this.sessions.size < this.maxConnections;
}
/**
* Returns the maximum number of incoming connections.
* @returns {number} the maximum connections we can allow.
*/
getMaxConnections() {
return this.maxConnections;
}
/**
* Sets the maximum number of allowed incoming connections.
* @param {number} allowed - maximum number of connections.
*/
setMaxConnections(allowed) {
this.maxConnections = allowed;
}
addSession(rinfo, mtuSize, incomingGuid) {
this.sessions.add(new require_Session.default(this, mtuSize, rinfo, incomingGuid));
this.logger.verbose(`Session created for client=${rinfo.address}:${rinfo.port}, mtu=${mtuSize}`);
this.serverName.setOnlinePlayerCount(this.sessions.size);
}
removeSession(session, reason) {
if (!this.sessions.delete(session)) return;
this.emit("closeConnection", session.getAddress(), reason);
this.logger.verbose(`Closed session for client=${session.getAddress()}, reason=${reason}`);
this.serverName.setOnlinePlayerCount(this.sessions.size);
}
getLogger() {
return this.logger;
}
getSessions() {
return Array.from(this.sessions.values());
}
getSessionByAddress(rinfo) {
return this.getSessions().find((session) => session.rinfo.address === rinfo.address && session.rinfo.port === rinfo.port) ?? null;
}
getSessionByGUID(guid) {
return this.getSessions().find((session) => session.guid === guid) ?? null;
}
getServerGuid() {
return this.guid;
}
sendPacket(packet, rinfo) {
packet.encode();
this.sendBuffer(packet.getBuffer(), rinfo);
}
sendBuffer(buffer, rinfo) {
this.socket.send(buffer, rinfo.port, rinfo.address);
}
};
//#endregion
exports.default = ServerSocket;
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU2VydmVyU29ja2V0LmNqcy5janMiLCJuYW1lcyI6W10sInNvdXJjZXMiOlsiLi4vc3JjL1NlcnZlclNvY2tldC50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBFdmVudEVtaXR0ZXIgfSBmcm9tICdldmVudHMnO1xuaW1wb3J0IERncmFtLCB7IHR5cGUgUmVtb3RlSW5mbyB9IGZyb20gJ25vZGU6ZGdyYW0nO1xuaW1wb3J0IHsgUkFLTkVUX1RQUyB9IGZyb20gJy4vQ29uc3RhbnRzJztcbmltcG9ydCBSYWtOZXRTZXNzaW9uIGZyb20gJy4vU2Vzc2lvbic7XG5pbXBvcnQgQml0RmxhZ3MgZnJvbSAnLi9wcm90b2NvbC9CaXRGbGFncyc7XG5pbXBvcnQgeyBPZmZsaW5lSGFuZGxlciB9IGZyb20gJy4vcHJvdG9jb2wvT2ZmbGluZUhhbmRsZXInO1xuaW1wb3J0IHR5cGUgUGFja2V0IGZyb20gJy4vcHJvdG9jb2wvUGFja2V0JztcbmltcG9ydCB0eXBlIHsgU2VydmVyTmFtZSB9IGZyb20gJy4vdXRpbHMvU2VydmVyTmFtZSc7XG5cbnR5cGUgTG9nZ2VyID0ge1xuICAgIGluZm86IEZ1bmN0aW9uO1xuICAgIHdhcm46IEZ1bmN0aW9uO1xuICAgIGVycm9yOiBGdW5jdGlvbjtcbiAgICB2ZXJib3NlOiBGdW5jdGlvbjtcbiAgICBkZWJ1ZzogRnVuY3Rpb247XG4gICAgc2lsbHk6IEZ1bmN0aW9uO1xufTtcblxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgU2VydmVyU29ja2V0IGV4dGVuZHMgRXZlbnRFbWl0dGVyIHtcbiAgICBwcml2YXRlIHJlYWRvbmx5IHNvY2tldDogRGdyYW0uU29ja2V0O1xuICAgIHByaXZhdGUgcmVhZG9ubHkgZ3VpZDogYmlnaW50O1xuICAgIHByaXZhdGUgcmVhZG9ubHkgc2Vzc2lvbnM6IFNldDxSYWtOZXRTZXNzaW9uPiA9IG5ldyBTZXQoKTtcbiAgICBwcml2YXRlIHRpY2tlcjogTm9kZUpTLlRpbWVvdXQgfCB1bmRlZmluZWQ7XG5cbiAgICBwcml2YXRlIHJlYWRvbmx5IG9mZmxpbmVIYW5kbGVyID0gbmV3IE9mZmxpbmVIYW5kbGVyKHRoaXMpO1xuXG4gICAgcHVibGljIGNvbnN0cnVjdG9yKFxuICAgICAgICBwcml2YXRlIG1heENvbm5lY3Rpb25zOiBudW1iZXIsXG4gICAgICAgIHByaXZhdGUgcmVhZG9ubHkgb25saW5lTW9kZTogYm9vbGVhbixcbiAgICAgICAgcHVibGljIHJlYWRvbmx5IHNlcnZlck5hbWU6IFNlcnZlck5hbWUsXG4gICAgICAgIHByaXZhdGUgcmVhZG9ubHkgbG9nZ2VyOiBMb2dnZXJcbiAgICApIHtcbiAgICAgICAgc3VwZXIoKTtcbiAgICAgICAgdGhpcy5zb2NrZXQgPSBEZ3JhbS5jcmVhdGVTb2NrZXQoJ3VkcDQnKS51bnJlZigpO1xuICAgICAgICB0aGlzLmd1aWQgPSBCdWZmZXIuYWxsb2NVbnNhZmUoOCkucmVhZEJpZ0ludDY0QkUoKTtcblxuICAgICAgICBpZiAodGhpcy5vbmxpbmVNb2RlKSB7XG4gICAgICAgICAgICB0aGlzLmxvZ2dlci53YXJuKCdPbmxpbmUgbW9kZSBpcyBjdXJyZW50bHkgbm90IHN1cHBvcnRlZC4nLCAnUmFrTmV0L1NlcnZlclNvY2tldCcpO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgcHVibGljIHN0YXJ0KGFkZHJlc3M6IHN0cmluZywgcG9ydDogbnVtYmVyKTogdm9pZCB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICB0aGlzLnNvY2tldC5iaW5kKHBvcnQsIGFkZHJlc3MpO1xuICAgICAgICB9IGNhdGNoIChlcnJvcjogdW5rbm93bikge1xuICAgICAgICAgICAgaWYgKGVycm9yIGluc3RhbmNlb2YgRXJyb3IpIHtcbiAgICAgICAgICAgICAgICB0aGlzLmxvZ2dlci5lcnJvcignRmFpbGVkIHRvIGJpbmQgc29ja2V0LCBlcnJvciBtZXNzYWdlPSVzJywgJ1Jha05ldC9TZXJ2ZXJTb2NrZXQvU3RhcnQnKTtcbiAgICAgICAgICAgICAgICB0aGlzLmxvZ2dlci5lcnJvcihlcnJvciwgJ1Jha05ldC9TZXJ2ZXJTb2NrZXQvU3RhcnQnKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGNvbnN0IHRpY2sgPSAoKSA9PlxuICAgICAgICAgICAgc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICAgICAgICAgICAgZm9yIChjb25zdCBzZXNzaW9uIG9mIHRoaXMuc2Vzc2lvbnMudmFsdWVzKCkpIHtcbiAgICAgICAgICAgICAgICAgICAgc2Vzc2lvbi51cGRhdGUoRGF0ZS5ub3coKSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIHRpY2soKTtcbiAgICAgICAgICAgIH0sIFJBS05FVF9UUFMpO1xuXG4gICAgICAgIC8vIFN0YXJ0IHRpY2tpbmdcbiAgICAgICAgdGhpcy50aWNrZXIgPSB0aWNrKCk7XG4gICAgICAgIHRoaXMudGlja2VyLnVucmVmKCk7XG5cbiAgICAgICAgdGhpcy5zb2NrZXQub24oJ21lc3NhZ2UnLCB0aGlzLmhhbmRsZU1lc3NhZ2UuYmluZCh0aGlzKSk7XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBoYW5kbGVNZXNzYWdlKG1zZzogQnVmZmVyLCByaW5mbzogUmVtb3RlSW5mbyk6IHZvaWQge1xuICAgICAgICAvLyBEaXJlY3RseSBjaGVjayBpZiBpdCdzIGEgb2ZmbGluZSBtZXNzYWdlXG4gICAgICAgIGlmICgobXNnWzBdISAmIEJpdEZsYWdzLlZBTElEKSA9PT0gMCkge1xuICAgICAgICAgICAgdGhpcy5vZmZsaW5lSGFuZGxlci5wcm9jZXNzKG1zZywgcmluZm8pO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgLy8gTm9ybWFsbHkgUmFrTmV0IGlnbm9yZXMgdW5oYW5kbGVkIHBhY2tldHMsIGJ1dCB3ZSBzdGlsbCB3YW50IHNvbWUgbG9ncy4uLlxuICAgICAgICAgICAgLy8gaHR0cHM6Ly9naXRodWIuY29tL2ZhY2Vib29rYXJjaGl2ZS9SYWtOZXQvYmxvYi9tYXN0ZXIvU291cmNlL1Jha1BlZXIuY3BwI0w1NDY1XG4gICAgICAgICAgICBsZXQgc2Vzc2lvbjtcbiAgICAgICAgICAgIGlmICgoc2Vzc2lvbiA9IHRoaXMuZ2V0U2Vzc2lvbkJ5QWRkcmVzcyhyaW5mbykpICE9PSBudWxsKSB7XG4gICAgICAgICAgICAgICAgc2Vzc2lvbi5oYW5kbGUobXNnKTtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgLy8gTWF5IGJlIHRyaWdnZXJlZCBieSB0aGUgQUNLIG9mIGRpc2Nvbm5lY3QgcGFja2V0IHRoYXQgaXMgcmVjZWl2ZWQgYWZ0ZXIgc2Vzc2lvbiBpcyBjbG9zZWRcbiAgICAgICAgICAgICAgICB0aGlzLmxvZ2dlci5kZWJ1ZyhcbiAgICAgICAgICAgICAgICAgICAgYENhbm5vdCBoYW5kbGUgRGF0YWdyYW0gZm9yIHVuY29ubmVjdGVkIGNsaWVudD0ke3JpbmZvLmFkZHJlc3N9OiR7cmluZm8ucG9ydH0sIGJ1ZmZlcj0ke21zZ31gLFxuICAgICAgICAgICAgICAgICAgICAnUmFrTmV0L1NlcnZlclNvY2tldC9oYW5kbGVNZXNzYWdlJ1xuICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBwdWJsaWMga2lsbCgpOiB2b2lkIHtcbiAgICAgICAgLy8gU2VuZCBsYXN0IHJlbWFpbmluZyBwYWNrZXRzIHRvIGFsbCBwbGF5ZXJzXG4gICAgICAgIGZvciAoY29uc3Qgc2Vzc2lvbiBvZiB0aGlzLmdldFNlc3Npb25zKCkpIHtcbiAgICAgICAgICAgIC8vIEZJWE1FOiBUaGlzIHNob3VsZCBiZSBhd2FpdGFibGUuXG4gICAgICAgICAgICBzZXNzaW9uLnNlbmRGcmFtZVF1ZXVlKCk7XG4gICAgICAgIH1cblxuICAgICAgICBjbGVhclRpbWVvdXQodGhpcy50aWNrZXIpO1xuXG4gICAgICAgIC8vIE1ha2Ugc3VyZSB3ZSBkb24ndCBzZW5kIGFueSBtb3JlIGV2ZW50cy5cbiAgICAgICAgdGhpcy5yZW1vdmVBbGxMaXN0ZW5lcnMoKTtcblxuICAgICAgICAvLyBGaW5hbGx5LCBjbG9zZSB0aGUgc29ja2V0LlxuICAgICAgICB0aGlzLnNvY2tldC5jbG9zZSgpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFVzZWQgdG8gcmV0cmlldmUgaWYgd2UgYXJlIG92ZXJmbG93aW5nIHRoZSBtYXhpbXVtXG4gICAgICogY29ubmVjdGlvbnMgd2UgY2FuIGFsbG93LCB2YWx1ZSBnaXZlbiBpbiB0aGUgY29uc3RydWN0b3IuXG4gICAgICogQHJldHVybnMge2Jvb2xlYW59IGlmIHdlIGNhbiBob2xkIG5ldyBjb25uZWN0aW9ucy5cbiAgICAgKi9cbiAgICBwdWJsaWMgYWxsb3dJbmNvbWluZ0Nvbm5lY3Rpb25zKCk6IGJvb2xlYW4ge1xuICAgICAgICByZXR1cm4gdGhpcy5zZXNzaW9ucy5zaXplIDwgdGhpcy5tYXhDb25uZWN0aW9ucztcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRoZSBtYXhpbXVtIG51bWJlciBvZiBpbmNvbWluZyBjb25uZWN0aW9ucy5cbiAgICAgKiBAcmV0dXJucyB7bnVtYmVyfSB0aGUgbWF4aW11bSBjb25uZWN0aW9ucyB3ZSBjYW4gYWxsb3cuXG4gICAgICovXG4gICAgcHVibGljIGdldE1heENvbm5lY3Rpb25zKCk6IG51bWJlciB7XG4gICAgICAgIHJldHVybiB0aGlzLm1heENvbm5lY3Rpb25zO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFNldHMgdGhlIG1heGltdW0gbnVtYmVyIG9mIGFsbG93ZWQgaW5jb21pbmcgY29ubmVjdGlvbnMuXG4gICAgICogQHBhcmFtIHtudW1iZXJ9IGFsbG93ZWQgLSBtYXhpbXVtIG51bWJlciBvZiBjb25uZWN0aW9ucy5cbiAgICAgKi9cbiAgICBwdWJsaWMgc2V0TWF4Q29ubmVjdGlvbnMoYWxsb3dlZDogbnVtYmVyKTogdm9pZCB7XG4gICAgICAgIHRoaXMubWF4Q29ubmVjdGlvbnMgPSBhbGxvd2VkO1xuICAgIH1cblxuICAgIHB1YmxpYyBhZGRTZXNzaW9uKHJpbmZvOiBSZW1vdGVJbmZvLCBtdHVTaXplOiBudW1iZXIsIGluY29taW5nR3VpZDogYmlnaW50KTogdm9pZCB7XG4gICAgICAgIHRoaXMuc2Vzc2lvbnMuYWRkKG5ldyBSYWtOZXRTZXNzaW9uKHRoaXMsIG10dVNpemUsIHJpbmZvLCBpbmNvbWluZ0d1aWQpKTtcbiAgICAgICAgdGhpcy5sb2dnZXIudmVyYm9zZShgU2Vzc2lvbiBjcmVhdGVkIGZvciBjbGllbnQ9JHtyaW5mby5hZGRyZXNzfToke3JpbmZvLnBvcnR9LCBtdHU9JHttdHVTaXplfWApO1xuICAgICAgICB0aGlzLnNlcnZlck5hbWUuc2V0T25saW5lUGxheWVyQ291bnQodGhpcy5zZXNzaW9ucy5zaXplKTtcbiAgICB9XG5cbiAgICBwdWJsaWMgcmVtb3ZlU2Vzc2lvbihzZXNzaW9uOiBSYWtOZXRTZXNzaW9uLCByZWFzb24/OiBzdHJpbmcpOiB2b2lkIHtcbiAgICAgICAgaWYgKCF0aGlzLnNlc3Npb25zLmRlbGV0ZShzZXNzaW9uKSkgcmV0dXJuO1xuICAgICAgICB0aGlzLmVtaXQoJ2Nsb3NlQ29ubmVjdGlvbicsIHNlc3Npb24uZ2V0QWRkcmVzcygpLCByZWFzb24pO1xuICAgICAgICB0aGlzLmxvZ2dlci52ZXJib3NlKGBDbG9zZWQgc2Vzc2lvbiBmb3IgY2xpZW50PSR7c2Vzc2lvbi5nZXRBZGRyZXNzKCl9LCByZWFzb249JHtyZWFzb259YCk7XG4gICAgICAgIHRoaXMuc2VydmVyTmFtZS5zZXRPbmxpbmVQbGF5ZXJDb3VudCh0aGlzLnNlc3Npb25zLnNpemUpO1xuICAgIH1cblxuICAgIHB1YmxpYyBnZXRMb2dnZXIoKTogTG9nZ2VyIHtcbiAgICAgICAgcmV0dXJuIHRoaXMubG9nZ2VyO1xuICAgIH1cblxuICAgIHB1YmxpYyBnZXRTZXNzaW9ucygpOiBSYWtOZXRTZXNzaW9uW10ge1xuICAgICAgICByZXR1cm4gQXJyYXkuZnJvbSh0aGlzLnNlc3Npb25zLnZhbHVlcygpKTtcbiAgICB9XG5cbiAgICBwdWJsaWMgZ2V0U2Vzc2lvbkJ5QWRkcmVzcyhyaW5mbzogUmVtb3RlSW5mbyk6IFJha05ldFNlc3Npb24gfCBudWxsIHtcbiAgICAgICAgcmV0dXJuIChcbiAgICAgICAgICAgIHRoaXMuZ2V0U2Vzc2lvbnMoKS5maW5kKFxuICAgICAgICAgICAgICAgIChzZXNzaW9uKSA9PiBzZXNzaW9uLnJpbmZvLmFkZHJlc3MgPT09IHJpbmZvLmFkZHJlc3MgJiYgc2Vzc2lvbi5yaW5mby5wb3J0ID09PSByaW5mby5wb3J0XG4gICAgICAgICAgICApID8/IG51bGxcbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICBwdWJsaWMgZ2V0U2Vzc2lvbkJ5R1VJRChndWlkOiBiaWdpbnQpOiBSYWtOZXRTZXNzaW9uIHwgbnVsbCB7XG4gICAgICAgIHJldHVybiB0aGlzLmdldFNlc3Npb25zKCkuZmluZCgoc2Vzc2lvbikgPT4gc2Vzc2lvbi5ndWlkID09PSBndWlkKSA/PyBudWxsO1xuICAgIH1cblxuICAgIHB1YmxpYyBnZXRTZXJ2ZXJHdWlkKCk6IGJpZ2ludCB7XG4gICAgICAgIHJldHVybiB0aGlzLmd1aWQ7XG4gICAgfVxuXG4gICAgcHVibGljIHNlbmRQYWNrZXQ8VCBleHRlbmRzIFBhY2tldD4ocGFja2V0OiBULCByaW5mbzogUmVtb3RlSW5mbyk6IHZvaWQge1xuICAgICAgICBwYWNrZXQuZW5jb2RlKCk7XG4gICAgICAgIHRoaXMuc2VuZEJ1ZmZlcihwYWNrZXQuZ2V0QnVmZmVyKCksIHJpbmZvKTtcbiAgICB9XG5cbiAgICBwdWJsaWMgc2VuZEJ1ZmZlcihidWZmZXI6IEJ1ZmZlciwgcmluZm86IFJlbW90ZUluZm8pOiB2b2lkIHtcbiAgICAgICAgdGhpcy5zb2NrZXQuc2VuZChidWZmZXIsIHJpbmZvLnBvcnQsIHJpbmZvLmFkZHJlc3MpO1xuICAgIH1cbn1cbiJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7QUFrQkEsSUFBcUIsZUFBckIsY0FBMEMsT0FBQSxhQUFhO0NBU3ZDO0NBQ1M7Q0FD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