feat: support websocket connections from game client (#2735)

For bootstrapper v0.11.11, out now.

Reviewed-on: OpenWF/SpaceNinjaServer#2735
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
This commit is contained in:
Sainan 2025-09-10 00:00:09 -07:00 committed by Sainan
parent d64531f4b2
commit 0d388b4b0f
42 changed files with 255 additions and 93 deletions

View File

@ -3,6 +3,7 @@ import { getAccountIdForRequest } from "../../services/loginService.ts";
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
import type { IInventoryClient, IUpgradeClient } from "../../types/inventoryTypes/inventoryTypes.ts"; import type { IInventoryClient, IUpgradeClient } from "../../types/inventoryTypes/inventoryTypes.ts";
import { addMods, getInventory } from "../../services/inventoryService.ts"; import { addMods, getInventory } from "../../services/inventoryService.ts";
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
export const artifactsController: RequestHandler = async (req, res) => { export const artifactsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -57,6 +58,7 @@ export const artifactsController: RequestHandler = async (req, res) => {
} }
res.send(itemId); res.send(itemId);
broadcastInventoryUpdate(req);
}; };
interface IArtifactsRequest { interface IArtifactsRequest {

View File

@ -1,6 +1,6 @@
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts"; 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 { getJSONfromString } from "../../helpers/stringHelpers.ts";
import { addMiscItems, getInventory } from "../../services/inventoryService.ts"; import { addMiscItems, getInventory } from "../../services/inventoryService.ts";
import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts"; import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
@ -75,5 +75,5 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
InventoryChanges: inventoryChanges, InventoryChanges: inventoryChanges,
AffiliationMods: affiliationMods AffiliationMods: affiliationMods
}); });
sendWsBroadcastTo(accountId, { update_inventory: true }); broadcastInventoryUpdate(req);
}; };

View File

@ -20,6 +20,7 @@ import {
applyCheatsToInfestedFoundry, applyCheatsToInfestedFoundry,
handleSubsumeCompletion handleSubsumeCompletion
} from "../../services/infestedFoundryService.ts"; } from "../../services/infestedFoundryService.ts";
import { sendWsBroadcastToGame } from "../../services/wsService.ts";
export const infestedFoundryController: RequestHandler = async (req, res) => { export const infestedFoundryController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req); const account = await getAccountForRequest(req);
@ -363,6 +364,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
); );
addRecipes(inventory, recipeChanges); addRecipes(inventory, recipeChanges);
await inventory.save(); await inventory.save();
sendWsBroadcastToGame(account._id.toString(), { sync_inventory: true });
} }
res.end(); res.end();
break; break;

View File

@ -8,7 +8,7 @@ import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } f
import type { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "../../types/loginTypes.ts"; import type { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "../../types/loginTypes.ts";
import { logger } from "../../utils/logger.ts"; import { logger } from "../../utils/logger.ts";
import { version_compare } from "../../helpers/inventoryHelpers.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) => { 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 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(); account.LastLogin = new Date();
await account.save(); await account.save();
sendWsBroadcastTo(account._id.toString(), { nonce_updated: true }); handleNonceInvalidation(account._id.toString());
response.json(createLoginResponse(myAddress, myUrlBase, account.toJSON(), buildLabel)); response.json(createLoginResponse(myAddress, myUrlBase, account.toJSON(), buildLabel));
}; };

View File

@ -1,6 +1,6 @@
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
import { Account } from "../../models/loginModel.ts"; 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) => { export const logoutController: RequestHandler = async (req, res) => {
if (!req.query.accountId) { if (!req.query.accountId) {
@ -21,7 +21,7 @@ export const logoutController: RequestHandler = async (req, res) => {
} }
); );
if (stat.modifiedCount) { if (stat.modifiedCount) {
sendWsBroadcastTo(req.query.accountId as string, { nonce_updated: true }); handleNonceInvalidation(req.query.accountId as string);
} }
res.writeHead(200, { res.writeHead(200, {

View File

@ -2,6 +2,7 @@ import { getJSONfromString } from "../../helpers/stringHelpers.ts";
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "../../services/inventoryService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts";
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
export const maturePetController: RequestHandler = async (req, res) => { export const maturePetController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); 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], : [details.DominantTraits.FurPattern, details.DominantTraits.FurPattern, details.DominantTraits.FurPattern],
unmature: data.revert unmature: data.revert
}); });
broadcastInventoryUpdate(req);
}; };
interface IMaturePetRequest { interface IMaturePetRequest {

View File

@ -1,6 +1,6 @@
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts"; 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 { getJSONfromString } from "../../helpers/stringHelpers.ts";
import { import {
getInventory, getInventory,
@ -197,5 +197,5 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
MiscItems: miscItemChanges MiscItems: miscItemChanges
} }
}); });
sendWsBroadcastTo(accountId, { update_inventory: true }); broadcastInventoryUpdate(req);
}; };

View File

