Coder Social home page Coder Social logo

rcon-client's Introduction

rcon-client

npm downloads

⚠️ This library has not been maintained for a while. Please use it with caution or switch to another implementation!

A simple and easy to use RCON client made to work with Minecraft servers. It's written in Typescript and uses async methods.

rcon-client has a built-in packet queue with a max pending setting which limits the number of packets sent before one is received. If you need to send a bunch of packets at once, this library might be right for you. This was mainly the reason why I created yet another implementation.

The Rcon class supports connecting and disconnecting at any time, making it easier to share an instance in many places.

Usage

import { Rcon } from "rcon-client"

const rcon = await Rcon.connect({
    host: "localhost", port: 25575, password: "1234"
})

console.log(await rcon.send("list"))

let responses = await Promise.all([
    rcon.send("help"),
    rcon.send("whitelist list")
])

for (response of responses) {
    console.log(response)
}

rcon.end()

Or alternatively you can create an instance via the constructor.

const rcon = new Rcon({ host: "localhost", port: 25575, password: "1234" })

await rcon.connect()
rcon.end()

More examples can be found inthe repository's examples/ folder.

Events

rcon-client uses node's event emitter internally. The event emitter is accessible with the emitter property. Additionally the on, once and off methods are exposed on the main class.

The Rcon class has these events:

  • connect
  • authenticated
  • end
  • error

Auto reconnect can be implemented with these events.

rcon-client's People

Contributors

janispritzkau avatar smibb avatar thecatontheflat avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

rcon-client's Issues

Error when using in combination with javascript.

When i start the bot i get this:
TypeError: Rcon is not a constructor

I have a file called Create.js:
const Rcon = require('rcon-client')
const rcon = new Rcon({
host: config.ip,
port: 25575,
password: config.RconPass
})

What is going wrong and how could i fix it?

Packets timing out seemingly randomly

Packets randomly time out according to my usage and the usage of Maxcr1. The linked pull is here, but here is a basic summary:
Packets just randomly time out and throw this error:

0|JammyBot  | Connection Error --- Details --- 
0|JammyBot  | NAME: Error 
0|JammyBot  | DESC: undefined 
0|JammyBot  | Stack: Error: Timeout for packet id 1
0|JammyBot  |     at Timeout._onTimeout (/opt/factorio/AwF-Bot-Production/node_modules/rcon-client/lib/rcon.js:117:28)
0|JammyBot  |     at listOnTimeout (internal/timers.js:549:17)
0|JammyBot  |     at processTimers (internal/timers.js:492:7)

The code for the function is here and it is mainly used here.
This is caused even on Factorio servers (connections) that should work as they do exist and are online, but the difference of just a few seconds changes the output, which shouldn't happen

