mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-01 08:23:24 +00:00
fix:name reveal works by publicid during game config (#4415)
## Description: Adds nameRevealPublicIds to GameConfig — the same per-player reveal as nameReveals but keyed by stable account publicId instead of per-game clientID. Lets an automated host (the admin bot / OFM) grant casters and observers real-name vision at create_game, where it only knows publicIds and never learns a client's per-game clientID. viewerSeesAllNames resolves the viewer's clientID to its publicId via allClients and checks membership; nameReveals (clientID) is unchanged. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory ## Please put your Discord username so you can be contacted if a bug or regression is found: zixer._
This commit is contained in:
@@ -293,6 +293,9 @@ export const GameConfigSchema = z.object({
|
||||
// While anonymizeNames is on, clientIDs the host has granted real-name
|
||||
// visibility to (e.g. casters / observers). Everyone else stays anonymized.
|
||||
nameReveals: z.string().array().optional(),
|
||||
// Like nameReveals but keyed by stable account publicId (for automated hosts
|
||||
// that only know publicIds at create_game); resolved to clientID at lookup.
|
||||
nameRevealPublicIds: z.string().array().max(200).optional(),
|
||||
waterNukes: z.boolean().nullable().optional(),
|
||||
randomSpawn: z.boolean(),
|
||||
maxPlayers: z.number().optional(),
|
||||
|
||||
@@ -152,12 +152,18 @@ export class GameServer {
|
||||
: undefined;
|
||||
}
|
||||
|
||||
// anonymizeNames: only players the host granted (nameReveals) see real names.
|
||||
// Nobody is exempt by default, not even the host, until he grants them.
|
||||
// anonymizeNames: only players the host granted (nameReveals, or by account via
|
||||
// nameRevealPublicIds) see real names. Nobody is exempt by default, not even the
|
||||
// host, until he grants them.
|
||||
private viewerSeesAllNames(viewer: ClientID | undefined): boolean {
|
||||
if (viewer === undefined) return false;
|
||||
if (this.gameConfig.nameReveals?.includes(viewer) ?? false) return true;
|
||||
// Resolve the per-game clientID to its stable account publicId so a host that
|
||||
// only knows publicIds (the admin bot) can grant reveal access at create_game.
|
||||
const publicId = this.allClients.get(viewer)?.publicId;
|
||||
return (
|
||||
viewer !== undefined &&
|
||||
(this.gameConfig.nameReveals?.includes(viewer) ?? false)
|
||||
publicId !== undefined &&
|
||||
(this.gameConfig.nameRevealPublicIds?.includes(publicId) ?? false)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -252,6 +258,9 @@ export class GameServer {
|
||||
if (gameConfig.nameReveals !== undefined) {
|
||||
this.gameConfig.nameReveals = gameConfig.nameReveals;
|
||||
}
|
||||
if (gameConfig.nameRevealPublicIds !== undefined) {
|
||||
this.gameConfig.nameRevealPublicIds = gameConfig.nameRevealPublicIds;
|
||||
}
|
||||
// Unconditional on purpose: the host clears cheats by omitting hostCheats
|
||||
// (the full config it sends has hostCheats: undefined when the toggle is
|
||||
// off), so `undefined` here means "clear", not "leave unchanged".
|
||||
|
||||
@@ -43,6 +43,7 @@ function makeGame(
|
||||
anonymizeNames: boolean,
|
||||
disableClanTags = false,
|
||||
nameReveals: string[] = [],
|
||||
nameRevealPublicIds: string[] = [],
|
||||
) {
|
||||
const logger: any = {
|
||||
child: vi.fn().mockReturnThis(),
|
||||
@@ -59,6 +60,7 @@ function makeGame(
|
||||
anonymizeNames,
|
||||
disableClanTags,
|
||||
nameReveals,
|
||||
nameRevealPublicIds,
|
||||
} as any,
|
||||
"creator-pid",
|
||||
);
|
||||
@@ -122,6 +124,21 @@ describe("anonymizeNames: gameInfo (lobby / HTTP / preview)", () => {
|
||||
expect(REAL_NAMES).not.toContain(byId(info, "alice").username);
|
||||
});
|
||||
|
||||
it("on: a viewer granted by account (nameRevealPublicIds) sees everyone's real names", () => {
|
||||
// alice's clientID is "alice", her account publicId is "alice-pub" — the grant
|
||||
// is keyed by publicId and resolved back to her clientID at lookup.
|
||||
const info = makeGame(true, false, [], ["alice-pub"]).gameInfo("alice");
|
||||
for (const id of ["creator", "admin", "bob"]) {
|
||||
expect(REAL_NAMES).toContain(byId(info, id).username);
|
||||
}
|
||||
});
|
||||
|
||||
it("on: a viewer NOT in nameRevealPublicIds still sees only themselves", () => {
|
||||
const info = makeGame(true, false, [], ["alice-pub"]).gameInfo("bob");
|
||||
expect(byId(info, "bob").username).toBe("BobReal"); // self
|
||||
expect(REAL_NAMES).not.toContain(byId(info, "alice").username);
|
||||
});
|
||||
|
||||
it("on: no viewer (HTTP / preview) anonymizes everyone", () => {
|
||||
const info = makeGame(true).gameInfo();
|
||||
for (const id of ["creator", "admin", "alice", "bob"]) {
|
||||
@@ -157,6 +174,15 @@ describe("anonymizeNames: config updates propagate", () => {
|
||||
game.updateGameConfig({ nameReveals: [] });
|
||||
expect(byId(game.gameInfo("alice"), "bob").username).not.toBe("BobReal"); // revoked
|
||||
});
|
||||
|
||||
it("granting nameRevealPublicIds at runtime reveals by account; clearing revokes", () => {
|
||||
const game = makeGame(true);
|
||||
expect(byId(game.gameInfo("alice"), "bob").username).not.toBe("BobReal"); // not granted
|
||||
game.updateGameConfig({ nameRevealPublicIds: ["alice-pub"] });
|
||||
expect(byId(game.gameInfo("alice"), "bob").username).toBe("BobReal"); // granted by account
|
||||
game.updateGameConfig({ nameRevealPublicIds: [] });
|
||||
expect(byId(game.gameInfo("alice"), "bob").username).not.toBe("BobReal"); // revoked
|
||||
});
|
||||
});
|
||||
|
||||
describe("anonymizeNames: startInfoFor (in-game start payload)", () => {
|
||||
|
||||
Reference in New Issue
Block a user