@ -3,7 +3,7 @@ import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getInventory, updateCurrency } from "../../services/inventoryService.ts"; import { getInventory, updateCurrency } from "../../services/inventoryService.ts";
import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { getJSONfromString } from "../../helpers/stringHelpers.ts";
import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts"; import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
import { sendWsBroadcastTo } from "../../services/wsService.ts"; import { broadcastInventoryUpdate } from "../../services/wsService.ts";
interface INameWeaponRequest { interface INameWeaponRequest {
ItemName: string; ItemName: string;
@ -28,5 +28,5 @@ export const nameWeaponController: RequestHandler = async (req, res) => {
res.json({ res.json({
InventoryChanges: currencyChanges InventoryChanges: currencyChanges
}); });
sendWsBroadcastTo(accountId, { update_inventory: true }); broadcastInventoryUpdate(req);
}; };

View File

@ -1,7 +1,7 @@
import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { getJSONfromString } from "../../helpers/stringHelpers.ts";
import { getInventory, updateCurrency } from "../../services/inventoryService.ts"; import { getInventory, updateCurrency } from "../../services/inventoryService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts";
import { sendWsBroadcastTo } from "../../services/wsService.ts"; import { broadcastInventoryUpdate } from "../../services/wsService.ts";
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
export const releasePetController: RequestHandler = async (req, res) => { export const releasePetController: RequestHandler = async (req, res) => {
@ -20,7 +20,7 @@ export const releasePetController: RequestHandler = async (req, res) => {
await inventory.save(); await inventory.save();
res.json({ inventoryChanges }); // Not a mistake; it's "inventoryChanges" here. res.json({ inventoryChanges }); // Not a mistake; it's "inventoryChanges" here.
sendWsBroadcastTo(accountId, { update_inventory: true }); broadcastInventoryUpdate(req);
}; };
interface IReleasePetRequest { interface IReleasePetRequest {

View File

@ -1,7 +1,7 @@
import { getJSONfromString } from "../../helpers/stringHelpers.ts"; import { getJSONfromString } from "../../helpers/stringHelpers.ts";
import { getInventory, updateCurrency } from "../../services/inventoryService.ts"; import { getInventory, updateCurrency } from "../../services/inventoryService.ts";
import { getAccountIdForRequest } from "../../services/loginService.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 { IInventoryChanges } from "../../types/purchaseTypes.ts";
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
@ -23,7 +23,7 @@ export const renamePetController: RequestHandler = async (req, res) => {
...data, ...data,
inventoryChanges: inventoryChanges inventoryChanges: inventoryChanges
}); });
sendWsBroadcastTo(accountId, { update_inventory: true }); broadcastInventoryUpdate(req);
}; };
interface IRenamePetRequest { interface IRenamePetRequest {

View File

@ -17,7 +17,7 @@ import { InventorySlot } from "../../types/inventoryTypes/inventoryTypes.ts";
import { ExportDojoRecipes } from "warframe-public-export-plus"; import { ExportDojoRecipes } from "warframe-public-export-plus";
import type { IInventoryChanges } from "../../types/purchaseTypes.ts"; import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
import type { TInventoryDatabaseDocument } from "../../models/inventoryModels/inventoryModel.ts"; import type { TInventoryDatabaseDocument } from "../../models/inventoryModels/inventoryModel.ts";
import { sendWsBroadcastEx } from "../../services/wsService.ts"; import { broadcastInventoryUpdate } from "../../services/wsService.ts";
import { parseFusionTreasure } from "../../helpers/inventoryHelpers.ts"; import { parseFusionTreasure } from "../../helpers/inventoryHelpers.ts";
export const sellController: RequestHandler = async (req, res) => { export const sellController: RequestHandler = async (req, res) => {
@ -307,7 +307,7 @@ export const sellController: RequestHandler = async (req, res) => {
res.json({ res.json({
inventoryChanges: inventoryChanges // "inventoryChanges" for this response instead of the usual "InventoryChanges" inventoryChanges: inventoryChanges // "inventoryChanges" for this response instead of the usual "InventoryChanges"
}); });
sendWsBroadcastEx({ update_inventory: true }, accountId, parseInt(String(req.query.wsid))); broadcastInventoryUpdate(req);
}; };
interface ISellRequest { interface ISellRequest {

View File

@ -1,6 +1,7 @@
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts";
import { Inventory } from "../../models/inventoryModels/inventoryModel.ts"; import { Inventory } from "../../models/inventoryModels/inventoryModel.ts";
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
export const setSupportedSyndicateController: RequestHandler = async (req, res) => { export const setSupportedSyndicateController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -15,4 +16,5 @@ export const setSupportedSyndicateController: RequestHandler = async (req, res)
); );
res.end(); res.end();
broadcastInventoryUpdate(req);
}; };

View File

@ -1,5 +1,6 @@
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "../../services/inventoryService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts";
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts"; import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
@ -19,6 +20,7 @@ export const abilityOverrideController: RequestHandler = async (req, res) => {
} }
} }
res.end(); res.end();
broadcastInventoryUpdate(req);
}; };
interface IAbilityOverrideRequest { interface IAbilityOverrideRequest {

View File

@ -3,6 +3,7 @@ import { getAccountIdForRequest } from "../../services/loginService.ts";
import { addFusionPoints, getInventory } from "../../services/inventoryService.ts"; import { addFusionPoints, getInventory } from "../../services/inventoryService.ts";
import { getGuildForRequestEx, hasGuildPermission } from "../../services/guildService.ts"; import { getGuildForRequestEx, hasGuildPermission } from "../../services/guildService.ts";
import { GuildPermission } from "../../types/guildTypes.ts"; import { GuildPermission } from "../../types/guildTypes.ts";
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
export const addCurrencyController: RequestHandler = async (req, res) => { export const addCurrencyController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -24,6 +25,7 @@ export const addCurrencyController: RequestHandler = async (req, res) => {
} }
if (!request.currency.startsWith("Vault")) { if (!request.currency.startsWith("Vault")) {
await inventory.save(); await inventory.save();
broadcastInventoryUpdate(req);
} }
res.end(); res.end();
}; };