This results with the following images (the first image should have all servers online and show all players, it instead shows errors. In the second image, the servers are shown just a minute apart (with no input from the backend done, Discord bot not restarted) with completely different output.
Screenshot 2021-01-01 at 21 17 03
Screenshot 2021-01-01 at 21 17 12

Maintained

Is this project maintained?

It seems to work roughly, but I do run into a lot of connection issues and have to retry commands often.

Error during connect leaves the client in broken state

If an error occurs during the connection of the socket then no close event is sent which means this.socket is not cleared and connect() errors out with "Already connected or connecting" when called a second time. See code below for reproduction.

Rcon = require("rcon-client").Rcon;

async function bug() {
    let client = new Rcon({
        host: "localhost",
        port: 1234,
        password: "blah",
    });

    client.on("error", () => { /* ignore */ });

    try {
        await client.connect();
    } catch (err) {
        console.log("first error:", err.message);
    }

    try {
        await client.connect();
    } catch (err) {
        console.log("second error:", err.message);
    }
}

bug().catch(console.log);
// Expected output
// first error: connect ECONNREFUSED 127.0.0.1:1234
// second error: connect ECONNREFUSED 127.0.0.1:1234

// Actual output
// first error: connect ECONNREFUSED 127.0.0.1:1234
// second error: Already connected or connecting

Raw Buffer as input and output

This is a feature request that I think may be useful in certain limited circumstances, and should be simple to implement. I want to be able to send a Buffer of bytes instead of a string, and I want to receive back a Buffer of bytes as the response instead of a string. Effectively bypassing the encoding and decoding as UTF-8.

Since Factorio doesn't care much about what bytes you send it, and it allows you to send any bytes as a response to an Rcon command (except for nul bytes) this feature would be useful for making a compact binary encoding to exchange data between Factorio and an external program.

Question

Is this only for Minecraft or does it work with any game?
You seem to advertise it as being only for Minecraft, but the name of the package kind of implies that this is a client for the protocol as a whole.
So yeah I'm confused and i'd like an answer so I don't waste my time trying to use it and failing.

How to use this with Javascript?

Hello, I found this package, and is genial, but, I don't know how to use this in Javascript and not Typescript, is a node package, so is possible, or not?

A way to filter color codes away from in-game command responses

I'm currently building an RCON function onto my Discord.js bot but only one issue with that is, color codes just look like normal text which can be an eyesore sometimes. Even console responses can look quite annoying at times as well so was kinda wondering, any possible way around this?

TypeError: net_1.connect is not a function

Trying to use this package in vuejs after importing, and passing the connection information to the Rcon class i receive the following error:

vue.runtime.esm.js?2b0e:1888 TypeError: net_1.connect is not a function
at Rcon.connect (rcon.js?53fe:34)
at Function.connect (rcon.js?53fe:28)
at VueComponent.addServer (Servers.vue?8d91:180)
at invokeWithErrorHandling (vue.runtime.esm.js?2b0e:1854)
at VueComponent.invoker (vue.runtime.esm.js?2b0e:2179)
at invokeWithErrorHandling (vue.runtime.esm.js?2b0e:1854)
at VueComponent.Vue.$emit (vue.runtime.esm.js?2b0e:3882)
at VueComponent.click (VBtn.js?8336:123)
at invokeWithErrorHandling (vue.runtime.esm.js?2b0e:1854)
at HTMLButtonElement.invoker (vue.runtime.esm.js?2b0e:2179)

My code is as follows:

import { Rcon } from 'rcon-client';

  const rcon = await Rcon.connect({
    host: 'HOST',
    port: 1234,
    password: 'PASSWORD',
  });
  console.log(await rcon.send('listplayers'));

I received the same error when trying to instantiate a new instance of the class.

Any suggestions?

Error on connection not being found when using .on("error")

@janispritzkau I'm not crashing (thanks for the fix), but I can't seem to pull out the error .on("error") never pulls up for me when there is a server that is not on. I've tried a similar process as midforger did above and also had no luck catching the connection error.
I wrote this to error #9 but realized I replied after it was fully closed so I wasn't sure if you would see it.

Crash

Rcon crashes when the server is not online with the unhandled error event message. I am new to JS can you guide me please?

Space Engineers: Timeout for packet id 1

I can't directly point at what is causing it besides the fact that it always happens when a command opens a dialogue on the server.
Here is the plugin used to create a rcon server:
https://github.com/Jimmacle/torch-rcon-plugin

Full error message:
"error": { "message": "Timeout for packet id 1", "stack": "Error: Timeout for packet id 1\n at Timeout._onTimeout (/opt/node_modules/rcon-client/lib/rcon.js:117:28)\n at listOnTimeout (internal/timers.js:557:17)\n at processTimers (internal/timers.js:500:7)" }

Events handlers are cleared on .end()

When closing the client using the .end() on the the Rcon class all the event listeners on the socket is cleared, this causes two problems.

  • It's not possible to wait for the socket to properly close as the end event is never emitted.
  • Errors during the close operation is not handled. For example if the game server connected to responds with RST instead of FIN you get the following unhelpful error message printed to the console:
events.js:174
      throw er; // Unhandled 'error' event
      ^

Error: read ECONNRESET
    at TCP.onStreamRead (internal/stream_base_commons.js:111:27)
Emitted 'error' event at:
    at emitErrorNT (internal/streams/destroy.js:91:8)
    at emitErrorAndCloseNT (internal/streams/destroy.js:59:3)
    at process._tickCallback (internal/process/next_tick.js:63:19)

(Question) Rcon output

Hi, I'm trying to retrieve the output from the server console without any send 🥲
Do you have any suggestions on how to accomplish this?

Does not respond with anything when no response received

When running a command in a game (Factorio in this case) a few of the commands do not get the "Command complete" on the game side, however instead of replying with (null) or "No Response" when using this we get an undefined variable. If we then try to catch other errors (like trying to connect to a shutdown server) we get stuck with the .catch statement showing up even when the command worked.

I would love if it sent and got nothing back (no error) it would tell us, then we could if(responses === "No Response") to define a variable if needed.

return value length capped?

Hey there!

First off I wanted to thank for this amazing tool! It helped me make some cool things happen, that I never thought would be possible that easily!

I'm now just trying to see what else I could do with it, and I stumbled across something odd. I'm trying to get the EnderItems for players, so far so good

const rcon = await Rcon.connect({
  host: process.env.RCON_HOST, port: process.env.RCON_PORT, password: process.env.RCON_PASSWORD
});

for (const player of players) {
  const ender = await rcon.send(`/data get entity ${player} EnderItems`);
}

This function gives me what I expect, which is the output of the command, but the length seems off, when i run the command on a certain player the output string length is 73130, tho I only get the first 4096(ish) characters by running it through rcon-client.

Which, as you can imagine, makes it kinda hard to parse in any way :D

Is there something I'm doing wrong, or is the output just limited to that?

Thanks in advance for any reply :3

// edit:

I also had a look through the issues, maybe this is related to the response being split into multiple packets? If so, is there a workaround for that?

Some more research did the trick :D
The max packet size for RCON is just limited to 4096, so I'ma close this now and hope for #14 ^^

Fix connection issues and fragmentation

There are many issues with how RCON connections are handled on Minecraft servers, so special hacks are required to make them work robustly.

I took some time to review the decompiled Java source code and created a minimal implementation for Deno:

export interface RconConnectOptions extends Deno.ConnectOptions {
  port: number;
  password: string;
  host?: string;
}

export class RconClient {
  static async connect(options: RconConnectOptions): Promise<RconClient> {
    const conn = await Deno.connect(options);
    conn.setNoDelay(true);
    const client = new RconClient(conn);
    await client.auth(options.password);
    return client;
  }

  #writer: WritableStreamDefaultWriter<Uint8Array>;
  #reader: ReadableStreamBYOBReader;

  #lockPromise: Promise<void> = Promise.resolve();
  #authed = false;
  #reqId = 0;
  #closed = false;

  #buf = new Uint8Array(14 + 3 * 4096); // 14 byte header + worst case utf-8 length of 4096 utf-16 code units
  #skipRead = false;
  #pos = 0;

  constructor(conn: Deno.Conn) {
    this.#writer = conn.writable.getWriter();
    this.#reader = conn.readable.getReader({ mode: "byob" });
  }

  async auth(password: string) {
    if (this.#authed) throw new Error("Already authenticated");
    const release = await this.#lock();
    try {
      const reqId = this.#nextReqId();
      await this.#send(reqId, 3, password);

      const res = await this.#recv();
      if (!res) throw new Error("Connection closed");

      if (res.id == -1) throw new Error("Authentication failed");
      if (res.id != reqId) throw new Error(`Invalid response id (expected ${reqId}, got ${res.id})`);
      if (res.type != 2) throw new Error(`Unexpected response type (expected 2, got ${res.type})`);

      this.#authed = true;
    } catch (e) {
      await this.close();
      throw e;
    } finally {
      release();
    }
  }

  async cmd(cmd: string): Promise<string | null> {
    if (!this.#authed) throw new Error("Not authenticated");
    const release = await this.#lock();
    try {
      const reqId = this.#nextReqId();
      await this.#send(reqId, 2, cmd);

      // By reading immediately after each write, we avoid packets being combined by the TCP stack,
      // thus avoiding an implementation bug in the Minecraft server
      const res = await this.#recv();
      if (!res) return null;

      if (res.id != reqId) throw new Error(`Invalid response id (expected ${reqId}, got ${res.id})`);
      if (res.type != 0) throw new Error(`Unexpected response type (expected 0, got ${res.type})`);

      if (res.message.length < 4096) {
        // message is guaranteed to be not fragmented
        // note: minecraft java counts the length as utf-16 code units (like we do here)
        this.#reqId += 1;
        return res.message;
      }

      const dummyReqId = this.#nextReqId();
      // send dummy request which is guaranteed to be not fragmented
      await this.#send(dummyReqId, -1, "");

      let output = res.message;
      while (true) {
        const res = await this.#recv();
        if (!res) return null;

        // message is complete when dummy response is received
        if (res.id == dummyReqId) break;

        if (res.id != reqId) throw new Error(`Invalid response id (expected ${reqId}, got ${res.id})`);
        if (res.type != 0) throw new Error(`Unexpected response type (expected 0, got ${res.type})`);

        output += res.message;
      }
      return output;
    } catch (e) {
      if (!this.#closed) await this.close();
      throw e;
    } finally {
      release();
    }
  }

  async close() {
    this.#closed = true;
    await this.#writer.close();
  }

  async #send(id: number, type: number, message: string) {
    if (this.#closed) throw new Error("Connection closed");
    const payload = new TextEncoder().encode(message);
    const buf = new Uint8Array(14 + payload.length);
    const view = new DataView(buf.buffer);
    view.setUint32(0, 10 + payload.length, true);
    view.setInt32(4, id, true);
    view.setInt32(8, type, true);
    buf.set(payload, 12);
    // this may still result in a fragmented tcp packet if used over external network interfaces,
    // therefore using RCON over anything other than localhost is not recommended
    if (buf.length > 1460) throw new Error("Message too long");
    await this.#writer.write(buf);
  }

  async #recv(): Promise<{ id: number; type: number; message: string } | null> {
    while (!this.#closed) {
      if (!this.#skipRead) {
        const result = await this.#reader.read(this.#buf.subarray(this.#pos));
        if (result.done) {
          this.#closed = true;
          return null;
        }
        this.#buf = new Uint8Array(result.value.buffer);
        this.#pos += result.value.length;
      }

      this.#skipRead = false;

      if (this.#pos < 4) continue;
      const view = new DataView(this.#buf.buffer, this.#buf.byteOffset, this.#pos);
      const len = view.getUint32(0, true);

      if (this.#pos < 4 + len) continue;
      const id = view.getInt32(4, true);
      const type = view.getInt32(8, true);
      const payload = this.#buf.subarray(12, len + 2);
      const message = new TextDecoder().decode(payload);

      this.#buf.copyWithin(0, len + 4);
      this.#pos -= len + 4;
      this.#skipRead = true;

      return { id, type, message };
    }
    return null;
  }

  #lock(): Promise<() => void> {
    return new Promise((resolve) => {
      this.#lockPromise = this.#lockPromise.then(() => {
        return new Promise((release) => resolve(release));
      });
    });
  }

  #nextReqId() {
    const id = this.#reqId;
    this.#reqId = (this.#reqId + 1) % 0x8000_0000;
    return id;
  }
}

I may use it as reference for improving/rewriting this library. Not sure how or if to handle non-Minecraft RCON implementations.

Not working for Chivalry Medieval Warfare

Title is pretty self explanatory

I took some time to debug the code and here is what happened... Couldn't authenticate

image

Does it have anything to do with the fact chivalry has this documentation for the Rcon Protocol: RCON Documentation

Would this be a problem?

const { Rcon } = require('rcon-client')

async function main() {
    const rcon = new Rcon({
        host: "79.98.25.135",
        port: 25575,
        password: "xxxxxx"
    })

    rcon.on("connect", () => console.log("connected"))
    rcon.on("authenticated", () => console.log("authenticated"))
    rcon.on("end", () => console.log("end"))

    await rcon.connect()

    console.log('connected')
    // console.log(await rcon.send("/list"))

    // await Promise.all([...Array(10)].map((_, i) => rcon.send(`/say ${i}`)))

    rcon.end()
}

main().catch(console.error)

Error: connect ETIMEDOUT 79.98.25.135:25575

Crash when there is no endpoint

basically the same as
#5
but the reporter unfortunately did not tell HOW he fixed it
https://pastebin.com/MsAJcuRX

!!! ERROR !!! Error: connect ECONNREFUSED 127.0.0.1:25574
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1142:16) {
  errno: -111,
  code: 'ECONNREFUSED',
  syscall: 'connect',
  address: '127.0.0.1',
  port: 25574
}

Tried to communicate with Palworld dedicated Linux Server

I try to connect to a Palworld dedicated linux server v0.1.4.1. this is my script:

import { Rcon } from "rcon-client"

const rcon = await Rcon.connect({
    host: "192.168.228.7", port: 25575, password: "pw"
});
rcon.on("authenticated", () => console.log("authenticated"));
// console.log(await rcon.send("Info"));

let responses = await Promise.all([
    rcon.send("Info"),
    rcon.send("Broadcast **OK_don-t_ignore_this_anyway_only_a_test**")
])

for (let response of responses) {
    console.log(response);
}

I get this error:

/home/websocket_client/node_modules/rcon-client/lib/rcon.js:117
reject(new Error(Timeout for packet id ${id}));
^

Error: Timeout for packet id 1
at Timeout._onTimeout (/home/websocket_client/node_modules/rcon-client/lib/rcon.js:117:28)
at listOnTimeout (node:internal/timers:569:17)
at process.processTimers (node:internal/timers:512:7)

Node.js v18.19.0

But the Broadcast gets still delievered and I can see the message on the server. I don't get any response from "Info" or "ShowPlayers" commands.
I tested many other clients, but sadly no client works for me.

I also use ARRCON shell utility, which is able to send & recieve messages from server without a problem.
Any chance to get this fixed?

Regards, Sascha

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.