tsrcon-client
Version:
A TypeScript RCON client for communicating with a RCON Server.
196 lines (194 loc) • 6.35 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
RCONClient: () => RCONClient
});
module.exports = __toCommonJS(index_exports);
var import_net = require("net");
var import_crypto = require("crypto");
var RCONClient = class {
constructor(host, port, password, opts) {
this.host = host;
this.port = port;
this.password = password;
this.buffer = Buffer.alloc(0);
this.isConnected = false;
this.reconnectAttempts = 0;
this.authenticated = false;
this.callbacks = /* @__PURE__ */ new Map();
this.requestQueue = [];
this.processingQueue = false;
this.lastRequestTime = 0;
this.socket = new import_net.Socket();
this.options = {
timeout: opts?.timeout ?? 3e3,
retries: opts?.retries ?? 1,
reconnect: opts?.reconnect ?? true,
reconnectDelay: opts?.reconnectDelay ?? 2e3,
maxReconnectAttempts: opts?.maxReconnectAttempts ?? 5,
minDelayBetweenRequests: opts?.minDelayBetweenRequests ?? 0
};
this.socket.on("data", this.handleData.bind(this));
this.socket.on("error", (err) => {
for (const cb of this.callbacks.values()) cb.reject(err);
this.callbacks.clear();
});
this.socket.on("close", () => {
this.isConnected = false;
this.authenticated = false;
if (this.options.reconnect) {
this.tryReconnect();
}
});
}
async connect() {
return new Promise((resolve, reject) => {
this.socket.connect(this.port, this.host, async () => {
try {
await this.authenticate();
this.isConnected = true;
this.reconnectAttempts = 0;
resolve();
} catch (e) {
reject(e);
}
});
});
}
disconnect() {
this.socket.end();
this.socket.destroy();
this.isConnected = false;
this.authenticated = false;
}
async tryReconnect() {
if (this.reconnectAttempts >= this.options.maxReconnectAttempts) return;
this.reconnectAttempts++;
setTimeout(async () => {
try {
await this.connect();
console.log("RCON: Reconnected successfully");
} catch (err) {
console.error("RCON: Reconnect failed:", err);
this.tryReconnect();
}
}, this.options.reconnectDelay);
}
handleData(data) {
this.buffer = Buffer.concat([this.buffer, data]);
while (this.buffer.length >= 4) {
const packetLength = this.buffer.readInt32LE(0);
if (this.buffer.length < packetLength + 4) return;
const packet = this.buffer.subarray(4, 4 + packetLength);
const requestId = packet.readInt32LE(0);
const type = packet.readInt32LE(4);
const body = packet.toString("utf8", 8, packet.length - 2);
this.buffer = this.buffer.subarray(4 + packetLength);
const cb = this.callbacks.get(requestId);
if (!cb) continue;
if (type === 2 /* AUTH_RESPONSE */ && requestId === -1) {
cb.reject(new Error("Authentication failed"));
this.callbacks.delete(requestId);
continue;
}
cb.buffer.push(body);
clearTimeout(cb.timeout);
cb.timeout = setTimeout(() => {
cb.resolve(cb.buffer.join(""));
this.callbacks.delete(requestId);
}, 10);
}
}
sendPacket(type, body) {
const requestId = (0, import_crypto.randomInt)(1, 2147483647);
const payload = Buffer.from(body + "\0", "utf8");
const size = 4 + 4 + payload.length + 1;
const buf = Buffer.alloc(4 + size);
buf.writeInt32LE(size, 0);
buf.writeInt32LE(requestId, 4);
buf.writeInt32LE(type, 8);
payload.copy(buf, 12);
buf.writeUInt8(0, 12 + payload.length);
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
this.callbacks.delete(requestId);
reject(new Error("RCON request timed out"));
}, this.options.timeout);
this.callbacks.set(requestId, {
resolve,
reject,
buffer: [],
timeout
});
this.socket.write(buf);
});
}
async authenticate() {
for (let attempt = 1; attempt <= this.options.retries + 1; attempt++) {
try {
await this.sendPacket(3 /* AUTH */, this.password);
this.authenticated = true;
return;
} catch (err) {
if (attempt > this.options.retries) throw err;
}
}
}
async sendCommand(command) {
return new Promise((resolve, reject) => {
const task = async () => {
const now = Date.now();
const delay = Math.max(this.options.minDelayBetweenRequests - (now - this.lastRequestTime), 0);
if (delay > 0) await new Promise((r) => setTimeout(r, delay));
try {
if (!this.authenticated) throw new Error("Not authenticated");
const result = await this.sendPacket(2 /* COMMAND */, command);
this.lastRequestTime = Date.now();
resolve(result);
} catch (err) {
reject(err);
}
this.processingQueue = false;
this.processQueue();
};
this.requestQueue.push(task);
this.processQueue();
});
}
async processQueue() {
if (this.processingQueue || this.requestQueue.length === 0) return;
this.processingQueue = true;
const next = this.requestQueue.shift();
if (next) await next();
}
async ping() {
try {
const result = await this.sendCommand("list");
return !!result;
} catch {
return false;
}
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
RCONClient
});