View File

@ -1,6 +1,7 @@
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getInventory, addItem } from "../../services/inventoryService.ts"; import { getInventory, addItem } from "../../services/inventoryService.ts";
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
export const addItemsController: RequestHandler = async (req, res) => { export const addItemsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -11,6 +12,7 @@ export const addItemsController: RequestHandler = async (req, res) => {
} }
await inventory.save(); await inventory.save();
res.end(); res.end();
broadcastInventoryUpdate(req);
}; };
interface IAddItemRequest { interface IAddItemRequest {

View File

@ -2,6 +2,7 @@ import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getInventory, addRecipes } from "../../services/inventoryService.ts"; import { getInventory, addRecipes } from "../../services/inventoryService.ts";
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
import { ExportRecipes } from "warframe-public-export-plus"; import { ExportRecipes } from "warframe-public-export-plus";
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
export const addMissingHelminthBlueprintsController: RequestHandler = async (req, res) => { export const addMissingHelminthBlueprintsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -21,4 +22,5 @@ export const addMissingHelminthBlueprintsController: RequestHandler = async (req
await inventory.save(); await inventory.save();
res.end(); res.end();
broadcastInventoryUpdate(req);
}; };

View File

@ -2,6 +2,7 @@ import { getInventory } from "../../services/inventoryService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts";
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
import { ExportArcanes, ExportUpgrades } from "warframe-public-export-plus"; import { ExportArcanes, ExportUpgrades } from "warframe-public-export-plus";
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
export const addMissingMaxRankModsController: RequestHandler = async (req, res) => { export const addMissingMaxRankModsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -41,4 +42,5 @@ export const addMissingMaxRankModsController: RequestHandler = async (req, res)
await inventory.save(); await inventory.save();
res.end(); res.end();
broadcastInventoryUpdate(req);
}; };

View File

@ -1,5 +1,6 @@
import { applyClientEquipmentUpdates, getInventory } from "../../services/inventoryService.ts"; import { applyClientEquipmentUpdates, getInventory } from "../../services/inventoryService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts";
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
import type { IOid } from "../../types/commonTypes.ts"; import type { IOid } from "../../types/commonTypes.ts";
import type { IEquipmentClient } from "../../types/equipmentTypes.ts"; import type { IEquipmentClient } from "../../types/equipmentTypes.ts";
import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts"; import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
@ -25,6 +26,7 @@ export const addXpController: RequestHandler = async (req, res) => {
} }
await inventory.save(); await inventory.save();
res.end(); res.end();
broadcastInventoryUpdate(req);
}; };
type IAddXpRequest = { type IAddXpRequest = {

View File

@ -1,5 +1,6 @@
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "../../services/inventoryService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts";
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts"; import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
@ -20,6 +21,7 @@ export const changeModularPartsController: RequestHandler = async (req, res) =>
await inventory.save(); await inventory.save();
} }
res.end(); res.end();
broadcastInventoryUpdate(req);
}; };
interface IUpdateFingerPrintRequest { interface IUpdateFingerPrintRequest {

View File

@ -1,6 +1,7 @@
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "../../services/inventoryService.ts";
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
const DEFAULT_UPGRADE_EXPIRY_MS = 7 * 24 * 60 * 60 * 1000; // 7 days 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(); await inventory.save();
res.end(); res.end();
broadcastInventoryUpdate(req);
}; };

View File

@ -3,6 +3,7 @@ import { getInventory } from "../../services/inventoryService.ts";
import { getLoadout } from "../../services/loadoutService.ts"; import { getLoadout } from "../../services/loadoutService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getPersonalRooms } from "../../services/personalRoomsService.ts"; import { getPersonalRooms } from "../../services/personalRoomsService.ts";
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
import type { IInventoryClient } from "../../types/inventoryTypes/inventoryTypes.ts"; import type { IInventoryClient } from "../../types/inventoryTypes/inventoryTypes.ts";
import type { IGetShipResponse } from "../../types/personalRoomsTypes.ts"; import type { IGetShipResponse } from "../../types/personalRoomsTypes.ts";
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
@ -32,6 +33,7 @@ export const importController: RequestHandler = async (req, res) => {
} }
res.end(); res.end();
broadcastInventoryUpdate(req);
}; };
interface IImportRequest { interface IImportRequest {

View File

@ -9,6 +9,7 @@ import {
import { logger } from "../../utils/logger.ts"; import { logger } from "../../utils/logger.ts";
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
import { ExportKeys } from "warframe-public-export-plus"; import { ExportKeys } from "warframe-public-export-plus";
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
export const manageQuestsController: RequestHandler = async (req, res) => { export const manageQuestsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -157,4 +158,5 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
await inventory.save(); await inventory.save();
res.status(200).end(); res.status(200).end();
broadcastInventoryUpdate(req);
}; };

View File

@ -1,6 +1,7 @@
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "../../services/inventoryService.ts";
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
export const popArchonCrystalUpgradeController: RequestHandler = async (req, res) => { export const popArchonCrystalUpgradeController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -12,6 +13,7 @@ export const popArchonCrystalUpgradeController: RequestHandler = async (req, res
); );
await inventory.save(); await inventory.save();
res.end(); res.end();
broadcastInventoryUpdate(req);
return; return;
} }
res.status(400).end(); res.status(400).end();

