UNPKG

rcon-node

Version:

A TypeScript RCON client library for modern game servers.

139 lines 5.04 kB
import { createConnection } from "node:net"; import { BaseClient } from "./base.client"; import { createPacket, PACKET_TYPE_AUTH, PACKET_TYPE_AUTH_RESPONSE, PACKET_TYPE_COMMAND, PACKET_TYPE_RESPONSE, } from "../utils/packet"; export class MinecraftClient extends BaseClient { constructor() { super(...arguments); this.socket = null; this.requestId = 0; this.pending = new Map(); this.connected = false; this.authenticated = false; this.buffer = Buffer.alloc(0); this.authCallback = null; } connect() { return new Promise((resolve, reject) => { this.socket = createConnection({ host: this.options.host, port: this.options.port, }, () => { this.connected = true; this.emit("connect"); this.authenticate().then(resolve).catch(reject); }); this.socket.on("data", (data) => this.onData(data)); this.socket.on("error", (err) => this.emit("error", err)); this.socket.on("end", () => { this.connected = false; this.authenticated = false; this.emit("end"); }); if (this.options.timeout) { this.socket.setTimeout(this.options.timeout, () => { if (!this.connected) { const err = new Error("Connection timed out."); this.emit("error", err); this.end(); reject(err); } }); } }); } authenticate() { return new Promise((resolve, reject) => { const timeout = setTimeout(() => { this.authCallback?.(new Error("Authentication timed out.")); }, this.options.timeout ?? 5000); this.authCallback = (err) => { clearTimeout(timeout); if (err) { this.end(); reject(err); } else { this.authenticated = true; this.emit("authenticated"); resolve(); } this.authCallback = null; }; this.sendPacket(PACKET_TYPE_AUTH, this.options.password).catch((err) => { this.authCallback?.(err); }); }); } onData(data) { this.buffer = Buffer.concat([this.buffer, data]); while (this.buffer.length >= 4) { const size = this.buffer.readInt32LE(0); if (this.buffer.length < 4 + size) { break; } const packet = this.buffer.subarray(4, 4 + size); this.buffer = this.buffer.subarray(4 + size); const id = packet.readInt32LE(0); const type = packet.readInt32LE(4); if (this.authCallback) { if (type === PACKET_TYPE_AUTH_RESPONSE) { if (id === -1) { this.authCallback(new Error("Authentication failed.")); } else if (id === this.requestId) { this.authCallback(); } } else if (type === PACKET_TYPE_RESPONSE) { // Minecraft sends an empty SERVERDATA_RESPONSE_VALUE before the auth response, which can be ignored. } continue; } if (type === PACKET_TYPE_RESPONSE) { const body = packet.toString("utf8", 8, packet.length - 2); const resolve = this.pending.get(id); if (resolve) { resolve(body); this.pending.delete(id); } else { this.emit("response", body); } } } } send(command) { return new Promise((resolve, reject) => { if (!this.authenticated) { return reject(new Error("Not authenticated.")); } this.sendPacket(PACKET_TYPE_COMMAND, command) .then((id) => { this.pending.set(id, resolve); }) .catch(reject); }); } sendPacket(type, body) { return new Promise((resolve, reject) => { if (!this.socket) { return reject(new Error("Socket not connected.")); } const id = ++this.requestId; const buffer = createPacket(id, type, body); this.socket.write(buffer, (err) => { if (err) { return reject(err); } resolve(id); }); }); } end() { if (this.socket) { this.socket.end(); this.socket = null; } } } //# sourceMappingURL=minecraft.client.js.map