UNPKG

working-rcon

Version:
213 lines (166 loc) 5.53 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TimeoutError = exports.AuthenticationError = exports.UnexpectedPacketError = exports.RconError = void 0; var _net = require("net"); var _promiseTimeout = require("promise-timeout"); var _packet = require("./packet"); function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } class RconError extends Error { constructor(message) { super(message); this.name = this.constructor.name; } } exports.RconError = RconError; class UnexpectedPacketError extends RconError { constructor(packet, expectedType) { super(`Got unexpected packet ${JSON.stringify(packet)}. Expected ${expectedType}.`); this._packet = packet; this._expectedType = expectedType; } } exports.UnexpectedPacketError = UnexpectedPacketError; class AuthenticationError extends RconError { constructor() { super('Authentication failed!'); } } exports.AuthenticationError = AuthenticationError; class TimeoutError extends RconError { constructor() { super('Request timed out.'); } } exports.TimeoutError = TimeoutError; const newPacket = size => ({ size, buffer: Buffer.alloc(size == null ? 4 : size), offset: 0 }); class RconClient { constructor(socket, timeout) { _defineProperty(this, "_onReceiveData", data => { let currentOffset = 0; while (currentOffset < data.length) { const packet = this._pendingPacket != null ? this._pendingPacket : newPacket((0, _packet.peekSize)(data, currentOffset)); const packetEnd = currentOffset + packet.buffer.length - packet.offset; const copyUntil = Math.min(packetEnd, data.length); data.copy(packet.buffer, packet.offset, currentOffset, copyUntil); if (packetEnd > data.length) { this._pendingPacket = { ...packet, offset: packet.offset + copyUntil - currentOffset }; } else { this._pendingPacket = null; if (packet.size == null) { this._onReceiveData(packet.buffer); } else { this._onReceivePacket(packet.buffer); } } currentOffset = copyUntil; } }); _defineProperty(this, "_onSocketError", err => { throw err; }); this._callbacks = new Map(); this._socket = socket; this._timeout = timeout; this._currentId = 0; this._pendingPacket = null; this._socket.on('data', this._onReceiveData); } async authenticate(password) { const id = this._uniqueId; this._write({ id, type: _packet.Types.SERVERDATA_AUTH, body: password }); await Promise.race([this._receive(id), this._receive(-1)]); // We cannot provide the rest of the implementation of the protocol // according to Valve's Wiki page, since some servers do not respond // with authentication results at all (e.g. CS:GO: it just accepts faulty // passwords but doesn't execute the commands sent). // Taking the pragmatic approach, we instead opt to require passwords to be correct. // The code below implements the rest of the protocol as described in the wiki page. // if (reply.type === Types.SERVERDATA_RESPONSE_VALUE) { // reply = await Promise.race([this._receive(id), this._receive(-1)]) // } // if (reply.type !== Types.SERVERDATA_AUTH_RESPONSE) { // throw new UnexpectedPacketError(packet, 'SERVERDATA_AUTH_RESPONSE') // } // if (reply.id === -1) { // throw new AuthenticationError() // } } async command(cmd) { const result = await this._query({ type: _packet.Types.SERVERDATA_EXECCOMMAND, body: cmd }); if (result.type !== _packet.Types.SERVERDATA_RESPONSE_VALUE) { throw new UnexpectedPacketError(packet, 'SERVERDATA_RESPONSE_VALUE'); } return result.body; } disconnect() { return new Promise(resolve => { this._socket.end(null, null, resolve); }); } _query(packet) { const id = this._uniqueId; this._write({ id, ...packet }); return this._receive(id); } _write(packet) { this._socket.write((0, _packet.encode)(packet)); } async _receive(conversationId) { try { return await (0, _promiseTimeout.timeout)(new Promise(resolve => { this._callbacks.set(conversationId, resolve); }), this._timeout); } catch (err) { if (err instanceof _promiseTimeout.TimeoutError) { this._callbacks.delete(conversationId); throw new TimeoutError(); } else { throw err; } } } _onReceivePacket(buf) { const packet = (0, _packet.decode)(buf); const callback = this._callbacks.get(packet.id); // If the callback doesn't exist it may be a query that timed out, ignore. if (callback != null) { this._callbacks.delete(packet.id); callback(packet); } } get _uniqueId() { return this._currentId++; } } exports.connect = (host, port, password, timeout = 1000) => new Promise((resolve, reject) => { const socket = (0, _net.createConnection)({ host, port }, async () => { try { const client = new RconClient(socket, timeout); await client.authenticate(password); resolve(client); } catch (err) { socket.destroy(); reject(err); } }); socket.once('error', reject); });