From 31523ddd19a35ef90b6ce643521a8e08ce049341 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sat, 5 Jul 2025 00:35:43 +0200 Subject: [PATCH] move websocket stuff into its own service --- src/controllers/api/loginController.ts | 2 +- src/controllers/api/loginRewardsController.ts | 2 +- .../api/loginRewardsSelectionController.ts | 2 +- src/controllers/api/logoutController.ts | 2 +- .../api/missionInventoryUpdateController.ts | 2 +- src/controllers/api/nameWeaponController.ts | 2 +- src/controllers/api/purchaseController.ts | 2 +- src/controllers/api/renamePetController.ts | 2 +- src/controllers/api/sellController.ts | 2 +- src/controllers/api/upgradesController.ts | 2 +- src/controllers/custom/configController.ts | 2 +- .../webuiFileChangeDetectedController.ts | 2 +- src/services/configWatcherService.ts | 3 +- src/services/webService.ts | 192 +---------------- src/services/wsService.ts | 200 ++++++++++++++++++ 15 files changed, 218 insertions(+), 201 deletions(-) create mode 100644 src/services/wsService.ts diff --git a/src/controllers/api/loginController.ts b/src/controllers/api/loginController.ts index c7b5c16d..fabc4945 100644 --- a/src/controllers/api/loginController.ts +++ b/src/controllers/api/loginController.ts @@ -8,7 +8,7 @@ import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } f import { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "@/src/types/loginTypes"; import { logger } from "@/src/utils/logger"; import { version_compare } from "@/src/helpers/inventoryHelpers"; -import { sendWsBroadcastTo } from "@/src/services/webService"; +import { sendWsBroadcastTo } from "@/src/services/wsService"; 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 diff --git a/src/controllers/api/loginRewardsController.ts b/src/controllers/api/loginRewardsController.ts index 7678722f..547724c2 100644 --- a/src/controllers/api/loginRewardsController.ts +++ b/src/controllers/api/loginRewardsController.ts @@ -9,7 +9,7 @@ import { } from "@/src/services/loginRewardService"; import { getInventory } from "@/src/services/inventoryService"; import { config } from "@/src/services/configService"; -import { sendWsBroadcastTo } from "@/src/services/webService"; +import { sendWsBroadcastTo } from "@/src/services/wsService"; export const loginRewardsController: RequestHandler = async (req, res) => { const account = await getAccountForRequest(req); diff --git a/src/controllers/api/loginRewardsSelectionController.ts b/src/controllers/api/loginRewardsSelectionController.ts index 63e160f6..174c5659 100644 --- a/src/controllers/api/loginRewardsSelectionController.ts +++ b/src/controllers/api/loginRewardsSelectionController.ts @@ -6,7 +6,7 @@ import { } from "@/src/services/loginRewardService"; import { getAccountForRequest } from "@/src/services/loginService"; import { handleStoreItemAcquisition } from "@/src/services/purchaseService"; -import { sendWsBroadcastTo } from "@/src/services/webService"; +import { sendWsBroadcastTo } from "@/src/services/wsService"; import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { logger } from "@/src/utils/logger"; import { RequestHandler } from "express"; diff --git a/src/controllers/api/logoutController.ts b/src/controllers/api/logoutController.ts index e2074f76..d290b1ee 100644 --- a/src/controllers/api/logoutController.ts +++ b/src/controllers/api/logoutController.ts @@ -1,6 +1,6 @@ import { RequestHandler } from "express"; import { Account } from "@/src/models/loginModel"; -import { sendWsBroadcastTo } from "@/src/services/webService"; +import { sendWsBroadcastTo } from "@/src/services/wsService"; export const logoutController: RequestHandler = async (req, res) => { if (!req.query.accountId) { diff --git a/src/controllers/api/missionInventoryUpdateController.ts b/src/controllers/api/missionInventoryUpdateController.ts index da3d4198..6e57a2b9 100644 --- a/src/controllers/api/missionInventoryUpdateController.ts +++ b/src/controllers/api/missionInventoryUpdateController.ts @@ -7,7 +7,7 @@ import { getInventory } from "@/src/services/inventoryService"; import { getInventoryResponse } from "./inventoryController"; import { logger } from "@/src/utils/logger"; import { IMissionInventoryUpdateResponse } from "@/src/types/missionTypes"; -import { sendWsBroadcastTo } from "@/src/services/webService"; +import { sendWsBroadcastTo } from "@/src/services/wsService"; import { generateRewardSeed } from "@/src/services/rngService"; /* diff --git a/src/controllers/api/nameWeaponController.ts b/src/controllers/api/nameWeaponController.ts index 8d378feb..c3c899ba 100644 --- a/src/controllers/api/nameWeaponController.ts +++ b/src/controllers/api/nameWeaponController.ts @@ -3,7 +3,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService"; import { getInventory, updateCurrency } from "@/src/services/inventoryService"; import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes"; -import { sendWsBroadcastTo } from "@/src/services/webService"; +import { sendWsBroadcastTo } from "@/src/services/wsService"; interface INameWeaponRequest { ItemName: string; diff --git a/src/controllers/api/purchaseController.ts b/src/controllers/api/purchaseController.ts index 390e8d72..69175739 100644 --- a/src/controllers/api/purchaseController.ts +++ b/src/controllers/api/purchaseController.ts @@ -3,7 +3,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService"; import { IPurchaseRequest } from "@/src/types/purchaseTypes"; import { handlePurchase } from "@/src/services/purchaseService"; import { getInventory } from "@/src/services/inventoryService"; -import { sendWsBroadcastTo } from "@/src/services/webService"; +import { sendWsBroadcastTo } from "@/src/services/wsService"; export const purchaseController: RequestHandler = async (req, res) => { const purchaseRequest = JSON.parse(String(req.body)) as IPurchaseRequest; diff --git a/src/controllers/api/renamePetController.ts b/src/controllers/api/renamePetController.ts index 40b4ec37..f3fe0c89 100644 --- a/src/controllers/api/renamePetController.ts +++ b/src/controllers/api/renamePetController.ts @@ -1,7 +1,7 @@ import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getInventory, updateCurrency } from "@/src/services/inventoryService"; import { getAccountIdForRequest } from "@/src/services/loginService"; -import { sendWsBroadcastTo } from "@/src/services/webService"; +import { sendWsBroadcastTo } from "@/src/services/wsService"; import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { RequestHandler } from "express"; diff --git a/src/controllers/api/sellController.ts b/src/controllers/api/sellController.ts index 7e2926ae..c82d3b6b 100644 --- a/src/controllers/api/sellController.ts +++ b/src/controllers/api/sellController.ts @@ -15,7 +15,7 @@ import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes"; import { ExportDojoRecipes } from "warframe-public-export-plus"; import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; -import { sendWsBroadcastTo } from "@/src/services/webService"; +import { sendWsBroadcastTo } from "@/src/services/wsService"; export const sellController: RequestHandler = async (req, res) => { const payload = JSON.parse(String(req.body)) as ISellRequest; diff --git a/src/controllers/api/upgradesController.ts b/src/controllers/api/upgradesController.ts index 1213362d..545e0038 100644 --- a/src/controllers/api/upgradesController.ts +++ b/src/controllers/api/upgradesController.ts @@ -8,7 +8,7 @@ import { getRecipeByResult } from "@/src/services/itemDataService"; import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { addInfestedFoundryXP, applyCheatsToInfestedFoundry } from "@/src/services/infestedFoundryService"; import { config } from "@/src/services/configService"; -import { sendWsBroadcastTo } from "@/src/services/webService"; +import { sendWsBroadcastTo } from "@/src/services/wsService"; import { EquipmentFeatures, IEquipmentDatabase } from "@/src/types/equipmentTypes"; export const upgradesController: RequestHandler = async (req, res) => { diff --git a/src/controllers/custom/configController.ts b/src/controllers/custom/configController.ts index 2249f48f..d64a3e3b 100644 --- a/src/controllers/custom/configController.ts +++ b/src/controllers/custom/configController.ts @@ -2,7 +2,7 @@ import { RequestHandler } from "express"; import { config } from "@/src/services/configService"; import { getAccountForRequest, isAdministrator } from "@/src/services/loginService"; import { saveConfig } from "@/src/services/configWatcherService"; -import { sendWsBroadcastExcept } from "@/src/services/webService"; +import { sendWsBroadcastExcept } from "@/src/services/wsService"; export const getConfigController: RequestHandler = async (req, res) => { const account = await getAccountForRequest(req); diff --git a/src/controllers/custom/webuiFileChangeDetectedController.ts b/src/controllers/custom/webuiFileChangeDetectedController.ts index aa6af978..5078b679 100644 --- a/src/controllers/custom/webuiFileChangeDetectedController.ts +++ b/src/controllers/custom/webuiFileChangeDetectedController.ts @@ -1,5 +1,5 @@ import { args } from "@/src/helpers/commandLineArguments"; -import { sendWsBroadcast } from "@/src/services/webService"; +import { sendWsBroadcast } from "@/src/services/wsService"; import { RequestHandler } from "express"; export const webuiFileChangeDetectedController: RequestHandler = (req, res) => { diff --git a/src/services/configWatcherService.ts b/src/services/configWatcherService.ts index 926467e6..e22a7def 100644 --- a/src/services/configWatcherService.ts +++ b/src/services/configWatcherService.ts @@ -2,7 +2,8 @@ import chokidar from "chokidar"; import fsPromises from "fs/promises"; import { logger } from "../utils/logger"; import { config, configPath, loadConfig } from "./configService"; -import { getWebPorts, sendWsBroadcast, startWebServer, stopWebServer } from "./webService"; +import { getWebPorts, startWebServer, stopWebServer } from "./webService"; +import { sendWsBroadcast } from "./wsService"; import { Inbox } from "../models/inboxModel"; import varzia from "@/static/fixed_responses/worldState/varzia.json"; diff --git a/src/services/webService.ts b/src/services/webService.ts index 11ff2654..68164298 100644 --- a/src/services/webService.ts +++ b/src/services/webService.ts @@ -5,17 +5,11 @@ import { config } from "./configService"; import { logger } from "../utils/logger"; import { app } from "../app"; import { AddressInfo } from "node:net"; -import ws from "ws"; -import { Account } from "../models/loginModel"; -import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "./loginService"; -import { IDatabaseAccountJson } from "../types/loginTypes"; -import { HydratedDocument } from "mongoose"; import { Agent, WebSocket as UnidiciWebSocket } from "undici"; +import { startWsServer, startWssServer, stopWsServers } from "./wsService"; let httpServer: http.Server | undefined; let httpsServer: https.Server | undefined; -let wsServer: ws.Server | undefined; -let wssServer: ws.Server | undefined; const tlsOptions = { key: fs.readFileSync("static/certs/key.pem"), @@ -29,16 +23,14 @@ export const startWebServer = (): void => { // eslint-disable-next-line @typescript-eslint/no-misused-promises httpServer = http.createServer(app); httpServer.listen(httpPort, () => { - wsServer = new ws.Server({ server: httpServer }); - wsServer.on("connection", wsOnConnect); + startWsServer(httpServer!); logger.info("HTTP server started on port " + httpPort); // eslint-disable-next-line @typescript-eslint/no-misused-promises httpsServer = https.createServer(tlsOptions, app); httpsServer.listen(httpsPort, () => { - wssServer = new ws.Server({ server: httpsServer }); - wssServer.on("connection", wsOnConnect); + startWssServer(httpsServer!); logger.info("HTTPS server started on port " + httpsPort); @@ -115,182 +107,6 @@ export const stopWebServer = async (): Promise => { }) ); } - if (wsServer) { - promises.push( - new Promise(resolve => { - wsServer!.close(() => { - resolve(); - }); - }) - ); - } - if (wssServer) { - promises.push( - new Promise(resolve => { - wssServer!.close(() => { - resolve(); - }); - }) - ); - } + stopWsServers(promises); await Promise.all(promises); }; - -let lastWsid: number = 0; - -interface IWsCustomData extends ws { - id?: number; - accountId?: string; -} - -interface IWsMsgFromClient { - auth?: { - email: string; - password: string; - isRegister: boolean; - }; - logout?: boolean; -} - -interface IWsMsgToClient { - //wsid?: number; - reload?: boolean; - ports?: { - http: number | undefined; - https: number | undefined; - }; - config_reloaded?: boolean; - auth_succ?: { - id: string; - DisplayName: string; - Nonce: number; - }; - auth_fail?: { - isRegister: boolean; - }; - logged_out?: boolean; - update_inventory?: boolean; -} - -const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => { - if (req.url == "/custom/selftest") { - ws.send("SpaceNinjaServer"); - ws.close(); - return; - } - - (ws as IWsCustomData).id = ++lastWsid; - ws.send(JSON.stringify({ wsid: lastWsid })); - - // eslint-disable-next-line @typescript-eslint/no-misused-promises - ws.on("message", async msg => { - const data = JSON.parse(String(msg)) as IWsMsgFromClient; - if (data.auth) { - let account: IDatabaseAccountJson | null = await Account.findOne({ email: data.auth.email }); - if (account) { - if (isCorrectPassword(data.auth.password, account.password)) { - if (!account.Nonce) { - account.ClientType = "webui"; - account.Nonce = createNonce(); - await (account as HydratedDocument).save(); - } - } else { - account = null; - } - } else if (data.auth.isRegister) { - const name = await getUsernameFromEmail(data.auth.email); - account = await createAccount({ - email: data.auth.email, - password: data.auth.password, - ClientType: "webui", - LastLogin: new Date(), - DisplayName: name, - Nonce: createNonce() - }); - } - if (account) { - (ws as IWsCustomData).accountId = account.id; - ws.send( - JSON.stringify({ - auth_succ: { - id: account.id, - DisplayName: account.DisplayName, - Nonce: account.Nonce - } - } satisfies IWsMsgToClient) - ); - } else { - ws.send( - JSON.stringify({ - auth_fail: { - isRegister: data.auth.isRegister - } - } satisfies IWsMsgToClient) - ); - } - } - if (data.logout) { - const accountId = (ws as IWsCustomData).accountId; - (ws as IWsCustomData).accountId = undefined; - await Account.updateOne( - { - _id: accountId, - ClientType: "webui" - }, - { - Nonce: 0 - } - ); - } - }); -}; - -export const sendWsBroadcast = (data: IWsMsgToClient): 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); - } - } -}; - -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); - } - } - } - if (wssServer) { - for (const client of wssServer.clients) { - if ((client as IWsCustomData).accountId == accountId) { - client.send(msg); - } - } - } -}; - -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); - } - } - } -}; diff --git a/src/services/wsService.ts b/src/services/wsService.ts new file mode 100644 index 00000000..3eb11048 --- /dev/null +++ b/src/services/wsService.ts @@ -0,0 +1,200 @@ +import http from "http"; +import https from "https"; +import ws from "ws"; +import { Account } from "../models/loginModel"; +import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "./loginService"; +import { IDatabaseAccountJson } from "../types/loginTypes"; +import { HydratedDocument } from "mongoose"; + +let wsServer: ws.Server | undefined; +let wssServer: ws.Server | undefined; + +export const startWsServer = (httpServer: http.Server): void => { + wsServer = new ws.Server({ server: httpServer }); + wsServer.on("connection", wsOnConnect); +}; + +export const startWssServer = (httpsServer: https.Server): void => { + wssServer = new ws.Server({ server: httpsServer }); + wssServer.on("connection", wsOnConnect); +}; + +export const stopWsServers = (promises: Promise[]): void => { + if (wsServer) { + promises.push( + new Promise(resolve => { + wsServer!.close(() => { + resolve(); + }); + }) + ); + } + if (wssServer) { + promises.push( + new Promise(resolve => { + wssServer!.close(() => { + resolve(); + }); + }) + ); + } +}; + +let lastWsid: number = 0; + +interface IWsCustomData extends ws { + id?: number; + accountId?: string; +} + +interface IWsMsgFromClient { + auth?: { + email: string; + password: string; + isRegister: boolean; + }; + logout?: boolean; +} + +interface IWsMsgToClient { + //wsid?: number; + reload?: boolean; + ports?: { + http: number | undefined; + https: number | undefined; + }; + config_reloaded?: boolean; + auth_succ?: { + id: string; + DisplayName: string; + Nonce: number; + }; + auth_fail?: { + isRegister: boolean; + }; + logged_out?: boolean; + update_inventory?: boolean; +} + +const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => { + if (req.url == "/custom/selftest") { + ws.send("SpaceNinjaServer"); + ws.close(); + return; + } + + (ws as IWsCustomData).id = ++lastWsid; + ws.send(JSON.stringify({ wsid: lastWsid })); + + // eslint-disable-next-line @typescript-eslint/no-misused-promises + ws.on("message", async msg => { + const data = JSON.parse(String(msg)) as IWsMsgFromClient; + if (data.auth) { + let account: IDatabaseAccountJson | null = await Account.findOne({ email: data.auth.email }); + if (account) { + if (isCorrectPassword(data.auth.password, account.password)) { + if (!account.Nonce) { + account.ClientType = "webui"; + account.Nonce = createNonce(); + await (account as HydratedDocument).save(); + } + } else { + account = null; + } + } else if (data.auth.isRegister) { + const name = await getUsernameFromEmail(data.auth.email); + account = await createAccount({ + email: data.auth.email, + password: data.auth.password, + ClientType: "webui", + LastLogin: new Date(), + DisplayName: name, + Nonce: createNonce() + }); + } + if (account) { + (ws as IWsCustomData).accountId = account.id; + ws.send( + JSON.stringify({ + auth_succ: { + id: account.id, + DisplayName: account.DisplayName, + Nonce: account.Nonce + } + } satisfies IWsMsgToClient) + ); + } else { + ws.send( + JSON.stringify({ + auth_fail: { + isRegister: data.auth.isRegister + } + } satisfies IWsMsgToClient) + ); + } + } + if (data.logout) { + const accountId = (ws as IWsCustomData).accountId; + (ws as IWsCustomData).accountId = undefined; + await Account.updateOne( + { + _id: accountId, + ClientType: "webui" + }, + { + Nonce: 0 + } + ); + } + }); +}; + +export const sendWsBroadcast = (data: IWsMsgToClient): 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); + } + } +}; + +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); + } + } + } + if (wssServer) { + for (const client of wssServer.clients) { + if ((client as IWsCustomData).accountId == accountId) { + client.send(msg); + } + } + } +}; + +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); + } + } + } +};