Add bidirectional whitelist sync

This commit is contained in:
Cooldude2606 committed 2025-05-06 17:14:27 +01:00
1 parent 47c5e0fabd
commit 405af0e173
4 files changed
+91 -10

No files matched your search

+9 -1
View File
@@ -266,6 +266,15 @@ export default class Instance extends lib.Link {
}
});
this.server.on("whitelist-change", (added: string[], removed: string[]) => {
for (const player of added) {
this._recordUserUpdate("WHITELISTED", player);
}
for (const player of removed) {
this._recordUserUpdate("UNWHITELISTED", player);
}
});
this.handle(lib.InstanceExtractPlayersRequest, this.handleInstanceExtractPlayersRequest.bind(this));
this.handle(lib.InstanceAdminlistUpdateEvent, this.handleInstanceAdminlistUpdateEvent.bind(this));
this.handle(lib.InstanceBanlistUpdateEvent, this.handleInstanceBanlistUpdateEvent.bind(this));
@@ -1031,7 +1040,6 @@ end`.replace(/\r?\n/g, " ");
name: string,
reason: string = ""
) {
// TODO: Implement bidirectional whitelist sync, likely by watching the json file
const addr = lib.Address.fromShorthand("allInstances");
const expectedIndex = this._expectedUserUpdates.findIndex(
expected => expected.name === name && expected.action === action && expected.reason === reason
+74
View File
@@ -500,6 +500,10 @@ export class FactorioServer extends events.EventEmitter {
_version: string | null = null;
_dataDir: string | null = null;
// Due to inconsistencies in the factorio api, we must manually watch the whitelist
// https://forums.factorio.com/viewtopic.php?t=123673
_whitelistWatcher: fs.FSWatcher | null = null;
_whitelist = new Set<string>();
_logger: lib.Logger;
_targetVersion: string;
@@ -741,6 +745,62 @@ export class FactorioServer extends events.EventEmitter {
}
}
_watchWhitelist() {
if (this._whitelistWatcher !== null) {
throw new Error("Whitelist watcher already started");
}
if (!this.enableWhitelist) {
return; // The file is not updated if the whitelist is disabled
}
const filePath = this.writePath("server-whitelist.json");
try {
this._whitelistWatcher = fs.watch(filePath);
} catch (err: any) {
this._logger.error(`Unable to watch whitelist, bidirectional sync will not be available:\n${err}`);
return;
}
this._whitelistWatcher.unref();
this._whitelistWatcher.on("error", (err) => {
this._whitelistWatcher = null;
this._logger.error(`Whitelist Watcher has errored:\n${err?.stack ?? err?.message ?? err}`);
});
this._whitelistWatcher.on("close", () => {
this._whitelistWatcher = null;
});
this._whitelistWatcher.on("change", async (eventType) => {
if (eventType !== "change") {
this._logger.warn(`Unexpected file watcher event: ${eventType}`);
return;
}
let newWhitelistJson;
try {
newWhitelistJson = await fs.readJSON(filePath);
} catch (err: any) {
this._logger.error(`Unable to read whitelist, bidirectional sync will not be available:\n${err}`);
return;
}
if (!(newWhitelistJson instanceof Array) || !newWhitelistJson.every(e => typeof e === "string")) {
// The whitelist must be an array of strings
this._logger.error("Unable to read whitelist, bidirectional sync will not be available:" +
"Unexpected whitelist format");
return;
}
const newWhitelist = new Set(newWhitelistJson);
// Set.difference added in node v22 and so we can not use this due to v20 support
const added = newWhitelistJson.filter(e => !this._whitelist.has(e));
const removed = [...this._whitelist.values()].filter(e => !newWhitelist.has(e));
this._whitelist = newWhitelist;
this.emit("whitelist-change", added, removed);
});
}
/** Maximum number of RCON commands transmitted in parallel on the RCON connection */
get maxConcurrentCommands() {
return this._maxConcurrentCommands;
@@ -854,6 +914,10 @@ export class FactorioServer extends events.EventEmitter {
this._rconClient.end().catch(() => {});
}
if (this._whitelistWatcher) {
this._whitelistWatcher.close();
}
this._resetState();
this.emit("exit");
});
@@ -868,6 +932,10 @@ export class FactorioServer extends events.EventEmitter {
this._rconClient.end().catch(() => {});
}
if (this._whitelistWatcher) {
this._whitelistWatcher.close();
}
this._resetState();
this.emit("exit");
});
@@ -986,6 +1054,7 @@ export class FactorioServer extends events.EventEmitter {
stdio: "pipe",
}
);
this._watchWhitelist();
this._attachStdio();
this._watchExit();
/* eslint-enable node/no-sync */
@@ -1036,12 +1105,14 @@ export class FactorioServer extends events.EventEmitter {
...(this.enableWhitelist ? ["--use-server-whitelist"] : []),
...(this.enableAuthserverBans ? ["--use-authserver-bans"] : []),
...(this.verboseLogging ? ["--verbose"] : []),
...(this.consoleLogging ? ["--console-log", this.writePath("console.log")] : []),
],
{
detached: true,
stdio: "pipe",
}
);
this._watchWhitelist();
this._attachStdio();
this._watchExit();
/* eslint-enable node/no-sync */
@@ -1117,6 +1188,9 @@ export class FactorioServer extends events.EventEmitter {
if (this._rconClient) {
this._rconClient.end().catch(() => {});
}
if (this._whitelistWatcher) {
this._whitelistWatcher.close();
}
this._server!.kill("SIGKILL");
}
+7 -6
View File
@@ -476,12 +476,13 @@ export class InstanceConfig extends classes.Config<InstanceConfigFields> {
},
"factorio.enable_whitelist": {
description: "Turn on whitelist for joining the server.",
restartRequired: true, // Because of the cli option "--use-server-whitelist"
type: "boolean",
initialValue: false,
},
"factorio.enable_authserver_bans": {
description: "Turn on Factorio.com based multiplayer bans.",
restartRequired: true,
restartRequired: true, // Because of the cli option "--use-authserver-bans"
type: "boolean",
initialValue: false,
},
@@ -499,13 +500,13 @@ export class InstanceConfig extends classes.Config<InstanceConfigFields> {
},
"factorio.verbose_logging": {
description: "Enable verbose logging on the Factorio server",
restartRequired: true,
restartRequired: true, // Because of the cli option "--verbose"
type: "boolean",
initialValue: false,
},
"factorio.console_logging": {
description: "Enable console logging to a separate file, useful for 3rd party integrations",
restartRequired: true,
restartRequired: true, // Because of the cli option "--console-log"
type: "boolean",
initialValue: false,
},
@@ -522,10 +523,10 @@ export class InstanceConfig extends classes.Config<InstanceConfigFields> {
initialValue: "bidirectional",
},
"factorio.sync_whitelist": {
description: "Synchronize whitelist with the controller",
description: "Synchronize whitelist with the controller (whitelist must be enabled)",
type: "string",
enum: ["disabled", "enabled"], // TODO: Implement bidirectional
initialValue: "enabled",
enum: ["disabled", "enabled", "bidirectional"],
initialValue: "bidirectional",
},
"factorio.sync_banlist": {
description: "Synchronize banlist with the controller",
+1 -3
View File
@@ -200,7 +200,7 @@ describe("class Instance", function() {
beforeEach(function() {
instance.config.set("factorio.sync_banlist", "bidirectional");
instance.config.set("factorio.sync_adminlist", "bidirectional");
// instance.config.set("factorio.sync_whitelist", "bidirectional"); // Not Implemented
instance.config.set("factorio.sync_whitelist", "bidirectional");
});
it("should send InstanceBanlistUpdateEvent for bans", function() {
instance._recordUserUpdate("BAN", "player", "reason");
@@ -257,7 +257,6 @@ describe("class Instance", function() {
);
});
it("should send InstanceWhitelistUpdateEvent for whitelist add", function() {
this.skip(); // Bidirectional not implemented
instance._recordUserUpdate("WHITELISTED", "player");
assert.deepEqual(
connector.sentMessages[0],
@@ -271,7 +270,6 @@ describe("class Instance", function() {
);
});
it("should send InstanceWhitelistUpdateEvent for whitelist remove", function() {
this.skip(); // Bidirectional not implemented
instance._recordUserUpdate("UNWHITELISTED", "player");
assert.deepEqual(
connector.sentMessages[0],