feat(webui): initial websocket integration to be more responsive #2221

Merged
Sainan merged 4 commits from ws into main 2025-06-20 14:00:57 -07:00
5 changed files with 107 additions and 3 deletions
Showing only changes of commit 626920cdb3 - Show all commits

34
package-lock.json generated
View File

@ -11,6 +11,7 @@
"dependencies": { "dependencies": {
"@types/express": "^5", "@types/express": "^5",
"@types/morgan": "^1.9.9", "@types/morgan": "^1.9.9",
"@types/ws": "^8.18.1",
"crc-32": "^1.2.2", "crc-32": "^1.2.2",
"express": "^5", "express": "^5",
"json-with-bigint": "^3.4.4", "json-with-bigint": "^3.4.4",
@ -21,7 +22,8 @@
"warframe-public-export-plus": "^0.5.68", "warframe-public-export-plus": "^0.5.68",
"warframe-riven-info": "^0.1.2", "warframe-riven-info": "^0.1.2",
"winston": "^3.17.0", "winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0" "winston-daily-rotate-file": "^5.0.0",
"ws": "^8.18.2"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^8.28.0", "@typescript-eslint/eslint-plugin": "^8.28.0",
@ -472,6 +474,15 @@
"@types/webidl-conversions": "*" "@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": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.32.0", "version": "8.32.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.0.tgz", "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==", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC" "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": { "node_modules/xtend": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",

View File

@ -18,6 +18,7 @@
"dependencies": { "dependencies": {
"@types/express": "^5", "@types/express": "^5",
"@types/morgan": "^1.9.9", "@types/morgan": "^1.9.9",
"@types/ws": "^8.18.1",
"crc-32": "^1.2.2", "crc-32": "^1.2.2",
"express": "^5", "express": "^5",
"json-with-bigint": "^3.4.4", "json-with-bigint": "^3.4.4",
@ -28,7 +29,8 @@
"warframe-public-export-plus": "^0.5.68", "warframe-public-export-plus": "^0.5.68",
"warframe-riven-info": "^0.1.2", "warframe-riven-info": "^0.1.2",
"winston": "^3.17.0", "winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0" "winston-daily-rotate-file": "^5.0.0",
"ws": "^8.18.2"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^8.28.0", "@typescript-eslint/eslint-plugin": "^8.28.0",

View File

@ -2,7 +2,7 @@ import fs from "fs";
import fsPromises from "fs/promises"; import fsPromises from "fs/promises";
import { logger } from "../utils/logger"; import { logger } from "../utils/logger";
import { config, configPath, loadConfig } from "./configService"; import { config, configPath, loadConfig } from "./configService";
import { getWebPorts, startWebServer, stopWebServer } from "./webService"; import { getWebPorts, sendWsBroadcast, startWebServer, stopWebServer } from "./webService";
let amnesia = false; let amnesia = false;
fs.watchFile(configPath, () => { fs.watchFile(configPath, () => {
@ -21,7 +21,13 @@ fs.watchFile(configPath, () => {
const webPorts = getWebPorts(); const webPorts = getWebPorts();
if (config.httpPort != webPorts.http || config.httpsPort != webPorts.https) { if (config.httpPort != webPorts.http || config.httpsPort != webPorts.https) {
logger.info(`Restarting web server to apply port changes.`); 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); 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 { logger } from "../utils/logger";
import { app } from "../app"; import { app } from "../app";
import { AddressInfo } from "node:net"; import { AddressInfo } from "node:net";
import ws from "ws";
let httpServer: http.Server | undefined; let httpServer: http.Server | undefined;
let httpsServer: https.Server | undefined; let httpsServer: https.Server | undefined;
let wsServer: ws.Server | undefined;
let wssServer: ws.Server | undefined;
const tlsOptions = { const tlsOptions = {
key: fs.readFileSync("static/certs/key.pem"), key: fs.readFileSync("static/certs/key.pem"),
@ -21,10 +24,17 @@ export const startWebServer = (): void => {
// eslint-disable-next-line @typescript-eslint/no-misused-promises // eslint-disable-next-line @typescript-eslint/no-misused-promises
httpServer = http.createServer(app); httpServer = http.createServer(app);
httpServer.listen(httpPort, () => { httpServer.listen(httpPort, () => {
wsServer = new ws.Server({ server: httpServer });
//wsServer.on("connection", wsOnConnect);
logger.info("HTTP server started on port " + httpPort); logger.info("HTTP server started on port " + httpPort);
// eslint-disable-next-line @typescript-eslint/no-misused-promises // eslint-disable-next-line @typescript-eslint/no-misused-promises
httpsServer = https.createServer(tlsOptions, app); httpsServer = https.createServer(tlsOptions, app);
httpsServer.listen(httpsPort, () => { httpsServer.listen(httpsPort, () => {
wssServer = new ws.Server({ server: httpsServer });
//wssServer.on("connection", wsOnConnect);
logger.info("HTTPS server started on port " + httpsPort); logger.info("HTTPS server started on port " + httpsPort);
logger.info( 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); 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,17 @@
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");
}
}
};
let loginOrRegisterPending = false; let loginOrRegisterPending = false;
window.registerSubmit = false; window.registerSubmit = false;
@ -1812,6 +1826,7 @@ single.getRoute("/webui/cheats").on("beforeload", function () {
clearInterval(interval); clearInterval(interval);
fetch("/custom/config?" + window.authz).then(async res => { fetch("/custom/config?" + window.authz).then(async res => {
if (res.status == 200) { if (res.status == 200) {
//window.is_admin = true;
$("#server-settings-no-perms").addClass("d-none"); $("#server-settings-no-perms").addClass("d-none");
$("#server-settings").removeClass("d-none"); $("#server-settings").removeClass("d-none");
res.json().then(json => res.json().then(json =>
@ -1822,6 +1837,8 @@ single.getRoute("/webui/cheats").on("beforeload", function () {
if (x.type == "checkbox") { if (x.type == "checkbox") {
if (value === true) { if (value === true) {
x.setAttribute("checked", "checked"); x.setAttribute("checked", "checked");
} else {
x.removeAttribute("checked");
} }
} else if (x.type == "number") { } else if (x.type == "number") {
x.setAttribute("value", `${value}`); x.setAttribute("value", `${value}`);
@ -1837,6 +1854,7 @@ single.getRoute("/webui/cheats").on("beforeload", function () {
} }
}); });
} else { } else {
//window.is_admin = false;
$("#server-settings-no-perms").removeClass("d-none"); $("#server-settings-no-perms").removeClass("d-none");
$("#server-settings").addClass("d-none"); $("#server-settings").addClass("d-none");
} }