├── README.md ├── dist ├── ControlPanel.d.ts ├── ControlPanel.js ├── ControlPanel.js.map ├── Global.d.ts ├── Global.js ├── Global.js.map ├── Server.d.ts ├── Server.js ├── Server.js.map ├── commands │ ├── connect.d.ts │ ├── connect.js │ ├── connect.js.map │ ├── openServer.d.ts │ ├── openServer.js │ └── openServer.js.map ├── debugging │ ├── DebuggingClient.d.ts │ ├── DebuggingClient.js │ ├── DebuggingClient.js.map │ ├── DebuggingInterface.d.ts │ ├── DebuggingInterface.js │ ├── DebuggingInterface.js.map │ ├── DebuggingServer.d.ts │ ├── DebuggingServer.js │ └── DebuggingServer.js.map ├── main.d.ts ├── main.js ├── main.js.map └── utils │ ├── escapeString.d.ts │ ├── escapeString.js │ ├── escapeString.js.map │ ├── getAvailablePort.d.ts │ ├── getAvailablePort.js │ ├── getAvailablePort.js.map │ ├── loadConfig.d.ts │ ├── loadConfig.js │ ├── loadConfig.js.map │ ├── log.d.ts │ ├── log.js │ └── log.js.map ├── package-lock.json ├── package.json ├── src ├── ControlPanel.ts ├── Global.ts ├── Server.ts ├── commands │ ├── connect.ts │ └── openServer.ts ├── debugging │ ├── DebuggingClient.ts │ ├── DebuggingInterface.ts │ └── DebuggingServer.ts ├── main.ts └── utils │ ├── escapeString.ts │ ├── getAvailablePort.ts │ ├── loadConfig.ts │ └── log.ts └── tsconfig.json /README.md: -------------------------------------------------------------------------------- 1 |

Haxball Server

2 | 3 |

Haxball Server is a feature-rich and stable headless server utility for Haxball.

4 | 5 |

6 | GitHub stars 7 | GitHub forks 8 | GitHub issues 9 | GitHub code size in bytes 10 | npm 11 |