View File

@ -1,6 +1,7 @@
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "../../services/inventoryService.ts";
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
export const pushArchonCrystalUpgradeController: RequestHandler = async (req, res) => { export const pushArchonCrystalUpgradeController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -15,6 +16,7 @@ export const pushArchonCrystalUpgradeController: RequestHandler = async (req, re
} }
await inventory.save(); await inventory.save();
res.end(); res.end();
broadcastInventoryUpdate(req);
return; return;
} }
} }

View File

@ -1,5 +1,6 @@
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "../../services/inventoryService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts";
import { sendWsBroadcastTo } from "../../services/wsService.ts";
import type { IAccountCheats } from "../../types/inventoryTypes/inventoryTypes.ts"; import type { IAccountCheats } from "../../types/inventoryTypes/inventoryTypes.ts";
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
@ -10,6 +11,9 @@ export const setAccountCheatController: RequestHandler = async (req, res) => {
inventory[payload.key] = payload.value; inventory[payload.key] = payload.value;
await inventory.save(); await inventory.save();
res.end(); res.end();
if (["infiniteCredits", "infinitePlatinum", "infiniteEndo", "infiniteRegalAya"].indexOf(payload.key) != -1) {
sendWsBroadcastTo(accountId, { update_inventory: true, sync_inventory: true });
}
}; };
interface ISetAccountCheatRequest { interface ISetAccountCheatRequest {

View File

@ -2,6 +2,7 @@ import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "../../services/inventoryService.ts";
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
import { ExportBoosters } from "warframe-public-export-plus"; import { ExportBoosters } from "warframe-public-export-plus";
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
const I32_MAX = 0x7fffffff; const I32_MAX = 0x7fffffff;
@ -42,4 +43,5 @@ export const setBoosterController: RequestHandler = async (req, res) => {
} }
await inventory.save(); await inventory.save();
res.end(); res.end();
broadcastInventoryUpdate(req);
}; };

View File

@ -1,6 +1,7 @@
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "../../services/inventoryService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts";
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
export const setEvolutionProgressController: RequestHandler = async (req, res) => { export const setEvolutionProgressController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -25,6 +26,7 @@ export const setEvolutionProgressController: RequestHandler = async (req, res) =
await inventory.save(); await inventory.save();
res.end(); res.end();
broadcastInventoryUpdate(req);
}; };
type ISetEvolutionProgressRequest = { type ISetEvolutionProgressRequest = {

View File

@ -2,19 +2,25 @@ import type { RequestHandler } from "express";
import { ExportResources, ExportVirtuals } from "warframe-public-export-plus"; import { ExportResources, ExportVirtuals } from "warframe-public-export-plus";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts";
import { addItem, getInventory } from "../../services/inventoryService.ts"; import { addItem, getInventory } from "../../services/inventoryService.ts";
import { sendWsBroadcastToGame } from "../../services/wsService.ts";
export const unlockAllCapturaScenesController: RequestHandler = async (req, res) => { export const unlockAllCapturaScenesController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
let needSync = false;
for (const uniqueName of Object.keys(ExportResources)) { for (const uniqueName of Object.keys(ExportResources)) {
if (resourceInheritsFrom(uniqueName, "/Lotus/Types/Items/MiscItems/PhotoboothTile")) { if (resourceInheritsFrom(uniqueName, "/Lotus/Types/Items/MiscItems/PhotoboothTile")) {
await addItem(inventory, uniqueName, 1); await addItem(inventory, uniqueName, 1);
needSync = true;
} }
} }
await inventory.save(); await inventory.save();
res.end(); res.end();
if (needSync) {
sendWsBroadcastToGame(accountId, { sync_inventory: true });
}
}; };
const resourceInheritsFrom = (resourceName: string, targetName: string): boolean => { const resourceInheritsFrom = (resourceName: string, targetName: string): boolean => {

View File

@ -1,6 +1,7 @@
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "../../services/inventoryService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts";
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
import { sendWsBroadcastToGame } from "../../services/wsService.ts";
export const unlockAllIntrinsicsController: RequestHandler = async (req, res) => { export const unlockAllIntrinsicsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -16,4 +17,5 @@ export const unlockAllIntrinsicsController: RequestHandler = async (req, res) =>
inventory.PlayerSkills.LPS_DRIFT_ENDURANCE = 10; inventory.PlayerSkills.LPS_DRIFT_ENDURANCE = 10;
await inventory.save(); await inventory.save();
res.end(); res.end();
sendWsBroadcastToGame(accountId, { sync_inventory: true });
}; };

View File

@ -1,6 +1,7 @@
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "../../services/inventoryService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts";
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
import { sendWsBroadcastToGame } from "../../services/wsService.ts";
const allEudicoHeistJobs = [ const allEudicoHeistJobs = [
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyOne", "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyOne",
@ -21,4 +22,5 @@ export const unlockAllProfitTakerStagesController: RequestHandler = async (req,
} }
await inventory.save(); await inventory.save();
res.end(); res.end();
sendWsBroadcastToGame(accountId, { sync_inventory: true });
}; };

View File

@ -1,6 +1,7 @@
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "../../services/inventoryService.ts";
import { sendWsBroadcastToGame } from "../../services/wsService.ts";
export const unlockAllSimarisResearchEntriesController: RequestHandler = async (req, res) => { export const unlockAllSimarisResearchEntriesController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -17,4 +18,5 @@ export const unlockAllSimarisResearchEntriesController: RequestHandler = async (
].map(type => ({ TargetType: type, Scans: 10, Completed: true })); ].map(type => ({ TargetType: type, Scans: 10, Completed: true }));
await inventory.save(); await inventory.save();
res.end(); res.end();
sendWsBroadcastToGame(accountId, { sync_inventory: true });
}; };

