chore(webui): keep config in sync with multiple tabs (#2325)

Adding "wsid" to uniquely identify a given tab (by the websocket connection) so we can avoid needless refreshing on the same tab.

Closes #2316

Reviewed-on: OpenWF/SpaceNinjaServer#2325
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-27 08:21:05 -07:00 committed by Sainan
parent 4895b4630b
commit abb5b8880f
3 changed files with 36 additions and 4 deletions

View File

@ -2,6 +2,7 @@ import { RequestHandler } from "express";
import { config } from "@/src/services/configService"; import { config } from "@/src/services/configService";
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService"; import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
import { saveConfig } from "@/src/services/configWatcherService"; import { saveConfig } from "@/src/services/configWatcherService";
import { sendWsBroadcastExcept } from "@/src/services/webService";
export const getConfigController: RequestHandler = async (req, res) => { export const getConfigController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req); const account = await getAccountForRequest(req);
@ -24,6 +25,7 @@ export const setConfigController: RequestHandler = async (req, res) => {
const [obj, idx] = configIdToIndexable(id); const [obj, idx] = configIdToIndexable(id);
obj[idx] = value; obj[idx] = value;
} }
sendWsBroadcastExcept(parseInt(String(req.query.wsid)), { config_reloaded: true });
await saveConfig(); await saveConfig();
res.end(); res.end();
} else { } else {

View File

@ -136,7 +136,10 @@ export const stopWebServer = async (): Promise<void> => {
await Promise.all(promises); await Promise.all(promises);
}; };
let lastWsid: number = 0;
interface IWsCustomData extends ws { interface IWsCustomData extends ws {
id?: number;
accountId?: string; accountId?: string;
} }
@ -150,6 +153,7 @@ interface IWsMsgFromClient {
} }
interface IWsMsgToClient { interface IWsMsgToClient {
//wsid?: number;
reload?: boolean; reload?: boolean;
ports?: { ports?: {
http: number | undefined; http: number | undefined;
@ -174,6 +178,10 @@ const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => {
ws.close(); ws.close();
return; return;
} }
(ws as IWsCustomData).id = ++lastWsid;
ws.send(JSON.stringify({ wsid: lastWsid }));
// eslint-disable-next-line @typescript-eslint/no-misused-promises // eslint-disable-next-line @typescript-eslint/no-misused-promises
ws.on("message", async msg => { ws.on("message", async msg => {
const data = JSON.parse(String(msg)) as IWsMsgFromClient; const data = JSON.parse(String(msg)) as IWsMsgFromClient;
@ -268,3 +276,21 @@ export const sendWsBroadcastTo = (accountId: string, data: IWsMsgToClient): void
} }
} }
}; };
export const sendWsBroadcastExcept = (wsid: number | undefined, data: IWsMsgToClient): void => {
const msg = JSON.stringify(data);
if (wsServer) {
for (const client of wsServer.clients) {
if ((client as IWsCustomData).id != wsid) {
client.send(msg);
}
}
}
if (wssServer) {
for (const client of wssServer.clients) {
if ((client as IWsCustomData).id != wsid) {
client.send(msg);
}
}
}
};

View File

@ -10,7 +10,8 @@
let auth_pending = false, let auth_pending = false,
did_initial_auth = false, did_initial_auth = false,
ws_is_open = false; ws_is_open = false,
wsid = 0;
const sendAuth = isRegister => { const sendAuth = isRegister => {
if (ws_is_open && localStorage.getItem("email") && localStorage.getItem("password")) { if (ws_is_open && localStorage.getItem("email") && localStorage.getItem("password")) {
auth_pending = true; auth_pending = true;
@ -34,6 +35,9 @@ function openWebSocket() {
}; };
window.ws.onmessage = e => { window.ws.onmessage = e => {
const msg = JSON.parse(e.data); const msg = JSON.parse(e.data);
if ("wsid" in msg) {
wsid = msg.wsid;
}
if ("reload" in msg) { if ("reload" in msg) {
setTimeout(() => { setTimeout(() => {
getWebSocket().then(() => { getWebSocket().then(() => {
@ -1858,7 +1862,7 @@ for (const id of uiConfigs) {
value = parseInt(value); value = parseInt(value);
} }
$.post({ $.post({
url: "/custom/setConfig?" + window.authz, url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid,
contentType: "application/json", contentType: "application/json",
data: JSON.stringify({ [id]: value }) data: JSON.stringify({ [id]: value })
}); });
@ -1866,7 +1870,7 @@ for (const id of uiConfigs) {
} else if (elm.type == "checkbox") { } else if (elm.type == "checkbox") {
elm.onchange = function () { elm.onchange = function () {
$.post({ $.post({
url: "/custom/setConfig?" + window.authz, url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid,
contentType: "application/json", contentType: "application/json",
data: JSON.stringify({ [id]: this.checked }) data: JSON.stringify({ [id]: this.checked })
}).then(() => { }).then(() => {
@ -1881,7 +1885,7 @@ for (const id of uiConfigs) {
function doSaveConfig(id) { function doSaveConfig(id) {
const elm = document.getElementById(id); const elm = document.getElementById(id);
$.post({ $.post({
url: "/custom/setConfig?" + window.authz, url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid,
contentType: "application/json", contentType: "application/json",
data: JSON.stringify({ [id]: parseInt(elm.value) }) data: JSON.stringify({ [id]: parseInt(elm.value) })
}); });