12 | 13 |
14 | 15 | * Easily close and open rooms 16 | * Manage your rooms using a Discord bot 17 | * Open more than 2 rooms on the same machine using multiple IPs and a proxy server 18 | * Remote access to Dev Tools 19 | * Custom settings 20 | * Resource usage reports 21 | * Keep things simple while doing a lot 22 | ## 📀 Installation 23 | 24 | ```bash 25 | npm install haxball-server -g 26 | ``` 27 | 28 | ## 💻 Usage 29 | Create a configuration file: 30 | ```json 31 | { 32 | "server": { 33 | "execPath": "your/path/to/chrome.exe" 34 | }, 35 | "panel": { 36 | "bots": [ 37 | { "name": "example1", "displayName": "Example room 1", "path": "path/to/example1.js" }, 38 | { "name": "example2", "displayName": "Example room 2", "path": "path/to/example2.js" } 39 | ], 40 | "discordToken": "a discord bot token", 41 | "discordPrefix": "!", 42 | "mastersDiscordId": ["your discord id"] 43 | } 44 | } 45 | ``` 46 | Open the server with a simple command: 47 | ```bash 48 | haxball-server open -f config.json 49 | ``` 50 | Using Linux? Lacking an UI? 51 | Connect to the server remotely using (AWS example): 52 | ```bash 53 | haxball-server connect --host "ec2-xx-xx-xx-xx.us-east-1.compute.amazonaws.com" --user "ubuntu" --privateKey "path/to/keys.pem" 54 | ``` 55 | You must open and close rooms directly on Discord using the bot whose token is being used in the config file. 56 | Use `!help` (or the prefix you assigned) to see the server commands. 57 | ## 🏡 Remote debugging 58 | Haxball Server allows you to remotely access Chrome Dev Tools for all of your rooms by means of a SSH tunnel. All you have to do is to run a single command. 59 | 60 | Once the connection is established you'll be able to access the Dev Tools feature in [http://localhost:9601](http://localhost:9500). 61 | ### 🔐 Connect using a password 62 | ```bash 63 | haxball-server connect --host "myhost.com" --user "myuser" --password "mypassword" 64 | ``` 65 | ### 🔑 Connect using a private key 66 | ```bash 67 | haxball-server connect --host "myhost.com" --user "myuser" --privateKey "path/to/keys.pem" 68 | ``` 69 | ## ⚙️ Configuration 70 | ### 💾 server 71 | #### proxyEnabled?: boolean 72 | Whether to use proxies or not. Haxball only allows 2 rooms per IP so if you want to open more than 2 rooms you'll have to create multiple IPs and assign them to a proxy server. 73 | #### proxyServers?: string[] 74 | The proxy IP addresses. This is required if you enable proxies. Example: 75 | ```js 76 | "proxyServers": ["127.0.0.1:8000", "127.0.0.1:8001"] 77 | ``` 78 | #### userDataDir?: string 79 | [Chrome user data dir path](https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/user_data_dir.md). Only works if cache is not disabled. 80 | #### disableCache?: boolean 81 | Disable all caching. Rooms will be started in incognito mode. This is highly recommended if you're not using localStorage or IndexedDB (and you shouldn't). 82 | #### disableRemote?: boolean 83 | Disable remote debugging. The server won't listen for connections. 84 | #### disableAnonymizeLocalIps?: boolean 85 | Adds the `--disable-features=WebRtcHideLocalIpsWithMdns` flag to Chrome. [See more here](https://github.com/haxball/haxball-issues/wiki/Headless-Host#connectivity-warning). 86 | #### maxMemoryUsage?: number 87 | Set the limit for Javascript memory usage. Max value of 1024 (1 GB) in 32-bit systems and 4096 (4 GB) in 64-bit systems. 88 | #### execPath: string 89 | The path to Chrome (or Chromium) executable file. If you are on Windows this will probably be `C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe`. 90 | 91 | On Ubuntu you can install Chromium using: 92 | ```bash 93 | sudo apt-get install chromium-browser 94 | ``` 95 | And `execPath` will be: 96 | ```js 97 | "execPath": "/usr/bin/chromium-browser" 98 | ``` 99 | ### 🖥️ panel 100 | #### bots: { [name: string]: string } | { name: string, path: string, displayName?: string }[] 101 | The list of bots and the path to their JS file. 102 | ##### Example (as an array - recommended): 103 | ```js 104 | "bots": [ 105 | { "name": "classic", "displayName": "Classic room", "path": "./bots/classic.js" }, 106 | { "name": "futsal", "displayName": "Futsal room", "path": "./bots/futsal.js" } 107 | ] 108 | ``` 109 | ##### Example (as an object, does not support `displayName`): 110 | ```js 111 | "bots": { 112 | "classic": "./bots/classic.js", 113 | "futsal": "./bots/futsal.js" 114 | } 115 | ``` 116 | #### discordToken: string 117 | The token of your Discord bot. 118 | #### discordPrefix: string 119 | The prefix for the bot commands. 120 | #### mastersDiscordId: string[] 121 | The players allowed to use the bot. Nobody but the users listed here will be able to run commands. 122 | #### customSettings?: CustomSettingsList 123 | See [custom settings](#-custom-settings). 124 | #### maxRooms?: number 125 | The maximum number of rooms. This is useful if you want more control over the server. 126 | ## 🔧 Custom settings 127 | Let's say you want to open 2 rooms. Both are futsal rooms, but one is 3v3 and the other is 4v4. Instead of creating two different bot files, 3v3.js and 4v4.js, you can use the `panel.customSettings` config to pass custom parameters to the bot script. 128 | 129 | Not only you can pass custom parameters but you can also customize the `HBInit` options. 130 | 131 | For example: 132 | ```json 133 | "customSettings": { 134 | "3v3": { 135 | "reserved.haxball.roomName": "Futsal 3v3", 136 | "gameMode": 3 137 | }, 138 | "4v4": { 139 | "reserved.haxball.roomName": "Futsal 4v4", 140 | "gameMode": 4 141 | } 142 | } 143 | ``` 144 | Bots loaded with the `3v3` settings will be named `Futsal 3v3`. The same applies to `4v4`. The `gameMode` setting will be available in `window.CustomSettings.gameMode`. 145 | 146 | Custom settings also support inheritance and multiple inheritance. Suppose you want to define your room geolocation using custom settings as well as create a private room for your league: 147 | ```json 148 | "customSettings": { 149 | "new-york": { 150 | "reserved.haxball.geo": { 151 | "code": "us", 152 | "lat": 40.730, 153 | "lon": -73.935 154 | } 155 | }, 156 | "competitive": { 157 | "reserved.haxball.password": "12345" 158 | }, 159 | "3v3": { 160 | "extends": "new-york", 161 | "reserved.haxball.roomName": "Futsal 3v3", 162 | "gameMode": 3 163 | }, 164 | "4v4": { 165 | "extends": "new-york", 166 | "reserved.haxball.roomName": "Futsal 4v4", 167 | "gameMode": 4 168 | }, 169 | "3v3-league": { 170 | "extends": ["competitive", "3v3"], 171 | "reserved.haxball.roomName": "Futsal 3v3 League" 172 | } 173 | } 174 | ``` 175 | With this configuration you'd open your public rooms with the `3v3` and `4v4` settings, and when there's a match in your league, you'd open a room with the `3v3-league` setting. All using the same `futsal.js` bot! 176 | 177 | For instance, on Discord you would open the `3v3` room like this: `!open futsal thr1.AAAAAGEdKD4xW3bEOZDBBA.ZCzb426KBF4 3v3` 178 | 179 | You can also create `default` custom settings. This way, every room you open without specifying custom settings will automatically be assigned the `default` settings. For example, if you want to make private every room that have been opened without specifying custom settings: 180 | ```json 181 | "customSettings": { 182 | "default": { 183 | "reserved.haxball.public": false 184 | } 185 | } 186 | ``` 187 | 188 | ## 🎮 Discord commands 189 | ### help 190 | Shows the commands. 191 | ### info 192 | Shows open rooms and available bots. 193 | ### meminfo 194 | Information about CPU and memory usage. 195 | ### open 196 | > Requires two parameters: bot name and token. 197 | > 198 | > One optional parameter: custom settings. 199 | > 200 | > Example 1: !open futsal thr1.AAAAAGEbIjtlEn43C3G3Pw.ylh4au9g0SM 201 | > 202 | > Example 2: !open futsal thr1.AAAAAGEbIjtlEn43C3G3Pw.ylh4au9g0SM 3v3 203 | > 204 | > Example 3: !open futsal Token obtained: "thr1.AAAAAGEdFfRipxH29kSsLQ.Om6FNTPlneE" 4v4 205 | 206 | Opens a room with the given bot. 207 | You can choose between the bots specified in the `panel.bots` config. 208 | 209 | The token parameter is a [Haxball headless token](https://www.haxball.com/headlesstoken). 210 | 211 | You can learn more about the custom settings parameter [here](#-custom-settings). 212 | 213 | Once the room is open you'll be given the ID of the browser process. You may use it to close the room. 214 | ### close 215 | > Requires one parameter: PID (process ID). 216 | > 217 | > Example: !close 5478 218 | 219 | Closes the room based on its process ID described above. 220 | 221 | You can also close all rooms at once using `close all`. 222 | ### reload 223 | Reloads the `panel.bots` and `panel.customSettings` configurations. 224 | ### exit 225 | Closes all rooms and stops Haxball Server. 226 | ### eval 227 | Executes Javascript code. 228 | ### tokenlink 229 | Gets the URL to the [Haxball headless token website](https://www.haxball.com/headlesstoken). 230 | ## 📡 Using proxies 231 | If you are hosting your Haxball server on AWS EC2, you can use the proxy feature (and therefore open more than 2 full functional rooms) by assigning an [Elastic IP](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/MultipleIP.html#StepThreeEIP) to a [secondary IPv4 private address](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/MultipleIP.html#assignIP-existing). 232 | 233 | Enabling the new secondary IP depends on which service you're using. This will work for Ubuntu 20.04 running on the T4G family (`t4g-small` is the best one). [And according to the official documentation, Amazon Linux will automatically assign it for you](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/MultipleIP.html#StepTwoConfigOS). If you're not using Amazon Linux or Ubuntu 20.04 with the T4G family, you'll have to look it up yourself; however, the steps will likely be similar to the steps below. 234 | 235 | After assigning them, you can enable the new secondary IP using (you'll have to repeat this step every time you restart the instance): 236 | ```bash 237 | sudo ip addr add xx.xx.xx.xx/20 dev ens5 label ens5:1 238 | ``` 239 | Where xx.xx.xx.xx is the new secondary IP. 240 | 241 | To add a proxy to the new secondary IP, install Squid: 242 | ```bash 243 | sudo apt-get install squid 244 | ``` 245 | Open the `squid.conf` file: 246 | ```bash 247 | sudo nano /etc/squid/squid.conf 248 | ``` 249 | 250 | Then add these lines to the file: 251 | ``` 252 | http_port 127.0.0.1:8000 name=8000 253 | http_port 127.0.0.1:8001 name=8001 254 | 255 | acl prt8000 myportname 8000 src xx.xx.xx.xx/24 256 | http_access allow prt8000 257 | tcp_outgoing_address xx.xx.xx.xx prt8000 258 | 259 | acl prt8001 myportname 8001 src yy.yy.yy.yy/24 260 | http_access allow prt8001 261 | tcp_outgoing_address yy.yy.yy.yy prt8001 262 | ``` 263 | Where xx.xx.xx.xx is your main private IP and yy.yy.yy.yy is the secondary one. If you want more than 2 proxies (more than 4 rooms), just add new configurations until you're done: 264 | ``` 265 | http_port 127.0.0.1:8002 name=8002 266 | 267 | acl prt8002 myportname 8002 src zz.zz.zz.zz/24 268 | http_access allow prt8002 269 | tcp_outgoing_address zz.zz.zz.zz prt8002 270 | ``` 271 | And then restart the service: 272 | ```bash 273 | sudo systemctl restart squid 274 | ``` 275 | Now you'll be able to use the proxy feature by simply enabling the `server.proxyEnabled` config and adding your proxy IPs to `server.proxyServers`. 276 | 277 | Example: 278 | ```json 279 | "server": { 280 | "execPath": "/usr/bin/chromium-browser", 281 | "proxyEnabled": true, 282 | "proxyServers": ["127.0.0.1:8000", "127.0.0.1:8001"] 283 | } 284 | ``` 285 | ## ⚙️ Full configuration example 286 | A full example in an Ubuntu machine with a `bots` folder with `futsal.js` and `classic.js` files and multiple settings for the futsal bot. 287 | 288 | Discord IDs and token are fictional. 289 | ```json 290 | { 291 | "server": { 292 | "execPath": "/usr/bin/chromium-browser", 293 | "userDataDir": "./userdatadir" 294 | }, 295 | "panel": { 296 | "bots": [ 297 | { "name": "futsal", "displayName": "Futsal room", "path": "./bots/futsal.js" }, 298 | { "name": "classic", "displayName": "Classic room", "path": "./bots/classic.js" } 299 | ], 300 | 301 | "discordToken": "4cDNNDATgTTODgE2xON35IyO.MYCAr_a.UIrBFWioA6Po9HPyrJAyjgvR4AA", 302 | "discordPrefix": "!", 303 | 304 | "mastersDiscordId": ["6833789556844662784", "5748686793348656842"], 305 | 306 | "customSettings": { 307 | "myGeo": { 308 | "reserved.haxball.geo": { 309 | "code": "fr", 310 | "lat": 48.8032, 311 | "lon": 2.3511 312 | } 313 | }, 314 | "testMode": { 315 | "reserved.haxball.public": false 316 | }, 317 | "3v3": { 318 | "extends": "myGeo", 319 | "reserved.haxball.roomName": "Futsal 3v3", 320 | "gameMode": 3 321 | }, 322 | "4v4": { 323 | "extends": "myGeo", 324 | "reserved.haxball.roomName": "Futsal 4v4", 325 | "gameMode": 4 326 | }, 327 | "testMode3v3": { 328 | "extends": ["3v3", "testMode"] 329 | }, 330 | "testMode4v4": { 331 | "extends": ["4v4", "testMode"] 332 | } 333 | } 334 | } 335 | } 336 | ``` 337 | -------------------------------------------------------------------------------- /dist/ControlPanel.d.ts: -------------------------------------------------------------------------------- 1 | import { Server } from "./Server"; 2 | import { PanelConfig } from "./Global"; 3 | export declare class ControlPanel { 4 | private server; 5 | private fileName?; 6 | private client; 7 | private cpu; 8 | private mem; 9 | private prefix; 10 | private token; 11 | private mastersDiscordId; 12 | private bots; 13 | private customSettings?; 14 | private maxRooms?; 15 | constructor(server: Server, config: PanelConfig, fileName?: string | undefined); 16 | private transformSetting; 17 | private loadCustomSettings; 18 | private loadBots; 19 | private logError; 20 | private getRoomNameList; 21 | private getRoomUsageList; 22 | private command; 23 | } 24 | -------------------------------------------------------------------------------- /dist/ControlPanel.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); 5 | }) : (function(o, m, k, k2) { 6 | if (k2 === undefined) k2 = k; 7 | o[k2] = m[k]; 8 | })); 9 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 10 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 11 | }) : function(o, v) { 12 | o["default"] = v; 13 | }); 14 | var __importStar = (this && this.__importStar) || function (mod) { 15 | if (mod && mod.__esModule) return mod; 16 | var result = {}; 17 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 18 | __setModuleDefault(result, mod); 19 | return result; 20 | }; 21 | var __importDefault = (this && this.__importDefault) || function (mod) { 22 | return (mod && mod.__esModule) ? mod : { "default": mod }; 23 | }; 24 | Object.defineProperty(exports, "__esModule", { value: true }); 25 | exports.ControlPanel = void 0; 26 | const node_os_utils_1 = __importDefault(require("node-os-utils")); 27 | const fs_1 = __importDefault(require("fs")); 28 | const pidusage_1 = __importDefault(require("pidusage")); 29 | const process_1 = __importDefault(require("process")); 30 | const Discord = __importStar(require("discord.js")); 31 | const loadConfig_1 = require("./utils/loadConfig"); 32 | const log_1 = require("./utils/log"); 33 | class Bot { 34 | constructor(name, path, displayName) { 35 | this.name = name; 36 | this.path = path; 37 | this.displayName = displayName; 38 | } 39 | read() { 40 | return new Promise((resolve, reject) => { 41 | fs_1.default.readFile(this.path, { encoding: 'utf-8' }, async (err, data) => { 42 | if (err) { 43 | reject(err); 44 | } 45 | else { 46 | resolve(data); 47 | } 48 | }); 49 | }); 50 | } 51 | run(server, data, tokens, settings) { 52 | return new Promise((resolve, reject) => { 53 | server.open(data, tokens, this.displayName, settings) 54 | .then(e => resolve(e)) 55 | .catch(err => reject(err)); 56 | }); 57 | } 58 | } 59 | class ControlPanel { 60 | constructor(server, config, fileName) { 61 | this.server = server; 62 | this.fileName = fileName; 63 | this.client = new Discord.Client(); 64 | this.cpu = node_os_utils_1.default.cpu; 65 | this.mem = node_os_utils_1.default.mem; 66 | this.bots = []; 67 | this.prefix = config.discordPrefix; 68 | this.token = config.discordToken; 69 | this.mastersDiscordId = config.mastersDiscordId; 70 | this.maxRooms = config.maxRooms; 71 | if (config.customSettings) 72 | this.loadCustomSettings(config.customSettings); 73 | this.loadBots(config.bots); 74 | this.client.on('ready', () => { 75 | var _a; 76 | (0, log_1.log)("DISCORD", `Logged in as ${(_a = this.client.user) === null || _a === void 0 ? void 0 : _a.tag}!`); 77 | }); 78 | this.client.on('message', async (msg) => { 79 | try { 80 | this.command(msg); 81 | } 82 | catch (e) { 83 | this.logError(e, msg.channel); 84 | } 85 | }); 86 | this.client.login(this.token); 87 | } 88 | transformSetting(setting, list) { 89 | if (setting.extends) { 90 | const extensions = typeof setting.extends === "string" ? [setting.extends] : setting.extends; 91 | let newSetting = {}; 92 | for (const e of extensions) { 93 | let ext = list[e]; 94 | if (ext) { 95 | if (ext.extends) 96 | ext = this.transformSetting(ext, list); 97 | newSetting = Object.assign(Object.assign({}, newSetting), ext); 98 | } 99 | } 100 | newSetting = Object.assign(Object.assign({}, newSetting), setting); 101 | delete newSetting.extends; 102 | return newSetting; 103 | } 104 | return setting; 105 | } 106 | loadCustomSettings(customSettings) { 107 | this.customSettings = undefined; 108 | for (const entry of Object.entries(customSettings)) { 109 | const key = entry[0]; 110 | const value = entry[1]; 111 | customSettings[key] = this.transformSetting(value, customSettings); 112 | } 113 | this.customSettings = customSettings; 114 | } 115 | loadBots(bots) { 116 | this.bots = []; 117 | if (!Array.isArray(bots)) { 118 | for (const entry of Object.entries(bots)) { 119 | const name = entry[0]; 120 | const path = entry[1]; 121 | this.bots.push(new Bot(name, path)); 122 | } 123 | } 124 | else { 125 | for (const bot of bots) { 126 | this.bots.push(new Bot(bot.name, bot.path, bot.displayName)); 127 | } 128 | } 129 | } 130 | async logError(e, channel) { 131 | const embed = new Discord.MessageEmbed() 132 | .setColor('#0099ff') 133 | .setTitle("Log Error") 134 | .setTimestamp(Date.now()) 135 | .setDescription(e); 136 | await channel.send(embed); 137 | } 138 | async getRoomNameList() { 139 | var _a; 140 | let rooms = []; 141 | for (const browser of this.server.browsers) { 142 | const page = (await browser.pages())[0]; 143 | const proxyServer = browser["proxyServer"]; 144 | const remotePort = browser["remotePort"]; 145 | let pageTitle = await page.title(); 146 | pageTitle = pageTitle != null && pageTitle != "" ? pageTitle : "Unnamed tab"; 147 | const nameStr = `${pageTitle} (${(_a = page.browser().process()) === null || _a === void 0 ? void 0 : _a.pid})${remotePort != null ? ` (localhost:${remotePort})` : ""}`; 148 | rooms.push({ name: nameStr, proxy: proxyServer }); 149 | } 150 | if (rooms.length === 0) 151 | return "There are no open rooms!"; 152 | if (rooms.every(r => r.proxy == null)) 153 | return rooms.map(r => r.name).join("\n"); 154 | let proxyRooms = []; 155 | for (const room of rooms) { 156 | let pRoom = proxyRooms.find(r => r.proxy === room.proxy); 157 | if (pRoom) { 158 | pRoom.text += room.name + "\n"; 159 | } 160 | else { 161 | proxyRooms.push({ text: room.name + "\n", proxy: room.proxy }); 162 | } 163 | } 164 | return proxyRooms.map(r => `• ${r.proxy}\n${r.text}`).join("\n"); 165 | } 166 | async getRoomUsageList() { 167 | var _a; 168 | const roomsUsage = []; 169 | for (const browser of this.server.browsers) { 170 | const page = (await browser.pages())[0]; 171 | roomsUsage.push({ process: await (0, pidusage_1.default)((_a = browser === null || browser === void 0 ? void 0 : browser.process()) === null || _a === void 0 ? void 0 : _a.pid), title: await page.title() }); 172 | } 173 | return roomsUsage; 174 | } 175 | async command(msg) { 176 | var _a, _b; 177 | if (!msg.content.startsWith(this.prefix)) 178 | return; 179 | const args = msg.content.slice(this.prefix.length).trim().split(' '); 180 | const text = msg.content.slice(this.prefix.length).trim().replace(args[0] + " ", ""); 181 | const command = (_a = args.shift()) === null || _a === void 0 ? void 0 : _a.toLowerCase(); 182 | const embed = new Discord.MessageEmbed().setColor('#0099ff'); 183 | if (this.mastersDiscordId.includes(msg.author.id)) { 184 | if (command === "help") { 185 | embed 186 | .setTitle("Help") 187 | .setDescription("Haxball Server is a small server utility for Haxball rooms.") 188 | .addField("help", "Command list.", true) 189 | .addField("info", "Server info.", true) 190 | .addField("meminfo", "CPU and memory info.", true) 191 | .addField("open", "Open a room.", true) 192 | .addField("close", "Close a room.", true) 193 | .addField("reload", "Reload the bot configuration.", true) 194 | .addField("exit", "Close the server.", true) 195 | .addField("eval", "Execute Javascript.", true) 196 | .addField("tokenlink", "Haxball Headless Token page.", true); 197 | msg.channel.send(embed); 198 | } 199 | if (command === "tokenlink") { 200 | embed 201 | .setTitle("Headless Token") 202 | .setDescription(`[Click here.](https://www.haxball.com/headlesstoken)`); 203 | msg.channel.send(embed); 204 | } 205 | if (command === "open") { 206 | embed.setTitle("Open room"); 207 | if (this.maxRooms != null && this.server.browsers.length >= this.maxRooms) { 208 | embed.setDescription(`Maximum number of rooms (${this.maxRooms}) excedeed. Update configuration to change this.`); 209 | return msg.channel.send(embed); 210 | } 211 | const bot = this.bots.find(b => b.name === args[0]); 212 | if (!bot) { 213 | embed.setDescription(`This bot does not exist. Type ${this.prefix}info to see the list of available bots.`); 214 | return msg.channel.send(embed); 215 | } 216 | let token = text.replace(args[0], "").trim().replace(/\"/g, "").replace("Token obtained: ", ""); 217 | if (!token || token === "") { 218 | embed.setDescription(`You have to define a [headless token](https://www.haxball.com/headlesstoken) as second argument: ${this.prefix}open `); 219 | return msg.channel.send(embed); 220 | } 221 | let settings; 222 | let settingsMsg = "No setting has been loaded (not specified or not found)."; 223 | if (this.customSettings != null) { 224 | const settingArg = args[args.length - 1]; 225 | settings = this.customSettings[settingArg]; 226 | if (settings) { 227 | settingsMsg = `\`${settingArg}\` settings have been loaded.`; 228 | token = token.replace(settingArg, "").trim(); 229 | } 230 | else if (this.customSettings["default"]) { 231 | settingsMsg = `Default settings have been loaded.`; 232 | settings = this.customSettings["default"]; 233 | } 234 | } 235 | embed.setDescription("Opening room..."); 236 | const message = await msg.channel.send(embed); 237 | bot.read().then(script => { 238 | bot.run(this.server, script, [token, token.substring(0, token.lastIndexOf(" "))], settings).then(e => { 239 | message.edit(embed.setDescription(`Room running! [Click here to join.](${e === null || e === void 0 ? void 0 : e.link})\nBrowser process: ${e === null || e === void 0 ? void 0 : e.pid}${(e === null || e === void 0 ? void 0 : e.remotePort) ? `\nRemote debugging: localhost:${e.remotePort}` : ""}\n${settingsMsg}`)); 240 | }) 241 | .catch(err => { 242 | message.edit(embed.setDescription(`Unable to open the room!\n ${err}`)); 243 | }); 244 | }) 245 | .catch(err => { 246 | embed.setDescription("Error: " + err); 247 | message.edit(embed); 248 | }); 249 | } 250 | if (command === "info") { 251 | const roomList = await this.getRoomNameList(); 252 | embed 253 | .setTitle("Information") 254 | .addField("Open rooms", roomList) 255 | .addField("Bot list", this.bots.map(b => b.name).join("\n")) 256 | .addField("Custom settings list", this.customSettings ? Object.keys(this.customSettings).join("\n") : "No custom settings have been specified."); 257 | msg.channel.send(embed); 258 | } 259 | if (command === "meminfo") { 260 | const embedLoading = new Discord.MessageEmbed() 261 | .setColor('#0099ff') 262 | .setTitle("Information") 263 | .setDescription("Loading..."); 264 | const message = await msg.channel.send(embedLoading); 265 | const roomsUsage = await this.getRoomUsageList(); 266 | const memInfo = await this.mem.info(); 267 | const cpuUsage = await this.cpu.usage(); 268 | embed 269 | .setTitle("Information") 270 | .addField("CPUs", this.cpu.count(), true) 271 | .addField("CPU usage", cpuUsage + "%", true) 272 | .addField("Free CPU", 100 - cpuUsage + "%", true) 273 | .addField("Memory", `${(memInfo.usedMemMb / 1000).toFixed(2)}/${(memInfo.totalMemMb / 1000).toFixed(2)} GB (${memInfo.freeMemPercentage}% livre)`, true) 274 | .addField("OS", await node_os_utils_1.default.os.oos(), true) 275 | .addField("Machine Uptime", new Date(node_os_utils_1.default.os.uptime() * 1000).toISOString().substr(11, 8), true); 276 | const serverPIDUsage = await (0, pidusage_1.default)(process_1.default.pid); 277 | const serverCPUUsage = `CPU server usage: ${(serverPIDUsage.cpu).toFixed(2)}%\nMemory server usage: ${(serverPIDUsage.memory * 1e-6).toFixed(2)} MB\n`; 278 | const roomCPUMessage = this.server.browsers.length > 0 ? "\n" + roomsUsage.map((room) => `**${room.title} (${room.process.pid})**:\n${(room.process.cpu).toFixed(2)}% CPU\n${(room.process.memory * 1e-6).toFixed(2)} MB memory\n`).join("\n") : ""; 279 | embed.setDescription(serverCPUUsage + roomCPUMessage + "\n"); 280 | message.edit(embed); 281 | } 282 | if (command === "close") { 283 | embed 284 | .setTitle("Close room") 285 | .setDescription("Unable to find room"); 286 | if (args[0] === "all") { 287 | let forcedClosedRooms = 0; 288 | let closedRooms = 0; 289 | for (const room of this.server.browsers) { 290 | const pid = (_b = room === null || room === void 0 ? void 0 : room.process()) === null || _b === void 0 ? void 0 : _b.pid; 291 | if (pid) { 292 | await this.server.close(pid); 293 | } 294 | else { 295 | await room.close(); 296 | forcedClosedRooms++; 297 | } 298 | closedRooms++; 299 | } 300 | if (forcedClosedRooms === 0) { 301 | embed.setDescription(`${closedRooms} rooms have been closed.`); 302 | } 303 | else { 304 | embed.setDescription(`${closedRooms} rooms have been closed.\n${forcedClosedRooms} rooms have been forced to close.`); 305 | } 306 | return msg.channel.send(embed); 307 | } 308 | const res = await this.server.close(text); 309 | if (res) 310 | embed.setDescription("Room closed!"); 311 | msg.channel.send(embed); 312 | } 313 | if (command === "exit") { 314 | embed 315 | .setTitle("Closing") 316 | .setDescription("Closing server..."); 317 | await msg.channel.send(embed); 318 | this.server.browsers.forEach(async (browser) => { 319 | await browser.close(); 320 | }); 321 | process_1.default.exit(); 322 | } 323 | if (command === "eval") { 324 | try { 325 | const code = args.join(" "); 326 | let evaled = eval(code); 327 | if (typeof evaled !== "string") 328 | evaled = require("util").inspect(evaled); 329 | msg.channel.send(evaled, { code: "javascript", split: true }); 330 | } 331 | catch (err) { 332 | msg.channel.send(`\`ERROR\` \`\`\`xl\n${err}\n\`\`\``); 333 | } 334 | } 335 | if (command === "reload") { 336 | embed.setTitle("Reload bots and custom settings").setColor(0xFF0000); 337 | (0, loadConfig_1.loadConfig)(this.fileName).then((config) => { 338 | if (!config.panel.bots) { 339 | embed.setDescription("Could not find bots in config file."); 340 | } 341 | else { 342 | this.loadBots(config.panel.bots); 343 | if (config.panel.customSettings) 344 | this.loadCustomSettings(config.panel.customSettings); 345 | this.maxRooms = config.panel.maxRooms; 346 | embed.setColor(0x0099FF).setDescription("Bot list and custom settings reloaded!"); 347 | } 348 | msg.channel.send(embed); 349 | }).catch(err => { 350 | embed.setDescription(`*${err.message}*\n\nSee logs for details.`); 351 | console.error(err); 352 | msg.channel.send(embed); 353 | }); 354 | } 355 | } 356 | } 357 | } 358 | exports.ControlPanel = ControlPanel; 359 | //# sourceMappingURL=ControlPanel.js.map -------------------------------------------------------------------------------- /dist/ControlPanel.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"ControlPanel.js","sourceRoot":"","sources":["../src/ControlPanel.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/Global.d.ts: -------------------------------------------------------------------------------- 1 | export declare const serverPort = 9500; 2 | export declare const serverRoomFirstPort = 9501; 3 | export declare const clientPort = 9600; 4 | export declare const expressPort = 9601; 5 | export declare const wsPort = 9602; 6 | export declare const clientRoomFirstPort = 9603; 7 | export declare const maxLengthLog = 300; 8 | export declare const maxTimeSSHConnection: number; 9 | export declare const roomCustomConfigsList: string[]; 10 | declare type BotList = { 11 | [key: string]: string; 12 | } | { 13 | name: string; 14 | path: string; 15 | displayName?: string; 16 | }[]; 17 | export interface PanelConfig { 18 | discordToken: string; 19 | discordPrefix: string; 20 | bots: BotList; 21 | mastersDiscordId: string[]; 22 | customSettings?: CustomSettingsList; 23 | maxRooms?: number; 24 | } 25 | export interface ServerConfig { 26 | proxyEnabled?: boolean; 27 | proxyServers?: string[]; 28 | disableCache?: boolean; 29 | disableRemote?: boolean; 30 | userDataDir?: string; 31 | disableAnonymizeLocalIps?: boolean; 32 | execPath: string; 33 | maxMemoryUsage: number; 34 | } 35 | export interface HaxballServerConfig { 36 | server: ServerConfig; 37 | panel: PanelConfig; 38 | } 39 | export interface CustomSettings { 40 | extends?: string | string[]; 41 | [key: string]: any; 42 | } 43 | export declare type ReservedCustomSettings = "reserved.haxball.roomName" | "reserved.haxball.playerName" | "reserved.haxball.password" | "reserved.haxball.maxPlayers" | "reserved.haxball.public" | "reserved.haxball.geo" | "reserved.haxball.noPlayer"; 44 | export declare type CustomSettingsList = { 45 | [key: string]: CustomSettings; 46 | }; 47 | export {}; 48 | -------------------------------------------------------------------------------- /dist/Global.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.roomCustomConfigsList = exports.maxTimeSSHConnection = exports.maxLengthLog = exports.clientRoomFirstPort = exports.wsPort = exports.expressPort = exports.clientPort = exports.serverRoomFirstPort = exports.serverPort = void 0; 4 | exports.serverPort = 9500; 5 | exports.serverRoomFirstPort = 9501; 6 | exports.clientPort = 9600; 7 | exports.expressPort = 9601; 8 | exports.wsPort = 9602; 9 | exports.clientRoomFirstPort = 9603; 10 | exports.maxLengthLog = 300; 11 | exports.maxTimeSSHConnection = 2 * 60 * 1000; 12 | exports.roomCustomConfigsList = [ 13 | "roomName", 14 | "playerName", 15 | "password", 16 | "maxPlayers", 17 | "public", 18 | "geo", 19 | "noPlayer" 20 | ]; 21 | //# sourceMappingURL=Global.js.map -------------------------------------------------------------------------------- /dist/Global.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"Global.js","sourceRoot":"","sources":["../src/Global.ts"],"names":[],"mappings":";;;AAAa,QAAA,UAAU,GAAG,IAAI,CAAC;AAClB,QAAA,mBAAmB,GAAG,IAAI,CAAC;AAE3B,QAAA,UAAU,GAAG,IAAI,CAAC;AAClB,QAAA,WAAW,GAAG,IAAI,CAAC;AACnB,QAAA,MAAM,GAAG,IAAI,CAAC;AACd,QAAA,mBAAmB,GAAG,IAAI,CAAC;AAE3B,QAAA,YAAY,GAAG,GAAG,CAAC;AAEnB,QAAA,oBAAoB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AAErC,QAAA,qBAAqB,GAAG;IACjC,UAAU;IACV,YAAY;IACZ,UAAU;IACV,YAAY;IACZ,QAAQ;IACR,KAAK;IACL,UAAU;CACb,CAAC"} -------------------------------------------------------------------------------- /dist/Server.d.ts: -------------------------------------------------------------------------------- 1 | import puppeteer from 'puppeteer-core'; 2 | import { CustomSettings, ServerConfig } from "./Global"; 3 | export declare class Server { 4 | browsers: puppeteer.Browser[]; 5 | private unnamedCount; 6 | private remoteChromePort; 7 | private proxyEnabled; 8 | private proxyServers; 9 | private execPath; 10 | private disableCache; 11 | private userDataDir?; 12 | private disableRemote; 13 | private disableAnonymizeLocalIps; 14 | private maxMemoryUsage?; 15 | private debuggingServer?; 16 | constructor(config: ServerConfig); 17 | private createNewBrowser; 18 | private checkTokenWorks; 19 | private openRoom; 20 | open(script: string, tokens: string | string[], name?: string, settings?: CustomSettings): Promise<{ 21 | link: string; 22 | pid: number | undefined; 23 | remotePort: any; 24 | }>; 25 | close(pidOrTitle: string | number): Promise; 26 | } 27 | -------------------------------------------------------------------------------- /dist/Server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); 5 | }) : (function(o, m, k, k2) { 6 | if (k2 === undefined) k2 = k; 7 | o[k2] = m[k]; 8 | })); 9 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 10 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 11 | }) : function(o, v) { 12 | o["default"] = v; 13 | }); 14 | var __importStar = (this && this.__importStar) || function (mod) { 15 | if (mod && mod.__esModule) return mod; 16 | var result = {}; 17 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 18 | __setModuleDefault(result, mod); 19 | return result; 20 | }; 21 | var __importDefault = (this && this.__importDefault) || function (mod) { 22 | return (mod && mod.__esModule) ? mod : { "default": mod }; 23 | }; 24 | Object.defineProperty(exports, "__esModule", { value: true }); 25 | exports.Server = void 0; 26 | const puppeteer_core_1 = __importDefault(require("puppeteer-core")); 27 | const DebuggingServer_1 = require("./debugging/DebuggingServer"); 28 | const getAvailablePort_1 = require("./utils/getAvailablePort"); 29 | const escapeString_1 = require("./utils/escapeString"); 30 | const log_1 = require("./utils/log"); 31 | const Global = __importStar(require("./Global")); 32 | const Global_1 = require("./Global"); 33 | const selectorFrame = 'body > iframe'; 34 | const selectorRoomLink = '#roomlink > p > a'; 35 | const blockedRes = [ 36 | '*/favicon.ico', 37 | '.css', 38 | '.jpg', 39 | '.jpeg', 40 | '.png', 41 | '.svg', 42 | '.woff', 43 | '*.optimizely.com', 44 | 'everesttech.net', 45 | 'userzoom.com', 46 | 'doubleclick.net', 47 | 'googleadservices.com', 48 | 'adservice.google.com/*', 49 | 'connect.facebook.com', 50 | 'connect.facebook.net', 51 | 'sp.analytics.yahoo.com' 52 | ]; 53 | class Server { 54 | constructor(config) { 55 | var _a, _b, _c, _d, _e; 56 | this.browsers = []; 57 | this.unnamedCount = 1; 58 | this.proxyEnabled = (_a = config === null || config === void 0 ? void 0 : config.proxyEnabled) !== null && _a !== void 0 ? _a : false; 59 | this.proxyServers = (_b = config === null || config === void 0 ? void 0 : config.proxyServers) !== null && _b !== void 0 ? _b : []; 60 | this.execPath = config.execPath; 61 | this.disableCache = (_c = config.disableCache) !== null && _c !== void 0 ? _c : false; 62 | this.userDataDir = config.userDataDir; 63 | this.remoteChromePort = Global.serverRoomFirstPort; 64 | this.disableRemote = (_d = config.disableRemote) !== null && _d !== void 0 ? _d : false; 65 | this.disableAnonymizeLocalIps = (_e = config.disableAnonymizeLocalIps) !== null && _e !== void 0 ? _e : false; 66 | this.maxMemoryUsage = config.maxMemoryUsage; 67 | if (!this.disableRemote) { 68 | this.debuggingServer = new DebuggingServer_1.DebuggingServer(); 69 | this.debuggingServer.listen(Global.serverPort); 70 | } 71 | } 72 | async createNewBrowser() { 73 | var _a; 74 | const args = [ 75 | "--no-sandbox", 76 | "--disable-setuid-sandbox", 77 | "--disable-dev-shm-usage", 78 | "--disable-accelerated-2d-canvas", 79 | "--no-first-run", 80 | "--no-zygote", 81 | "--single-process", 82 | "--disable-gpu", 83 | ]; 84 | const remotePort = await (0, getAvailablePort_1.getAvailablePort)(this.remoteChromePort); 85 | if (!this.disableRemote) 86 | args.push(`--remote-debugging-port=${remotePort}`); 87 | if (this.disableCache) 88 | args.push("--incognito"); 89 | if (this.disableAnonymizeLocalIps) 90 | args.push(`--disable-features=WebRtcHideLocalIpsWithMdns`); 91 | if (this.maxMemoryUsage) 92 | args.push("--max-old-space-size=" + this.maxMemoryUsage); 93 | let proxyServer = ""; 94 | if (this.proxyEnabled) { 95 | let availableProxies = this.proxyServers.filter(s => { 96 | let a = 0; 97 | for (const browser of this.browsers) { 98 | if (browser["proxyServer"] === s) { 99 | a++; 100 | } 101 | } 102 | return a < 2; 103 | }); 104 | if (availableProxies.length === 0) { 105 | proxyServer = this.proxyServers[this.proxyServers.length - 1]; 106 | } 107 | else { 108 | proxyServer = availableProxies[0]; 109 | } 110 | args.push("--proxy-server=" + proxyServer); 111 | } 112 | const puppeteerArgs = { 113 | headless: true, 114 | args: args, 115 | executablePath: this.execPath 116 | }; 117 | if (this.userDataDir && this.disableCache !== true) 118 | puppeteerArgs["userDataDir"] = this.userDataDir; 119 | const browser = await puppeteer_core_1.default.launch(puppeteerArgs); 120 | if (!this.disableRemote) 121 | browser["remotePort"] = remotePort; 122 | if (proxyServer != "") 123 | browser["proxyServer"] = proxyServer; 124 | this.browsers.push(browser); 125 | browser.on("disconnected", () => { 126 | this.browsers = this.browsers.filter(b => { 127 | var _a; 128 | const isConnected = b.isConnected(); 129 | if (!isConnected) 130 | b.close(); 131 | if (!this.disableRemote) 132 | (_a = this.debuggingServer) === null || _a === void 0 ? void 0 : _a.removeRoom(remotePort); 133 | return isConnected; 134 | }); 135 | }); 136 | if (!this.disableRemote) 137 | (_a = this.debuggingServer) === null || _a === void 0 ? void 0 : _a.addRoom(remotePort); 138 | return browser; 139 | } 140 | async checkTokenWorks(page, token) { 141 | return await page.evaluate(async (token) => { 142 | return await new Promise((resolve) => { 143 | const server = new WebSocket(`wss://p2p2.haxball.com/host?token=${token}`); 144 | server.onopen = function () { 145 | resolve(true); 146 | }; 147 | server.onerror = function () { 148 | resolve(false); 149 | }; 150 | }); 151 | }, token); 152 | } 153 | async openRoom(page, script, tokens, name, settings) { 154 | page.on("pageerror", ({ message }) => (0, log_1.log)("PAGE ERROR", message)) 155 | .on("response", response => (0, log_1.log)("PAGE RESPONSE", `${response.status()} : ${response.url()}`)) 156 | .on("requestfailed", request => { var _a; return (0, log_1.log)("REQUEST FAILED", `${(_a = request.failure()) === null || _a === void 0 ? void 0 : _a.errorText} : ${request.url()}`); }) 157 | .on("error", (err) => (0, log_1.log)("PAGE CRASHED", `${err}`)) 158 | .on("pageerror", (err) => (0, log_1.log)("ERROR IN PAGE", `${err}`)); 159 | if (this.disableCache) 160 | await page.setCacheEnabled(false); 161 | const client = await page.target().createCDPSession(); 162 | name = `(args[0]["roomName"] ?? "Unnamed room ${this.unnamedCount++}")` + (name ? ` + " (${(0, escapeString_1.escapeString)(name)})"` : ""); 163 | let reservedHBInitCustomSettingsScript = ""; 164 | let customSettingsScript = {}; 165 | if (settings) { 166 | for (const setting of Object.entries(settings)) { 167 | const key = setting[0]; 168 | const value = setting[1]; 169 | if (Global_1.roomCustomConfigsList.map(config => "reserved.haxball." + config).includes(key)) { 170 | reservedHBInitCustomSettingsScript += `args[0]["${(0, escapeString_1.escapeString)(key.replace("reserved.haxball.", ""))}"] = ${JSON.stringify(value)};`; 171 | } 172 | else { 173 | customSettingsScript[key] = value; 174 | } 175 | } 176 | } 177 | await client.send('Network.setBlockedURLs', { urls: blockedRes }); 178 | await page.goto('https://www.haxball.com/headless', { waitUntil: 'networkidle2' }); 179 | let token; 180 | for (const t of tokens) { 181 | if (t != "" && await this.checkTokenWorks(page, t)) 182 | token = t; 183 | } 184 | const tokenListStr = tokens.filter(t => t && t != "").map(t => "`" + t + "`").join(", "); 185 | if (token == null) 186 | throw new Error(`Invalid token (tried ${tokenListStr}).`); 187 | const scripts = ` 188 | window.HBInit = new Proxy(window.HBInit, { 189 | apply: (target, thisArg, args) => { 190 | args[0]["token"] = "${token}"; 191 | 192 | ${reservedHBInitCustomSettingsScript} 193 | 194 | document.title = ${name}; 195 | 196 | return target(...args); 197 | } 198 | }); 199 | 200 | window["CustomSettings"] = ${JSON.stringify(customSettingsScript)}; 201 | `; 202 | await page.addScriptTag({ content: scripts }); 203 | await page.addScriptTag({ content: script }); 204 | await page.waitForSelector("iframe"); 205 | const elementHandle = await page.$(selectorFrame); 206 | const frame = await elementHandle.contentFrame(); 207 | await frame.waitForSelector(selectorRoomLink); 208 | const roomLinkElement = await frame.$(selectorRoomLink); 209 | const link = await frame.evaluate(el => el.textContent, roomLinkElement); 210 | return link; 211 | } 212 | async open(script, tokens, name, settings) { 213 | var _a; 214 | const browser = await this.createNewBrowser(); 215 | const pid = (_a = browser === null || browser === void 0 ? void 0 : browser.process()) === null || _a === void 0 ? void 0 : _a.pid; 216 | const [page] = await browser.pages(); 217 | tokens = typeof tokens === "string" ? [tokens] : tokens; 218 | try { 219 | const link = await this.openRoom(page, script, tokens, name, settings); 220 | return { link, pid, remotePort: browser["remotePort"] }; 221 | } 222 | catch (e) { 223 | this.close(pid); 224 | throw e; 225 | } 226 | } 227 | async close(pidOrTitle) { 228 | var _a; 229 | let success = false; 230 | let pOT = pidOrTitle; 231 | for (const browser of this.browsers) { 232 | const title = await (await browser.pages())[0].title(); 233 | if (title == pOT) 234 | pOT = (_a = browser === null || browser === void 0 ? void 0 : browser.process()) === null || _a === void 0 ? void 0 : _a.pid; 235 | } 236 | this.browsers = this.browsers.filter(b => { 237 | var _a; 238 | const pid = (_a = b === null || b === void 0 ? void 0 : b.process()) === null || _a === void 0 ? void 0 : _a.pid; 239 | if (pid == pOT) { 240 | b.close().then(() => { 241 | var _a; 242 | if (!this.disableRemote) 243 | (_a = this.debuggingServer) === null || _a === void 0 ? void 0 : _a.removeRoom(b["remotePort"]); 244 | }); 245 | success = true; 246 | } 247 | return pid != pOT; 248 | }); 249 | return success; 250 | } 251 | } 252 | exports.Server = Server; 253 | //# sourceMappingURL=Server.js.map -------------------------------------------------------------------------------- /dist/Server.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"Server.js","sourceRoot":"","sources":["../src/Server.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/commands/connect.d.ts: -------------------------------------------------------------------------------- 1 | import { Config } from 'tunnel-ssh'; 2 | export declare function connect(connectConfig: Config): Promise; 3 | -------------------------------------------------------------------------------- /dist/commands/connect.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); 5 | }) : (function(o, m, k, k2) { 6 | if (k2 === undefined) k2 = k; 7 | o[k2] = m[k]; 8 | })); 9 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 10 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 11 | }) : function(o, v) { 12 | o["default"] = v; 13 | }); 14 | var __importStar = (this && this.__importStar) || function (mod) { 15 | if (mod && mod.__esModule) return mod; 16 | var result = {}; 17 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 18 | __setModuleDefault(result, mod); 19 | return result; 20 | }; 21 | var __importDefault = (this && this.__importDefault) || function (mod) { 22 | return (mod && mod.__esModule) ? mod : { "default": mod }; 23 | }; 24 | Object.defineProperty(exports, "__esModule", { value: true }); 25 | exports.connect = void 0; 26 | const tunnel_ssh_1 = __importDefault(require("tunnel-ssh")); 27 | const open_1 = __importDefault(require("open")); 28 | const DebuggingInterface_1 = require("../debugging/DebuggingInterface"); 29 | const DebuggingClient_1 = require("../debugging/DebuggingClient"); 30 | const Global = __importStar(require("../Global")); 31 | function openTunnel(config) { 32 | return (0, tunnel_ssh_1.default)(Object.assign(Object.assign({}, config), { readyTimeout: Global.maxTimeSSHConnection })); 33 | } 34 | async function connect(connectConfig) { 35 | let tunnels = []; 36 | console.log("Establishing remote connection..."); 37 | openTunnel(Object.assign(Object.assign({}, connectConfig), { dstPort: Global.serverPort, localPort: Global.clientPort })) 38 | .on("error", (err) => { 39 | console.error("Error: " + err.message); 40 | console.error("A Haxball Server connection could not be opened."); 41 | process.exit(); 42 | }); 43 | const client = new DebuggingClient_1.DebuggingClient(); 44 | client.on("set", async (rooms) => { 45 | for (const room of rooms) { 46 | const tunnelSrv = openTunnel(Object.assign(Object.assign({}, connectConfig), { dstPort: room.server, localPort: room.client })); 47 | tunnels.push({ port: room.client, server: tunnelSrv }); 48 | } 49 | const url = new DebuggingInterface_1.ConnectInterface().listen(Global.expressPort, Global.wsPort, client); 50 | await (0, open_1.default)(url); 51 | }); 52 | client.on("add", async (server, client) => { 53 | const tunnelSrv = openTunnel(Object.assign(Object.assign({}, connectConfig), { dstPort: server, localPort: client })) 54 | .on("error", (err) => { 55 | console.error("Error: " + err.message); 56 | console.error("Failed to connect to room."); 57 | }); 58 | tunnels.push({ port: client, server: tunnelSrv }); 59 | }); 60 | client.on("remove", async (server, client) => { 61 | var _a; 62 | const tunnel = tunnels.find(t => t.port === client); 63 | if (tunnel) { 64 | (_a = tunnel.server) === null || _a === void 0 ? void 0 : _a.close(); 65 | tunnels = tunnels.filter(t => t.port !== tunnel.port); 66 | } 67 | }); 68 | client.listen(Global.clientPort); 69 | } 70 | exports.connect = connect; 71 | //# sourceMappingURL=connect.js.map -------------------------------------------------------------------------------- /dist/commands/connect.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"connect.js","sourceRoot":"","sources":["../../src/commands/connect.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAAA,4DAAgC;AAChC,gDAAwB;AAGxB,wEAAmE;AACnE,kEAA8D;AAE9D,kDAAoC;AAIpC,SAAS,UAAU,CAAC,MAAqB;IACrC,OAAO,IAAA,oBAAM,kCAAM,MAAM,KAAE,YAAY,EAAE,MAAM,CAAC,oBAAoB,IAAG,CAAC;AAC5E,CAAC;AAEM,KAAK,UAAU,OAAO,CAAC,aAAqB;IAC/C,IAAI,OAAO,GAAa,EAAE,CAAC;IAE3B,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;IAEjD,UAAU,iCAAM,aAAa,KAAE,OAAO,EAAE,MAAM,CAAC,UAAU,EAAE,SAAS,EAAE,MAAM,CAAC,UAAU,IAAG;SACzF,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;QACjB,OAAO,CAAC,KAAK,CAAC,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,CAAA;QACtC,OAAO,CAAC,KAAK,CAAC,kDAAkD,CAAC,CAAC;QAClE,OAAO,CAAC,IAAI,EAAE,CAAC;IACnB,CAAC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,IAAI,iCAAe,EAAE,CAAC;IAErC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;QAC7B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE;YACtB,MAAM,SAAS,GAAG,UAAU,iCAAM,aAAa,KAAE,OAAO,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,MAAM,IAAG,CAAA;YAEhG,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;SAC1D;QAED,MAAM,GAAG,GAAG,IAAI,qCAAgB,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAErF,MAAM,IAAA,cAAI,EAAC,GAAG,CAAC,CAAC;IACpB,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;QACtC,MAAM,SAAS,GAAG,UAAU,iCAAM,aAAa,KAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,IAAG;aACrF,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACjB,OAAO,CAAC,KAAK,CAAC,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,CAAA;YACtC,OAAO,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;QAChD,CAAC,CAAC,CAAC;QAEH,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;;QACzC,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;QAEpD,IAAI,MAAM,EAAE;YACR,MAAA,MAAM,CAAC,MAAM,0CAAE,KAAK,EAAE,CAAC;YACvB,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC;SACzD;IACL,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;AACrC,CAAC;AA9CD,0BA8CC"} -------------------------------------------------------------------------------- /dist/commands/openServer.d.ts: -------------------------------------------------------------------------------- 1 | export declare function openServer(file?: string): void; 2 | -------------------------------------------------------------------------------- /dist/commands/openServer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.openServer = void 0; 4 | const Server_1 = require("../Server"); 5 | const ControlPanel_1 = require("../ControlPanel"); 6 | const loadConfig_1 = require("../utils/loadConfig"); 7 | function openServer(file) { 8 | (0, loadConfig_1.loadConfig)(file).then(config => { 9 | const server = new Server_1.Server(config.server); 10 | new ControlPanel_1.ControlPanel(server, config.panel, file); 11 | }).catch(err => { 12 | console.error(err.error ? err.message + ", " + err.error : err.message); 13 | process.exit(); 14 | }); 15 | } 16 | exports.openServer = openServer; 17 | //# sourceMappingURL=openServer.js.map -------------------------------------------------------------------------------- /dist/commands/openServer.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"openServer.js","sourceRoot":"","sources":["../../src/commands/openServer.ts"],"names":[],"mappings":";;;AAAA,sCAAmC;AACnC,kDAA+C;AAE/C,oDAAiD;AAEjD,SAAgB,UAAU,CAAC,IAAa;IACpC,IAAA,uBAAU,EAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;QAC3B,MAAM,MAAM,GAAG,IAAI,eAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACzC,IAAI,2BAAY,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;QACX,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,GAAG,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACxE,OAAO,CAAC,IAAI,EAAE,CAAC;IACnB,CAAC,CAAC,CAAC;AACP,CAAC;AARD,gCAQC"} -------------------------------------------------------------------------------- /dist/debugging/DebuggingClient.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import net from "net"; 3 | import EventEmitter from "events"; 4 | export declare class DebuggingClient extends EventEmitter { 5 | private roomsConn; 6 | client?: net.Socket; 7 | get rooms(): number[]; 8 | get size(): number; 9 | constructor(); 10 | listen(port: number): void; 11 | } 12 | -------------------------------------------------------------------------------- /dist/debugging/DebuggingClient.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); 5 | }) : (function(o, m, k, k2) { 6 | if (k2 === undefined) k2 = k; 7 | o[k2] = m[k]; 8 | })); 9 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 10 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 11 | }) : function(o, v) { 12 | o["default"] = v; 13 | }); 14 | var __importStar = (this && this.__importStar) || function (mod) { 15 | if (mod && mod.__esModule) return mod; 16 | var result = {}; 17 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 18 | __setModuleDefault(result, mod); 19 | return result; 20 | }; 21 | var __importDefault = (this && this.__importDefault) || function (mod) { 22 | return (mod && mod.__esModule) ? mod : { "default": mod }; 23 | }; 24 | Object.defineProperty(exports, "__esModule", { value: true }); 25 | exports.DebuggingClient = void 0; 26 | const net_1 = __importDefault(require("net")); 27 | const events_1 = __importDefault(require("events")); 28 | const DebuggingServer_1 = require("./DebuggingServer"); 29 | const getAvailablePort_1 = require("../utils/getAvailablePort"); 30 | const Global = __importStar(require("../Global")); 31 | class DebuggingClient extends events_1.default { 32 | constructor() { 33 | super(); 34 | this.roomsConn = []; 35 | } 36 | get rooms() { 37 | return this.roomsConn.map(r => r.client); 38 | } 39 | get size() { 40 | return this.rooms.length; 41 | } 42 | listen(port) { 43 | this.client = new net_1.default.Socket(); 44 | this.client.connect(port, 'localhost', () => { 45 | console.log("Listening to client tunnel"); 46 | }); 47 | this.client.on('data', async (data) => { 48 | var _a; 49 | try { 50 | const str = data.toString("utf-8"); 51 | const json = JSON.parse(str); 52 | if (json.type === DebuggingServer_1.RoomDebuggingMessageType.UpdateRooms) { 53 | const r = []; 54 | let prevPort = Global.clientRoomFirstPort; 55 | for (const serverPort of json.message) { 56 | const availablePort = await (0, getAvailablePort_1.getAvailablePort)(prevPort); 57 | r.push({ server: serverPort, client: availablePort }); 58 | prevPort = availablePort + 1; 59 | } 60 | this.roomsConn = r; 61 | this.emit("set", r); 62 | } 63 | if (json.type === DebuggingServer_1.RoomDebuggingMessageType.AddRoom) { 64 | const port = await (0, getAvailablePort_1.getAvailablePort)(Global.clientRoomFirstPort); 65 | this.roomsConn.push({ server: json.message, client: port }); 66 | this.emit("add", json.message, port); 67 | } 68 | if (json.type === DebuggingServer_1.RoomDebuggingMessageType.RemoveRoom) { 69 | const port = (_a = this.roomsConn.find(r => r.server === json.message)) === null || _a === void 0 ? void 0 : _a.client; 70 | this.roomsConn = this.roomsConn.filter(r => r.server !== json.message); 71 | this.emit("remove", json.message, port); 72 | } 73 | } 74 | catch (err) { 75 | console.error(err); 76 | } 77 | }); 78 | this.client.on('close', () => { 79 | console.log('Connection to debugging server closed'); 80 | }); 81 | this.client.on("error", (err) => { 82 | var _a; 83 | console.error(err); 84 | (_a = this.client) === null || _a === void 0 ? void 0 : _a.destroy(); 85 | }); 86 | this.client.on("timeout", () => { 87 | var _a; 88 | console.log('Connection to debugging server timed out'); 89 | (_a = this.client) === null || _a === void 0 ? void 0 : _a.destroy(); 90 | }); 91 | } 92 | } 93 | exports.DebuggingClient = DebuggingClient; 94 | //# sourceMappingURL=DebuggingClient.js.map -------------------------------------------------------------------------------- /dist/debugging/DebuggingClient.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"DebuggingClient.js","sourceRoot":"","sources":["../../src/debugging/DebuggingClient.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/debugging/DebuggingInterface.d.ts: -------------------------------------------------------------------------------- 1 | import { DebuggingClient } from "./DebuggingClient"; 2 | export declare class ConnectInterface { 3 | listen(expressPort: number, wsPort: number, debuggingClient: DebuggingClient): string; 4 | private openExpressServer; 5 | private openWSServer; 6 | private addRoomToList; 7 | private removeRoomFromList; 8 | } 9 | -------------------------------------------------------------------------------- /dist/debugging/DebuggingInterface.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); 5 | }) : (function(o, m, k, k2) { 6 | if (k2 === undefined) k2 = k; 7 | o[k2] = m[k]; 8 | })); 9 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 10 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 11 | }) : function(o, v) { 12 | o["default"] = v; 13 | }); 14 | var __importStar = (this && this.__importStar) || function (mod) { 15 | if (mod && mod.__esModule) return mod; 16 | var result = {}; 17 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 18 | __setModuleDefault(result, mod); 19 | return result; 20 | }; 21 | var __importDefault = (this && this.__importDefault) || function (mod) { 22 | return (mod && mod.__esModule) ? mod : { "default": mod }; 23 | }; 24 | Object.defineProperty(exports, "__esModule", { value: true }); 25 | exports.ConnectInterface = void 0; 26 | const express_1 = __importDefault(require("express")); 27 | const http_1 = __importDefault(require("http")); 28 | const WebSocket = __importStar(require("ws")); 29 | const Global = __importStar(require("../Global")); 30 | const html = ` 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |

