UNPKG

rcon-node

Version:

A TypeScript RCON client library for modern game servers.

105 lines 3.96 kB
import { createSocket } from "node:dgram"; import { BaseClient } from "./base.client"; import { BattlEyePacketType, createAckPacket, createCommandPacket, createLoginPacket, parseBattlEyePacket, } from "../utils/battleye"; export class BattlEyeClient extends BaseClient { constructor() { super(...arguments); this.socket = null; this.seq = 0; this.keepAlive = null; } connect() { return new Promise((resolve, reject) => { this.socket = createSocket("udp4"); const socket = this.socket; const timeout = setTimeout(() => { socket.close(); reject(new Error("Authentication timed out.")); }, this.options.timeout ?? 5000); const onMessage = (msg) => { const packet = parseBattlEyePacket(msg); if (packet.type === BattlEyePacketType.LOGIN) { socket.off("message", onMessage); clearTimeout(timeout); if (packet.success) { this.emit("connect"); this.emit("authenticated"); socket.on("message", (data) => this.handleMessage(data)); this.keepAlive = setInterval(() => { this.send(""); }, 30000); resolve(); } else { socket.close(); reject(new Error("Authentication failed.")); } } }; socket.on("message", onMessage); socket.on("error", (err) => { socket.close(); reject(err); }); socket.on("close", () => { if (this.keepAlive) { clearInterval(this.keepAlive); } this.emit("end"); }); const packet = createLoginPacket(this.options.password); socket.send(packet, this.options.port, this.options.host); }); } handleMessage(msg) { try { const packet = parseBattlEyePacket(msg); if (packet.type === BattlEyePacketType.MESSAGE) { this.emit("response", packet.payload.toString("ascii")); if (this.socket) { const ack = createAckPacket(packet.seq); this.socket.send(ack, this.options.port, this.options.host); } } } catch (e) { this.emit("error", e); } } send(command) { return new Promise((resolve, reject) => { if (!this.socket) { return reject(new Error("Socket not connected.")); } const seq = this.seq; this.seq = (this.seq + 1) & 0xff; const packet = createCommandPacket(seq, command); const onMessage = (msg) => { try { const data = parseBattlEyePacket(msg); if (data.type === BattlEyePacketType.COMMAND && data.seq === seq) { this.socket?.off("message", onMessage); resolve(data.payload.toString("ascii")); } } catch { // Ignore parsing errors for other packet types } }; this.socket.on("message", onMessage); this.socket.send(packet, this.options.port, this.options.host, (err) => { if (err) { this.socket?.off("message", onMessage); reject(err); } }); }); } end() { if (this.socket) { this.socket.close(); this.socket = null; } } } //# sourceMappingURL=battleye.client.js.map