UNPKG

tsrcon-client

Version:

A TypeScript RCON client for communicating with a RCON Server.

171 lines (170 loc) 5.31 kB
// src/index.ts import { Socket } from "net"; import { randomInt } from "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 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 = 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; } } }; export { RCONClient };