View File

@ -2,6 +2,7 @@ import { getInventory } from "../../services/inventoryService.ts";
import type { WeaponTypeInternal } from "../../services/itemDataService.ts"; import type { WeaponTypeInternal } from "../../services/itemDataService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts";
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
import { sendWsBroadcastToGame } from "../../services/wsService.ts";
export const updateFingerprintController: RequestHandler = async (req, res) => { export const updateFingerprintController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -22,6 +23,7 @@ export const updateFingerprintController: RequestHandler = async (req, res) => {
await inventory.save(); await inventory.save();
} }
res.end(); res.end();
sendWsBroadcastToGame(accountId, { sync_inventory: true });
}; };
interface IUpdateFingerPrintRequest { interface IUpdateFingerPrintRequest {

View File

@ -1,12 +1,13 @@
import type http from "http"; import type http from "http";
import type https from "https"; import type https from "https";
import type { default as ws } from "ws"; import type { WebSocket } from "ws";
import { WebSocketServer } from "ws"; import { WebSocketServer } from "ws";
import { Account } from "../models/loginModel.ts"; import { Account } from "../models/loginModel.ts";
import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "./loginService.ts"; import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "./loginService.ts";
import type { IDatabaseAccountJson } from "../types/loginTypes.ts"; import type { IDatabaseAccountJson } from "../types/loginTypes.ts";
import type { HydratedDocument } from "mongoose"; import type { HydratedDocument } from "mongoose";
import { logError } from "../utils/logger.ts"; import { logError, logger } from "../utils/logger.ts";
import type { Request } from "express";
let wsServer: WebSocketServer | undefined; let wsServer: WebSocketServer | undefined;
let wssServer: WebSocketServer | undefined; let wssServer: WebSocketServer | undefined;
@ -44,9 +45,10 @@ export const stopWsServers = (promises: Promise<void>[]): void => {
let lastWsid: number = 0; let lastWsid: number = 0;
interface IWsCustomData extends ws { interface IWsCustomData extends WebSocket {
id: number; id: number;
accountId?: string; accountId?: string;
isGame?: boolean;
} }
interface IWsMsgFromClient { interface IWsMsgFromClient {
@ -55,11 +57,19 @@ interface IWsMsgFromClient {
password: string; password: string;
isRegister: boolean; isRegister: boolean;
}; };
auth_game?: {
accountId: string;
nonce: number;
};
logout?: boolean; logout?: boolean;
sync_inventory?: boolean;
} }
interface IWsMsgToClient { interface IWsMsgToClient {
//wsid?: number; // common
wsid?: number;
// to webui
reload?: boolean; reload?: boolean;
ports?: { ports?: {
http: number | undefined; http: number | undefined;
@ -77,9 +87,13 @@ interface IWsMsgToClient {
nonce_updated?: boolean; nonce_updated?: boolean;
update_inventory?: boolean; update_inventory?: boolean;
logged_out?: boolean; logged_out?: boolean;
have_game_ws?: boolean;
// to game
sync_inventory?: boolean;
} }
const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => { const wsOnConnect = (ws: WebSocket, req: http.IncomingMessage): void => {
if (req.url == "/custom/selftest") { if (req.url == "/custom/selftest") {
ws.send("SpaceNinjaServer"); ws.send("SpaceNinjaServer");
ws.close(); ws.close();
@ -87,11 +101,12 @@ const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => {
} }
(ws as IWsCustomData).id = ++lastWsid; (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 // eslint-disable-next-line @typescript-eslint/no-misused-promises
ws.on("message", async msg => { ws.on("message", async msg => {
try { try {
//console.log(String(msg));
const data = JSON.parse(String(msg)) as IWsMsgFromClient; const data = JSON.parse(String(msg)) as IWsMsgFromClient;
if (data.auth) { if (data.auth) {
let account: IDatabaseAccountJson | null = await Account.findOne({ email: data.auth.email }); let account: IDatabaseAccountJson | null = await Account.findOne({ email: data.auth.email });
@ -124,7 +139,8 @@ const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => {
id: account.id, id: account.id,
DisplayName: account.DisplayName, DisplayName: account.DisplayName,
Nonce: account.Nonce Nonce: account.Nonce
} },
have_game_ws: haveGameWs(account.id)
} satisfies IWsMsgToClient) } satisfies IWsMsgToClient)
); );
} else { } else {
@ -137,8 +153,23 @@ 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;
logger.debug(`got bootstrapper connection for ${account.id}`);
sendWsBroadcastToWebui({ have_game_ws: true }, account.id);
}
}
}
if (data.logout) { if (data.logout) {
const accountId = (ws as IWsCustomData).accountId; const accountId = (ws as IWsCustomData).accountId;
if (accountId) {
(ws as IWsCustomData).accountId = undefined; (ws as IWsCustomData).accountId = undefined;
await Account.updateOne( await Account.updateOne(
{ {
@ -150,64 +181,124 @@ const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => {
} }
); );
} }
}
if (data.sync_inventory) {
const accountId = (ws as IWsCustomData).accountId;
if (accountId) {
sendWsBroadcastToGame(accountId, { sync_inventory: true });
}
}
} catch (e) { } catch (e) {
logError(e as Error, `processing websocket message`); logError(e as Error, `processing websocket message`);
} }
}); });
// 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}`);
sendWsBroadcastToWebui({ have_game_ws: false }, (ws as IWsCustomData).accountId);
await Account.updateOne(
{
_id: (ws as IWsCustomData).accountId
},
{
Dropped: true
}
);
}
});
};
const forEachClient = (cb: (client: IWsCustomData) => void): void => {
if (wsServer) {
for (const client of wsServer.clients) {
cb(client as IWsCustomData);
}
}
if (wssServer) {
for (const client of wssServer.clients) {
cb(client as IWsCustomData);
}
}
};
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 => { export const sendWsBroadcast = (data: IWsMsgToClient): void => {
const msg = JSON.stringify(data); const msg = JSON.stringify(data);
if (wsServer) { forEachClient(client => {
for (const client of wsServer.clients) {
client.send(msg); client.send(msg);
} });
}
if (wssServer) {
for (const client of wssServer.clients) {
client.send(msg);
}
}
}; };
export const sendWsBroadcastTo = (accountId: string, data: IWsMsgToClient): void => { export const sendWsBroadcastTo = (accountId: string, data: IWsMsgToClient): void => {
const msg = JSON.stringify(data); const msg = JSON.stringify(data);
if (wsServer) { forEachClient(client => {
for (const client of wsServer.clients) { if (client.accountId == accountId) {
if ((client as IWsCustomData).accountId == accountId) {
client.send(msg); client.send(msg);
} }
} });
} };
if (wssServer) {
for (const client of wssServer.clients) { export const sendWsBroadcastToGame = (accountId: string, data: IWsMsgToClient): void => {
if ((client as IWsCustomData).accountId == accountId) { const msg = JSON.stringify(data);
forEachClient(client => {
if (client.isGame && client.accountId == accountId) {
client.send(msg); client.send(msg);
} }
} });
}
}; };
export const sendWsBroadcastEx = (data: IWsMsgToClient, accountId?: string, excludeWsid?: number): void => { export const sendWsBroadcastEx = (data: IWsMsgToClient, accountId?: string, excludeWsid?: number): void => {
const msg = JSON.stringify(data); const msg = JSON.stringify(data);
if (wsServer) { forEachClient(client => {
for (const client of wsServer.clients) { if ((!accountId || client.accountId == accountId) && client.id != excludeWsid) {
if (
(!accountId || (client as IWsCustomData).accountId == accountId) &&
(client as IWsCustomData).id != excludeWsid
) {
client.send(msg); client.send(msg);
} }
} });
} };
if (wssServer) {
for (const client of wssServer.clients) { export const sendWsBroadcastToWebui = (data: IWsMsgToClient, accountId?: string, excludeWsid?: number): void => {
if ( const msg = JSON.stringify(data);
(!accountId || (client as IWsCustomData).accountId == accountId) && forEachClient(client => {
(client as IWsCustomData).id != excludeWsid if (!client.isGame && (!accountId || client.accountId == accountId) && client.id != excludeWsid) {
) {
client.send(msg); client.send(msg);
} }
} });
};
export const broadcastInventoryUpdate = (req: Request): void => {
const accountId = req.query.accountId as string;
if (req.query.wsid) {
// 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
sendWsBroadcastToWebui({ update_inventory: true }, accountId, parseInt(String(req.query.wsid)));
} }
}; };
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, have_game_ws: false } satisfies IWsMsgToClient));
}
}
});
};

