UNPKG

rcon-ts-valve

Version:

Improved RCON client in typescript

266 lines (265 loc) 11.1 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Rcon = exports.Defaults = exports.RconError = exports.ExtendableError = void 0; const net = require("net"); const buffer_1 = require("buffer"); class ExtendableError extends Error { constructor(message = '', innerException) { super(message); this.innerException = innerException; this.message = message; this.name = this.constructor.name; Error.captureStackTrace(this, this.constructor); } } exports.ExtendableError = ExtendableError; class RconError extends ExtendableError { constructor(message, innerException) { super(message, innerException); Object.freeze(this); } } exports.RconError = RconError; var Defaults; (function (Defaults) { Defaults.PORT = 25575; Defaults.TIMEOUT = 5000; })(Defaults || (exports.Defaults = Defaults = {})); Object.freeze(Defaults); class Rcon { get errors() { return this._errors.slice(); } get state() { return this._state; } constructor(config) { this.enableConsoleLogging = false; this._authPacketId = NaN; this._state = 0 /* State.Disconnected */; this._lastRequestId = 0xF4240; this._callbacks = new Map(); this._errors = []; this._sessionCount = 0; this._tempResponse = ''; let host = config.host; this.host = host = host && host.trim(); if (!host) throw new TypeError('"host" argument cannot be empty'); this.port = config.port || Defaults.PORT; const password = config.password; if (!password || !password.trim()) throw new TypeError('"password" argument cannot be empty'); this.password = password; this.timeout = config.timeout || Defaults.TIMEOUT; } connect() { const _ = this; let p = _._connector; if (!p) _._connector = p = new Promise((resolve, reject) => { _._state = 0.5 /* State.Connecting */; if (_.enableConsoleLogging) console.log(this.toString(), "Connecting..."); const s = _._socket = net.createConnection(_.port, _.host); function cleanup(message, error) { if (error) _._errors.push(error); s.removeAllListeners(); if (_._socket == s) _._socket = undefined; if (_._connector == p) _._connector = undefined; if (message) { if (_.enableConsoleLogging) console.error(_.toString(), message); if (message) return new RconError(message, error); } } // Look for connection failure... s.once('error', error => { _._state = -1 /* State.Refused */; reject(cleanup("Connection refused.", error)); // ** First point of failure. }); // Look for successful connection... s.once('connect', () => { s.removeAllListeners('error'); _._state = 1 /* State.Connected */; if (_.enableConsoleLogging) console.log(_.toString(), "Connected. Authorizing ..."); s.on('data', data => _._handleResponse(data)); s.on('error', error => { _._errors.push(error); if (_.enableConsoleLogging) console.error(_.toString(), error); }); _._send(_.password, 3 /* PacketType.AUTH */).then(() => { _._state = 2 /* State.Authorized */; if (_.enableConsoleLogging) console.log(_.toString(), "Authorized."); resolve(_); }).catch(error => { _._state = -2 /* State.Unauthorized */; reject(cleanup("Authorization failed.", error)); // ** Second point of failure. }); }); s.once('end', () => { if (_.enableConsoleLogging) console.warn(this.toString(), "Disconnected."); _._state = 0 /* State.Disconnected */; cleanup(); }); }); return p; } session(context) { return __awaiter(this, void 0, void 0, function* () { const sessionId = ++this._sessionCount; let rcon; try { rcon = yield this.connect(); return yield context(rcon, sessionId); } finally { this._sessionCount--; if (!this._sessionCount && rcon) rcon.disconnect(); } }); } toString() { return `RCON: ${this.host}:${this.port}`; } disconnect() { const s = this._socket; this._callbacks.clear(); if (s) s.end(); this._socket = undefined; this._connector = undefined; } _handleResponse(data) { const len = data.readInt32LE(0); if (!len) throw new RconError('Received empty response package'); let id = data.readInt32LE(4); const type = data.readInt32LE(8); const callbacks = this._callbacks; const authId = this._authPacketId; let payload = data.toString('utf8', 12, len + 2); if (payload.charAt(payload.length - 1) === '\n') payload = payload.substring(0, payload.length - 1); // console.log("Received\n", "type:", type ,", response:", payload, ", current id: ", id, ", authId: ", authId); // console.log("callback: ", callbacks); if (id === -1 && !isNaN(authId) && type === 2 /* PacketType.RESPONSE_AUTH */) { if (callbacks.has(authId)) { id = authId; this._authPacketId = NaN; callbacks.get(authId)(null, new RconError('Authentication failed.')); } } if (callbacks.has(id) && type === 2 /* PacketType.RESPONSE_AUTH */) { callbacks.get(id)(""); } if (type === 0 /* PacketType.RESPONSE */) { if (callbacks.has(id + 1)) { this._tempResponse = this._tempResponse + payload; } else if (!callbacks.has(id + 1) && this._tempResponse.length > 0 && payload.length === 0) { callbacks.get(id - 1)(this._tempResponse); this._tempResponse = ''; callbacks.get(id)(""); } } // if (callbacks.has(id)) { // let str = data.toString('utf8', 12, len + 2); // if (str.charAt(str.length - 1) === '\n') // str = str.substring(0, str.length - 1); // callbacks.get(id)!(str); // } // callbacks.delete(id); // Possibly superfluous but best to be sure. } send(data) { return __awaiter(this, void 0, void 0, function* () { if (!this._connector || this._state <= 0) throw new RconError('Instance is not connected.'); yield this._connector; // console.log("exist callback: ", this._callbacks); const response = this._send(data, 2 /* PacketType.COMMAND */); this._send('', 0 /* PacketType.RESPONSE */); return yield response; }); } sequentialSend(data) { return __awaiter(this, void 0, void 0, function* () { if (!this._connector || this._state <= 0) throw new RconError('Instance is not connected.'); yield this._connector; let responseList = []; for (let i = 0; i < data.length; i++) { const response = this._send(data[i], 2 /* PacketType.COMMAND */); this._send('', 0 /* PacketType.RESPONSE */); responseList.push(yield response); } return responseList; }); } _send(data, cmd) { return __awaiter(this, void 0, void 0, function* () { const s = this._socket; if (!s || this._state <= 0) throw new RconError('Instance was disconnected.'); const length = buffer_1.Buffer.byteLength(data); const id = ++this._lastRequestId; if (cmd === 3 /* PacketType.AUTH */) this._authPacketId = id; // console.log("Send command:", data, "current id: ", this._lastRequestId); const buf = buffer_1.Buffer.allocUnsafe(length + 14); buf.writeInt32LE(length + 10, 0); buf.writeInt32LE(id, 4); // Not sure how this is used or needed. buf.writeInt32LE(cmd, 8); buf.write(data, 12); buf.fill(0x00, length + 12); yield s.write(buf, 'binary'); return yield new Promise((resolve, reject) => { const cleanup = () => { clearTimeout(timeout); s.removeListener('end', onEnded); this._callbacks.delete(id); if (cmd === 3 /* PacketType.AUTH */) this._authPacketId = NaN; }; const timeout = setTimeout(() => { cleanup(); reject(new RconError('Request timed out')); }, this.timeout); const onEnded = () => { cleanup(); reject(new RconError('Disconnected before response.')); }; s.once('end', onEnded); this._callbacks.set(id, (data, err) => { cleanup(); if (err) reject(err); if (data == null) reject(new RconError("No data returned.")); else resolve(data); }); }); }); } } exports.Rcon = Rcon; exports.default = Rcon;