diff --git a/package-lock.json b/package-lock.json index ca339e31..1708e273 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@types/express": "^5", "@types/morgan": "^1.9.9", + "@types/ws": "^8.18.1", "crc-32": "^1.2.2", "express": "^5", "json-with-bigint": "^3.4.4", @@ -21,7 +22,8 @@ "warframe-public-export-plus": "^0.5.68", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", - "winston-daily-rotate-file": "^5.0.0" + "winston-daily-rotate-file": "^5.0.0", + "ws": "^8.18.2" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^8.28.0", @@ -472,6 +474,15 @@ "@types/webidl-conversions": "*" } }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.32.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.0.tgz", @@ -3931,6 +3942,27 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index b8d00e39..c5a98e44 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "dependencies": { "@types/express": "^5", "@types/morgan": "^1.9.9", + "@types/ws": "^8.18.1", "crc-32": "^1.2.2", "express": "^5", "json-with-bigint": "^3.4.4", @@ -28,7 +29,8 @@ "warframe-public-export-plus": "^0.5.68", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", - "winston-daily-rotate-file": "^5.0.0" + "winston-daily-rotate-file": "^5.0.0", + "ws": "^8.18.2" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^8.28.0", diff --git a/src/services/configWatcherService.ts b/src/services/configWatcherService.ts index 197a9567..b6f8e83c 100644 --- a/src/services/configWatcherService.ts +++ b/src/services/configWatcherService.ts @@ -2,7 +2,7 @@ import fs from "fs"; import fsPromises from "fs/promises"; import { logger } from "../utils/logger"; import { config, configPath, loadConfig } from "./configService"; -import { getWebPorts, startWebServer, stopWebServer } from "./webService"; +import { getWebPorts, sendWsBroadcast, startWebServer, stopWebServer } from "./webService"; let amnesia = false; fs.watchFile(configPath, () => { @@ -21,7 +21,13 @@ fs.watchFile(configPath, () => { const webPorts = getWebPorts(); if (config.httpPort != webPorts.http || config.httpsPort != webPorts.https) { logger.info(`Restarting web server to apply port changes.`); + + // Tell webui clients to reload with new port + sendWsBroadcast({ ports: { http: config.httpPort, https: config.httpsPort } }); + void stopWebServer().then(startWebServer); + } else { + sendWsBroadcast({ config_reloaded: true }); } } }); diff --git a/src/services/webService.ts b/src/services/webService.ts index 77fe01fe..d4d1227c 100644 --- a/src/services/webService.ts +++ b/src/services/webService.ts @@ -5,9 +5,12 @@ import { config } from "./configService"; import { logger } from "../utils/logger"; import { app } from "../app"; import { AddressInfo } from "node:net"; +import ws from "ws"; let httpServer: http.Server | undefined; let httpsServer: https.Server | undefined; +let wsServer: ws.Server | undefined; +let wssServer: ws.Server | undefined; const tlsOptions = { key: fs.readFileSync("static/certs/key.pem"), @@ -21,10 +24,17 @@ export const startWebServer = (): void => { // eslint-disable-next-line @typescript-eslint/no-misused-promises httpServer = http.createServer(app); httpServer.listen(httpPort, () => { + wsServer = new ws.Server({ server: httpServer }); + //wsServer.on("connection", wsOnConnect); + logger.info("HTTP server started on port " + httpPort); + // eslint-disable-next-line @typescript-eslint/no-misused-promises httpsServer = https.createServer(tlsOptions, app); httpsServer.listen(httpsPort, () => { + wssServer = new ws.Server({ server: httpsServer }); + //wssServer.on("connection", wsOnConnect); + logger.info("HTTPS server started on port " + httpsPort); logger.info( @@ -61,5 +71,41 @@ export const stopWebServer = async (): Promise => { }) ); } + if (wsServer) { + promises.push( + new Promise(resolve => { + wsServer!.close(() => { + resolve(); + }); + }) + ); + } + if (wssServer) { + promises.push( + new Promise(resolve => { + wssServer!.close(() => { + resolve(); + }); + }) + ); + } await Promise.all(promises); }; + +/*const wsOnConnect = (ws: ws, _req: http.IncomingMessage): void => { + ws.on("message", console.log); +};*/ + +export const sendWsBroadcast = (data: T): void => { + const msg = JSON.stringify(data); + if (wsServer) { + for (const client of wsServer.clients) { + client.send(msg); + } + } + if (wssServer) { + for (const client of wssServer.clients) { + client.send(msg); + } + } +}; diff --git a/static/webui/script.js b/static/webui/script.js index f4c43175..dfe4e096 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -1,3 +1,23 @@ +function openWebSocket() { + window.ws = new WebSocket("/custom/ws"); + window.ws.onmessage = e => { + const msg = JSON.parse(e.data); + if ("ports" in msg) { + location.port = location.protocol == "https:" ? msg.ports.https : msg.ports.http; + } + if ("config_reloaded" in msg) { + //window.is_admin = undefined; + if (single.getCurrentPath() == "/webui/cheats") { + single.loadRoute("/webui/cheats"); + } + } + }; + window.ws.onclose = function () { + setTimeout(openWebSocket, 3000); + }; +} +openWebSocket(); + let loginOrRegisterPending = false; window.registerSubmit = false; @@ -1822,6 +1842,7 @@ single.getRoute("/webui/cheats").on("beforeload", function () { clearInterval(interval); fetch("/custom/config?" + window.authz).then(async res => { if (res.status == 200) { + //window.is_admin = true; $("#server-settings-no-perms").addClass("d-none"); $("#server-settings").removeClass("d-none"); res.json().then(json => @@ -1830,9 +1851,7 @@ single.getRoute("/webui/cheats").on("beforeload", function () { var x = document.getElementById(`${key}`); if (x != null) { if (x.type == "checkbox") { - if (value === true) { - x.setAttribute("checked", "checked"); - } + x.checked = value; } else if (x.type == "number") { x.setAttribute("value", `${value}`); } @@ -1847,6 +1866,7 @@ single.getRoute("/webui/cheats").on("beforeload", function () { } }); } else { + //window.is_admin = false; $("#server-settings-no-perms").removeClass("d-none"); $("#server-settings").addClass("d-none"); }