View File

@ -93,7 +93,7 @@
</form> </form>
</div> </div>
<div data-route="/webui/inventory" data-title="Inventory | OpenWF WebUI"> <div data-route="/webui/inventory" data-title="Inventory | OpenWF WebUI">
<p class="mb-3" data-loc="general_inventoryUpdateNote"></p> <p class="mb-3 inventory-update-note"></p>
<div class="card mb-3"> <div class="card mb-3">
<div class="card-header"> <div class="card-header">
<ul class="nav nav-tabs card-header-tabs"> <ul class="nav nav-tabs card-header-tabs">
@ -715,7 +715,7 @@
</div> </div>
</div> </div>
<div data-route="/webui/mods" data-title="Mods | OpenWF WebUI"> <div data-route="/webui/mods" data-title="Mods | OpenWF WebUI">
<p class="mb-3" data-loc="general_inventoryUpdateNote"></p> <p class="mb-3 inventory-update-note"></p>
<div class="row g-3"> <div class="row g-3">
<div class="col-xxl-6"> <div class="col-xxl-6">
<div class="card mb-3"> <div class="card mb-3">
@ -771,7 +771,7 @@
</div> </div>
</div> </div>
<div data-route="/webui/quests" data-title="Quests | OpenWF WebUI"> <div data-route="/webui/quests" data-title="Quests | OpenWF WebUI">
<p class="mb-3" data-loc="general_inventoryUpdateNote"></p> <p class="mb-3 inventory-update-note"></p>
<div class="row g-3"> <div class="row g-3">
<div class="col-md-6"> <div class="col-md-6">
<div class="card"> <div class="card">