Room list

41 | 63 | 64 | 65 | `; 66 | class ConnectInterface { 67 | listen(expressPort, wsPort, debuggingClient) { 68 | this.openWSServer(wsPort, debuggingClient); 69 | const url = this.openExpressServer(expressPort); 70 | return url; 71 | } 72 | openExpressServer(port) { 73 | const app = (0, express_1.default)(); 74 | const url = `http://localhost:${port}`; 75 | app.get('/', (req, res) => { 76 | res.send(html); 77 | }); 78 | app.listen(port, "localhost", () => { 79 | console.log(`Listening web server at ${url}`); 80 | }); 81 | return url; 82 | } 83 | openWSServer(port, client) { 84 | const wss = new WebSocket.Server({ port }); 85 | wss.on('connection', (ws) => { 86 | client.on("add", async (server, client) => { 87 | setTimeout(() => this.addRoomToList(client, ws), 2000); 88 | }); 89 | client.on("remove", async (server, client) => { 90 | this.removeRoomFromList(client, ws); 91 | }); 92 | for (const room of client.rooms) { 93 | this.addRoomToList(room, ws); 94 | } 95 | }); 96 | } 97 | addRoomToList(room, ws, iteration = 0) { 98 | const url = `http://localhost:${room}`; 99 | if (iteration >= 3) 100 | return; 101 | http_1.default.get(`${url}/json`, (res) => { 102 | let body = ""; 103 | res.on("data", (chunk) => body += chunk); 104 | res.on("end", () => { 105 | const data = JSON.parse(body)[0]; 106 | ws.send(JSON.stringify({ 107 | type: "add", 108 | message: { 109 | port: room, 110 | source: url, 111 | url: data.devtoolsFrontendUrl, 112 | title: data.title 113 | } 114 | })); 115 | }); 116 | }).on("error", (error) => { 117 | console.error(error); 118 | setTimeout(() => this.addRoomToList(room, ws, iteration + 1), 2000); 119 | }); 120 | } 121 | removeRoomFromList(room, ws) { 122 | try { 123 | ws.send(JSON.stringify({ 124 | type: "remove", 125 | message: { 126 | port: room 127 | } 128 | })); 129 | } 130 | catch (err) { 131 | console.error(err); 132 | } 133 | } 134 | } 135 | exports.ConnectInterface = ConnectInterface; 136 | //# sourceMappingURL=DebuggingInterface.js.map -------------------------------------------------------------------------------- /dist/debugging/DebuggingInterface.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"DebuggingInterface.js","sourceRoot":"","sources":["../../src/debugging/DebuggingInterface.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/debugging/DebuggingServer.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import net from "net"; 3 | export declare enum RoomDebuggingMessageType { 4 | UpdateRooms = 0, 5 | AddRoom = 1, 6 | RemoveRoom = 2 7 | } 8 | export declare class DebuggingServer { 9 | server: net.Server; 10 | sockets: net.Socket[]; 11 | roomServers: number[]; 12 | constructor(); 13 | private message; 14 | private broadcast; 15 | listen(port: number): void; 16 | setRooms(rooms: number[]): void; 17 | addRoom(room: number): void; 18 | removeRoom(room: number): void; 19 | } 20 | -------------------------------------------------------------------------------- /dist/debugging/DebuggingServer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.DebuggingServer = exports.RoomDebuggingMessageType = void 0; 7 | const net_1 = __importDefault(require("net")); 8 | const log_1 = require("../utils/log"); 9 | var RoomDebuggingMessageType; 10 | (function (RoomDebuggingMessageType) { 11 | RoomDebuggingMessageType[RoomDebuggingMessageType["UpdateRooms"] = 0] = "UpdateRooms"; 12 | RoomDebuggingMessageType[RoomDebuggingMessageType["AddRoom"] = 1] = "AddRoom"; 13 | RoomDebuggingMessageType[RoomDebuggingMessageType["RemoveRoom"] = 2] = "RemoveRoom"; 14 | })(RoomDebuggingMessageType = exports.RoomDebuggingMessageType || (exports.RoomDebuggingMessageType = {})); 15 | class DebuggingServer { 16 | constructor() { 17 | this.sockets = []; 18 | this.roomServers = []; 19 | this.server = net_1.default.createServer(); 20 | } 21 | message(socket, type, message) { 22 | const msg = JSON.stringify({ type, message }); 23 | socket.write(msg); 24 | } 25 | broadcast(type, message) { 26 | this.sockets.forEach(s => { 27 | this.message(s, type, message); 28 | }); 29 | } 30 | listen(port) { 31 | this.server.on("listening", () => { 32 | (0, log_1.log)("REMOTE DEBUGGING", `Listening to remote connections on port ${port}`); 33 | }); 34 | this.server.on("connection", (socket) => { 35 | socket.setEncoding('utf8'); 36 | this.sockets.push(socket); 37 | this.message(socket, RoomDebuggingMessageType.UpdateRooms, this.roomServers); 38 | }); 39 | this.server.on("error", (err) => { 40 | if (err.message.includes("EADDRINUSE")) { 41 | (0, log_1.log)("FATAL ERROR", `Remote debugging port ${port} is already in use. Make sure you are not running another instance of Haxball Server in the background.`); 42 | process.exit(); 43 | } 44 | else { 45 | throw err; 46 | } 47 | }); 48 | this.server.listen(port, "localhost"); 49 | } 50 | setRooms(rooms) { 51 | this.roomServers = rooms; 52 | this.broadcast(RoomDebuggingMessageType.UpdateRooms, rooms); 53 | (0, log_1.log)("UPDATE ROOM PORTS", rooms.join(", ")); 54 | } 55 | addRoom(room) { 56 | this.roomServers.push(room); 57 | this.broadcast(RoomDebuggingMessageType.AddRoom, room); 58 | (0, log_1.log)("ADD ROOM PORT", room + ""); 59 | } 60 | removeRoom(room) { 61 | if (!this.roomServers.includes(room)) 62 | return; 63 | this.roomServers = this.roomServers.filter(r => r !== room); 64 | this.broadcast(RoomDebuggingMessageType.RemoveRoom, room); 65 | (0, log_1.log)("DELETE ROOM PORT", room + ""); 66 | } 67 | } 68 | exports.DebuggingServer = DebuggingServer; 69 | //# sourceMappingURL=DebuggingServer.js.map -------------------------------------------------------------------------------- /dist/debugging/DebuggingServer.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"DebuggingServer.js","sourceRoot":"","sources":["../../src/debugging/DebuggingServer.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/main.d.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | export {}; 3 | -------------------------------------------------------------------------------- /dist/main.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | var __importDefault = (this && this.__importDefault) || function (mod) { 4 | return (mod && mod.__esModule) ? mod : { "default": mod }; 5 | }; 6 | Object.defineProperty(exports, "__esModule", { value: true }); 7 | const yargs_1 = __importDefault(require("yargs")); 8 | const fs_1 = __importDefault(require("fs")); 9 | const connect_1 = require("./commands/connect"); 10 | const openServer_1 = require("./commands/openServer"); 11 | const args = (0, yargs_1.default)(process.argv.slice(2)); 12 | args.command({ 13 | command: "open", 14 | aliases: ["o", "r", "run", "server"], 15 | describe: "Opens a Haxball server.", 16 | builder: { 17 | file: { 18 | describe: "The config.json file.", 19 | demandOption: false, 20 | type: "string", 21 | } 22 | }, 23 | handler: (argv) => { 24 | (0, openServer_1.openServer)(argv.file); 25 | } 26 | }); 27 | args.command({ 28 | command: "connect", 29 | aliases: ["c"], 30 | describe: "Forwards a remote debugging SSH tunnel.", 31 | builder: { 32 | host: { 33 | describe: "The host name or IP address.", 34 | demandOption: true, 35 | type: "string" 36 | }, 37 | user: { 38 | describe: "The user name.", 39 | demandOption: true, 40 | type: "string" 41 | }, 42 | password: { 43 | describe: "Password for password-based authentication.", 44 | demandOption: false, 45 | type: "string", 46 | conflicts: "privateKey" 47 | }, 48 | privateKey: { 49 | describe: "Private key for key-based authentication.", 50 | demandOption: false, 51 | type: "string", 52 | conflicts: "password" 53 | } 54 | }, 55 | handler: async (argv) => { 56 | const params = { 57 | username: argv.user, 58 | host: argv.host, 59 | keepAlive: true 60 | }; 61 | if (argv.password != null) 62 | await (0, connect_1.connect)(Object.assign(Object.assign({}, params), { password: argv.password })); 63 | if (argv.privateKey != null) { 64 | fs_1.default.readFile(argv.privateKey, { encoding: "utf-8" }, async (err, data) => { 65 | if (err) 66 | return console.error(`${err.message}\nError while loading private key file`); 67 | await (0, connect_1.connect)(Object.assign(Object.assign({}, params), { privateKey: Buffer.from(data) })); 68 | }); 69 | } 70 | } 71 | }); 72 | args.demandCommand(); 73 | args.parse(); 74 | //# sourceMappingURL=main.js.map -------------------------------------------------------------------------------- /dist/main.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";;;;;;AAEA,kDAA0B;AAC1B,4CAAoB;AAEpB,gDAA6C;AAC7C,sDAAmD;AAInD,MAAM,IAAI,GAAG,IAAA,eAAK,EAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAE1C,IAAI,CAAC,OAAO,CAAC;IACT,OAAO,EAAE,MAAM;IACf,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,CAAC;IACpC,QAAQ,EAAE,yBAAyB;IACnC,OAAO,EAAE;QACL,IAAI,EAAE;YACF,QAAQ,EAAE,uBAAuB;YACjC,YAAY,EAAE,KAAK;YACnB,IAAI,EAAE,QAAQ;SAEjB;KACJ;IACD,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;QACd,IAAA,uBAAU,EAAC,IAAI,CAAC,IAAc,CAAC,CAAC;IACpC,CAAC;CACJ,CAAC,CAAC;AAEH,IAAI,CAAC,OAAO,CAAC;IACT,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,CAAC,GAAG,CAAC;IACd,QAAQ,EAAE,yCAAyC;IACnD,OAAO,EAAE;QACL,IAAI,EAAE;YACF,QAAQ,EAAE,8BAA8B;YACxC,YAAY,EAAE,IAAI;YAClB,IAAI,EAAE,QAAQ;SACjB;QACD,IAAI,EAAE;YACF,QAAQ,EAAE,gBAAgB;YAC1B,YAAY,EAAE,IAAI;YAClB,IAAI,EAAE,QAAQ;SACjB;QACD,QAAQ,EAAE;YACN,QAAQ,EAAE,6CAA6C;YACvD,YAAY,EAAE,KAAK;YACnB,IAAI,EAAE,QAAQ;YACd,SAAS,EAAE,YAAY;SAC1B;QACD,UAAU,EAAE;YACR,QAAQ,EAAE,2CAA2C;YACrD,YAAY,EAAE,KAAK;YACnB,IAAI,EAAE,QAAQ;YACd,SAAS,EAAE,UAAU;SACxB;KACJ;IACD,OAAO,EAAE,KAAK,EAAE,IAAS,EAAE,EAAE;QACzB,MAAM,MAAM,GAAW;YACnB,QAAQ,EAAE,IAAI,CAAC,IAAI;YACnB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,SAAS,EAAE,IAAI;SAClB,CAAA;QAED,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI;YAAE,MAAM,IAAA,iBAAO,kCAAM,MAAM,KAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAG,CAAC;QAEjF,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,EAAE;YACzB,YAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;gBACpE,IAAI,GAAG;oBAAE,OAAO,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,OAAO,wCAAwC,CAAC,CAAC;gBAEtF,MAAM,IAAA,iBAAO,kCAAM,MAAM,KAAE,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAG,CAAC;YAChE,CAAC,CAAC,CAAC;SACN;IACL,CAAC;CACJ,CAAC,CAAC;AAEH,IAAI,CAAC,aAAa,EAAE,CAAA;AACpB,IAAI,CAAC,KAAK,EAAE,CAAC"} -------------------------------------------------------------------------------- /dist/utils/escapeString.d.ts: -------------------------------------------------------------------------------- 1 | export declare function escapeString(str: any): any; 2 | -------------------------------------------------------------------------------- /dist/utils/escapeString.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.escapeString = void 0; 4 | function escapeString(str) { 5 | if (typeof str !== "string") 6 | return str; 7 | return str.replace(/"/g, '\\"'); 8 | } 9 | exports.escapeString = escapeString; 10 | //# sourceMappingURL=escapeString.js.map -------------------------------------------------------------------------------- /dist/utils/escapeString.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"escapeString.js","sourceRoot":"","sources":["../../src/utils/escapeString.ts"],"names":[],"mappings":";;;AAAA,SAAgB,YAAY,CAAC,GAAQ;IACjC,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,GAAG,CAAC;IAExC,OAAO,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AACpC,CAAC;AAJD,oCAIC"} -------------------------------------------------------------------------------- /dist/utils/getAvailablePort.d.ts: -------------------------------------------------------------------------------- 1 | export declare function getAvailablePort(startingPort: number): Promise; 2 | -------------------------------------------------------------------------------- /dist/utils/getAvailablePort.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); 5 | }) : (function(o, m, k, k2) { 6 | if (k2 === undefined) k2 = k; 7 | o[k2] = m[k]; 8 | })); 9 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 10 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 11 | }) : function(o, v) { 12 | o["default"] = v; 13 | }); 14 | var __importStar = (this && this.__importStar) || function (mod) { 15 | if (mod && mod.__esModule) return mod; 16 | var result = {}; 17 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 18 | __setModuleDefault(result, mod); 19 | return result; 20 | }; 21 | Object.defineProperty(exports, "__esModule", { value: true }); 22 | exports.getAvailablePort = void 0; 23 | const PortScanner = __importStar(require("portscanner")); 24 | async function getAvailablePort(startingPort) { 25 | let port = startingPort; 26 | while (true) { 27 | const taken = await PortScanner.checkPortStatus(port); 28 | if (taken === "closed") 29 | break; 30 | port++; 31 | } 32 | return port; 33 | } 34 | exports.getAvailablePort = getAvailablePort; 35 | //# sourceMappingURL=getAvailablePort.js.map -------------------------------------------------------------------------------- /dist/utils/getAvailablePort.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"getAvailablePort.js","sourceRoot":"","sources":["../../src/utils/getAvailablePort.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA,yDAA2C;AAEpC,KAAK,UAAU,gBAAgB,CAAC,YAAoB;IACvD,IAAI,IAAI,GAAG,YAAY,CAAC;IAExB,OAAM,IAAI,EAAE;QACR,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QAEtD,IAAI,KAAK,KAAK,QAAQ;YAAE,MAAM;QAE9B,IAAI,EAAE,CAAC;KACV;IAED,OAAO,IAAI,CAAC;AAChB,CAAC;AAZD,4CAYC"} -------------------------------------------------------------------------------- /dist/utils/loadConfig.d.ts: -------------------------------------------------------------------------------- 1 | import { HaxballServerConfig } from "../Global"; 2 | export declare function loadConfig(file?: string): Promise; 3 | -------------------------------------------------------------------------------- /dist/utils/loadConfig.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.loadConfig = void 0; 7 | const fs_1 = __importDefault(require("fs")); 8 | const path_1 = __importDefault(require("path")); 9 | function validate(object) { 10 | if (!object.server) 11 | return false; 12 | if (!object.panel) 13 | return false; 14 | return true; 15 | } 16 | function loadConfig(file) { 17 | return new Promise((resolve, reject) => { 18 | const filePath = file == null || file == "" ? path_1.default.resolve(path_1.default.resolve('.'), "config.json") : file; 19 | fs_1.default.readFile(filePath, { encoding: "utf-8", flag: "r" }, (err, data) => { 20 | if (err) 21 | reject({ 22 | message: `Error while loading config file`, 23 | error: err 24 | }); 25 | try { 26 | const json = JSON.parse(data); 27 | if (!validate(json)) 28 | reject({ 29 | message: `Invalid configuration`, 30 | error: null 31 | }); 32 | resolve(JSON.parse(data)); 33 | } 34 | catch (err) { 35 | reject({ 36 | message: `Error while parsing config file`, 37 | error: err 38 | }); 39 | } 40 | }); 41 | }); 42 | } 43 | exports.loadConfig = loadConfig; 44 | //# sourceMappingURL=loadConfig.js.map -------------------------------------------------------------------------------- /dist/utils/loadConfig.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"loadConfig.js","sourceRoot":"","sources":["../../src/utils/loadConfig.ts"],"names":[],"mappings":";;;;;;AAAA,4CAAoB;AACpB,gDAAwB;AAIxB,SAAS,QAAQ,CAAC,MAAW;IACzB,IAAI,CAAE,MAA8B,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC1D,IAAI,CAAE,MAA8B,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IAEzD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,SAAgB,UAAU,CAAC,IAAa;IACpC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACnC,MAAM,QAAQ,GAAG,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,cAAI,CAAC,OAAO,CAAC,cAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAEpG,YAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;YAClE,IAAI,GAAG;gBAAE,MAAM,CAAC;oBACZ,OAAO,EAAE,iCAAiC;oBAC1C,KAAK,EAAE,GAAG;iBACb,CAAC,CAAC;YAEH,IAAI;gBACA,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAE9B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;oBAAE,MAAM,CAAC;wBACxB,OAAO,EAAE,uBAAuB;wBAChC,KAAK,EAAE,IAAI;qBACd,CAAC,CAAC;gBAEH,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;aAC7B;YAAC,OAAO,GAAG,EAAE;gBACV,MAAM,CAAC;oBACH,OAAO,EAAE,iCAAiC;oBAC1C,KAAK,EAAE,GAAG;iBACb,CAAC,CAAC;aACN;QACL,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;AACP,CAAC;AA3BD,gCA2BC"} -------------------------------------------------------------------------------- /dist/utils/log.d.ts: -------------------------------------------------------------------------------- 1 | export declare function log(prefix: string, message: string): void; 2 | -------------------------------------------------------------------------------- /dist/utils/log.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.log = void 0; 4 | const Global_1 = require("../Global"); 5 | function log(prefix, message) { 6 | const timestamp = new Date().toLocaleTimeString("pt-BR"); 7 | if (message.length > Global_1.maxLengthLog) { 8 | message = message.slice(0, Global_1.maxLengthLog) + "..."; 9 | } 10 | console.log(`[${timestamp}] [${prefix}] ${message}`); 11 | } 12 | exports.log = log; 13 | //# sourceMappingURL=log.js.map -------------------------------------------------------------------------------- /dist/utils/log.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"log.js","sourceRoot":"","sources":["../../src/utils/log.ts"],"names":[],"mappings":";;;AAAA,sCAAyC;AAEzC,SAAgB,GAAG,CAAC,MAAc,EAAE,OAAe;IAC/C,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAEzD,IAAI,OAAO,CAAC,MAAM,GAAG,qBAAY,EAAE;QAC/B,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,qBAAY,CAAC,GAAG,KAAK,CAAC;KACpD;IAED,OAAO,CAAC,GAAG,CAAC,IAAI,SAAS,MAAM,MAAM,KAAK,OAAO,EAAE,CAAC,CAAC;AACzD,CAAC;AARD,kBAQC"} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "haxball-server", 3 | "version": "4.3.0", 4 | "description": "A feature-rich and stable headless server utility for Haxball", 5 | "main": "./dist/main.js", 6 | "homepage": "https://github.com/gabrielbrop/haxball-server#readme", 7 | "publishConfig": { 8 | "access": "public" 9 | }, 10 | "scripts": { 11 | "start": "tsc && node .", 12 | "test": "echo \"No test specified\"" 13 | }, 14 | "author": "gabrielbrop", 15 | "license": "ISC", 16 | "bin": { 17 | "haxballserver": "./dist/main.js", 18 | "haxball-server": "./dist/main.js" 19 | }, 20 | "files": [ 21 | "dist" 22 | ], 23 | "dependencies": { 24 | "discord.js": "^12.5.3", 25 | "express": "^4.17.1", 26 | "node-os-utils": "^1.3.5", 27 | "open": "^8.4.0", 28 | "pidusage": "^2.0.21", 29 | "portscanner": "^2.2.0", 30 | "puppeteer-core": "^10.1.0", 31 | "tunnel-ssh": "^4.1.4", 32 | "ws": "^8.1.0", 33 | "yargs": "^17.0.1" 34 | }, 35 | "devDependencies": { 36 | "@types/express": "^4.17.13", 37 | "@types/node": "^15.14.2", 38 | "@types/node-os-utils": "^1.2.0", 39 | "@types/pidusage": "^2.0.1", 40 | "@types/portscanner": "^2.1.1", 41 | "@types/tunnel-ssh": "^4.1.1", 42 | "@types/ws": "^7.4.5", 43 | "@types/yargs": "^17.0.2", 44 | "typescript": "^4.3.5" 45 | }, 46 | "keywords": [ 47 | "haxball", 48 | "haxball bot", 49 | "haxball server", 50 | "headless", 51 | "haxball headless", 52 | "hax server", 53 | "headless bot" 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /src/ControlPanel.ts: -------------------------------------------------------------------------------- 1 | import os from "node-os-utils"; 2 | import fs from "fs"; 3 | import pidusage from "pidusage"; 4 | import process from "process"; 5 | import * as Discord from 'discord.js'; 6 | 7 | import { Server } from "./Server"; 8 | import { CustomSettings, CustomSettingsList, PanelConfig } from "./Global"; 9 | 10 | import { loadConfig } from "./utils/loadConfig"; 11 | import { log } from "./utils/log"; 12 | 13 | class Bot { 14 | constructor( 15 | public name: string, 16 | public path: string, 17 | public displayName?: string 18 | ) {} 19 | 20 | read(): Promise { 21 | return new Promise((resolve, reject) => { 22 | fs.readFile(this.path, { encoding: 'utf-8' }, async (err, data) => { 23 | if (err) { 24 | reject(err); 25 | } else { 26 | resolve(data); 27 | } 28 | }); 29 | }); 30 | } 31 | 32 | run(server: Server, data: string, tokens: string | string[], settings?: CustomSettings): ReturnType { 33 | return new Promise((resolve, reject) => { 34 | server.open(data, tokens, this.displayName, settings) 35 | .then(e => resolve(e)) 36 | .catch(err => reject(err)); 37 | }); 38 | } 39 | } 40 | 41 | export class ControlPanel { 42 | private client = new Discord.Client(); 43 | 44 | private cpu = os.cpu; 45 | private mem = os.mem; 46 | 47 | private prefix: string; 48 | private token: string; 49 | 50 | private mastersDiscordId: string[]; 51 | 52 | private bots: Bot[] = []; 53 | 54 | private customSettings?: CustomSettingsList; 55 | 56 | private maxRooms?: number; 57 | 58 | constructor(private server: Server, config: PanelConfig, private fileName?: string) { 59 | this.prefix = config.discordPrefix; 60 | this.token = config.discordToken; 61 | this.mastersDiscordId = config.mastersDiscordId; 62 | this.maxRooms = config.maxRooms; 63 | 64 | if (config.customSettings) this.loadCustomSettings(config.customSettings); 65 | this.loadBots(config.bots); 66 | 67 | this.client.on('ready', () => { 68 | log("DISCORD", `Logged in as ${this.client.user?.tag}!`); 69 | }); 70 | 71 | this.client.on('message', async msg => { 72 | try { 73 | this.command(msg); 74 | } catch (e) { 75 | this.logError(e, msg.channel as Discord.TextChannel); 76 | } 77 | }); 78 | 79 | this.client.login(this.token); 80 | } 81 | 82 | private transformSetting(setting: CustomSettings, list: CustomSettingsList) { 83 | if (setting.extends) { 84 | const extensions = typeof setting.extends === "string" ? [setting.extends] : setting.extends; 85 | let newSetting: CustomSettings = {}; 86 | 87 | for (const e of extensions) { 88 | let ext = list[e]; 89 | 90 | if (ext) { 91 | if (ext.extends) ext = this.transformSetting(ext, list); 92 | 93 | newSetting = { ...newSetting, ...ext }; 94 | } 95 | } 96 | 97 | newSetting = { ...newSetting, ...setting }; 98 | 99 | delete newSetting.extends; 100 | 101 | return newSetting; 102 | } 103 | 104 | return setting; 105 | } 106 | 107 | private loadCustomSettings(customSettings: CustomSettingsList) { 108 | this.customSettings = undefined; 109 | 110 | for (const entry of Object.entries(customSettings)) { 111 | const key = entry[0]; 112 | const value = entry[1]; 113 | 114 | customSettings[key] = this.transformSetting(value, customSettings); 115 | } 116 | 117 | this.customSettings = customSettings; 118 | } 119 | 120 | private loadBots(bots: PanelConfig["bots"]) { 121 | this.bots = []; 122 | 123 | if (!Array.isArray(bots)) { 124 | for (const entry of Object.entries(bots)) { 125 | const name = entry[0]; 126 | const path = entry[1]; 127 | 128 | this.bots.push(new Bot(name, path)); 129 | } 130 | } else { 131 | for (const bot of bots) { 132 | this.bots.push(new Bot(bot.name, bot.path, bot.displayName)); 133 | } 134 | } 135 | } 136 | 137 | private async logError(e: any, channel: Discord.TextChannel) { 138 | const embed = new Discord.MessageEmbed() 139 | .setColor('#0099ff') 140 | .setTitle("Log Error") 141 | .setTimestamp(Date.now()) 142 | .setDescription(e); 143 | 144 | await channel.send(embed); 145 | } 146 | 147 | private async getRoomNameList() { 148 | let rooms = []; 149 | 150 | for (const browser of this.server.browsers) { 151 | const page = (await browser.pages())[0]; 152 | const proxyServer = browser["proxyServer"]; 153 | const remotePort = browser["remotePort"]; 154 | 155 | let pageTitle = await page.title(); 156 | pageTitle = pageTitle != null && pageTitle != "" ? pageTitle : "Unnamed tab"; 157 | 158 | const nameStr = `${pageTitle} (${page.browser().process()?.pid})${remotePort != null ? ` (localhost:${remotePort})` : ""}`; 159 | 160 | rooms.push({ name: nameStr, proxy: proxyServer }); 161 | } 162 | 163 | if (rooms.length === 0) return "There are no open rooms!"; 164 | if (rooms.every(r => r.proxy == null)) return rooms.map(r => r.name).join("\n"); 165 | 166 | let proxyRooms: { text: string, proxy: string }[] = []; 167 | 168 | for (const room of rooms) { 169 | let pRoom = proxyRooms.find(r => r.proxy === room.proxy); 170 | 171 | if (pRoom) { 172 | pRoom.text += room.name + "\n"; 173 | } else { 174 | proxyRooms.push({ text: room.name + "\n", proxy: room.proxy }); 175 | } 176 | } 177 | 178 | return proxyRooms.map(r => `• ${r.proxy}\n${r.text}`).join("\n"); 179 | } 180 | 181 | private async getRoomUsageList() { 182 | const roomsUsage: { process: pidusage.Status, title: string }[] = []; 183 | 184 | for (const browser of this.server.browsers) { 185 | const page = (await browser.pages())[0]; 186 | 187 | roomsUsage.push({ process: await pidusage(browser?.process()?.pid as number), title: await page.title() }); 188 | } 189 | 190 | return roomsUsage; 191 | } 192 | 193 | private async command(msg: Discord.Message) { 194 | if (!msg.content.startsWith(this.prefix)) return; 195 | 196 | const args = msg.content.slice(this.prefix.length).trim().split(' '); 197 | const text = msg.content.slice(this.prefix.length).trim().replace(args[0] + " ", ""); 198 | const command = args.shift()?.toLowerCase(); 199 | 200 | const embed = new Discord.MessageEmbed().setColor('#0099ff'); 201 | 202 | if (this.mastersDiscordId.includes(msg.author.id)) { 203 | if (command === "help") { 204 | embed 205 | .setTitle("Help") 206 | .setDescription("Haxball Server is a small server utility for Haxball rooms.") 207 | .addField("help", "Command list.", true) 208 | .addField("info", "Server info.", true) 209 | .addField("meminfo", "CPU and memory info.", true) 210 | .addField("open", "Open a room.", true) 211 | .addField("close", "Close a room.", true) 212 | .addField("reload", "Reload the bot configuration.", true) 213 | .addField("exit", "Close the server.", true) 214 | .addField("eval", "Execute Javascript.", true) 215 | .addField("tokenlink", "Haxball Headless Token page.", true); 216 | 217 | msg.channel.send(embed); 218 | } 219 | 220 | if (command === "tokenlink") { 221 | embed 222 | .setTitle("Headless Token") 223 | .setDescription(`[Click here.](https://www.haxball.com/headlesstoken)`); 224 | 225 | msg.channel.send(embed); 226 | } 227 | 228 | if (command === "open") { 229 | embed.setTitle("Open room"); 230 | 231 | if (this.maxRooms != null && this.server.browsers.length >= this.maxRooms) { 232 | embed.setDescription(`Maximum number of rooms (${this.maxRooms}) excedeed. Update configuration to change this.`); 233 | 234 | return msg.channel.send(embed); 235 | } 236 | 237 | const bot = this.bots.find(b => b.name === args[0]); 238 | 239 | if (!bot) { 240 | embed.setDescription(`This bot does not exist. Type ${this.prefix}info to see the list of available bots.`); 241 | 242 | return msg.channel.send(embed); 243 | } 244 | 245 | let token = text.replace(args[0], "").trim().replace(/\"/g, "").replace("Token obtained: ", ""); 246 | 247 | if (!token || token === "") { 248 | embed.setDescription(`You have to define a [headless token](https://www.haxball.com/headlesstoken) as second argument: ${this.prefix}open `); 249 | 250 | return msg.channel.send(embed); 251 | } 252 | 253 | let settings: CustomSettings; 254 | let settingsMsg = "No setting has been loaded (not specified or not found)."; 255 | 256 | if (this.customSettings != null) { 257 | const settingArg = args[args.length - 1]; 258 | 259 | settings = this.customSettings[settingArg]; 260 | 261 | if (settings) { 262 | settingsMsg = `\`${settingArg}\` settings have been loaded.`; 263 | token = token.replace(settingArg, "").trim(); 264 | } else if (this.customSettings["default"]) { 265 | settingsMsg = `Default settings have been loaded.`; 266 | settings = this.customSettings["default"]; 267 | } 268 | } 269 | 270 | embed.setDescription("Opening room..."); 271 | 272 | const message = await msg.channel.send(embed); 273 | 274 | bot.read().then(script => { 275 | bot.run(this.server, script, [token, token.substring(0, token.lastIndexOf(" "))], settings).then(e => { 276 | message.edit(embed.setDescription(`Room running! [Click here to join.](${e?.link})\nBrowser process: ${e?.pid}${e?.remotePort ? `\nRemote debugging: localhost:${e.remotePort}`: ""}\n${settingsMsg}`)); 277 | }) 278 | .catch(err => { 279 | message.edit(embed.setDescription(`Unable to open the room!\n ${err}`)); 280 | }); 281 | }) 282 | .catch(err => { 283 | embed.setDescription("Error: " + err); 284 | 285 | message.edit(embed); 286 | }); 287 | } 288 | 289 | if (command === "info") { 290 | const roomList = await this.getRoomNameList(); 291 | 292 | embed 293 | .setTitle("Information") 294 | .addField("Open rooms", roomList) 295 | .addField("Bot list", this.bots.map(b => b.name).join("\n")) 296 | .addField("Custom settings list", this.customSettings ? Object.keys(this.customSettings).join("\n") : "No custom settings have been specified."); 297 | 298 | msg.channel.send(embed); 299 | } 300 | 301 | if (command === "meminfo") { 302 | const embedLoading = new Discord.MessageEmbed() 303 | .setColor('#0099ff') 304 | .setTitle("Information") 305 | .setDescription("Loading..."); 306 | 307 | const message = await msg.channel.send(embedLoading); 308 | 309 | const roomsUsage = await this.getRoomUsageList(); 310 | 311 | const memInfo = await this.mem.info(); 312 | const cpuUsage = await this.cpu.usage(); 313 | 314 | embed 315 | .setTitle("Information") 316 | .addField("CPUs", this.cpu.count(), true) 317 | .addField("CPU usage", cpuUsage + "%", true) 318 | .addField("Free CPU", 100 - cpuUsage + "%", true) 319 | .addField("Memory", `${(memInfo.usedMemMb / 1000).toFixed(2)}/${(memInfo.totalMemMb / 1000).toFixed(2)} GB (${memInfo.freeMemPercentage}% livre)`, true) 320 | .addField("OS", await os.os.oos(), true) 321 | .addField("Machine Uptime", new Date(os.os.uptime() * 1000).toISOString().substr(11, 8), true) 322 | 323 | const serverPIDUsage = await pidusage(process.pid); 324 | 325 | const serverCPUUsage = `CPU server usage: ${(serverPIDUsage.cpu).toFixed(2)}%\nMemory server usage: ${(serverPIDUsage.memory * 1e-6).toFixed(2)} MB\n`; 326 | const roomCPUMessage = this.server.browsers.length > 0 ? "\n" + roomsUsage.map((room) => `**${room.title} (${room.process.pid})**:\n${(room.process.cpu).toFixed(2)}% CPU\n${(room.process.memory * 1e-6).toFixed(2)} MB memory\n`).join("\n") : ""; 327 | 328 | embed.setDescription(serverCPUUsage + roomCPUMessage + "\n"); 329 | 330 | message.edit(embed); 331 | } 332 | 333 | if (command === "close") { 334 | embed 335 | .setTitle("Close room") 336 | .setDescription("Unable to find room"); 337 | 338 | if (args[0] === "all") { 339 | let forcedClosedRooms = 0; 340 | let closedRooms = 0; 341 | 342 | for (const room of this.server.browsers) { 343 | const pid = room?.process()?.pid; 344 | 345 | if (pid) { 346 | await this.server.close(pid); 347 | } else { 348 | await room.close(); 349 | 350 | forcedClosedRooms++; 351 | } 352 | 353 | closedRooms++; 354 | } 355 | 356 | if (forcedClosedRooms === 0) { 357 | embed.setDescription(`${closedRooms} rooms have been closed.`) 358 | } else { 359 | embed.setDescription(`${closedRooms} rooms have been closed.\n${forcedClosedRooms} rooms have been forced to close.`) 360 | } 361 | 362 | return msg.channel.send(embed); 363 | } 364 | 365 | const res = await this.server.close(text); 366 | 367 | if (res) embed.setDescription("Room closed!"); 368 | 369 | msg.channel.send(embed); 370 | } 371 | 372 | if (command === "exit") { 373 | embed 374 | .setTitle("Closing") 375 | .setDescription("Closing server..."); 376 | 377 | await msg.channel.send(embed); 378 | 379 | this.server.browsers.forEach(async browser => { 380 | await browser.close(); 381 | }); 382 | 383 | process.exit(); 384 | } 385 | 386 | if (command === "eval") { 387 | try { 388 | const code = args.join(" "); 389 | let evaled = eval(code); 390 | 391 | if (typeof evaled !== "string") evaled = require("util").inspect(evaled); 392 | 393 | msg.channel.send(evaled, { code: "javascript", split: true }); 394 | } catch (err) { 395 | msg.channel.send(`\`ERROR\` \`\`\`xl\n${err}\n\`\`\``); 396 | } 397 | } 398 | 399 | if (command === "reload") { 400 | embed.setTitle("Reload bots and custom settings").setColor(0xFF0000); 401 | 402 | loadConfig(this.fileName).then((config) => { 403 | if (!config.panel.bots) { 404 | embed.setDescription("Could not find bots in config file."); 405 | } else { 406 | this.loadBots(config.panel.bots); 407 | if (config.panel.customSettings) this.loadCustomSettings(config.panel.customSettings); 408 | this.maxRooms = config.panel.maxRooms; 409 | 410 | embed.setColor(0x0099FF).setDescription("Bot list and custom settings reloaded!"); 411 | } 412 | 413 | msg.channel.send(embed); 414 | }).catch(err => { 415 | embed.setDescription(`*${err.message}*\n\nSee logs for details.`); 416 | 417 | console.error(err); 418 | 419 | msg.channel.send(embed); 420 | }); 421 | } 422 | } 423 | } 424 | } -------------------------------------------------------------------------------- /src/Global.ts: -------------------------------------------------------------------------------- 1 | export const serverPort = 9500; 2 | export const serverRoomFirstPort = 9501; 3 | 4 | export const clientPort = 9600; 5 | export const expressPort = 9601; 6 | export const wsPort = 9602; 7 | export const clientRoomFirstPort = 9603; 8 | 9 | export const maxLengthLog = 300; 10 | 11 | export const maxTimeSSHConnection = 2 * 60 * 1000; 12 | 13 | export const roomCustomConfigsList = [ 14 | "roomName", 15 | "playerName", 16 | "password", 17 | "maxPlayers", 18 | "public", 19 | "geo", 20 | "noPlayer" 21 | ]; 22 | 23 | type BotList = { [key: string]: string } | { name: string, path: string, displayName?: string }[]; 24 | 25 | export interface PanelConfig { 26 | discordToken: string; 27 | discordPrefix: string; 28 | bots: BotList; 29 | mastersDiscordId: string[]; 30 | customSettings?: CustomSettingsList; 31 | maxRooms?: number; 32 | } 33 | 34 | export interface ServerConfig { 35 | proxyEnabled?: boolean, 36 | proxyServers?: string[], 37 | disableCache?: boolean, 38 | disableRemote?: boolean, 39 | userDataDir?: string, 40 | disableAnonymizeLocalIps?: boolean, 41 | execPath: string, 42 | maxMemoryUsage: number 43 | } 44 | 45 | export interface HaxballServerConfig { 46 | server: ServerConfig, 47 | panel: PanelConfig 48 | } 49 | 50 | export interface CustomSettings { 51 | extends?: string | string[]; 52 | [key: string]: any; 53 | } 54 | 55 | export type ReservedCustomSettings = 56 | "reserved.haxball.roomName" | 57 | "reserved.haxball.playerName" | 58 | "reserved.haxball.password" | 59 | "reserved.haxball.maxPlayers" | 60 | "reserved.haxball.public" | 61 | "reserved.haxball.geo" | 62 | "reserved.haxball.noPlayer"; 63 | 64 | export type CustomSettingsList = { 65 | [key: string]: CustomSettings 66 | }; -------------------------------------------------------------------------------- /src/Server.ts: -------------------------------------------------------------------------------- 1 | import puppeteer, { Page } from 'puppeteer-core'; 2 | 3 | import { DebuggingServer } from "./debugging/DebuggingServer"; 4 | import { getAvailablePort } from "./utils/getAvailablePort"; 5 | import { escapeString } from "./utils/escapeString"; 6 | import { log } from "./utils/log"; 7 | 8 | import * as Global from "./Global"; 9 | import { CustomSettings, roomCustomConfigsList, ServerConfig } from "./Global"; 10 | 11 | const selectorFrame = 'body > iframe'; 12 | const selectorRoomLink = '#roomlink > p > a'; 13 | 14 | const blockedRes = [ 15 | '*/favicon.ico', 16 | '.css', 17 | '.jpg', 18 | '.jpeg', 19 | '.png', 20 | '.svg', 21 | '.woff', 22 | 23 | '*.optimizely.com', 24 | 'everesttech.net', 25 | 'userzoom.com', 26 | 'doubleclick.net', 27 | 'googleadservices.com', 28 | 'adservice.google.com/*', 29 | 'connect.facebook.com', 30 | 'connect.facebook.net', 31 | 'sp.analytics.yahoo.com' 32 | ]; 33 | 34 | export class Server { 35 | browsers: puppeteer.Browser[] = []; 36 | 37 | private unnamedCount = 1; 38 | private remoteChromePort: number; 39 | 40 | private proxyEnabled: boolean; 41 | private proxyServers: string[]; 42 | private execPath: string; 43 | private disableCache: boolean; 44 | private userDataDir?: string; 45 | private disableRemote: boolean; 46 | private disableAnonymizeLocalIps: boolean; 47 | private maxMemoryUsage?: number; 48 | 49 | private debuggingServer?: DebuggingServer; 50 | 51 | constructor(config: ServerConfig) { 52 | this.proxyEnabled = config?.proxyEnabled ?? false; 53 | this.proxyServers = config?.proxyServers ?? []; 54 | this.execPath = config.execPath; 55 | this.disableCache = config.disableCache ?? false; 56 | this.userDataDir = config.userDataDir; 57 | this.remoteChromePort = Global.serverRoomFirstPort; 58 | this.disableRemote = config.disableRemote ?? false; 59 | this.disableAnonymizeLocalIps = config.disableAnonymizeLocalIps ?? false; 60 | this.maxMemoryUsage = config.maxMemoryUsage; 61 | 62 | if (!this.disableRemote) { 63 | this.debuggingServer = new DebuggingServer(); 64 | this.debuggingServer.listen(Global.serverPort); 65 | } 66 | } 67 | 68 | private async createNewBrowser() { 69 | const args = [ 70 | "--no-sandbox", 71 | "--disable-setuid-sandbox", 72 | "--disable-dev-shm-usage", 73 | "--disable-accelerated-2d-canvas", 74 | "--no-first-run", 75 | "--no-zygote", 76 | "--single-process", 77 | "--disable-gpu", 78 | ]; 79 | 80 | const remotePort = await getAvailablePort(this.remoteChromePort); 81 | 82 | if (!this.disableRemote) args.push(`--remote-debugging-port=${remotePort}`); 83 | if (this.disableCache) args.push("--incognito"); 84 | if (this.disableAnonymizeLocalIps) args.push(`--disable-features=WebRtcHideLocalIpsWithMdns`); 85 | if (this.maxMemoryUsage) args.push("--max-old-space-size=" + this.maxMemoryUsage); 86 | 87 | let proxyServer = ""; 88 | 89 | if (this.proxyEnabled) { 90 | let availableProxies = this.proxyServers.filter(s => { 91 | let a = 0; 92 | 93 | for (const browser of this.browsers) { 94 | if (browser["proxyServer"] === s) { 95 | a++; 96 | } 97 | } 98 | 99 | return a < 2; 100 | }); 101 | 102 | if (availableProxies.length === 0) { 103 | proxyServer = this.proxyServers[this.proxyServers.length - 1]; 104 | } else { 105 | proxyServer = availableProxies[0]; 106 | } 107 | 108 | args.push("--proxy-server=" + proxyServer); 109 | } 110 | 111 | const puppeteerArgs = { 112 | headless: true, 113 | args: args, 114 | executablePath: this.execPath 115 | }; 116 | 117 | if (this.userDataDir && this.disableCache !== true) puppeteerArgs["userDataDir"] = this.userDataDir; 118 | 119 | const browser = await puppeteer.launch(puppeteerArgs); 120 | 121 | if (!this.disableRemote) browser["remotePort"] = remotePort; 122 | if (proxyServer != "") browser["proxyServer"] = proxyServer; 123 | 124 | this.browsers.push(browser); 125 | 126 | browser.on("disconnected", () => { 127 | this.browsers = this.browsers.filter(b => { 128 | const isConnected = b.isConnected(); 129 | 130 | if (!isConnected) b.close(); 131 | 132 | if (!this.disableRemote) this.debuggingServer?.removeRoom(remotePort); 133 | 134 | return isConnected; 135 | }); 136 | }); 137 | 138 | if (!this.disableRemote) this.debuggingServer?.addRoom(remotePort); 139 | 140 | return browser; 141 | } 142 | 143 | private async checkTokenWorks(page: Page, token: string) { 144 | return await page.evaluate(async (token) => { 145 | return await new Promise((resolve) => { 146 | const server = new WebSocket(`wss://p2p2.haxball.com/host?token=${token}`); 147 | 148 | server.onopen = function() { 149 | resolve(true); 150 | }; 151 | 152 | server.onerror = function() { 153 | resolve(false); 154 | }; 155 | }); 156 | }, token); 157 | } 158 | 159 | private async openRoom(page: puppeteer.Page, script: string, tokens: string[], name?: string, settings?: CustomSettings): Promise { 160 | page.on("pageerror", ({ message }) => log("PAGE ERROR", message)) 161 | .on("response", response => log("PAGE RESPONSE", `${response.status()} : ${response.url()}`)) 162 | .on("requestfailed", request => log("REQUEST FAILED", `${request.failure()?.errorText} : ${request.url()}`)) 163 | .on("error", (err) => log("PAGE CRASHED", `${err}`)) 164 | .on("pageerror", (err) => log("ERROR IN PAGE", `${err}`)); 165 | 166 | if (this.disableCache) await page.setCacheEnabled(false); 167 | 168 | const client = await page.target().createCDPSession(); 169 | 170 | name = `(args[0]["roomName"] ?? "Unnamed room ${this.unnamedCount++}")` + (name ? ` + " (${escapeString(name)})"` : ""); 171 | 172 | let reservedHBInitCustomSettingsScript = ""; 173 | let customSettingsScript = {}; 174 | 175 | if (settings) { 176 | for (const setting of Object.entries(settings)) { 177 | const key = setting[0]; 178 | const value = setting[1]; 179 | 180 | if (roomCustomConfigsList.map(config => "reserved.haxball." + config).includes(key)) { 181 | reservedHBInitCustomSettingsScript += `args[0]["${escapeString(key.replace("reserved.haxball.", ""))}"] = ${JSON.stringify(value)};`; 182 | } else { 183 | customSettingsScript[key] = value; 184 | } 185 | } 186 | } 187 | 188 | await client.send('Network.setBlockedURLs', { urls: blockedRes }); 189 | await page.goto('https://www.haxball.com/headless', { waitUntil: 'networkidle2' }); 190 | 191 | let token; 192 | 193 | for (const t of tokens) { 194 | if (t != "" && await this.checkTokenWorks(page, t)) token = t; 195 | } 196 | 197 | const tokenListStr = tokens.filter(t => t && t != "").map(t => "`" + t + "`").join(", "); 198 | 199 | if (token == null) throw new Error(`Invalid token (tried ${tokenListStr}).`); 200 | 201 | const scripts = ` 202 | window.HBInit = new Proxy(window.HBInit, { 203 | apply: (target, thisArg, args) => { 204 | args[0]["token"] = "${token}"; 205 | 206 | ${reservedHBInitCustomSettingsScript} 207 | 208 | document.title = ${name}; 209 | 210 | return target(...args); 211 | } 212 | }); 213 | 214 | window["CustomSettings"] = ${JSON.stringify(customSettingsScript)}; 215 | `; 216 | 217 | await page.addScriptTag({ content: scripts }); 218 | await page.addScriptTag({ content: script }); 219 | 220 | await page.waitForSelector("iframe"); 221 | 222 | const elementHandle = await page.$(selectorFrame); 223 | const frame = await elementHandle!.contentFrame(); 224 | 225 | await frame!.waitForSelector(selectorRoomLink); 226 | 227 | const roomLinkElement = await frame!.$(selectorRoomLink); 228 | const link = await frame!.evaluate(el => el.textContent, roomLinkElement) 229 | 230 | return link; 231 | } 232 | 233 | async open(script: string, tokens: string | string[], name?: string, settings?: CustomSettings) { 234 | const browser = await this.createNewBrowser(); 235 | const pid = browser?.process()?.pid; 236 | const [ page ] = await browser.pages(); 237 | 238 | tokens = typeof tokens === "string" ? [tokens] : tokens; 239 | 240 | try { 241 | const link = await this.openRoom(page, script, tokens, name, settings); 242 | 243 | return { link, pid, remotePort: browser["remotePort"] }; 244 | } catch (e) { 245 | this.close(pid as number); 246 | 247 | throw e; 248 | } 249 | } 250 | 251 | async close(pidOrTitle: string | number) { 252 | let success = false; 253 | let pOT: number | string | undefined = pidOrTitle; 254 | 255 | for (const browser of this.browsers) { 256 | const title = await (await browser.pages())[0].title(); 257 | 258 | if (title == pOT) pOT = browser?.process()?.pid; 259 | } 260 | 261 | this.browsers = this.browsers.filter(b => { 262 | const pid = b?.process()?.pid; 263 | 264 | if (pid == pOT) { 265 | b.close().then(() => { 266 | if (!this.disableRemote) this.debuggingServer?.removeRoom(b["remotePort"]); 267 | }); 268 | 269 | success = true; 270 | } 271 | 272 | return pid != pOT; 273 | }); 274 | 275 | return success; 276 | } 277 | } -------------------------------------------------------------------------------- /src/commands/connect.ts: -------------------------------------------------------------------------------- 1 | import tunnel from 'tunnel-ssh'; 2 | import open from "open"; 3 | import { Config } from 'tunnel-ssh'; 4 | 5 | import { ConnectInterface } from "../debugging/DebuggingInterface"; 6 | import { DebuggingClient } from "../debugging/DebuggingClient" 7 | 8 | import * as Global from "../Global"; 9 | 10 | type Tunnel = { port: number, server: any }; 11 | 12 | function openTunnel(config: tunnel.Config) { 13 | return tunnel({ ...config, readyTimeout: Global.maxTimeSSHConnection }); 14 | } 15 | 16 | export async function connect(connectConfig: Config) { 17 | let tunnels: Tunnel[] = []; 18 | 19 | console.log("Establishing remote connection..."); 20 | 21 | openTunnel({ ...connectConfig, dstPort: Global.serverPort, localPort: Global.clientPort }) 22 | .on("error", (err) => { 23 | console.error("Error: " + err.message) 24 | console.error("A Haxball Server connection could not be opened."); 25 | process.exit(); 26 | }); 27 | 28 | const client = new DebuggingClient(); 29 | 30 | client.on("set", async (rooms) => { 31 | for (const room of rooms) { 32 | const tunnelSrv = openTunnel({ ...connectConfig, dstPort: room.server, localPort: room.client }) 33 | 34 | tunnels.push({ port: room.client, server: tunnelSrv }); 35 | } 36 | 37 | const url = new ConnectInterface().listen(Global.expressPort, Global.wsPort, client); 38 | 39 | await open(url); 40 | }); 41 | 42 | client.on("add", async (server, client) => { 43 | const tunnelSrv = openTunnel({ ...connectConfig, dstPort: server, localPort: client }) 44 | .on("error", (err) => { 45 | console.error("Error: " + err.message) 46 | console.error("Failed to connect to room."); 47 | }); 48 | 49 | tunnels.push({ port: client, server: tunnelSrv }); 50 | }); 51 | 52 | client.on("remove", async (server, client) => { 53 | const tunnel = tunnels.find(t => t.port === client); 54 | 55 | if (tunnel) { 56 | tunnel.server?.close(); 57 | tunnels = tunnels.filter(t => t.port !== tunnel.port); 58 | } 59 | }); 60 | 61 | client.listen(Global.clientPort); 62 | } -------------------------------------------------------------------------------- /src/commands/openServer.ts: -------------------------------------------------------------------------------- 1 | import { Server } from "../Server"; 2 | import { ControlPanel } from "../ControlPanel"; 3 | 4 | import { loadConfig } from "../utils/loadConfig"; 5 | 6 | export function openServer(file?: string) { 7 | loadConfig(file).then(config => { 8 | const server = new Server(config.server); 9 | new ControlPanel(server, config.panel, file); 10 | }).catch(err => { 11 | console.error(err.error ? err.message + ", " + err.error : err.message); 12 | process.exit(); 13 | }); 14 | } -------------------------------------------------------------------------------- /src/debugging/DebuggingClient.ts: -------------------------------------------------------------------------------- 1 | import net from "net"; 2 | import EventEmitter from "events"; 3 | 4 | import { RoomDebuggingMessageType } from "./DebuggingServer" 5 | import { getAvailablePort } from "../utils/getAvailablePort"; 6 | 7 | import * as Global from "../Global"; 8 | 9 | export class DebuggingClient extends EventEmitter { 10 | private roomsConn: { server: number, client: number }[] = []; 11 | 12 | client?: net.Socket; 13 | 14 | get rooms() { 15 | return this.roomsConn.map(r => r.client); 16 | } 17 | 18 | get size() { 19 | return this.rooms.length; 20 | } 21 | 22 | constructor() { 23 | super(); 24 | } 25 | 26 | listen(port: number) { 27 | this.client = new net.Socket(); 28 | 29 | this.client.connect(port, 'localhost', () => { 30 | console.log("Listening to client tunnel"); 31 | }); 32 | 33 | this.client.on('data', async (data) => { 34 | try { 35 | const str = data.toString("utf-8"); 36 | const json = JSON.parse(str); 37 | 38 | if (json.type === RoomDebuggingMessageType.UpdateRooms) { 39 | const r = []; 40 | 41 | let prevPort = Global.clientRoomFirstPort; 42 | 43 | for (const serverPort of json.message) { 44 | const availablePort = await getAvailablePort(prevPort); 45 | 46 | r.push({ server: serverPort, client: availablePort }); 47 | 48 | prevPort = availablePort + 1; 49 | } 50 | 51 | this.roomsConn = r; 52 | 53 | this.emit("set", r); 54 | } 55 | 56 | if (json.type === RoomDebuggingMessageType.AddRoom) { 57 | const port = await getAvailablePort(Global.clientRoomFirstPort); 58 | 59 | this.roomsConn.push({ server: json.message, client: port }); 60 | this.emit("add", json.message, port); 61 | } 62 | 63 | if (json.type === RoomDebuggingMessageType.RemoveRoom) { 64 | const port = this.roomsConn.find(r => r.server === json.message)?.client; 65 | 66 | this.roomsConn = this.roomsConn.filter(r => r.server !== json.message); 67 | this.emit("remove", json.message, port); 68 | } 69 | } catch (err) { 70 | console.error(err); 71 | } 72 | }); 73 | 74 | this.client.on('close', () => { 75 | console.log('Connection to debugging server closed'); 76 | }); 77 | 78 | this.client.on("error", (err) => { 79 | console.error(err); 80 | 81 | this.client?.destroy(); 82 | }); 83 | 84 | this.client.on("timeout", () => { 85 | console.log('Connection to debugging server timed out'); 86 | 87 | this.client?.destroy(); 88 | }); 89 | } 90 | } -------------------------------------------------------------------------------- /src/debugging/DebuggingInterface.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import http from "http"; 3 | import * as WebSocket from "ws"; 4 | 5 | import { DebuggingClient } from "./DebuggingClient"; 6 | 7 | import * as Global from "../Global"; 8 | 9 | const html = ` 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |

