UNPKG

rce.js

Version:

A wrapper for Rust Console Edition (RCE) that allows you to use RCON to create integrations.

170 lines 7.68 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const ws_1 = require("ws"); const types_1 = require("../types"); const commandManager_1 = __importDefault(require("../commands/commandManager")); const responseHandler_1 = __importDefault(require("../commands/responseHandler")); class SocketManager { _manager; _socket = null; connectionAttempts = 0; _reconnectionTimer = null; _isDestroyed = false; _hasEverConnected = false; constructor(manager) { this._manager = manager; } connect(opts) { return new Promise((resolve) => { if (this._isDestroyed) return resolve(false); let settled = false; const settle = (value) => { if (settled) return; settled = true; resolve(value); }; const { rcon } = opts; const { host, port, password } = rcon; if (this._reconnectionTimer) { clearTimeout(this._reconnectionTimer); this._reconnectionTimer = null; } const url = `ws://${host}:${port}/${password}`; this._socket = new ws_1.WebSocket(url); // ✅ unified error handler this._socket.on("error", (error) => { const isConnectionRefused = error.code === "ECONNREFUSED" || error.code === "ENOTFOUND"; this._manager.emit(types_1.RCEEvent.Error, { error: `WebSocket error for server "${opts.identifier}": ${error.message}`, server: this._manager.getServer(opts.identifier) || undefined, }); // stop reconnection if first connection is refused if (isConnectionRefused && this.connectionAttempts === 0) { this._manager.logger.warn(`[${opts.identifier}] Connection refused — stopping reconnection attempts.`); this._isDestroyed = true; } settle(false); }); const handleSuccess = () => { this.connectionAttempts = 0; this._hasEverConnected = true; const server = this._manager.getServer(opts.identifier); if (server) { server.socket = this._socket; if (!server.flags.includes("READY")) server.flags.push("READY"); this._manager.updateServer(server); } const s = this._manager.getServer(opts.identifier); if (!s) { this._manager.logger.warn(`[${opts.identifier}] No server found during Ready emit.`); settle(false); return; } this._manager.emit(types_1.RCEEvent.Ready, { server: s }); this._manager.logger.debug(`[${opts.identifier}] WebSocket connection established.`); settle(true); }; this._socket.once("open", handleSuccess); this._socket.on("close", (code, reason) => { this._manager.logger.debug(`[${opts.identifier}] WebSocket closed: ${code} - ${reason}`); if (this._socket) { if (this._socket.readyState === ws_1.WebSocket.OPEN || this._socket.readyState === ws_1.WebSocket.CLOSING) { this._socket.terminate(); } setTimeout(() => { if (this._socket) { this._socket.removeAllListeners(); this._socket = null; } }, 25); } const server = this._manager.getServer(opts.identifier); if (server) { server.flags = server.flags.filter((flag) => flag !== "READY"); this._manager.updateServer(server); } // ✅ only reconnect if we’ve previously connected if (code !== 1000 && !this._isDestroyed && this._hasEverConnected) { this.attemptReconnection(opts); } }); this._socket.on("message", (data) => { try { const parsed = JSON.parse(data.toString()); if (!parsed.Message) return; const uniqueId = parsed.Identifier; const message = parsed.Message.replace(/\u0000/g, "").trim(); const server = this._manager.getServer(opts.identifier); this._manager.logger.debug(`[${opts.identifier}] Received message: ${JSON.stringify(message)}`); // resolve any pending command if (uniqueId > 0) { const cmd = commandManager_1.default.get(opts.identifier, Number(uniqueId)); if (cmd) { if (cmd.timeout) clearTimeout(cmd.timeout); cmd.resolve(message); } } if (!server) { this._manager.logger.warn(`[${opts.identifier}] Received message for unknown server.`); return; } responseHandler_1.default.handle(this._manager, server, message); } catch (err) { this._manager.logger.error(`[${opts.identifier}] Failed to parse WebSocket message: ${err.message}`); } }); }); } attemptReconnection(opts) { if (this._isDestroyed) return; const { reconnection } = opts; const isReconnectionEnabled = reconnection?.enabled !== false; const maxAttempts = reconnection?.maxAttempts ?? -1; const interval = Math.min((reconnection?.interval ?? 5000) * Math.pow(2, this.connectionAttempts), 60_000); if (!isReconnectionEnabled) { this._manager.logger.warn(`[${opts.identifier}] Reconnection is disabled.`); return; } if (maxAttempts !== -1 && this.connectionAttempts >= maxAttempts) { this._manager.emit(types_1.RCEEvent.Error, { error: `WebSocket connection failed for server "${opts.identifier}" after ${maxAttempts} attempts.`, server: this._manager.getServer(opts.identifier) || undefined, }); return; } this.connectionAttempts++; this._manager.logger.warn(`[${opts.identifier}] Attempting to reconnect WebSocket... (Attempt ${this.connectionAttempts}${maxAttempts !== -1 ? `/${maxAttempts}` : ""})`); this._reconnectionTimer = setTimeout(() => { if (!this._isDestroyed) this.connect(opts); }, interval); } destroy() { this._isDestroyed = true; if (this._reconnectionTimer) { clearTimeout(this._reconnectionTimer); this._reconnectionTimer = null; } if (this._socket) { this._socket.removeAllListeners(); this._socket.terminate(); this._socket = null; } } getSocket() { return this._socket; } } exports.default = SocketManager; //# sourceMappingURL=socketManager.js.map