View File

@ -63,7 +63,7 @@ function openWebSocket() {
} }
$(".displayname").text(data.DisplayName); $(".displayname").text(data.DisplayName);
window.accountId = data.id; 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) { if (window.dict) {
updateLocElements(); updateLocElements();
} }
@ -90,6 +90,14 @@ function openWebSocket() {
if ("logged_out" in msg) { if ("logged_out" in msg) {
logout(); 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 () { window.ws.onclose = function () {
ws_is_open = false; ws_is_open = false;
@ -223,6 +231,9 @@ function updateLocElements() {
document.querySelectorAll("[data-loc-replace]").forEach(elm => { document.querySelectorAll("[data-loc-replace]").forEach(elm => {
elm.innerHTML = elm.innerHTML.replace("|VAL|", elm.getAttribute("data-loc-replace")); 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) { function setActiveLanguage(lang) {
@ -2582,7 +2593,7 @@ function disposeOfGear(category, oid) {
]; ];
revalidateAuthz().then(() => { revalidateAuthz().then(() => {
$.post({ $.post({
url: "/api/sell.php?" + window.authz + "&wsid=" + wsid, url: "/api/sell.php?" + window.authz,
contentType: "text/plain", contentType: "text/plain",
data: JSON.stringify(data) data: JSON.stringify(data)
}); });
@ -2604,7 +2615,7 @@ function disposeOfItems(category, type, count) {
]; ];
revalidateAuthz().then(() => { revalidateAuthz().then(() => {
$.post({ $.post({
url: "/api/sell.php?" + window.authz + "&wsid=" + wsid, url: "/api/sell.php?" + window.authz,
contentType: "text/plain", contentType: "text/plain",
data: JSON.stringify(data) data: JSON.stringify(data)
}); });
@ -2858,7 +2869,7 @@ for (const id of uiConfigs) {
value = parseInt(value); value = parseInt(value);
} }
$.post({ $.post({
url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid, url: "/custom/setConfig?" + window.authz,
contentType: "application/json", contentType: "application/json",
data: JSON.stringify({ [id]: value }) data: JSON.stringify({ [id]: value })
}); });
@ -2866,13 +2877,9 @@ for (const id of uiConfigs) {
} else if (elm.type == "checkbox") { } else if (elm.type == "checkbox") {
elm.onchange = function () { elm.onchange = function () {
$.post({ $.post({
url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid, url: "/custom/setConfig?" + window.authz,
contentType: "application/json", contentType: "application/json",
data: JSON.stringify({ [id]: this.checked }) data: JSON.stringify({ [id]: this.checked })
}).then(() => {
if (["infiniteCredits", "infinitePlatinum", "infiniteEndo", "infiniteRegalAya"].indexOf(id) != -1) {
updateInventory();
}
}); });
}; };
} }
@ -2893,7 +2900,7 @@ document.querySelectorAll(".config-form .input-group").forEach(grp => {
function doSaveConfigInt(id) { function doSaveConfigInt(id) {
$.post({ $.post({
url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid, url: "/custom/setConfig?" + window.authz,
contentType: "application/json", contentType: "application/json",
data: JSON.stringify({ data: JSON.stringify({
[id]: parseInt(document.getElementById(id).value) [id]: parseInt(document.getElementById(id).value)
@ -2903,7 +2910,7 @@ function doSaveConfigInt(id) {
function doSaveConfigFloat(id) { function doSaveConfigFloat(id) {
$.post({ $.post({
url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid, url: "/custom/setConfig?" + window.authz,
contentType: "application/json", contentType: "application/json",
data: JSON.stringify({ data: JSON.stringify({
[id]: parseFloat(document.getElementById(id).value) [id]: parseFloat(document.getElementById(id).value)
@ -2913,7 +2920,7 @@ function doSaveConfigFloat(id) {
function doSaveConfigStringArray(id) { function doSaveConfigStringArray(id) {
$.post({ $.post({
url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid, url: "/custom/setConfig?" + window.authz,
contentType: "application/json", contentType: "application/json",
data: JSON.stringify({ data: JSON.stringify({
[id]: document [id]: document
@ -2997,6 +3004,9 @@ function doUnlockAllFocusSchools() {
toast(loc("code_focusAllUnlocked")); toast(loc("code_focusAllUnlocked"));
} else { } else {
toast(loc("code_focusUnlocked").split("|COUNT|").join(Object.keys(missingFocusUpgrades).length)); toast(loc("code_focusUnlocked").split("|COUNT|").join(Object.keys(missingFocusUpgrades).length));
if (ws_is_open) {
window.ws.send(JSON.stringify({ sync_inventory: true }));
}
} }
}); });
}); });
@ -3040,7 +3050,7 @@ document.querySelectorAll("#account-cheats input[type=checkbox]").forEach(elm =>
elm.onchange = function () { elm.onchange = function () {
revalidateAuthz().then(() => { revalidateAuthz().then(() => {
$.post({ $.post({
url: "/custom/setAccountCheat?" + window.authz /*+ "&wsid=" + wsid*/, url: "/custom/setAccountCheat?" + window.authz,
contentType: "application/json", contentType: "application/json",
data: JSON.stringify({ data: JSON.stringify({
key: elm.id, key: elm.id,
@ -3112,7 +3122,7 @@ function doRemoveUnrankedMods() {
req.done(inventory => { req.done(inventory => {
window.itemListPromise.then(itemMap => { window.itemListPromise.then(itemMap => {
$.post({ $.post({
url: "/api/sell.php?" + window.authz + "&wsid=" + wsid, url: "/api/sell.php?" + window.authz,
contentType: "text/plain", contentType: "text/plain",
data: JSON.stringify({ data: JSON.stringify({
SellCurrency: "SC_RegularCredits", SellCurrency: "SC_RegularCredits",
@ -3469,13 +3479,13 @@ async function doUnlockAllScans() {
async function doUnlockAllShipFeatures() { async function doUnlockAllShipFeatures() {
await revalidateAuthz(); await revalidateAuthz();
await fetch("/custom/unlockAllShipFeatures?" + window.authz); await fetch("/custom/unlockAllShipFeatures?" + window.authz);
toast(loc("cheats_unlockSuccInventory")); toast(loc(window.have_game_ws ? "code_succAdded" : "cheats_unlockSuccInventory"));
} }
async function doUnlockAllCapturaScenes() { async function doUnlockAllCapturaScenes() {
await revalidateAuthz(); await revalidateAuthz();
await fetch("/custom/unlockAllCapturaScenes?" + window.authz); await fetch("/custom/unlockAllCapturaScenes?" + window.authz);
toast(loc("cheats_unlockSuccInventory")); toast(loc(window.have_game_ws ? "code_succAdded" : "cheats_unlockSuccInventory"));
} }
async function unlockAllMissions() { async function unlockAllMissions() {
@ -3487,13 +3497,13 @@ async function unlockAllMissions() {
async function unlockAllProfitTakerStages() { async function unlockAllProfitTakerStages() {
await revalidateAuthz(); await revalidateAuthz();
await fetch("/custom/unlockAllProfitTakerStages?" + window.authz); await fetch("/custom/unlockAllProfitTakerStages?" + window.authz);
toast(loc("cheats_unlockSuccInventory")); toast(loc(window.have_game_ws ? "code_succAdded" : "cheats_unlockSuccInventory"));
} }
async function unlockAllSimarisResearchEntries() { async function unlockAllSimarisResearchEntries() {
await revalidateAuthz(); await revalidateAuthz();
await fetch("/custom/unlockAllSimarisResearchEntries?" + window.authz); await fetch("/custom/unlockAllSimarisResearchEntries?" + window.authz);
toast(loc("cheats_unlockSuccInventory")); toast(loc(window.have_game_ws ? "code_succAdded" : "cheats_unlockSuccInventory"));
} }
const importSamples = { const importSamples = {

View File

@ -1,6 +1,7 @@
// German translation by Animan8000 // German translation by Animan8000
dict = { 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_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_addButton: `Hinzufügen`,
general_setButton: `Festlegen`, general_setButton: `Festlegen`,
general_none: `Keines`, general_none: `Keines`,

View File

@ -1,5 +1,6 @@
dict = { 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_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_addButton: `Add`,
general_setButton: `Set`, general_setButton: `Set`,
general_none: `None`, general_none: `None`,

View File

@ -1,6 +1,7 @@
// Spanish translation by hxedcl // Spanish translation by hxedcl
dict = { 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_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_addButton: `Agregar`,
general_setButton: `Establecer`, general_setButton: `Establecer`,
general_none: `Ninguno`, general_none: `Ninguno`,

View File

@ -1,6 +1,7 @@
// French translation by Vitruvio // French translation by Vitruvio
dict = { 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_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_addButton: `Ajouter`,
general_setButton: `Définir`, general_setButton: `Définir`,
general_none: `Aucun`, general_none: `Aucun`,

View File

@ -1,6 +1,7 @@
// Russian translation by AMelonInsideLemon, LoseFace // Russian translation by AMelonInsideLemon, LoseFace
dict = { dict = {
general_inventoryUpdateNote: `Примечание: Чтобы увидеть изменения в игре, вам нужно повторно синхронизировать свой инвентарь, например, используя команду /sync загрузчика, посетив Додзё/Реле или перезагрузив игру.`, 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_addButton: `Добавить`,
general_setButton: `Установить`, general_setButton: `Установить`,
general_none: `Отсутствует`, general_none: `Отсутствует`,

View File

@ -1,6 +1,7 @@
// Ukrainian translation by LoseFace // Ukrainian translation by LoseFace
dict = { dict = {
general_inventoryUpdateNote: `Пам'ятка: Щоб побачити зміни в грі, вам потрібно повторно синхронізувати своє спорядження, наприклад, використовуючи команду /sync завантажувача, відвідавши Доджьо/Реле або перезавантаживши гру.`, 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_addButton: `Добавити`,
general_setButton: `Встановити`, general_setButton: `Встановити`,
general_none: `Відсутній`, general_none: `Відсутній`,

View File

@ -1,6 +1,7 @@
// Chinese translation by meb154, bishan178, nyaoouo, qianlishun, CrazyZhang, Corvus, & qingchun // Chinese translation by meb154, bishan178, nyaoouo, qianlishun, CrazyZhang, Corvus, & qingchun
dict = { dict = {
general_inventoryUpdateNote: `注意: 要在游戏中查看更改,您需要重新同步库存,例如使用客户端的 /sync 命令,访问道场/中继站或重新登录.`, 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_addButton: `添加`,
general_setButton: `设置`, general_setButton: `设置`,
general_none: ``, general_none: ``,