├── 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 |
7 |
8 |
9 |
10 |
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 | }
--------------------------------------------------------------------------------