feat: add config options for bootstrapper tunables (#3010)
All checks were successful
Build Docker image / docker (push) Successful in 1m42s
Build / build (push) Successful in 5m22s

Reviewed-on: #3010
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
This commit was merged in pull request #3010.
This commit is contained in:
2025-11-09 08:39:41 -08:00
committed by Sainan
parent 5c1cf3bfb1
commit 04b504fa18
6 changed files with 89 additions and 17 deletions

View File

@@ -12,10 +12,19 @@ import { handleNonceInvalidation } from "../../services/wsService.ts";
import { getInventory } from "../../services/inventoryService.ts";
import { createMessage } from "../../services/inboxService.ts";
import { fromStoreItem } from "../../services/itemDataService.ts";
import { getTokenForClient } from "../../services/tunablesService.ts";
import type { AddressInfo } from "node:net";
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
if (config.tunables?.useLoginToken) {
if (request.query.token !== getTokenForClient((request.socket.address() as AddressInfo).address)) {
response.status(400).json({ error: "missing or incorrect token" });
return;
}
}
const account = await Account.findOne({ email: loginRequest.email });
const buildLabel: string =

View File

@@ -1,14 +1,11 @@
import type { RequestHandler } from "express";
import type { ITunables } from "../../types/bootstrapperTypes.ts";
import { getTunablesForClient } from "../../services/tunablesService.ts";
import type { AddressInfo } from "node:net";
// This endpoint is specific to the OpenWF Bootstrapper: https://openwf.io/bootstrapper-manual
export const tunablesController: RequestHandler = (_req, res) => {
const tunables: ITunables = {};
//tunables.prohibit_skip_mission_start_timer = true;
//tunables.prohibit_fov_override = true;
//tunables.prohibit_freecam = true;
//tunables.prohibit_teleport = true;
//tunables.prohibit_scripts = true;
export const tunablesController: RequestHandler = (req, res) => {
const tunables: ITunables = getTunablesForClient((req.socket.address() as AddressInfo).address);
res.json(tunables);
};

View File

@@ -75,6 +75,14 @@ export interface IConfig {
circuitGameModes?: string[];
darvoStockMultiplier?: number;
};
tunables?: {
useLoginToken?: boolean;
prohibitSkipMissionStartTimer?: boolean;
prohibitFovOverride?: boolean;
prohibitFreecam?: boolean;
prohibitTeleport?: boolean;
prohibitScripts?: boolean;
};
dev?: {
keepVendorsExpired?: boolean;
};

View File

@@ -11,11 +11,14 @@ import {
} from "./configService.ts";
import { saveConfig, shouldReloadConfig } from "./configWriterService.ts";
import { getWebBindings, startWebServer, stopWebServer } from "./webService.ts";
import { sendWsBroadcast } from "./wsService.ts";
import { forEachWsClient, sendWsBroadcast, type IWsMsgToClient } from "./wsService.ts";
import varzia from "../../static/fixed_responses/worldState/varzia.json" with { type: "json" };
import { getTunablesForClient } from "./tunablesService.ts";
chokidar.watch(configPath).on("change", () => {
if (shouldReloadConfig()) {
const prevTunables = JSON.stringify(config.tunables);
logger.info("Detected a change to config file, reloading its contents.");
try {
loadConfig();
@@ -26,6 +29,17 @@ chokidar.watch(configPath).on("change", () => {
validateConfig();
syncConfigWithDatabase();
if (JSON.stringify(config.tunables) != prevTunables) {
logger.debug(`tunables changed, informing clients`);
forEachWsClient(client => {
if (client.isGame) {
client.send(
JSON.stringify({ tunables: getTunablesForClient(client.address) } satisfies IWsMsgToClient)
);
}
});
}
const configBindings = configGetWebBindings();
const bindings = getWebBindings();
if (

View File

@@ -0,0 +1,41 @@
import crypto from "node:crypto";
import { args } from "../helpers/commandLineArguments.ts";
import type { ITunables } from "../types/bootstrapperTypes.ts";
import { config } from "./configService.ts";
let secret;
if (args.secret) {
secret = args.secret; // Maintain same secret across hot reloads in dev mode
} else {
secret = "";
for (let i = 0; i != 10; ++i) {
secret += String.fromCharCode(Math.floor(Math.random() * 26) + 0x41);
}
}
export const getTokenForClient = (clientAddress: string): string => {
return crypto.createHmac("sha256", secret).update(clientAddress).digest("hex");
};
export const getTunablesForClient = (clientAddress: string): ITunables => {
const tunables: ITunables = {};
if (config.tunables?.useLoginToken) {
tunables.token = getTokenForClient(clientAddress);
}
if (config.tunables?.prohibitSkipMissionStartTimer) {
tunables.prohibit_skip_mission_start_timer = true;
}
if (config.tunables?.prohibitFovOverride) {
tunables.prohibit_fov_override = true;
}
if (config.tunables?.prohibitFreecam) {
tunables.prohibit_freecam = true;
}
if (config.tunables?.prohibitTeleport) {
tunables.prohibit_teleport = true;
}
if (config.tunables?.prohibitScripts) {
tunables.prohibit_scripts = true;
}
return tunables;
};

View File

@@ -9,6 +9,7 @@ import type { HydratedDocument } from "mongoose";
import { logError, logger } from "../utils/logger.ts";
import type { Request } from "express";
import type { ITunables } from "../types/bootstrapperTypes.ts";
import type { AddressInfo } from "node:net";
let wsServer: WebSocketServer | undefined;
let wssServer: WebSocketServer | undefined;
@@ -48,6 +49,7 @@ let lastWsid: number = 0;
interface IWsCustomData extends WebSocket {
id: number;
address: string;
accountId?: string;
isGame?: boolean;
}
@@ -66,7 +68,7 @@ interface IWsMsgFromClient {
sync_inventory?: boolean;
}
interface IWsMsgToClient {
export interface IWsMsgToClient {
// common
wsid?: number;
@@ -104,6 +106,7 @@ const wsOnConnect = (ws: WebSocket, req: http.IncomingMessage): void => {
}
(ws as IWsCustomData).id = ++lastWsid;
(ws as IWsCustomData).address = (req.socket.address() as AddressInfo).address;
ws.send(JSON.stringify({ wsid: lastWsid } satisfies IWsMsgToClient));
// eslint-disable-next-line @typescript-eslint/no-misused-promises
@@ -212,7 +215,7 @@ const wsOnConnect = (ws: WebSocket, req: http.IncomingMessage): void => {
});
};
const forEachClient = (cb: (client: IWsCustomData) => void): void => {
export const forEachWsClient = (cb: (client: IWsCustomData) => void): void => {
if (wsServer) {
for (const client of wsServer.clients) {
cb(client as IWsCustomData);
@@ -227,7 +230,7 @@ const forEachClient = (cb: (client: IWsCustomData) => void): void => {
export const haveGameWs = (accountId: string): boolean => {
let ret = false;
forEachClient(client => {
forEachWsClient(client => {
if (client.isGame && client.accountId == accountId) {
ret = true;
}
@@ -237,14 +240,14 @@ export const haveGameWs = (accountId: string): boolean => {
export const sendWsBroadcast = (data: IWsMsgToClient): void => {
const msg = JSON.stringify(data);
forEachClient(client => {
forEachWsClient(client => {
client.send(msg);
});
};
export const sendWsBroadcastTo = (accountId: string, data: IWsMsgToClient): void => {
const msg = JSON.stringify(data);
forEachClient(client => {
forEachWsClient(client => {
if (client.accountId == accountId) {
client.send(msg);
}
@@ -253,7 +256,7 @@ export const sendWsBroadcastTo = (accountId: string, data: IWsMsgToClient): void
export const sendWsBroadcastToGame = (accountId: string, data: IWsMsgToClient): void => {
const msg = JSON.stringify(data);
forEachClient(client => {
forEachWsClient(client => {
if (client.isGame && client.accountId == accountId) {
client.send(msg);
}
@@ -262,7 +265,7 @@ export const sendWsBroadcastToGame = (accountId: string, data: IWsMsgToClient):
export const sendWsBroadcastEx = (data: IWsMsgToClient, accountId?: string, excludeWsid?: number): void => {
const msg = JSON.stringify(data);
forEachClient(client => {
forEachWsClient(client => {
if ((!accountId || client.accountId == accountId) && client.id != excludeWsid) {
client.send(msg);
}
@@ -271,7 +274,7 @@ export const sendWsBroadcastEx = (data: IWsMsgToClient, accountId?: string, excl
export const sendWsBroadcastToWebui = (data: IWsMsgToClient, accountId?: string, excludeWsid?: number): void => {
const msg = JSON.stringify(data);
forEachClient(client => {
forEachWsClient(client => {
if (!client.isGame && (!accountId || client.accountId == accountId) && client.id != excludeWsid) {
client.send(msg);
}
@@ -294,7 +297,7 @@ export const broadcastInventoryUpdate = (req: Request): void => {
};
export const handleNonceInvalidation = (accountId: string): void => {
forEachClient(client => {
forEachWsClient(client => {
if (client.accountId == accountId) {
if (client.isGame) {
client.accountId = undefined; // prevent processing of the close event