Skip to content

purnellbp/htmx-rcon

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

rcon-htmx

Source RCON and Rust (WebSocket RCON) bridge for htmx. Uses Node.js built-ins plus the ws package.

Connect your htmx frontend to game servers over secure WebSockets: the library supports the Source RCON binary protocol (CS2, Garry's Mod, ARK, etc.) and Rust WebSocket RCON. You get HTML fragments (e.g. hx-swap-oob) and build the UI however you want.

Why

Browsers can't open raw TCP sockets, so you can't talk RCON directly. If your web panel runs on HTTPS, you also can't use unencrypted WebSockets (mixed content blocking). This package solves both problems:

Browser (HTTPS)  <-- wss:// -->  Node Backend  <-- TCP -->  Game Server
     htmx                        rcon-htmx                  RCON :27015

The WebSocket rides on the same HTTPS server that serves your page — no mixed content, no CORS, no extra ports.

Install

npm install rcon-htmx ws

ws is a peer dependency — you likely already have it if you're using WebSockets.

Quick Start

Server (5 lines)

const express = require('express');
const { createRconBridge } = require('rcon-htmx');

const app = express();
const server = app.listen(3000);

createRconBridge(server, {
  host: '192.168.1.10',
  port: 27015,
  password: process.env.RCON_PASSWORD,
});

HTML (htmx)

<script src="https://unpkg.com/htmx.org@2/dist/htmx.min.js"></script>
<script src="https://unpkg.com/htmx-ext-ws@2/dist/ws.js"></script>

<div hx-ext="ws" ws-connect="/ws/rcon">

  <!-- RCON output appears here automatically via hx-swap-oob -->
  <div id="rcon-output"></div>

  <!-- ws-send serializes the form as JSON and sends over WebSocket -->
  <form ws-send>
    <input type="text" name="command" placeholder="Enter command..." />
    <button type="submit">Run</button>
  </form>

</div>

That's it. Type status and hit Run — the server executes the RCON command and pushes styled HTML back into #rcon-output.

API

createRconBridge(httpServer, options)

Attaches a WebSocket endpoint to your HTTP server that bridges to RCON.

Returns: WebSocketServer instance.

Options

Option Type Default Description
host string RCON server hostname (required if authMode is 'server')
port number 27015 RCON server port
password string RCON password (required if authMode is 'server')
path string '/ws/rcon' WebSocket endpoint path
authMode 'server' | 'client' 'server' Who provides credentials (see below)
timeout number 5000 Connection and command timeout in ms
targetId string 'rcon-output' DOM element ID for OOB swap target
swapStyle string 'beforeend' htmx swap strategy (beforeend, innerHTML, etc.)
formatLine function built-in Custom formatter: (text, meta) => htmlString
onConnect function Called when a session connects: (ws, rcon) => void
onCommand function Command filter: (command, ws) => false to block

Auth Modes

Server-side auth (default)

RCON credentials are in your server config. Every WebSocket connection auto-authenticates. Use this when your web panel has its own auth layer.

createRconBridge(server, {
  host: '192.168.1.10',
  password: process.env.RCON_PASSWORD,
});

Client-side auth

The browser sends credentials as the first WebSocket message. Good for multi-server panels where the user picks which server to connect to.

createRconBridge(server, {
  authMode: 'client',
});

The first message from the client must be:

{ "auth": { "host": "192.168.1.10", "port": 27015, "password": "secret" } }

In htmx, you can do this with a login form:

<div hx-ext="ws" ws-connect="/ws/rcon">
  <!-- Auth form (shown first) -->
  <form ws-send id="auth-form">
    <input type="hidden" name="auth.host" value="192.168.1.10" />
    <input type="hidden" name="auth.port" value="27015" />
    <input type="password" name="auth.password" placeholder="RCON password" />
    <button type="submit">Connect</button>
  </form>

  <div id="rcon-output"></div>

  <form ws-send>
    <input type="text" name="command" />
    <button>Run</button>
  </form>
</div>

Blocking Commands

Use onCommand to filter dangerous commands:

createRconBridge(server, {
  host: '192.168.1.10',
  password: 'secret',
  onCommand: (command) => {
    const blocked = ['quit', 'exit', 'rcon_password', 'changelevel'];
    if (blocked.some((b) => command.toLowerCase().startsWith(b))) {
      return false; // sends error HTML back to the client
    }
  },
});

Custom Formatting

Override the default HTML output:

createRconBridge(server, {
  host: '192.168.1.10',
  password: 'secret',
  formatLine: (text, meta) => {
    // meta.type is 'response', 'error', or 'info'
    // meta.command is the command that was run (for responses)
    // meta.timestamp is the current time string
    return `<pre class="my-line">[${meta.timestamp}] ${text}</pre>`;
  },
});

Advanced: Direct RCON Access

For custom setups, you can use the RCON client directly:

const { RconConnection } = require('rcon-htmx');

const rcon = new RconConnection({
  host: '192.168.1.10',
  port: 27015,
  password: 'secret',
});

await rcon.connect();
const response = await rcon.exec('status');
console.log(response);
rcon.destroy();

HTML Output Format

The default formatter produces HTML like this for each command:

<!-- Appended to #rcon-output via hx-swap-oob="beforeend" -->
<div id="rcon-output" hx-swap-oob="beforeend">
  <div class="rcon-response">
    <div class="rcon-meta">
      <span class="rcon-cmd">> status</span>
      <span class="rcon-time">8:30:15 AM</span>
    </div>
    <div class="rcon-body">
      <span class="rcon-line">hostname: My Server</span>
      <span class="rcon-line">players : 12/24</span>
    </div>
  </div>
</div>

CSS classes you can style:

  • .rcon-response — command + output wrapper
  • .rcon-meta — header row with command and timestamp
  • .rcon-cmd — the command that was run
  • .rcon-time — timestamp
  • .rcon-body — response text container
  • .rcon-line — individual output line
  • .rcon-error — error message
  • .rcon-info — info message (connect, disconnect)
  • .rcon-status — connection status badge (has .connected or .disconnected)

HTTPS / Production

For HTTPS, put the Node server behind a TLS-terminating reverse proxy (nginx, Caddy) or use Node's https module directly. The htmx WS extension automatically upgrades ws:// to wss:// when the page is served over HTTPS — no config needed.

server {
    listen 443 ssl;
    server_name panel.example.com;

    ssl_certificate     /etc/ssl/cert.pem;
    ssl_certificate_key /etc/ssl/key.pem;

    location / {
        proxy_pass http://localhost:3001;
    }

    location /ws/rcon {
        proxy_pass http://localhost:3001;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

License

MIT

About

Source RCON and Rust (WebSocket RCON) bridge for htmx. Uses Node.js built-ins plus the ws package.

Topics

Resources

Stars

Watchers

Forks

Contributors