import { Rcon } from "rcon-client"; import { sanitizeRconOutput } from "./rcon-validation"; const TIMEOUT_MS = 5_000; const CLEANUP_TIMEOUT_MS = 1_000; const MAX_ACTIVE_CONNECTIONS = 8; const activeConnections = new Set(); type Connection = { id?: string; host: string; port: number; password: string }; type FailureReason = "busy" | "timeout" | "unavailable"; export interface RconTransport { connect(): Promise; send(command: string): Promise; end(): Promise; destroy?(): void; } type TransportFactory = (connection: Connection) => RconTransport; class RconDeadlineError extends Error {} async function deadline(operation: Promise, timeout: () => void, timeoutMs = TIMEOUT_MS) { let timer: ReturnType | undefined; try { return await Promise.race([ operation, new Promise((_, reject) => { timer = setTimeout(() => { timeout(); reject(new RconDeadlineError("RCON operation timed out")); }, timeoutMs); }), ]); } finally { if (timer) clearTimeout(timer); } } function defaultTransport(connection: Connection): RconTransport { const client = new Rcon({ host: connection.host, port: connection.port, password: connection.password, timeout: TIMEOUT_MS, maxPending: 1, }); return { connect: () => client.connect(), send: (command) => client.send(command), end: async () => { if (!client.socket) return; if (client.socket.connecting || !client.socket.writable) { client.socket.destroy(); return; } await client.end(); }, destroy: () => client.socket?.destroy(), }; } function failure(error: unknown): { ok: false; reason: FailureReason } { return { ok: false, reason: error instanceof RconDeadlineError ? "timeout" : "unavailable" }; } async function withTransport( connection: Connection, operation: (transport: RconTransport) => Promise, factory: TransportFactory, ): Promise { const key = connection.id ?? `${connection.host}:${connection.port}`; if (activeConnections.has(key) || activeConnections.size >= MAX_ACTIVE_CONNECTIONS) { return { ok: false, reason: "busy" }; } activeConnections.add(key); let transport: RconTransport | null = null; try { transport = factory(connection); return await deadline(operation(transport), () => transport?.destroy?.()); } catch (error) { return failure(error); } finally { if (transport) { await deadline(transport.end(), () => transport?.destroy?.(), CLEANUP_TIMEOUT_MS).catch(() => undefined); } activeConnections.delete(key); } } export async function testRconConnection(connection: Connection, factory: TransportFactory = defaultTransport) { return withTransport(connection, async (transport) => { await transport.connect(); return { ok: true as const }; }, factory); } export async function executeRcon(connection: Connection, command: string, factory: TransportFactory = defaultTransport) { return withTransport(connection, async (transport) => { await transport.connect(); const response = await transport.send(command); return { ok: true as const, response: sanitizeRconOutput(response) }; }, factory); }