rcon-node
Version:
A TypeScript RCON client library for modern game servers.
147 lines • 5 kB
JavaScript
import { createConnection } from "node:net";
import { BaseClient } from "./base.client";
import { PACKET_TYPE_AUTH, PACKET_TYPE_AUTH_RESPONSE, PACKET_TYPE_COMMAND, PACKET_TYPE_RESPONSE, } from "../utils/packet";
import { buildValheimPacket, extractValheimPackets, } from "../utils/valheim.utils";
export class ValheimClient 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]);
const { packets, remaining } = extractValheimPackets(this.buffer);
this.buffer = remaining;
for (const packet of packets) {
this.handlePacket(packet);
}
}
handlePacket(packet) {
const { id, type, body } = packet;
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) {
// Some servers send an empty response during auth; ignore it.
}
return;
}
if (type === PACKET_TYPE_RESPONSE) {
const resolve = this.pending.get(id);
if (resolve) {
this.pending.delete(id);
resolve(body);
}
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 = buildValheimPacket(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;
}
}
async testAuthentication() {
try {
if (!this.connected) {
await this.connect();
}
}
catch {
throw new Error("Authentication failed.");
}
}
}
//# sourceMappingURL=valheim.client.js.map