返回提交历史
Modified
src/controllers/api/loginController.ts
+9
-0
Modified
src/controllers/custom/tunablesController.ts
+4
-7
Modified
src/services/configService.ts
+8
-0
Modified
src/services/configWatcherService.ts
+15
-1
Added
src/services/tunablesService.ts
+41
-0
Modified
src/services/wsService.ts
+12
-9
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
代码差异
6 个文件
+89
-17
@@ -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 =
@@ -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
};
@@ -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
};
@@ -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 (
@@ -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
};
@@ -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