XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

SpaceNinjaServer

A simple server for a small space ninja game

公开
关注 0 Fork 1 Star 0
返回提交历史

XFEstudio/SpaceNinjaServer

feat: add config options for bootstrapper tunables (#3010)

Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/3010 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>

04b504fa
Sainan <63328889+Sainan@users.noreply.github.com>
提交于

代码差异

6 个文件 +89 -17
Modified src/controllers/api/loginController.ts +9 -0
@@ -12,10 +12,19 @@ import { handleNonceInvalidation } from "../../services/wsService.ts";
12 12 import { getInventory } from "../../services/inventoryService.ts";
13 13 import { createMessage } from "../../services/inboxService.ts";
14 14 import { fromStoreItem } from "../../services/itemDataService.ts";
15 import { getTokenForClient } from "../../services/tunablesService.ts";
16 import type { AddressInfo } from "node:net";
15 17
16 18 export const loginController: RequestHandler = async (request, response) => {
17 19 const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object
18 20
21 if (config.tunables?.useLoginToken) {
22 if (request.query.token !== getTokenForClient((request.socket.address() as AddressInfo).address)) {
23 response.status(400).json({ error: "missing or incorrect token" });
24 return;
25 }
26 }
27
19 28 const account = await Account.findOne({ email: loginRequest.email });
20 29
21 30 const buildLabel: string =
Modified src/controllers/custom/tunablesController.ts +4 -7
@@ -1,14 +1,11 @@
1 1 import type { RequestHandler } from "express";
2 2 import type { ITunables } from "../../types/bootstrapperTypes.ts";
3 import { getTunablesForClient } from "../../services/tunablesService.ts";
4 import type { AddressInfo } from "node:net";
3 5
4 6 // This endpoint is specific to the OpenWF Bootstrapper: https://openwf.io/bootstrapper-manual
5 7
6 export const tunablesController: RequestHandler = (_req, res) => {
7 const tunables: ITunables = {};
8 //tunables.prohibit_skip_mission_start_timer = true;
9 //tunables.prohibit_fov_override = true;
10 //tunables.prohibit_freecam = true;
11 //tunables.prohibit_teleport = true;
12 //tunables.prohibit_scripts = true;
8 export const tunablesController: RequestHandler = (req, res) => {
9 const tunables: ITunables = getTunablesForClient((req.socket.address() as AddressInfo).address);
13 10 res.json(tunables);
14 11 };
Modified src/services/configService.ts +8 -0
@@ -75,6 +75,14 @@ export interface IConfig {
75 75 circuitGameModes?: string[];
76 76 darvoStockMultiplier?: number;
77 77 };
78 tunables?: {
79 useLoginToken?: boolean;
80 prohibitSkipMissionStartTimer?: boolean;
81 prohibitFovOverride?: boolean;
82 prohibitFreecam?: boolean;
83 prohibitTeleport?: boolean;
84 prohibitScripts?: boolean;
85 };
78 86 dev?: {
79 87 keepVendorsExpired?: boolean;
80 88 };
Modified src/services/configWatcherService.ts +15 -1
@@ -11,11 +11,14 @@ import {
11 11 } from "./configService.ts";
12 12 import { saveConfig, shouldReloadConfig } from "./configWriterService.ts";
13 13 import { getWebBindings, startWebServer, stopWebServer } from "./webService.ts";
14 import { sendWsBroadcast } from "./wsService.ts";
14 import { forEachWsClient, sendWsBroadcast, type IWsMsgToClient } from "./wsService.ts";
15 15 import varzia from "../../static/fixed_responses/worldState/varzia.json" with { type: "json" };
16 import { getTunablesForClient } from "./tunablesService.ts";
16 17
17 18 chokidar.watch(configPath).on("change", () => {
18 19 if (shouldReloadConfig()) {
20 const prevTunables = JSON.stringify(config.tunables);
21
19 22 logger.info("Detected a change to config file, reloading its contents.");
20 23 try {
21 24 loadConfig();
@@ -26,6 +29,17 @@ chokidar.watch(configPath).on("change", () => {
26 29 validateConfig();
27 30 syncConfigWithDatabase();
28 31
32 if (JSON.stringify(config.tunables) != prevTunables) {
33 logger.debug(`tunables changed, informing clients`);
34 forEachWsClient(client => {
35 if (client.isGame) {
36 client.send(
37 JSON.stringify({ tunables: getTunablesForClient(client.address) } satisfies IWsMsgToClient)
38 );
39 }
40 });
41 }
42
29 43 const configBindings = configGetWebBindings();
30 44 const bindings = getWebBindings();
31 45 if (
Added src/services/tunablesService.ts +41 -0
@@ -0,0 +1,41 @@
1 import crypto from "node:crypto";
2 import { args } from "../helpers/commandLineArguments.ts";
3 import type { ITunables } from "../types/bootstrapperTypes.ts";
4 import { config } from "./configService.ts";
5
6 let secret;
7 if (args.secret) {
8 secret = args.secret; // Maintain same secret across hot reloads in dev mode
9 } else {
10 secret = "";
11 for (let i = 0; i != 10; ++i) {
12 secret += String.fromCharCode(Math.floor(Math.random() * 26) + 0x41);
13 }
14 }
15
16 export const getTokenForClient = (clientAddress: string): string => {
17 return crypto.createHmac("sha256", secret).update(clientAddress).digest("hex");
18 };
19
20 export const getTunablesForClient = (clientAddress: string): ITunables => {
21 const tunables: ITunables = {};
22 if (config.tunables?.useLoginToken) {
23 tunables.token = getTokenForClient(clientAddress);
24 }
25 if (config.tunables?.prohibitSkipMissionStartTimer) {
26 tunables.prohibit_skip_mission_start_timer = true;
27 }
28 if (config.tunables?.prohibitFovOverride) {
29 tunables.prohibit_fov_override = true;
30 }
31 if (config.tunables?.prohibitFreecam) {
32 tunables.prohibit_freecam = true;
33 }
34 if (config.tunables?.prohibitTeleport) {
35 tunables.prohibit_teleport = true;
36 }
37 if (config.tunables?.prohibitScripts) {
38 tunables.prohibit_scripts = true;
39 }
40 return tunables;
41 };
Modified src/services/wsService.ts +12 -9
@@ -9,6 +9,7 @@ import type { HydratedDocument } from "mongoose";
9 9 import { logError, logger } from "../utils/logger.ts";
10 10 import type { Request } from "express";
11 11 import type { ITunables } from "../types/bootstrapperTypes.ts";
12 import type { AddressInfo } from "node:net";
12 13
13 14 let wsServer: WebSocketServer | undefined;
14 15 let wssServer: WebSocketServer | undefined;
@@ -48,6 +49,7 @@ let lastWsid: number = 0;
48 49
49 50 interface IWsCustomData extends WebSocket {
50 51 id: number;
52 address: string;
51 53 accountId?: string;
52 54 isGame?: boolean;
53 55 }
@@ -66,7 +68,7 @@ interface IWsMsgFromClient {
66 68 sync_inventory?: boolean;
67 69 }
68 70
69 interface IWsMsgToClient {
71 export interface IWsMsgToClient {
70 72 // common
71 73 wsid?: number;
72 74
@@ -104,6 +106,7 @@ const wsOnConnect = (ws: WebSocket, req: http.IncomingMessage): void => {
104 106 }
105 107
106 108 (ws as IWsCustomData).id = ++lastWsid;
109 (ws as IWsCustomData).address = (req.socket.address() as AddressInfo).address;
107 110 ws.send(JSON.stringify({ wsid: lastWsid } satisfies IWsMsgToClient));
108 111
109 112 // eslint-disable-next-line @typescript-eslint/no-misused-promises
@@ -212,7 +215,7 @@ const wsOnConnect = (ws: WebSocket, req: http.IncomingMessage): void => {
212 215 });
213 216 };
214 217
215 const forEachClient = (cb: (client: IWsCustomData) => void): void => {
218 export const forEachWsClient = (cb: (client: IWsCustomData) => void): void => {
216 219 if (wsServer) {
217 220 for (const client of wsServer.clients) {
218 221 cb(client as IWsCustomData);
@@ -227,7 +230,7 @@ const forEachClient = (cb: (client: IWsCustomData) => void): void => {
227 230
228 231 export const haveGameWs = (accountId: string): boolean => {
229 232 let ret = false;
230 forEachClient(client => {
233 forEachWsClient(client => {
231 234 if (client.isGame && client.accountId == accountId) {
232 235 ret = true;
233 236 }
@@ -237,14 +240,14 @@ export const haveGameWs = (accountId: string): boolean => {
237 240
238 241 export const sendWsBroadcast = (data: IWsMsgToClient): void => {
239 242 const msg = JSON.stringify(data);
240 forEachClient(client => {
243 forEachWsClient(client => {
241 244 client.send(msg);
242 245 });
243 246 };
244 247
245 248 export const sendWsBroadcastTo = (accountId: string, data: IWsMsgToClient): void => {
246 249 const msg = JSON.stringify(data);
247 forEachClient(client => {
250 forEachWsClient(client => {
248 251 if (client.accountId == accountId) {
249 252 client.send(msg);
250 253 }
@@ -253,7 +256,7 @@ export const sendWsBroadcastTo = (accountId: string, data: IWsMsgToClient): void
253 256
254 257 export const sendWsBroadcastToGame = (accountId: string, data: IWsMsgToClient): void => {
255 258 const msg = JSON.stringify(data);
256 forEachClient(client => {
259 forEachWsClient(client => {
257 260 if (client.isGame && client.accountId == accountId) {
258 261 client.send(msg);
259 262 }
@@ -262,7 +265,7 @@ export const sendWsBroadcastToGame = (accountId: string, data: IWsMsgToClient):
262 265
263 266 export const sendWsBroadcastEx = (data: IWsMsgToClient, accountId?: string, excludeWsid?: number): void => {
264 267 const msg = JSON.stringify(data);
265 forEachClient(client => {
268 forEachWsClient(client => {
266 269 if ((!accountId || client.accountId == accountId) && client.id != excludeWsid) {
267 270 client.send(msg);
268 271 }
@@ -271,7 +274,7 @@ export const sendWsBroadcastEx = (data: IWsMsgToClient, accountId?: string, excl
271 274
272 275 export const sendWsBroadcastToWebui = (data: IWsMsgToClient, accountId?: string, excludeWsid?: number): void => {
273 276 const msg = JSON.stringify(data);
274 forEachClient(client => {
277 forEachWsClient(client => {
275 278 if (!client.isGame && (!accountId || client.accountId == accountId) && client.id != excludeWsid) {
276 279 client.send(msg);
277 280 }
@@ -294,7 +297,7 @@ export const broadcastInventoryUpdate = (req: Request): void => {
294 297 };
295 298
296 299 export const handleNonceInvalidation = (accountId: string): void => {
297 forEachClient(client => {
300 forEachWsClient(client => {
298 301 if (client.accountId == accountId) {
299 302 if (client.isGame) {
300 303 client.accountId = undefined; // prevent processing of the close event