/** * Rust WebSocket RCON Client — pure vanilla JS, zero dependencies. * * Protocol: * - Connect: ws://host:port/password * - Outgoing: { Identifier: int, Message: string, Name: "WebRcon" } * - Identifier > 1000 → request/response (callback tracked) * - Identifier <= 1000 → unsolicited server push (Generic/Log/Error/Warning/Chat) * * Usage: * Rcon.on('connected', () => { ... }); * Rcon.on('disconnected', (ev) => { ... }); * Rcon.on('message', (msg) => { ... }); * Rcon.connect('192.168.1.100:28016', 'mypassword'); * Rcon.request('serverinfo', (msg) => { console.log(JSON.parse(msg.Message)); }); * Rcon.command('say Hello world'); * Rcon.getPlayers((players) => { ... }); */ var Rcon = (function () { 'use strict'; var ConnectionStatus = { CONNECTING: 0, OPEN: 1, CLOSING: 2, CLOSED: 3 }; var socket = null; var address = null; var callbacks = {}; var lastIndex = 1001; var listeners = {}; /** * Simple pub/sub event emitter. */ function emit(event, data) { var cbs = listeners[event]; if (cbs) { for (var i = 0; i < cbs.length; i++) { cbs[i](data); } } } /** * Connect to a Rust server's WebSocket RCON. * @param {string} addr - "host:port" * @param {string} pass - RCON password */ function connect(addr, pass) { disconnect(); address = addr; socket = new WebSocket('ws://' + addr + '/' + pass); socket.onopen = function () { emit('connected'); }; socket.onclose = function (ev) { emit('disconnected', ev); }; socket.onerror = function (ev) { emit('error', ev); }; socket.onmessage = function (e) { var data = JSON.parse(e.data); // Targeted response — match to pending callback if (data.Identifier > 1000) { var cb = callbacks[data.Identifier]; if (cb) { cb(data); } delete callbacks[data.Identifier]; return; } // Unsolicited server push (console, chat, etc.) emit('message', data); }; } /** * Disconnect from the server. */ function disconnect() { callbacks = {}; if (socket) { socket.close(); socket = null; } address = null; } /** * Send a fire-and-forget command (no response expected). * @param {string} msg - RCON command * @param {number} [identifier] - Defaults to -1 */ function command(msg, identifier) { if (!socket || !isConnected()) return; if (identifier === undefined || identifier === null) { identifier = -1; } socket.send(JSON.stringify({ Identifier: identifier, Message: msg, Name: 'WebRcon' })); } /** * Send a command and invoke callback with the response. * @param {string} msg - RCON command * @param {function} cb - callback(response) */ function request(msg, cb) { lastIndex++; callbacks[lastIndex] = cb; command(msg, lastIndex); } /** * Returns true if the WebSocket is open. */ function isConnected() { return socket !== null && socket.readyState === ConnectionStatus.OPEN; } /** * Get the current connected address (host:port). */ function getAddress() { return address; } /** * Convenience: fetch the player list. * @param {function} cb - callback(playersArray) */ function getPlayers(cb) { request('playerlist', function (response) { cb(JSON.parse(response.Message)); }); } /** * Register an event listener. * Events: 'connected', 'disconnected', 'error', 'message' */ function on(event, cb) { if (!listeners[event]) listeners[event] = []; listeners[event].push(cb); } /** * Remove an event listener. */ function off(event, cb) { var cbs = listeners[event]; if (cbs) { listeners[event] = cbs.filter(function (f) { return f !== cb; }); } } return { connect: connect, disconnect: disconnect, command: command, request: request, isConnected: isConnected, getAddress: getAddress, getPlayers: getPlayers, on: on, off: off }; })();