From f82bd09ee49ea3eed798969796ca8d776995ceb3 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] 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 => {