feat(webui): initial websocket integration to be more responsive (#2221)
All checks were successful
Build / build (push) Successful in 1m22s
Build Docker image / docker-arm64 (push) Successful in 1m1s
Build Docker image / docker-amd64 (push) Successful in 54s

For now just handles changes to config.json but in the future might keep the inventory tabs up-to-date with in-game actions.

Reviewed-on: #2221
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
This commit is contained in:
Sainan 2025-06-20 14:00:55 -07:00 committed by Sainan
parent eadc9c4ecb
commit 4cb0f8b167
5 changed files with 112 additions and 6 deletions

34
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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 });
}
}
});

View File

@ -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<void> => {
})
);
}
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 = <T>(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);
}
}
};

View File

@ -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");
}