Room list

20 | 42 | 43 | 44 | `; 45 | 46 | export class ConnectInterface { 47 | listen(expressPort: number, wsPort: number, debuggingClient: DebuggingClient) { 48 | this.openWSServer(wsPort, debuggingClient); 49 | 50 | const url = this.openExpressServer(expressPort); 51 | 52 | return url; 53 | } 54 | 55 | private openExpressServer(port: number) { 56 | const app = express(); 57 | 58 | const url = `http://localhost:${port}`; 59 | 60 | app.get('/', (req, res) => { 61 | res.send(html); 62 | }); 63 | 64 | app.listen(port, "localhost", () => { 65 | console.log(`Listening web server at ${url}`) 66 | }); 67 | 68 | return url; 69 | } 70 | 71 | private openWSServer(port: number, client: DebuggingClient) { 72 | const wss = new WebSocket.Server({ port }); 73 | 74 | wss.on('connection', (ws) => { 75 | client.on("add", async (server, client) => { 76 | setTimeout(() => this.addRoomToList(client, ws), 2000); 77 | }); 78 | 79 | client.on("remove", async (server, client) => { 80 | this.removeRoomFromList(client, ws); 81 | }); 82 | 83 | for (const room of client.rooms) { 84 | this.addRoomToList(room, ws); 85 | } 86 | }); 87 | } 88 | 89 | private addRoomToList(room: number, ws: any, iteration: number = 0) { 90 | const url = `http://localhost:${room}`; 91 | 92 | if (iteration >= 3) return; 93 | 94 | http.get(`${url}/json`, (res) => { 95 | let body = ""; 96 | 97 | res.on("data", (chunk) => body += chunk); 98 | res.on("end", () => { 99 | const data = JSON.parse(body)[0]; 100 | 101 | ws.send(JSON.stringify({ 102 | type: "add", 103 | message: { 104 | port: room, 105 | source: url, 106 | url: data.devtoolsFrontendUrl, 107 | title: data.title 108 | } 109 | })); 110 | }); 111 | }).on("error", (error) => { 112 | console.error(error); 113 | setTimeout(() => this.addRoomToList(room, ws, iteration + 1), 2000); 114 | }); 115 | } 116 | 117 | private removeRoomFromList(room: number, ws: any) { 118 | try { 119 | ws.send(JSON.stringify({ 120 | type: "remove", 121 | message: { 122 | port: room 123 | } 124 | })); 125 | } catch (err) { 126 | console.error(err); 127 | } 128 | } 129 | } -------------------------------------------------------------------------------- /src/debugging/DebuggingServer.ts: -------------------------------------------------------------------------------- 1 | import net from "net"; 2 | 3 | import { log } from "../utils/log"; 4 | 5 | export enum RoomDebuggingMessageType { 6 | UpdateRooms, 7 | AddRoom, 8 | RemoveRoom 9 | } 10 | 11 | export class DebuggingServer { 12 | server: net.Server; 13 | sockets: net.Socket[] = []; 14 | 15 | roomServers: number[] = []; 16 | 17 | constructor() { 18 | this.server = net.createServer(); 19 | } 20 | 21 | private message(socket: net.Socket, type: RoomDebuggingMessageType, message: any) { 22 | const msg = JSON.stringify({ type, message }); 23 | 24 | socket.write(msg); 25 | } 26 | 27 | private broadcast(type: RoomDebuggingMessageType, message: any) { 28 | this.sockets.forEach(s => { 29 | this.message(s, type, message); 30 | }); 31 | } 32 | 33 | listen(port: number) { 34 | this.server.on("listening", () => { 35 | log("REMOTE DEBUGGING", `Listening to remote connections on port ${port}`); 36 | }) 37 | 38 | this.server.on("connection", (socket) => { 39 | socket.setEncoding('utf8'); 40 | 41 | this.sockets.push(socket); 42 | 43 | this.message(socket, RoomDebuggingMessageType.UpdateRooms, this.roomServers); 44 | }); 45 | 46 | this.server.on("error", (err) => { 47 | if (err.message.includes("EADDRINUSE")) { 48 | log("FATAL ERROR", `Remote debugging port ${port} is already in use. Make sure you are not running another instance of Haxball Server in the background.`); 49 | process.exit(); 50 | } else { 51 | throw err; 52 | } 53 | }); 54 | 55 | this.server.listen(port, "localhost"); 56 | } 57 | 58 | setRooms(rooms: number[]) { 59 | this.roomServers = rooms; 60 | this.broadcast(RoomDebuggingMessageType.UpdateRooms, rooms); 61 | 62 | log("UPDATE ROOM PORTS", rooms.join(", ")); 63 | } 64 | 65 | addRoom(room: number) { 66 | this.roomServers.push(room); 67 | this.broadcast(RoomDebuggingMessageType.AddRoom, room); 68 | 69 | log("ADD ROOM PORT", room + ""); 70 | } 71 | 72 | removeRoom(room: number) { 73 | if (!this.roomServers.includes(room)) return; 74 | 75 | this.roomServers = this.roomServers.filter(r => r !== room); 76 | this.broadcast(RoomDebuggingMessageType.RemoveRoom, room); 77 | 78 | log("DELETE ROOM PORT", room + ""); 79 | } 80 | } -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import yargs from "yargs"; 4 | import fs from "fs"; 5 | 6 | import { connect } from "./commands/connect"; 7 | import { openServer } from "./commands/openServer"; 8 | 9 | import { Config } from 'tunnel-ssh'; 10 | 11 | const args = yargs(process.argv.slice(2)); 12 | 13 | args.command({ 14 | command: "open", 15 | aliases: ["o", "r", "run", "server"], 16 | describe: "Opens a Haxball server.", 17 | builder: { 18 | file: { 19 | describe: "The config.json file.", 20 | demandOption: false, 21 | type: "string", 22 | 23 | } 24 | }, 25 | handler: (argv) => { 26 | openServer(argv.file as string); 27 | } 28 | }); 29 | 30 | args.command({ 31 | command: "connect", 32 | aliases: ["c"], 33 | describe: "Forwards a remote debugging SSH tunnel.", 34 | builder: { 35 | host: { 36 | describe: "The host name or IP address.", 37 | demandOption: true, 38 | type: "string" 39 | }, 40 | user: { 41 | describe: "The user name.", 42 | demandOption: true, 43 | type: "string" 44 | }, 45 | password: { 46 | describe: "Password for password-based authentication.", 47 | demandOption: false, 48 | type: "string", 49 | conflicts: "privateKey" 50 | }, 51 | privateKey: { 52 | describe: "Private key for key-based authentication.", 53 | demandOption: false, 54 | type: "string", 55 | conflicts: "password" 56 | } 57 | }, 58 | handler: async (argv: any) => { 59 | const params: Config = { 60 | username: argv.user, 61 | host: argv.host, 62 | keepAlive: true 63 | } 64 | 65 | if (argv.password != null) await connect({ ...params, password: argv.password }); 66 | 67 | if (argv.privateKey != null) { 68 | fs.readFile(argv.privateKey, { encoding: "utf-8" }, async (err, data) => { 69 | if (err) return console.error(`${err.message}\nError while loading private key file`); 70 | 71 | await connect({ ...params, privateKey: Buffer.from(data) }); 72 | }); 73 | } 74 | } 75 | }); 76 | 77 | args.demandCommand() 78 | args.parse(); -------------------------------------------------------------------------------- /src/utils/escapeString.ts: -------------------------------------------------------------------------------- 1 | export function escapeString(str: any) { 2 | if (typeof str !== "string") return str; 3 | 4 | return str.replace(/"/g, '\\"'); 5 | } -------------------------------------------------------------------------------- /src/utils/getAvailablePort.ts: -------------------------------------------------------------------------------- 1 | import * as PortScanner from "portscanner"; 2 | 3 | export async function getAvailablePort(startingPort: number) { 4 | let port = startingPort; 5 | 6 | while(true) { 7 | const taken = await PortScanner.checkPortStatus(port); 8 | 9 | if (taken === "closed") break; 10 | 11 | port++; 12 | } 13 | 14 | return port; 15 | } -------------------------------------------------------------------------------- /src/utils/loadConfig.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | 4 | import { HaxballServerConfig } from "../Global"; 5 | 6 | function validate(object: any): object is HaxballServerConfig { 7 | if (!(object as HaxballServerConfig).server) return false; 8 | if (!(object as HaxballServerConfig).panel) return false; 9 | 10 | return true; 11 | } 12 | 13 | export function loadConfig(file?: string): Promise { 14 | return new Promise((resolve, reject) => { 15 | const filePath = file == null || file == "" ? path.resolve(path.resolve('.'), "config.json") : file; 16 | 17 | fs.readFile(filePath, { encoding: "utf-8", flag: "r" }, (err, data) => { 18 | if (err) reject({ 19 | message: `Error while loading config file`, 20 | error: err 21 | }); 22 | 23 | try { 24 | const json = JSON.parse(data); 25 | 26 | if (!validate(json)) reject({ 27 | message: `Invalid configuration`, 28 | error: null 29 | }); 30 | 31 | resolve(JSON.parse(data)); 32 | } catch (err) { 33 | reject({ 34 | message: `Error while parsing config file`, 35 | error: err 36 | }); 37 | } 38 | }); 39 | }); 40 | } -------------------------------------------------------------------------------- /src/utils/log.ts: -------------------------------------------------------------------------------- 1 | import { maxLengthLog } from "../Global"; 2 | 3 | export function log(prefix: string, message: string) { 4 | const timestamp = new Date().toLocaleTimeString("pt-BR"); 5 | 6 | if (message.length > maxLengthLog) { 7 | message = message.slice(0, maxLengthLog) + "..."; 8 | } 9 | 10 | console.log(`[${timestamp}] [${prefix}] ${message}`); 11 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "target": "ES2017", 5 | "lib": ["dom", "esnext"], 6 | "types": ["node"], 7 | "module": "commonjs", 8 | "esModuleInterop": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "node", 11 | "noUnusedLocals": true, 12 | "sourceMap": true, 13 | "declaration": true, 14 | "declarationDir": "dist", 15 | "outDir": "dist", 16 | "suppressImplicitAnyIndexErrors": true, 17 | "experimentalDecorators": true, 18 | "resolveJsonModule": true 19 | }, 20 | "files": [ 21 | "src/main.ts" 22 | ], 23 | "include": [ 24 | "public" 25 | ] 26 | } --------------------------------------------------------------------------------