From a9d96d67256f0810daa8c68c1b4553f62b944244 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 1 Sep 2025 03:52:36 +0200 Subject: [PATCH 01/19] feat: support websocket connections from game client --- src/controllers/api/sellController.ts | 5 ++- src/controllers/custom/addItemsController.ts | 2 + src/controllers/custom/addXpController.ts | 2 + src/services/wsService.ts | 40 +++++++++++++++++++- 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/controllers/api/sellController.ts b/src/controllers/api/sellController.ts index dd160c5a..c6f4662b 100644 --- a/src/controllers/api/sellController.ts +++ b/src/controllers/api/sellController.ts @@ -17,7 +17,7 @@ import { InventorySlot } from "../../types/inventoryTypes/inventoryTypes.ts"; import { ExportDojoRecipes } from "warframe-public-export-plus"; import type { IInventoryChanges } from "../../types/purchaseTypes.ts"; import type { TInventoryDatabaseDocument } from "../../models/inventoryModels/inventoryModel.ts"; -import { sendWsBroadcastEx } from "../../services/wsService.ts"; +import { sendWsBroadcastEx, sendWsBroadcastTo } from "../../services/wsService.ts"; import { parseFusionTreasure } from "../../helpers/inventoryHelpers.ts"; export const sellController: RequestHandler = async (req, res) => { @@ -308,6 +308,9 @@ export const sellController: RequestHandler = async (req, res) => { inventoryChanges: inventoryChanges // "inventoryChanges" for this response instead of the usual "InventoryChanges" }); sendWsBroadcastEx({ update_inventory: true }, accountId, parseInt(String(req.query.wsid))); + if (req.query.wsid) { + sendWsBroadcastTo(accountId, { sync_inventory: true }); + } }; interface ISellRequest { diff --git a/src/controllers/custom/addItemsController.ts b/src/controllers/custom/addItemsController.ts index 1dc76718..203e648c 100644 --- a/src/controllers/custom/addItemsController.ts +++ b/src/controllers/custom/addItemsController.ts @@ -1,6 +1,7 @@ import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getInventory, addItem } from "../../services/inventoryService.ts"; import type { RequestHandler } from "express"; +import { sendWsBroadcastTo } from "../../services/wsService.ts"; export const addItemsController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); @@ -9,6 +10,7 @@ export const addItemsController: RequestHandler = async (req, res) => { for (const request of requests) { await addItem(inventory, request.ItemType, request.ItemCount, true, undefined, request.Fingerprint, true); } + sendWsBroadcastTo(accountId, { sync_inventory: true }); await inventory.save(); res.end(); }; diff --git a/src/controllers/custom/addXpController.ts b/src/controllers/custom/addXpController.ts index 0b469168..6c89bc7c 100644 --- a/src/controllers/custom/addXpController.ts +++ b/src/controllers/custom/addXpController.ts @@ -1,5 +1,6 @@ import { applyClientEquipmentUpdates, getInventory } from "../../services/inventoryService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts"; +import { sendWsBroadcastTo } from "../../services/wsService.ts"; import type { IOid } from "../../types/commonTypes.ts"; import type { IEquipmentClient } from "../../types/equipmentTypes.ts"; import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts"; @@ -23,6 +24,7 @@ export const addXpController: RequestHandler = async (req, res) => { } applyClientEquipmentUpdates(inventory, gear, category as TEquipmentKey); } + sendWsBroadcastTo(accountId, { sync_inventory: true }); await inventory.save(); res.end(); }; diff --git a/src/services/wsService.ts b/src/services/wsService.ts index b80cd81e..c87325a4 100644 --- a/src/services/wsService.ts +++ b/src/services/wsService.ts @@ -47,6 +47,7 @@ let lastWsid: number = 0; interface IWsCustomData extends ws { id: number; accountId?: string; + isGame?: boolean; } interface IWsMsgFromClient { @@ -55,11 +56,18 @@ interface IWsMsgFromClient { password: string; isRegister: boolean; }; + auth_game?: { + accountId: string; + nonce: number; + }; logout?: boolean; } interface IWsMsgToClient { - //wsid?: number; + // common + wsid?: number; + + // to webui reload?: boolean; ports?: { http: number | undefined; @@ -77,6 +85,9 @@ interface IWsMsgToClient { nonce_updated?: boolean; update_inventory?: boolean; logged_out?: boolean; + + // to game + sync_inventory?: boolean; } const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => { @@ -87,11 +98,12 @@ const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => { } (ws as IWsCustomData).id = ++lastWsid; - ws.send(JSON.stringify({ wsid: lastWsid })); + ws.send(JSON.stringify({ wsid: lastWsid } satisfies IWsMsgToClient)); // eslint-disable-next-line @typescript-eslint/no-misused-promises ws.on("message", async msg => { try { + //console.log(String(msg)); const data = JSON.parse(String(msg)) as IWsMsgFromClient; if (data.auth) { let account: IDatabaseAccountJson | null = await Account.findOne({ email: data.auth.email }); @@ -137,6 +149,18 @@ const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => { ); } } + if (data.auth_game) { + (ws as IWsCustomData).isGame = true; + if (data.auth_game.nonce) { + const account: IDatabaseAccountJson | null = await Account.findOne({ + _id: data.auth_game.accountId, + Nonce: data.auth_game.nonce + }); + if (account) { + (ws as IWsCustomData).accountId = account.id; + } + } + } if (data.logout) { const accountId = (ws as IWsCustomData).accountId; (ws as IWsCustomData).accountId = undefined; @@ -154,6 +178,18 @@ const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => { logError(e as Error, `processing websocket message`); } }); + ws.on("close", () => { + if ((ws as IWsCustomData).isGame && (ws as IWsCustomData).accountId) { + void Account.updateOne( + { + _id: (ws as IWsCustomData).accountId + }, + { + Dropped: true + } + ).then(() => {}); + } + }); }; export const sendWsBroadcast = (data: IWsMsgToClient): void => { -- 2.47.2 From 07b9bc94152b665a6d990d77319208e2c460ae38 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 1 Sep 2025 04:03:04 +0200 Subject: [PATCH 02/19] forcibly close game ws connections when nonce is invalidated --- src/controllers/api/loginController.ts | 4 ++-- src/controllers/api/logoutController.ts | 4 ++-- src/services/wsService.ts | 25 +++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/controllers/api/loginController.ts b/src/controllers/api/loginController.ts index d93a9229..beac741e 100644 --- a/src/controllers/api/loginController.ts +++ b/src/controllers/api/loginController.ts @@ -8,7 +8,7 @@ import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } f import type { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "../../types/loginTypes.ts"; import { logger } from "../../utils/logger.ts"; import { version_compare } from "../../helpers/inventoryHelpers.ts"; -import { sendWsBroadcastTo } from "../../services/wsService.ts"; +import { handleNonceInvalidation } from "../../services/wsService.ts"; export const loginController: RequestHandler = async (request, response) => { const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object @@ -74,7 +74,7 @@ export const loginController: RequestHandler = async (request, response) => { account.LastLogin = new Date(); await account.save(); - sendWsBroadcastTo(account._id.toString(), { nonce_updated: true }); + handleNonceInvalidation(account._id.toString()); response.json(createLoginResponse(myAddress, myUrlBase, account.toJSON(), buildLabel)); }; diff --git a/src/controllers/api/logoutController.ts b/src/controllers/api/logoutController.ts index d1c13c2d..371d76fa 100644 --- a/src/controllers/api/logoutController.ts +++ b/src/controllers/api/logoutController.ts @@ -1,6 +1,6 @@ import type { RequestHandler } from "express"; import { Account } from "../../models/loginModel.ts"; -import { sendWsBroadcastTo } from "../../services/wsService.ts"; +import { handleNonceInvalidation } from "../../services/wsService.ts"; export const logoutController: RequestHandler = async (req, res) => { if (!req.query.accountId) { @@ -21,7 +21,7 @@ export const logoutController: RequestHandler = async (req, res) => { } ); if (stat.modifiedCount) { - sendWsBroadcastTo(req.query.accountId as string, { nonce_updated: true }); + handleNonceInvalidation(req.query.accountId as string); } res.writeHead(200, { diff --git a/src/services/wsService.ts b/src/services/wsService.ts index c87325a4..2c7ebe13 100644 --- a/src/services/wsService.ts +++ b/src/services/wsService.ts @@ -247,3 +247,28 @@ export const sendWsBroadcastEx = (data: IWsMsgToClient, accountId?: string, excl } } }; + +export const handleNonceInvalidation = (accountId: string): void => { + if (wsServer) { + for (const client of wsServer.clients) { + if ((client as IWsCustomData).accountId == accountId) { + if ((client as IWsCustomData).isGame) { + client.close(); + } else { + client.send(JSON.stringify({ nonce_updated: true } satisfies IWsMsgToClient)); + } + } + } + } + if (wssServer) { + for (const client of wssServer.clients) { + if ((client as IWsCustomData).accountId == accountId) { + if ((client as IWsCustomData).isGame) { + client.close(); + } else { + client.send(JSON.stringify({ nonce_updated: true } satisfies IWsMsgToClient)); + } + } + } + } +}; -- 2.47.2 From b03096d1803d5855035740b5d2fd83eebb5c9937 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 1 Sep 2025 04:26:51 +0200 Subject: [PATCH 03/19] logging --- src/services/wsService.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/services/wsService.ts b/src/services/wsService.ts index 2c7ebe13..237d1de4 100644 --- a/src/services/wsService.ts +++ b/src/services/wsService.ts @@ -6,7 +6,7 @@ import { Account } from "../models/loginModel.ts"; import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "./loginService.ts"; import type { IDatabaseAccountJson } from "../types/loginTypes.ts"; import type { HydratedDocument } from "mongoose"; -import { logError } from "../utils/logger.ts"; +import { logError, logger } from "../utils/logger.ts"; let wsServer: WebSocketServer | undefined; let wssServer: WebSocketServer | undefined; @@ -158,6 +158,7 @@ const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => { }); if (account) { (ws as IWsCustomData).accountId = account.id; + logger.debug(`got bootstrapper connection for ${account.id}`); } } } @@ -180,6 +181,7 @@ const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => { }); ws.on("close", () => { if ((ws as IWsCustomData).isGame && (ws as IWsCustomData).accountId) { + logger.debug(`lost bootstrapper connection for ${(ws as IWsCustomData).accountId}`); void Account.updateOne( { _id: (ws as IWsCustomData).accountId -- 2.47.2 From a6294a0324a644a88355a306f1eab43ec25a9221 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 1 Sep 2025 04:29:54 +0200 Subject: [PATCH 04/19] ignore drop after logout --- src/services/wsService.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/services/wsService.ts b/src/services/wsService.ts index 237d1de4..94246bf6 100644 --- a/src/services/wsService.ts +++ b/src/services/wsService.ts @@ -179,17 +179,15 @@ const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => { logError(e as Error, `processing websocket message`); } }); - ws.on("close", () => { + // eslint-disable-next-line @typescript-eslint/no-misused-promises + ws.on("close", async () => { if ((ws as IWsCustomData).isGame && (ws as IWsCustomData).accountId) { logger.debug(`lost bootstrapper connection for ${(ws as IWsCustomData).accountId}`); - void Account.updateOne( - { - _id: (ws as IWsCustomData).accountId - }, - { - Dropped: true - } - ).then(() => {}); + const account = await Account.findOne({ _id: (ws as IWsCustomData).accountId }); + if (account?.Nonce) { + account.Dropped = true; + await account.save(); + } } }); }; -- 2.47.2 From 2e13fd2c46bebd069607fcfa76c111d53293125d Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 8 Sep 2025 05:09:39 +0200 Subject: [PATCH 05/19] change ws import --- src/services/wsService.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/wsService.ts b/src/services/wsService.ts index 94246bf6..b7f7efb1 100644 --- a/src/services/wsService.ts +++ b/src/services/wsService.ts @@ -1,6 +1,6 @@ import type http from "http"; import type https from "https"; -import type { default as ws } from "ws"; +import type { WebSocket } from "ws"; import { WebSocketServer } from "ws"; import { Account } from "../models/loginModel.ts"; import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "./loginService.ts"; @@ -44,7 +44,7 @@ export const stopWsServers = (promises: Promise[]): void => { let lastWsid: number = 0; -interface IWsCustomData extends ws { +interface IWsCustomData extends WebSocket { id: number; accountId?: string; isGame?: boolean; @@ -90,7 +90,7 @@ interface IWsMsgToClient { sync_inventory?: boolean; } -const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => { +const wsOnConnect = (ws: WebSocket, req: http.IncomingMessage): void => { if (req.url == "/custom/selftest") { ws.send("SpaceNinjaServer"); ws.close(); -- 2.47.2 From 831e72c6919222cf9c463cb59efe32abd7e69a6c Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 8 Sep 2025 05:11:42 +0200 Subject: [PATCH 06/19] add forEachClient --- src/services/wsService.ts | 81 ++++++++++++--------------------------- 1 file changed, 25 insertions(+), 56 deletions(-) diff --git a/src/services/wsService.ts b/src/services/wsService.ts index b7f7efb1..4404eb9a 100644 --- a/src/services/wsService.ts +++ b/src/services/wsService.ts @@ -192,83 +192,52 @@ const wsOnConnect = (ws: WebSocket, req: http.IncomingMessage): void => { }); }; -export const sendWsBroadcast = (data: IWsMsgToClient): void => { - const msg = JSON.stringify(data); +const forEachClient = (cb: (client: IWsCustomData) => void): void => { if (wsServer) { for (const client of wsServer.clients) { - client.send(msg); + cb(client as IWsCustomData); } } if (wssServer) { for (const client of wssServer.clients) { - client.send(msg); + cb(client as IWsCustomData); } } }; +export const sendWsBroadcast = (data: IWsMsgToClient): void => { + const msg = JSON.stringify(data); + forEachClient(client => { + client.send(msg); + }); +}; + export const sendWsBroadcastTo = (accountId: string, data: IWsMsgToClient): void => { const msg = JSON.stringify(data); - if (wsServer) { - for (const client of wsServer.clients) { - if ((client as IWsCustomData).accountId == accountId) { - client.send(msg); - } + forEachClient(client => { + if (client.accountId == accountId) { + client.send(msg); } - } - if (wssServer) { - for (const client of wssServer.clients) { - if ((client as IWsCustomData).accountId == accountId) { - client.send(msg); - } - } - } + }); }; export const sendWsBroadcastEx = (data: IWsMsgToClient, accountId?: string, excludeWsid?: number): void => { const msg = JSON.stringify(data); - if (wsServer) { - for (const client of wsServer.clients) { - if ( - (!accountId || (client as IWsCustomData).accountId == accountId) && - (client as IWsCustomData).id != excludeWsid - ) { - client.send(msg); - } + forEachClient(client => { + if ((!accountId || client.accountId == accountId) && client.id != excludeWsid) { + client.send(msg); } - } - if (wssServer) { - for (const client of wssServer.clients) { - if ( - (!accountId || (client as IWsCustomData).accountId == accountId) && - (client as IWsCustomData).id != excludeWsid - ) { - client.send(msg); - } - } - } + }); }; export const handleNonceInvalidation = (accountId: string): void => { - if (wsServer) { - for (const client of wsServer.clients) { - if ((client as IWsCustomData).accountId == accountId) { - if ((client as IWsCustomData).isGame) { - client.close(); - } else { - client.send(JSON.stringify({ nonce_updated: true } satisfies IWsMsgToClient)); - } + forEachClient(client => { + if (client.accountId == accountId) { + if (client.isGame) { + client.close(); + } else { + client.send(JSON.stringify({ nonce_updated: true } satisfies IWsMsgToClient)); } } - } - if (wssServer) { - for (const client of wssServer.clients) { - if ((client as IWsCustomData).accountId == accountId) { - if ((client as IWsCustomData).isGame) { - client.close(); - } else { - client.send(JSON.stringify({ nonce_updated: true } satisfies IWsMsgToClient)); - } - } - } - } + }); }; -- 2.47.2 From 26d30105d644ef86262d26c536cc9fc364332f90 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 8 Sep 2025 05:16:06 +0200 Subject: [PATCH 07/19] add broadcastInventoryUpdate --- src/controllers/api/sellController.ts | 7 ++----- src/services/wsService.ts | 9 +++++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/controllers/api/sellController.ts b/src/controllers/api/sellController.ts index c6f4662b..cf74ca92 100644 --- a/src/controllers/api/sellController.ts +++ b/src/controllers/api/sellController.ts @@ -17,7 +17,7 @@ import { InventorySlot } from "../../types/inventoryTypes/inventoryTypes.ts"; import { ExportDojoRecipes } from "warframe-public-export-plus"; import type { IInventoryChanges } from "../../types/purchaseTypes.ts"; import type { TInventoryDatabaseDocument } from "../../models/inventoryModels/inventoryModel.ts"; -import { sendWsBroadcastEx, sendWsBroadcastTo } from "../../services/wsService.ts"; +import { broadcastInventoryUpdate } from "../../services/wsService.ts"; import { parseFusionTreasure } from "../../helpers/inventoryHelpers.ts"; export const sellController: RequestHandler = async (req, res) => { @@ -307,10 +307,7 @@ export const sellController: RequestHandler = async (req, res) => { res.json({ inventoryChanges: inventoryChanges // "inventoryChanges" for this response instead of the usual "InventoryChanges" }); - sendWsBroadcastEx({ update_inventory: true }, accountId, parseInt(String(req.query.wsid))); - if (req.query.wsid) { - sendWsBroadcastTo(accountId, { sync_inventory: true }); - } + broadcastInventoryUpdate(req); }; interface ISellRequest { diff --git a/src/services/wsService.ts b/src/services/wsService.ts index 4404eb9a..bf5dd036 100644 --- a/src/services/wsService.ts +++ b/src/services/wsService.ts @@ -7,6 +7,7 @@ import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } f import type { IDatabaseAccountJson } from "../types/loginTypes.ts"; import type { HydratedDocument } from "mongoose"; import { logError, logger } from "../utils/logger.ts"; +import type { Request } from "express"; let wsServer: WebSocketServer | undefined; let wssServer: WebSocketServer | undefined; @@ -230,6 +231,14 @@ export const sendWsBroadcastEx = (data: IWsMsgToClient, accountId?: string, excl }); }; +export const broadcastInventoryUpdate = (req: Request): void => { + const accountId = req.query.accountId as string; + sendWsBroadcastEx({ update_inventory: true }, accountId, parseInt(String(req.query.wsid))); + if (req.query.wsid) { + sendWsBroadcastTo(accountId, { sync_inventory: true }); + } +}; + export const handleNonceInvalidation = (accountId: string): void => { forEachClient(client => { if (client.accountId == accountId) { -- 2.47.2 From 3b10dc374f908223d6de8d237fc36db6ad488186 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 8 Sep 2025 05:19:54 +0200 Subject: [PATCH 08/19] move wsid to authz --- static/webui/script.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/static/webui/script.js b/static/webui/script.js index 4497e073..d31ab4a8 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -63,7 +63,7 @@ function openWebSocket() { } $(".displayname").text(data.DisplayName); window.accountId = data.id; - window.authz = "accountId=" + data.id + "&nonce=" + data.Nonce; + window.authz = "accountId=" + data.id + "&nonce=" + data.Nonce + "&wsid=" + wsid; if (window.dict) { updateLocElements(); } @@ -2582,7 +2582,7 @@ function disposeOfGear(category, oid) { ]; revalidateAuthz().then(() => { $.post({ - url: "/api/sell.php?" + window.authz + "&wsid=" + wsid, + url: "/api/sell.php?" + window.authz, contentType: "text/plain", data: JSON.stringify(data) }); @@ -2604,7 +2604,7 @@ function disposeOfItems(category, type, count) { ]; revalidateAuthz().then(() => { $.post({ - url: "/api/sell.php?" + window.authz + "&wsid=" + wsid, + url: "/api/sell.php?" + window.authz, contentType: "text/plain", data: JSON.stringify(data) }); @@ -2858,7 +2858,7 @@ for (const id of uiConfigs) { value = parseInt(value); } $.post({ - url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid, + url: "/custom/setConfig?" + window.authz, contentType: "application/json", data: JSON.stringify({ [id]: value }) }); @@ -2866,7 +2866,7 @@ for (const id of uiConfigs) { } else if (elm.type == "checkbox") { elm.onchange = function () { $.post({ - url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid, + url: "/custom/setConfig?" + window.authz, contentType: "application/json", data: JSON.stringify({ [id]: this.checked }) }).then(() => { @@ -2893,7 +2893,7 @@ document.querySelectorAll(".config-form .input-group").forEach(grp => { function doSaveConfigInt(id) { $.post({ - url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid, + url: "/custom/setConfig?" + window.authz, contentType: "application/json", data: JSON.stringify({ [id]: parseInt(document.getElementById(id).value) @@ -2903,7 +2903,7 @@ function doSaveConfigInt(id) { function doSaveConfigFloat(id) { $.post({ - url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid, + url: "/custom/setConfig?" + window.authz, contentType: "application/json", data: JSON.stringify({ [id]: parseFloat(document.getElementById(id).value) @@ -2913,7 +2913,7 @@ function doSaveConfigFloat(id) { function doSaveConfigStringArray(id) { $.post({ - url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid, + url: "/custom/setConfig?" + window.authz, contentType: "application/json", data: JSON.stringify({ [id]: document @@ -3040,7 +3040,7 @@ document.querySelectorAll("#account-cheats input[type=checkbox]").forEach(elm => elm.onchange = function () { revalidateAuthz().then(() => { $.post({ - url: "/custom/setAccountCheat?" + window.authz /*+ "&wsid=" + wsid*/, + url: "/custom/setAccountCheat?" + window.authz, contentType: "application/json", data: JSON.stringify({ key: elm.id, @@ -3112,7 +3112,7 @@ function doRemoveUnrankedMods() { req.done(inventory => { window.itemListPromise.then(itemMap => { $.post({ - url: "/api/sell.php?" + window.authz + "&wsid=" + wsid, + url: "/api/sell.php?" + window.authz, contentType: "text/plain", data: JSON.stringify({ SellCurrency: "SC_RegularCredits", -- 2.47.2 From 7b99502cf88e22afa3d7b1afe24e6822fd03a35c Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 8 Sep 2025 05:26:24 +0200 Subject: [PATCH 09/19] note --- src/services/wsService.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/services/wsService.ts b/src/services/wsService.ts index bf5dd036..70cf92f3 100644 --- a/src/services/wsService.ts +++ b/src/services/wsService.ts @@ -233,7 +233,11 @@ export const sendWsBroadcastEx = (data: IWsMsgToClient, accountId?: string, excl export const broadcastInventoryUpdate = (req: Request): void => { const accountId = req.query.accountId as string; + + // for webui requests, let other tabs know. for game requests, let all tabs know. sendWsBroadcastEx({ update_inventory: true }, accountId, parseInt(String(req.query.wsid))); + + // for webui requests, also let the game know. if (req.query.wsid) { sendWsBroadcastTo(accountId, { sync_inventory: true }); } -- 2.47.2 From ed48e251f053afada333e8aa66dd2b243238c3f0 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 8 Sep 2025 05:39:40 +0200 Subject: [PATCH 10/19] make sure all webui options trigger respective inventory syncs --- src/controllers/api/artifactsController.ts | 2 ++ src/controllers/api/gildWeaponController.ts | 4 ++-- src/controllers/api/infestedFoundryController.ts | 2 ++ src/controllers/api/maturePetController.ts | 2 ++ src/controllers/api/modularWeaponCraftingController.ts | 4 ++-- src/controllers/api/nameWeaponController.ts | 4 ++-- src/controllers/api/releasePetController.ts | 4 ++-- src/controllers/api/renamePetController.ts | 4 ++-- src/controllers/api/setSupportedSyndicateController.ts | 2 ++ src/controllers/custom/abilityOverrideController.ts | 2 ++ src/controllers/custom/addCurrencyController.ts | 2 ++ src/controllers/custom/addItemsController.ts | 4 ++-- .../custom/addMissingHelminthBlueprintsController.ts | 2 ++ src/controllers/custom/addMissingMaxRankModsController.ts | 2 ++ src/controllers/custom/addXpController.ts | 4 ++-- src/controllers/custom/changeModularPartsController.ts | 2 ++ .../custom/editSuitInvigorationUpgradeController.ts | 2 ++ src/controllers/custom/importController.ts | 2 ++ src/controllers/custom/manageQuestsController.ts | 2 ++ src/controllers/custom/popArchonCrystalUpgradeController.ts | 2 ++ .../custom/pushArchonCrystalUpgradeController.ts | 2 ++ src/controllers/custom/setBoosterController.ts | 2 ++ src/controllers/custom/setEvolutionProgressController.ts | 2 ++ src/controllers/custom/unlockAllCapturaScenesController.ts | 6 ++++++ src/controllers/custom/unlockAllIntrinsicsController.ts | 2 ++ .../custom/unlockAllProfitTakerStagesController.ts | 2 ++ .../custom/unlockAllSimarisResearchEntriesController.ts | 2 ++ src/controllers/custom/updateFingerprintController.ts | 2 ++ 28 files changed, 60 insertions(+), 14 deletions(-) diff --git a/src/controllers/api/artifactsController.ts b/src/controllers/api/artifactsController.ts index 4fb2155d..362aa971 100644 --- a/src/controllers/api/artifactsController.ts +++ b/src/controllers/api/artifactsController.ts @@ -3,6 +3,7 @@ import { getAccountIdForRequest } from "../../services/loginService.ts"; import type { RequestHandler } from "express"; import type { IInventoryClient, IUpgradeClient } from "../../types/inventoryTypes/inventoryTypes.ts"; import { addMods, getInventory } from "../../services/inventoryService.ts"; +import { broadcastInventoryUpdate } from "../../services/wsService.ts"; export const artifactsController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); @@ -57,6 +58,7 @@ export const artifactsController: RequestHandler = async (req, res) => { } res.send(itemId); + broadcastInventoryUpdate(req); }; interface IArtifactsRequest { diff --git a/src/controllers/api/gildWeaponController.ts b/src/controllers/api/gildWeaponController.ts index 0bd921b3..dea62765 100644 --- a/src/controllers/api/gildWeaponController.ts +++ b/src/controllers/api/gildWeaponController.ts @@ -1,6 +1,6 @@ import type { RequestHandler } from "express"; import { getAccountIdForRequest } from "../../services/loginService.ts"; -import { sendWsBroadcastTo } from "../../services/wsService.ts"; +import { broadcastInventoryUpdate } from "../../services/wsService.ts"; import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { addMiscItems, getInventory } from "../../services/inventoryService.ts"; import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts"; @@ -75,5 +75,5 @@ export const gildWeaponController: RequestHandler = async (req, res) => { InventoryChanges: inventoryChanges, AffiliationMods: affiliationMods }); - sendWsBroadcastTo(accountId, { update_inventory: true }); + broadcastInventoryUpdate(req); }; diff --git a/src/controllers/api/infestedFoundryController.ts b/src/controllers/api/infestedFoundryController.ts index de0b714e..27510f80 100644 --- a/src/controllers/api/infestedFoundryController.ts +++ b/src/controllers/api/infestedFoundryController.ts @@ -20,6 +20,7 @@ import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "../../services/infestedFoundryService.ts"; +import { sendWsBroadcastTo } from "../../services/wsService.ts"; export const infestedFoundryController: RequestHandler = async (req, res) => { const account = await getAccountForRequest(req); @@ -363,6 +364,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => { ); addRecipes(inventory, recipeChanges); await inventory.save(); + sendWsBroadcastTo(account._id.toString(), { sync_inventory: true }); } res.end(); break; diff --git a/src/controllers/api/maturePetController.ts b/src/controllers/api/maturePetController.ts index 9ddc2786..38c59523 100644 --- a/src/controllers/api/maturePetController.ts +++ b/src/controllers/api/maturePetController.ts @@ -2,6 +2,7 @@ import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { getInventory } from "../../services/inventoryService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts"; import type { RequestHandler } from "express"; +import { broadcastInventoryUpdate } from "../../services/wsService.ts"; export const maturePetController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); @@ -19,6 +20,7 @@ export const maturePetController: RequestHandler = async (req, res) => { : [details.DominantTraits.FurPattern, details.DominantTraits.FurPattern, details.DominantTraits.FurPattern], unmature: data.revert }); + broadcastInventoryUpdate(req); }; interface IMaturePetRequest { diff --git a/src/controllers/api/modularWeaponCraftingController.ts b/src/controllers/api/modularWeaponCraftingController.ts index 7e235241..52559fb8 100644 --- a/src/controllers/api/modularWeaponCraftingController.ts +++ b/src/controllers/api/modularWeaponCraftingController.ts @@ -1,6 +1,6 @@ import type { RequestHandler } from "express"; import { getAccountIdForRequest } from "../../services/loginService.ts"; -import { sendWsBroadcastTo } from "../../services/wsService.ts"; +import { broadcastInventoryUpdate } from "../../services/wsService.ts"; import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { getInventory, @@ -197,5 +197,5 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res) MiscItems: miscItemChanges } }); - sendWsBroadcastTo(accountId, { update_inventory: true }); + broadcastInventoryUpdate(req); }; diff --git a/src/controllers/api/nameWeaponController.ts b/src/controllers/api/nameWeaponController.ts index ece87fef..aecf7ea6 100644 --- a/src/controllers/api/nameWeaponController.ts +++ b/src/controllers/api/nameWeaponController.ts @@ -3,7 +3,7 @@ import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getInventory, updateCurrency } from "../../services/inventoryService.ts"; import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts"; -import { sendWsBroadcastTo } from "../../services/wsService.ts"; +import { broadcastInventoryUpdate } from "../../services/wsService.ts"; interface INameWeaponRequest { ItemName: string; @@ -28,5 +28,5 @@ export const nameWeaponController: RequestHandler = async (req, res) => { res.json({ InventoryChanges: currencyChanges }); - sendWsBroadcastTo(accountId, { update_inventory: true }); + broadcastInventoryUpdate(req); }; diff --git a/src/controllers/api/releasePetController.ts b/src/controllers/api/releasePetController.ts index fb707b7a..f034f731 100644 --- a/src/controllers/api/releasePetController.ts +++ b/src/controllers/api/releasePetController.ts @@ -1,7 +1,7 @@ import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { getInventory, updateCurrency } from "../../services/inventoryService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts"; -import { sendWsBroadcastTo } from "../../services/wsService.ts"; +import { broadcastInventoryUpdate } from "../../services/wsService.ts"; import type { RequestHandler } from "express"; export const releasePetController: RequestHandler = async (req, res) => { @@ -20,7 +20,7 @@ export const releasePetController: RequestHandler = async (req, res) => { await inventory.save(); res.json({ inventoryChanges }); // Not a mistake; it's "inventoryChanges" here. - sendWsBroadcastTo(accountId, { update_inventory: true }); + broadcastInventoryUpdate(req); }; interface IReleasePetRequest { diff --git a/src/controllers/api/renamePetController.ts b/src/controllers/api/renamePetController.ts index d1f0cc3b..73e54172 100644 --- a/src/controllers/api/renamePetController.ts +++ b/src/controllers/api/renamePetController.ts @@ -1,7 +1,7 @@ import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { getInventory, updateCurrency } from "../../services/inventoryService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts"; -import { sendWsBroadcastTo } from "../../services/wsService.ts"; +import { broadcastInventoryUpdate } from "../../services/wsService.ts"; import type { IInventoryChanges } from "../../types/purchaseTypes.ts"; import type { RequestHandler } from "express"; @@ -23,7 +23,7 @@ export const renamePetController: RequestHandler = async (req, res) => { ...data, inventoryChanges: inventoryChanges }); - sendWsBroadcastTo(accountId, { update_inventory: true }); + broadcastInventoryUpdate(req); }; interface IRenamePetRequest { diff --git a/src/controllers/api/setSupportedSyndicateController.ts b/src/controllers/api/setSupportedSyndicateController.ts index 53db10b9..8357f424 100644 --- a/src/controllers/api/setSupportedSyndicateController.ts +++ b/src/controllers/api/setSupportedSyndicateController.ts @@ -1,6 +1,7 @@ import type { RequestHandler } from "express"; import { getAccountIdForRequest } from "../../services/loginService.ts"; import { Inventory } from "../../models/inventoryModels/inventoryModel.ts"; +import { broadcastInventoryUpdate } from "../../services/wsService.ts"; export const setSupportedSyndicateController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); @@ -15,4 +16,5 @@ export const setSupportedSyndicateController: RequestHandler = async (req, res) ); res.end(); + broadcastInventoryUpdate(req); }; diff --git a/src/controllers/custom/abilityOverrideController.ts b/src/controllers/custom/abilityOverrideController.ts index 47b7e13a..135e0eb9 100644 --- a/src/controllers/custom/abilityOverrideController.ts +++ b/src/controllers/custom/abilityOverrideController.ts @@ -1,5 +1,6 @@ import { getInventory } from "../../services/inventoryService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts"; +import { broadcastInventoryUpdate } from "../../services/wsService.ts"; import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts"; import type { RequestHandler } from "express"; @@ -19,6 +20,7 @@ export const abilityOverrideController: RequestHandler = async (req, res) => { } } res.end(); + broadcastInventoryUpdate(req); }; interface IAbilityOverrideRequest { diff --git a/src/controllers/custom/addCurrencyController.ts b/src/controllers/custom/addCurrencyController.ts index 3df52a64..c186818a 100644 --- a/src/controllers/custom/addCurrencyController.ts +++ b/src/controllers/custom/addCurrencyController.ts @@ -3,6 +3,7 @@ import { getAccountIdForRequest } from "../../services/loginService.ts"; import { addFusionPoints, getInventory } from "../../services/inventoryService.ts"; import { getGuildForRequestEx, hasGuildPermission } from "../../services/guildService.ts"; import { GuildPermission } from "../../types/guildTypes.ts"; +import { broadcastInventoryUpdate } from "../../services/wsService.ts"; export const addCurrencyController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); @@ -24,6 +25,7 @@ export const addCurrencyController: RequestHandler = async (req, res) => { } if (!request.currency.startsWith("Vault")) { await inventory.save(); + broadcastInventoryUpdate(req); } res.end(); }; diff --git a/src/controllers/custom/addItemsController.ts b/src/controllers/custom/addItemsController.ts index 203e648c..4add37ac 100644 --- a/src/controllers/custom/addItemsController.ts +++ b/src/controllers/custom/addItemsController.ts @@ -1,7 +1,7 @@ import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getInventory, addItem } from "../../services/inventoryService.ts"; import type { RequestHandler } from "express"; -import { sendWsBroadcastTo } from "../../services/wsService.ts"; +import { broadcastInventoryUpdate } from "../../services/wsService.ts"; export const addItemsController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); @@ -10,9 +10,9 @@ export const addItemsController: RequestHandler = async (req, res) => { for (const request of requests) { await addItem(inventory, request.ItemType, request.ItemCount, true, undefined, request.Fingerprint, true); } - sendWsBroadcastTo(accountId, { sync_inventory: true }); await inventory.save(); res.end(); + broadcastInventoryUpdate(req); }; interface IAddItemRequest { diff --git a/src/controllers/custom/addMissingHelminthBlueprintsController.ts b/src/controllers/custom/addMissingHelminthBlueprintsController.ts index 27f6dec8..3aae37a3 100644 --- a/src/controllers/custom/addMissingHelminthBlueprintsController.ts +++ b/src/controllers/custom/addMissingHelminthBlueprintsController.ts @@ -2,6 +2,7 @@ import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getInventory, addRecipes } from "../../services/inventoryService.ts"; import type { RequestHandler } from "express"; import { ExportRecipes } from "warframe-public-export-plus"; +import { broadcastInventoryUpdate } from "../../services/wsService.ts"; export const addMissingHelminthBlueprintsController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); @@ -21,4 +22,5 @@ export const addMissingHelminthBlueprintsController: RequestHandler = async (req await inventory.save(); res.end(); + broadcastInventoryUpdate(req); }; diff --git a/src/controllers/custom/addMissingMaxRankModsController.ts b/src/controllers/custom/addMissingMaxRankModsController.ts index 46a299a3..ddff0cf7 100644 --- a/src/controllers/custom/addMissingMaxRankModsController.ts +++ b/src/controllers/custom/addMissingMaxRankModsController.ts @@ -2,6 +2,7 @@ import { getInventory } from "../../services/inventoryService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts"; import type { RequestHandler } from "express"; import { ExportArcanes, ExportUpgrades } from "warframe-public-export-plus"; +import { broadcastInventoryUpdate } from "../../services/wsService.ts"; export const addMissingMaxRankModsController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); @@ -41,4 +42,5 @@ export const addMissingMaxRankModsController: RequestHandler = async (req, res) await inventory.save(); res.end(); + broadcastInventoryUpdate(req); }; diff --git a/src/controllers/custom/addXpController.ts b/src/controllers/custom/addXpController.ts index 6c89bc7c..7ca1d2d9 100644 --- a/src/controllers/custom/addXpController.ts +++ b/src/controllers/custom/addXpController.ts @@ -1,6 +1,6 @@ import { applyClientEquipmentUpdates, getInventory } from "../../services/inventoryService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts"; -import { sendWsBroadcastTo } from "../../services/wsService.ts"; +import { broadcastInventoryUpdate } from "../../services/wsService.ts"; import type { IOid } from "../../types/commonTypes.ts"; import type { IEquipmentClient } from "../../types/equipmentTypes.ts"; import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts"; @@ -24,9 +24,9 @@ export const addXpController: RequestHandler = async (req, res) => { } applyClientEquipmentUpdates(inventory, gear, category as TEquipmentKey); } - sendWsBroadcastTo(accountId, { sync_inventory: true }); await inventory.save(); res.end(); + broadcastInventoryUpdate(req); }; type IAddXpRequest = { diff --git a/src/controllers/custom/changeModularPartsController.ts b/src/controllers/custom/changeModularPartsController.ts index df63da13..32e17fda 100644 --- a/src/controllers/custom/changeModularPartsController.ts +++ b/src/controllers/custom/changeModularPartsController.ts @@ -1,5 +1,6 @@ import { getInventory } from "../../services/inventoryService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts"; +import { broadcastInventoryUpdate } from "../../services/wsService.ts"; import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts"; import type { RequestHandler } from "express"; @@ -20,6 +21,7 @@ export const changeModularPartsController: RequestHandler = async (req, res) => await inventory.save(); } res.end(); + broadcastInventoryUpdate(req); }; interface IUpdateFingerPrintRequest { diff --git a/src/controllers/custom/editSuitInvigorationUpgradeController.ts b/src/controllers/custom/editSuitInvigorationUpgradeController.ts index 200b403a..713b1c0e 100644 --- a/src/controllers/custom/editSuitInvigorationUpgradeController.ts +++ b/src/controllers/custom/editSuitInvigorationUpgradeController.ts @@ -1,6 +1,7 @@ import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getInventory } from "../../services/inventoryService.ts"; import type { RequestHandler } from "express"; +import { broadcastInventoryUpdate } from "../../services/wsService.ts"; const DEFAULT_UPGRADE_EXPIRY_MS = 7 * 24 * 60 * 60 * 1000; // 7 days @@ -31,4 +32,5 @@ export const editSuitInvigorationUpgradeController: RequestHandler = async (req, } await inventory.save(); res.end(); + broadcastInventoryUpdate(req); }; diff --git a/src/controllers/custom/importController.ts b/src/controllers/custom/importController.ts index 02528e64..1dda3189 100644 --- a/src/controllers/custom/importController.ts +++ b/src/controllers/custom/importController.ts @@ -3,6 +3,7 @@ import { getInventory } from "../../services/inventoryService.ts"; import { getLoadout } from "../../services/loadoutService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getPersonalRooms } from "../../services/personalRoomsService.ts"; +import { broadcastInventoryUpdate } from "../../services/wsService.ts"; import type { IInventoryClient } from "../../types/inventoryTypes/inventoryTypes.ts"; import type { IGetShipResponse } from "../../types/personalRoomsTypes.ts"; import type { RequestHandler } from "express"; @@ -32,6 +33,7 @@ export const importController: RequestHandler = async (req, res) => { } res.end(); + broadcastInventoryUpdate(req); }; interface IImportRequest { diff --git a/src/controllers/custom/manageQuestsController.ts b/src/controllers/custom/manageQuestsController.ts index 4c7d614d..b1cbde84 100644 --- a/src/controllers/custom/manageQuestsController.ts +++ b/src/controllers/custom/manageQuestsController.ts @@ -9,6 +9,7 @@ import { import { logger } from "../../utils/logger.ts"; import type { RequestHandler } from "express"; import { ExportKeys } from "warframe-public-export-plus"; +import { broadcastInventoryUpdate } from "../../services/wsService.ts"; export const manageQuestsController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); @@ -157,4 +158,5 @@ export const manageQuestsController: RequestHandler = async (req, res) => { await inventory.save(); res.status(200).end(); + broadcastInventoryUpdate(req); }; diff --git a/src/controllers/custom/popArchonCrystalUpgradeController.ts b/src/controllers/custom/popArchonCrystalUpgradeController.ts index 2d5e40b9..9fdb12ff 100644 --- a/src/controllers/custom/popArchonCrystalUpgradeController.ts +++ b/src/controllers/custom/popArchonCrystalUpgradeController.ts @@ -1,6 +1,7 @@ import type { RequestHandler } from "express"; import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getInventory } from "../../services/inventoryService.ts"; +import { broadcastInventoryUpdate } from "../../services/wsService.ts"; export const popArchonCrystalUpgradeController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); @@ -12,6 +13,7 @@ export const popArchonCrystalUpgradeController: RequestHandler = async (req, res ); await inventory.save(); res.end(); + broadcastInventoryUpdate(req); return; } res.status(400).end(); diff --git a/src/controllers/custom/pushArchonCrystalUpgradeController.ts b/src/controllers/custom/pushArchonCrystalUpgradeController.ts index 6eb7276e..aae46b31 100644 --- a/src/controllers/custom/pushArchonCrystalUpgradeController.ts +++ b/src/controllers/custom/pushArchonCrystalUpgradeController.ts @@ -1,6 +1,7 @@ import type { RequestHandler } from "express"; import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getInventory } from "../../services/inventoryService.ts"; +import { broadcastInventoryUpdate } from "../../services/wsService.ts"; export const pushArchonCrystalUpgradeController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); @@ -15,6 +16,7 @@ export const pushArchonCrystalUpgradeController: RequestHandler = async (req, re } await inventory.save(); res.end(); + broadcastInventoryUpdate(req); return; } } diff --git a/src/controllers/custom/setBoosterController.ts b/src/controllers/custom/setBoosterController.ts index 756aad98..b3109a2e 100644 --- a/src/controllers/custom/setBoosterController.ts +++ b/src/controllers/custom/setBoosterController.ts @@ -2,6 +2,7 @@ import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getInventory } from "../../services/inventoryService.ts"; import type { RequestHandler } from "express"; import { ExportBoosters } from "warframe-public-export-plus"; +import { broadcastInventoryUpdate } from "../../services/wsService.ts"; const I32_MAX = 0x7fffffff; @@ -42,4 +43,5 @@ export const setBoosterController: RequestHandler = async (req, res) => { } await inventory.save(); res.end(); + broadcastInventoryUpdate(req); }; diff --git a/src/controllers/custom/setEvolutionProgressController.ts b/src/controllers/custom/setEvolutionProgressController.ts index c68ccaff..2c6a28a7 100644 --- a/src/controllers/custom/setEvolutionProgressController.ts +++ b/src/controllers/custom/setEvolutionProgressController.ts @@ -1,6 +1,7 @@ import { getInventory } from "../../services/inventoryService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts"; import type { RequestHandler } from "express"; +import { broadcastInventoryUpdate } from "../../services/wsService.ts"; export const setEvolutionProgressController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); @@ -25,6 +26,7 @@ export const setEvolutionProgressController: RequestHandler = async (req, res) = await inventory.save(); res.end(); + broadcastInventoryUpdate(req); }; type ISetEvolutionProgressRequest = { diff --git a/src/controllers/custom/unlockAllCapturaScenesController.ts b/src/controllers/custom/unlockAllCapturaScenesController.ts index 45dffcf0..7565ffa2 100644 --- a/src/controllers/custom/unlockAllCapturaScenesController.ts +++ b/src/controllers/custom/unlockAllCapturaScenesController.ts @@ -2,19 +2,25 @@ import type { RequestHandler } from "express"; import { ExportResources, ExportVirtuals } from "warframe-public-export-plus"; import { getAccountIdForRequest } from "../../services/loginService.ts"; import { addItem, getInventory } from "../../services/inventoryService.ts"; +import { sendWsBroadcastTo } from "../../services/wsService.ts"; export const unlockAllCapturaScenesController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); const inventory = await getInventory(accountId); + let needSync = false; for (const uniqueName of Object.keys(ExportResources)) { if (resourceInheritsFrom(uniqueName, "/Lotus/Types/Items/MiscItems/PhotoboothTile")) { await addItem(inventory, uniqueName, 1); + needSync = true; } } await inventory.save(); res.end(); + if (needSync) { + sendWsBroadcastTo(accountId, { sync_inventory: true }); + } }; const resourceInheritsFrom = (resourceName: string, targetName: string): boolean => { diff --git a/src/controllers/custom/unlockAllIntrinsicsController.ts b/src/controllers/custom/unlockAllIntrinsicsController.ts index 8dd484ce..ad7fc107 100644 --- a/src/controllers/custom/unlockAllIntrinsicsController.ts +++ b/src/controllers/custom/unlockAllIntrinsicsController.ts @@ -1,6 +1,7 @@ import { getInventory } from "../../services/inventoryService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts"; import type { RequestHandler } from "express"; +import { sendWsBroadcastTo } from "../../services/wsService.ts"; export const unlockAllIntrinsicsController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); @@ -16,4 +17,5 @@ export const unlockAllIntrinsicsController: RequestHandler = async (req, res) => inventory.PlayerSkills.LPS_DRIFT_ENDURANCE = 10; await inventory.save(); res.end(); + sendWsBroadcastTo(accountId, { sync_inventory: true }); }; diff --git a/src/controllers/custom/unlockAllProfitTakerStagesController.ts b/src/controllers/custom/unlockAllProfitTakerStagesController.ts index 79fe3b87..4c362d3d 100644 --- a/src/controllers/custom/unlockAllProfitTakerStagesController.ts +++ b/src/controllers/custom/unlockAllProfitTakerStagesController.ts @@ -1,6 +1,7 @@ import { getInventory } from "../../services/inventoryService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts"; import type { RequestHandler } from "express"; +import { sendWsBroadcastTo } from "../../services/wsService.ts"; const allEudicoHeistJobs = [ "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyOne", @@ -21,4 +22,5 @@ export const unlockAllProfitTakerStagesController: RequestHandler = async (req, } await inventory.save(); res.end(); + sendWsBroadcastTo(accountId, { sync_inventory: true }); }; diff --git a/src/controllers/custom/unlockAllSimarisResearchEntriesController.ts b/src/controllers/custom/unlockAllSimarisResearchEntriesController.ts index fae3c55d..a12626dd 100644 --- a/src/controllers/custom/unlockAllSimarisResearchEntriesController.ts +++ b/src/controllers/custom/unlockAllSimarisResearchEntriesController.ts @@ -1,6 +1,7 @@ import type { RequestHandler } from "express"; import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getInventory } from "../../services/inventoryService.ts"; +import { sendWsBroadcastTo } from "../../services/wsService.ts"; export const unlockAllSimarisResearchEntriesController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); @@ -17,4 +18,5 @@ export const unlockAllSimarisResearchEntriesController: RequestHandler = async ( ].map(type => ({ TargetType: type, Scans: 10, Completed: true })); await inventory.save(); res.end(); + sendWsBroadcastTo(accountId, { sync_inventory: true }); }; diff --git a/src/controllers/custom/updateFingerprintController.ts b/src/controllers/custom/updateFingerprintController.ts index f600b578..48e83c85 100644 --- a/src/controllers/custom/updateFingerprintController.ts +++ b/src/controllers/custom/updateFingerprintController.ts @@ -2,6 +2,7 @@ import { getInventory } from "../../services/inventoryService.ts"; import type { WeaponTypeInternal } from "../../services/itemDataService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts"; import type { RequestHandler } from "express"; +import { sendWsBroadcastTo } from "../../services/wsService.ts"; export const updateFingerprintController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); @@ -22,6 +23,7 @@ export const updateFingerprintController: RequestHandler = async (req, res) => { await inventory.save(); } res.end(); + sendWsBroadcastTo(accountId, { sync_inventory: true }); }; interface IUpdateFingerPrintRequest { -- 2.47.2 From 8499f3ee675a4307b663236c995ed2d45ae2e986 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 8 Sep 2025 05:41:37 +0200 Subject: [PATCH 11/19] NaN is not undefined --- src/services/wsService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/wsService.ts b/src/services/wsService.ts index 70cf92f3..74bad174 100644 --- a/src/services/wsService.ts +++ b/src/services/wsService.ts @@ -222,7 +222,7 @@ export const sendWsBroadcastTo = (accountId: string, data: IWsMsgToClient): void }); }; -export const sendWsBroadcastEx = (data: IWsMsgToClient, accountId?: string, excludeWsid?: number): void => { +export const sendWsBroadcastEx = (data: IWsMsgToClient, accountId: string | undefined, excludeWsid: number): void => { const msg = JSON.stringify(data); forEachClient(client => { if ((!accountId || client.accountId == accountId) && client.id != excludeWsid) { -- 2.47.2 From 4443259724220639f21a3e2c843a75fab3821b69 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 8 Sep 2025 05:47:47 +0200 Subject: [PATCH 12/19] refresh inventory for infinite cheats --- src/controllers/custom/setAccountCheatController.ts | 4 ++++ static/webui/script.js | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/controllers/custom/setAccountCheatController.ts b/src/controllers/custom/setAccountCheatController.ts index 5a188777..07a1da4f 100644 --- a/src/controllers/custom/setAccountCheatController.ts +++ b/src/controllers/custom/setAccountCheatController.ts @@ -1,5 +1,6 @@ import { getInventory } from "../../services/inventoryService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts"; +import { sendWsBroadcastTo } from "../../services/wsService.ts"; import type { IAccountCheats } from "../../types/inventoryTypes/inventoryTypes.ts"; import type { RequestHandler } from "express"; @@ -10,6 +11,9 @@ export const setAccountCheatController: RequestHandler = async (req, res) => { inventory[payload.key] = payload.value; await inventory.save(); res.end(); + if (["infiniteCredits", "infinitePlatinum", "infiniteEndo", "infiniteRegalAya"].indexOf(payload.key) != -1) { + sendWsBroadcastTo(accountId, { update_inventory: true, sync_inventory: true }); + } }; interface ISetAccountCheatRequest { diff --git a/static/webui/script.js b/static/webui/script.js index d31ab4a8..9d15b4fe 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -2869,10 +2869,6 @@ for (const id of uiConfigs) { url: "/custom/setConfig?" + window.authz, contentType: "application/json", data: JSON.stringify({ [id]: this.checked }) - }).then(() => { - if (["infiniteCredits", "infinitePlatinum", "infiniteEndo", "infiniteRegalAya"].indexOf(id) != -1) { - updateInventory(); - } }); }; } -- 2.47.2 From 9b52c1c1dba42d0f0103a9ae1892ae6ec8b0d950 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 8 Sep 2025 05:52:25 +0200 Subject: [PATCH 13/19] add sendWsBroadcastToGame --- src/controllers/api/infestedFoundryController.ts | 4 ++-- .../custom/unlockAllCapturaScenesController.ts | 4 ++-- src/controllers/custom/unlockAllIntrinsicsController.ts | 4 ++-- .../custom/unlockAllProfitTakerStagesController.ts | 4 ++-- .../custom/unlockAllSimarisResearchEntriesController.ts | 4 ++-- src/controllers/custom/updateFingerprintController.ts | 4 ++-- src/services/wsService.ts | 9 +++++++++ 7 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/controllers/api/infestedFoundryController.ts b/src/controllers/api/infestedFoundryController.ts index 27510f80..f46abd6c 100644 --- a/src/controllers/api/infestedFoundryController.ts +++ b/src/controllers/api/infestedFoundryController.ts @@ -20,7 +20,7 @@ import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "../../services/infestedFoundryService.ts"; -import { sendWsBroadcastTo } from "../../services/wsService.ts"; +import { sendWsBroadcastToGame } from "../../services/wsService.ts"; export const infestedFoundryController: RequestHandler = async (req, res) => { const account = await getAccountForRequest(req); @@ -364,7 +364,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => { ); addRecipes(inventory, recipeChanges); await inventory.save(); - sendWsBroadcastTo(account._id.toString(), { sync_inventory: true }); + sendWsBroadcastToGame(account._id.toString(), { sync_inventory: true }); } res.end(); break; diff --git a/src/controllers/custom/unlockAllCapturaScenesController.ts b/src/controllers/custom/unlockAllCapturaScenesController.ts index 7565ffa2..a5eb6215 100644 --- a/src/controllers/custom/unlockAllCapturaScenesController.ts +++ b/src/controllers/custom/unlockAllCapturaScenesController.ts @@ -2,7 +2,7 @@ import type { RequestHandler } from "express"; import { ExportResources, ExportVirtuals } from "warframe-public-export-plus"; import { getAccountIdForRequest } from "../../services/loginService.ts"; import { addItem, getInventory } from "../../services/inventoryService.ts"; -import { sendWsBroadcastTo } from "../../services/wsService.ts"; +import { sendWsBroadcastToGame } from "../../services/wsService.ts"; export const unlockAllCapturaScenesController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); @@ -19,7 +19,7 @@ export const unlockAllCapturaScenesController: RequestHandler = async (req, res) await inventory.save(); res.end(); if (needSync) { - sendWsBroadcastTo(accountId, { sync_inventory: true }); + sendWsBroadcastToGame(accountId, { sync_inventory: true }); } }; diff --git a/src/controllers/custom/unlockAllIntrinsicsController.ts b/src/controllers/custom/unlockAllIntrinsicsController.ts index ad7fc107..6e61596f 100644 --- a/src/controllers/custom/unlockAllIntrinsicsController.ts +++ b/src/controllers/custom/unlockAllIntrinsicsController.ts @@ -1,7 +1,7 @@ import { getInventory } from "../../services/inventoryService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts"; import type { RequestHandler } from "express"; -import { sendWsBroadcastTo } from "../../services/wsService.ts"; +import { sendWsBroadcastToGame } from "../../services/wsService.ts"; export const unlockAllIntrinsicsController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); @@ -17,5 +17,5 @@ export const unlockAllIntrinsicsController: RequestHandler = async (req, res) => inventory.PlayerSkills.LPS_DRIFT_ENDURANCE = 10; await inventory.save(); res.end(); - sendWsBroadcastTo(accountId, { sync_inventory: true }); + sendWsBroadcastToGame(accountId, { sync_inventory: true }); }; diff --git a/src/controllers/custom/unlockAllProfitTakerStagesController.ts b/src/controllers/custom/unlockAllProfitTakerStagesController.ts index 4c362d3d..a46dd23b 100644 --- a/src/controllers/custom/unlockAllProfitTakerStagesController.ts +++ b/src/controllers/custom/unlockAllProfitTakerStagesController.ts @@ -1,7 +1,7 @@ import { getInventory } from "../../services/inventoryService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts"; import type { RequestHandler } from "express"; -import { sendWsBroadcastTo } from "../../services/wsService.ts"; +import { sendWsBroadcastToGame } from "../../services/wsService.ts"; const allEudicoHeistJobs = [ "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyOne", @@ -22,5 +22,5 @@ export const unlockAllProfitTakerStagesController: RequestHandler = async (req, } await inventory.save(); res.end(); - sendWsBroadcastTo(accountId, { sync_inventory: true }); + sendWsBroadcastToGame(accountId, { sync_inventory: true }); }; diff --git a/src/controllers/custom/unlockAllSimarisResearchEntriesController.ts b/src/controllers/custom/unlockAllSimarisResearchEntriesController.ts index a12626dd..3e013292 100644 --- a/src/controllers/custom/unlockAllSimarisResearchEntriesController.ts +++ b/src/controllers/custom/unlockAllSimarisResearchEntriesController.ts @@ -1,7 +1,7 @@ import type { RequestHandler } from "express"; import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getInventory } from "../../services/inventoryService.ts"; -import { sendWsBroadcastTo } from "../../services/wsService.ts"; +import { sendWsBroadcastToGame } from "../../services/wsService.ts"; export const unlockAllSimarisResearchEntriesController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); @@ -18,5 +18,5 @@ export const unlockAllSimarisResearchEntriesController: RequestHandler = async ( ].map(type => ({ TargetType: type, Scans: 10, Completed: true })); await inventory.save(); res.end(); - sendWsBroadcastTo(accountId, { sync_inventory: true }); + sendWsBroadcastToGame(accountId, { sync_inventory: true }); }; diff --git a/src/controllers/custom/updateFingerprintController.ts b/src/controllers/custom/updateFingerprintController.ts index 48e83c85..8278744e 100644 --- a/src/controllers/custom/updateFingerprintController.ts +++ b/src/controllers/custom/updateFingerprintController.ts @@ -2,7 +2,7 @@ import { getInventory } from "../../services/inventoryService.ts"; import type { WeaponTypeInternal } from "../../services/itemDataService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts"; import type { RequestHandler } from "express"; -import { sendWsBroadcastTo } from "../../services/wsService.ts"; +import { sendWsBroadcastToGame } from "../../services/wsService.ts"; export const updateFingerprintController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); @@ -23,7 +23,7 @@ export const updateFingerprintController: RequestHandler = async (req, res) => { await inventory.save(); } res.end(); - sendWsBroadcastTo(accountId, { sync_inventory: true }); + sendWsBroadcastToGame(accountId, { sync_inventory: true }); }; interface IUpdateFingerPrintRequest { diff --git a/src/services/wsService.ts b/src/services/wsService.ts index 74bad174..87a834d7 100644 --- a/src/services/wsService.ts +++ b/src/services/wsService.ts @@ -222,6 +222,15 @@ export const sendWsBroadcastTo = (accountId: string, data: IWsMsgToClient): void }); }; +export const sendWsBroadcastToGame = (accountId: string, data: IWsMsgToClient): void => { + const msg = JSON.stringify(data); + forEachClient(client => { + if (client.isGame && client.accountId == accountId) { + client.send(msg); + } + }); +}; + export const sendWsBroadcastEx = (data: IWsMsgToClient, accountId: string | undefined, excludeWsid: number): void => { const msg = JSON.stringify(data); forEachClient(client => { -- 2.47.2 From 85db8e21c50e2991034484d0c352cf3f5c9ec20e Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 8 Sep 2025 05:53:50 +0200 Subject: [PATCH 14/19] simplify broadcastInventoryUpdate --- src/services/wsService.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/services/wsService.ts b/src/services/wsService.ts index 87a834d7..edac93bd 100644 --- a/src/services/wsService.ts +++ b/src/services/wsService.ts @@ -242,13 +242,16 @@ export const sendWsBroadcastEx = (data: IWsMsgToClient, accountId: string | unde export const broadcastInventoryUpdate = (req: Request): void => { const accountId = req.query.accountId as string; - - // for webui requests, let other tabs know. for game requests, let all tabs know. - sendWsBroadcastEx({ update_inventory: true }, accountId, parseInt(String(req.query.wsid))); - - // for webui requests, also let the game know. if (req.query.wsid) { - sendWsBroadcastTo(accountId, { sync_inventory: true }); + // for webui requests, let other tabs and the game know + sendWsBroadcastEx( + { sync_inventory: true, update_inventory: true }, + accountId, + parseInt(String(req.query.wsid)) + ); + } else { + // for game requests, let all webui tabs know + sendWsBroadcastEx({ update_inventory: true }, accountId, parseInt(String(req.query.wsid))); // TODO: exclude game ws from this somehow } }; -- 2.47.2 From ae9030d2cee398a0162980a2fcd805ef6b5d1d6b Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 8 Sep 2025 05:57:39 +0200 Subject: [PATCH 15/19] add sendWsBroadcastToWebui --- src/services/wsService.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/services/wsService.ts b/src/services/wsService.ts index edac93bd..752c74b7 100644 --- a/src/services/wsService.ts +++ b/src/services/wsService.ts @@ -240,6 +240,19 @@ export const sendWsBroadcastEx = (data: IWsMsgToClient, accountId: string | unde }); }; +export const sendWsBroadcastToWebui = ( + data: IWsMsgToClient, + accountId: string | undefined, + excludeWsid: number +): void => { + const msg = JSON.stringify(data); + forEachClient(client => { + if (!client.isGame && (!accountId || client.accountId == accountId) && client.id != excludeWsid) { + client.send(msg); + } + }); +}; + export const broadcastInventoryUpdate = (req: Request): void => { const accountId = req.query.accountId as string; if (req.query.wsid) { @@ -251,7 +264,7 @@ export const broadcastInventoryUpdate = (req: Request): void => { ); } else { // for game requests, let all webui tabs know - sendWsBroadcastEx({ update_inventory: true }, accountId, parseInt(String(req.query.wsid))); // TODO: exclude game ws from this somehow + sendWsBroadcastToWebui({ update_inventory: true }, accountId, parseInt(String(req.query.wsid))); } }; -- 2.47.2 From 88f343f39aae7fc49de29e0b02846367b2f8af12 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 8 Sep 2025 06:09:26 +0200 Subject: [PATCH 16/19] let webui know when game ws is available --- src/services/wsService.ts | 40 ++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/services/wsService.ts b/src/services/wsService.ts index 752c74b7..21244273 100644 --- a/src/services/wsService.ts +++ b/src/services/wsService.ts @@ -86,6 +86,7 @@ interface IWsMsgToClient { nonce_updated?: boolean; update_inventory?: boolean; logged_out?: boolean; + have_game_ws?: boolean; // to game sync_inventory?: boolean; @@ -137,7 +138,8 @@ const wsOnConnect = (ws: WebSocket, req: http.IncomingMessage): void => { id: account.id, DisplayName: account.DisplayName, Nonce: account.Nonce - } + }, + have_game_ws: haveGameWs(account.id) } satisfies IWsMsgToClient) ); } else { @@ -160,6 +162,7 @@ const wsOnConnect = (ws: WebSocket, req: http.IncomingMessage): void => { if (account) { (ws as IWsCustomData).accountId = account.id; logger.debug(`got bootstrapper connection for ${account.id}`); + sendWsBroadcastToWebui({ have_game_ws: true }, account.id); } } } @@ -184,11 +187,15 @@ const wsOnConnect = (ws: WebSocket, req: http.IncomingMessage): void => { ws.on("close", async () => { if ((ws as IWsCustomData).isGame && (ws as IWsCustomData).accountId) { logger.debug(`lost bootstrapper connection for ${(ws as IWsCustomData).accountId}`); - const account = await Account.findOne({ _id: (ws as IWsCustomData).accountId }); - if (account?.Nonce) { - account.Dropped = true; - await account.save(); - } + sendWsBroadcastToWebui({ have_game_ws: false }, (ws as IWsCustomData).accountId); + await Account.updateOne( + { + _id: (ws as IWsCustomData).accountId + }, + { + Dropped: true + } + ); } }); }; @@ -206,6 +213,16 @@ const forEachClient = (cb: (client: IWsCustomData) => void): void => { } }; +export const haveGameWs = (accountId: string): boolean => { + let ret = false; + forEachClient(client => { + if (client.isGame && client.accountId == accountId) { + ret = true; + } + }); + return ret; +}; + export const sendWsBroadcast = (data: IWsMsgToClient): void => { const msg = JSON.stringify(data); forEachClient(client => { @@ -231,7 +248,7 @@ export const sendWsBroadcastToGame = (accountId: string, data: IWsMsgToClient): }); }; -export const sendWsBroadcastEx = (data: IWsMsgToClient, accountId: string | undefined, excludeWsid: number): void => { +export const sendWsBroadcastEx = (data: IWsMsgToClient, accountId?: string, excludeWsid?: number): void => { const msg = JSON.stringify(data); forEachClient(client => { if ((!accountId || client.accountId == accountId) && client.id != excludeWsid) { @@ -240,11 +257,7 @@ export const sendWsBroadcastEx = (data: IWsMsgToClient, accountId: string | unde }); }; -export const sendWsBroadcastToWebui = ( - data: IWsMsgToClient, - accountId: string | undefined, - excludeWsid: number -): void => { +export const sendWsBroadcastToWebui = (data: IWsMsgToClient, accountId?: string, excludeWsid?: number): void => { const msg = JSON.stringify(data); forEachClient(client => { if (!client.isGame && (!accountId || client.accountId == accountId) && client.id != excludeWsid) { @@ -272,9 +285,10 @@ export const handleNonceInvalidation = (accountId: string): void => { forEachClient(client => { if (client.accountId == accountId) { if (client.isGame) { + client.accountId = undefined; // prevent processing of the close event client.close(); } else { - client.send(JSON.stringify({ nonce_updated: true } satisfies IWsMsgToClient)); + client.send(JSON.stringify({ nonce_updated: true, have_game_ws: false } satisfies IWsMsgToClient)); } } }); -- 2.47.2 From d52cbb9ac48d5e1c08831703a9c4f311b8baeef9 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 8 Sep 2025 06:09:02 +0200 Subject: [PATCH 17/19] sync inventory when focus schools were unlocked --- src/services/wsService.ts | 29 +++++++++++++++++++---------- static/webui/script.js | 3 +++ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/services/wsService.ts b/src/services/wsService.ts index 21244273..0e35d0c2 100644 --- a/src/services/wsService.ts +++ b/src/services/wsService.ts @@ -62,6 +62,7 @@ interface IWsMsgFromClient { nonce: number; }; logout?: boolean; + sync_inventory?: boolean; } interface IWsMsgToClient { @@ -168,16 +169,24 @@ const wsOnConnect = (ws: WebSocket, req: http.IncomingMessage): void => { } if (data.logout) { const accountId = (ws as IWsCustomData).accountId; - (ws as IWsCustomData).accountId = undefined; - await Account.updateOne( - { - _id: accountId, - ClientType: "webui" - }, - { - Nonce: 0 - } - ); + if (accountId) { + (ws as IWsCustomData).accountId = undefined; + await Account.updateOne( + { + _id: accountId, + ClientType: "webui" + }, + { + Nonce: 0 + } + ); + } + } + if (data.sync_inventory) { + const accountId = (ws as IWsCustomData).accountId; + if (accountId) { + sendWsBroadcastToGame(accountId, { sync_inventory: true }); + } } } catch (e) { logError(e as Error, `processing websocket message`); diff --git a/static/webui/script.js b/static/webui/script.js index 9d15b4fe..0a757340 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -2993,6 +2993,9 @@ function doUnlockAllFocusSchools() { toast(loc("code_focusAllUnlocked")); } else { toast(loc("code_focusUnlocked").split("|COUNT|").join(Object.keys(missingFocusUpgrades).length)); + if (ws_is_open) { + window.ws.send(JSON.stringify({ sync_inventory: true })); + } } }); }); -- 2.47.2 From 22a9cd24366d17fced87b4a7b013773fa4e2d2e5 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 8 Sep 2025 06:22:04 +0200 Subject: [PATCH 18/19] update webui messaging around syncing when game ws is available --- static/webui/index.html | 6 +++--- static/webui/script.js | 15 +++++++++++---- static/webui/translations/de.js | 1 + static/webui/translations/en.js | 1 + static/webui/translations/es.js | 1 + static/webui/translations/fr.js | 1 + static/webui/translations/ru.js | 1 + static/webui/translations/uk.js | 1 + static/webui/translations/zh.js | 1 + 9 files changed, 21 insertions(+), 7 deletions(-) diff --git a/static/webui/index.html b/static/webui/index.html index fb09e919..2ad2751d 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -93,7 +93,7 @@
-

+

-

+

@@ -771,7 +771,7 @@
-

+

diff --git a/static/webui/script.js b/static/webui/script.js index 0a757340..7d01f47d 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -90,6 +90,12 @@ function openWebSocket() { if ("logged_out" in msg) { logout(); } + if ("have_game_ws" in msg) { + window.have_game_ws = msg.have_game_ws; + if (window.dict) { + $(".inventory-update-note").text(loc(msg.have_game_ws ? "general_inventoryUpdateNoteGameWs" : "general_inventoryUpdateNote")); + } + } }; window.ws.onclose = function () { ws_is_open = false; @@ -223,6 +229,7 @@ function updateLocElements() { document.querySelectorAll("[data-loc-replace]").forEach(elm => { elm.innerHTML = elm.innerHTML.replace("|VAL|", elm.getAttribute("data-loc-replace")); }); + $(".inventory-update-note").text(loc(window.have_game_ws ? "general_inventoryUpdateNoteGameWs" : "general_inventoryUpdateNote")); } function setActiveLanguage(lang) { @@ -3468,13 +3475,13 @@ async function doUnlockAllScans() { async function doUnlockAllShipFeatures() { await revalidateAuthz(); await fetch("/custom/unlockAllShipFeatures?" + window.authz); - toast(loc("cheats_unlockSuccInventory")); + toast(loc(window.have_game_ws ? "code_succAdded" : "cheats_unlockSuccInventory")); } async function doUnlockAllCapturaScenes() { await revalidateAuthz(); await fetch("/custom/unlockAllCapturaScenes?" + window.authz); - toast(loc("cheats_unlockSuccInventory")); + toast(loc(window.have_game_ws ? "code_succAdded" : "cheats_unlockSuccInventory")); } async function unlockAllMissions() { @@ -3486,13 +3493,13 @@ async function unlockAllMissions() { async function unlockAllProfitTakerStages() { await revalidateAuthz(); await fetch("/custom/unlockAllProfitTakerStages?" + window.authz); - toast(loc("cheats_unlockSuccInventory")); + toast(loc(window.have_game_ws ? "code_succAdded" : "cheats_unlockSuccInventory")); } async function unlockAllSimarisResearchEntries() { await revalidateAuthz(); await fetch("/custom/unlockAllSimarisResearchEntries?" + window.authz); - toast(loc("cheats_unlockSuccInventory")); + toast(loc(window.have_game_ws ? "code_succAdded" : "cheats_unlockSuccInventory")); } const importSamples = { diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index cb23eda8..d370142c 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -1,6 +1,7 @@ // German translation by Animan8000 dict = { general_inventoryUpdateNote: `Hinweis: Um Änderungen im Spiel zu sehen, musst du dein Inventar neu synchronisieren, z. B. mit dem /sync Befehl des Bootstrappers, durch Besuch eines Dojo/Relais oder durch erneutes Einloggen.`, + general_inventoryUpdateNoteGameWs: `[UNTRANSLATED] Note: You may need to reopen any menu you are on for changes to be reflected.`, general_addButton: `Hinzufügen`, general_setButton: `Festlegen`, general_none: `Keines`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index d47ebf90..5f82beca 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -1,5 +1,6 @@ dict = { general_inventoryUpdateNote: `Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`, + general_inventoryUpdateNoteGameWs: `Note: You may need to reopen any menu you are on for changes to be reflected.`, general_addButton: `Add`, general_setButton: `Set`, general_none: `None`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index 546c019d..337cc27a 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -1,6 +1,7 @@ // Spanish translation by hxedcl dict = { general_inventoryUpdateNote: `Para ver los cambios en el juego, necesitas volver a sincronizar tu inventario, por ejemplo, usando el comando /sync del bootstrapper, visitando un dojo o repetidor, o volviendo a iniciar sesión.`, + general_inventoryUpdateNoteGameWs: `[UNTRANSLATED] Note: You may need to reopen any menu you are on for changes to be reflected.`, general_addButton: `Agregar`, general_setButton: `Establecer`, general_none: `Ninguno`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index d2570117..6230c60d 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -1,6 +1,7 @@ // French translation by Vitruvio dict = { general_inventoryUpdateNote: `Note : Pour voir les changements en jeu, l'inventaire doit être actualisé. Cela se fait en tapant /sync dans le tchat, en visitant un dojo/relais ou en se reconnectant.`, + general_inventoryUpdateNoteGameWs: `[UNTRANSLATED] Note: You may need to reopen any menu you are on for changes to be reflected.`, general_addButton: `Ajouter`, general_setButton: `Définir`, general_none: `Aucun`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index 2b26fd38..c1157079 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -1,6 +1,7 @@ // Russian translation by AMelonInsideLemon, LoseFace dict = { general_inventoryUpdateNote: `Примечание: Чтобы увидеть изменения в игре, вам нужно повторно синхронизировать свой инвентарь, например, используя команду /sync загрузчика, посетив Додзё/Реле или перезагрузив игру.`, + general_inventoryUpdateNoteGameWs: `[UNTRANSLATED] Note: You may need to reopen any menu you are on for changes to be reflected.`, general_addButton: `Добавить`, general_setButton: `Установить`, general_none: `Отсутствует`, diff --git a/static/webui/translations/uk.js b/static/webui/translations/uk.js index f223590f..7981eae1 100644 --- a/static/webui/translations/uk.js +++ b/static/webui/translations/uk.js @@ -1,6 +1,7 @@ // Ukrainian translation by LoseFace dict = { general_inventoryUpdateNote: `Пам'ятка: Щоб побачити зміни в грі, вам потрібно повторно синхронізувати своє спорядження, наприклад, використовуючи команду /sync завантажувача, відвідавши Доджьо/Реле або перезавантаживши гру.`, + general_inventoryUpdateNoteGameWs: `[UNTRANSLATED] Note: You may need to reopen any menu you are on for changes to be reflected.`, general_addButton: `Добавити`, general_setButton: `Встановити`, general_none: `Відсутній`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index 24e65f82..4b806799 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -1,6 +1,7 @@ // Chinese translation by meb154, bishan178, nyaoouo, qianlishun, CrazyZhang, Corvus, & qingchun dict = { general_inventoryUpdateNote: `注意: 要在游戏中查看更改,您需要重新同步库存,例如使用客户端的 /sync 命令,访问道场/中继站或重新登录.`, + general_inventoryUpdateNoteGameWs: `[UNTRANSLATED] Note: You may need to reopen any menu you are on for changes to be reflected.`, general_addButton: `添加`, general_setButton: `设置`, general_none: `无`, -- 2.47.2 From 1e2341af887dceb4eabf4a7aae8625f7e35f5008 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 8 Sep 2025 06:23:21 +0200 Subject: [PATCH 19/19] prettier --- static/webui/script.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/static/webui/script.js b/static/webui/script.js index 7d01f47d..0497ec33 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -93,7 +93,9 @@ function openWebSocket() { if ("have_game_ws" in msg) { window.have_game_ws = msg.have_game_ws; if (window.dict) { - $(".inventory-update-note").text(loc(msg.have_game_ws ? "general_inventoryUpdateNoteGameWs" : "general_inventoryUpdateNote")); + $(".inventory-update-note").text( + loc(msg.have_game_ws ? "general_inventoryUpdateNoteGameWs" : "general_inventoryUpdateNote") + ); } } }; @@ -229,7 +231,9 @@ function updateLocElements() { document.querySelectorAll("[data-loc-replace]").forEach(elm => { elm.innerHTML = elm.innerHTML.replace("|VAL|", elm.getAttribute("data-loc-replace")); }); - $(".inventory-update-note").text(loc(window.have_game_ws ? "general_inventoryUpdateNoteGameWs" : "general_inventoryUpdateNote")); + $(".inventory-update-note").text( + loc(window.have_game_ws ? "general_inventoryUpdateNoteGameWs" : "general_inventoryUpdateNote") + ); } function setActiveLanguage(lang) { -- 2.47.2