mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-23 21:36:16 +00:00
Game Stats - backward compatibility (#4623)
## Description: fixes a bug where old games under the old schema don't open correctly main.openfront.dev: <img width="1043" height="405" alt="image" src="https://github.com/user-attachments/assets/d94811b9-ecfe-4243-9afb-5b25df4366f8" /> this branch: <img width="1034" height="942" alt="image" src="https://github.com/user-attachments/assets/8dcf07e6-2934-4a85-85a7-3f51a493c504" /> ## 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: w.o.n
This commit is contained in:
+4
-2
@@ -20,7 +20,7 @@ import {
|
||||
} from "../core/ApiSchemas";
|
||||
import {
|
||||
AnalyticsRecord,
|
||||
AnalyticsRecordSchema,
|
||||
ArchivedAnalyticsRecordSchema,
|
||||
GameInfo,
|
||||
} from "../core/Schemas";
|
||||
import { getAuthHeader, getPlayToken, logOut, userAuth } from "./Auth";
|
||||
@@ -613,7 +613,9 @@ export async function fetchGameById(
|
||||
}
|
||||
|
||||
const json = await res.json();
|
||||
const parsed = AnalyticsRecordSchema.safeParse(json);
|
||||
// Lenient schema: archives written by older builds predate several
|
||||
// schema changes (see ArchivedAnalyticsRecordSchema).
|
||||
const parsed = ArchivedAnalyticsRecordSchema.safeParse(json);
|
||||
if (!parsed.success) {
|
||||
console.warn("fetchGameById: Zod validation failed", parsed.error);
|
||||
return false;
|
||||
|
||||
+29
-1
@@ -21,7 +21,7 @@ import {
|
||||
Trios,
|
||||
UnitType,
|
||||
} from "./game/Game";
|
||||
import { PlayerStatsSchema } from "./StatsSchemas";
|
||||
import { ArchivedPlayerStatsSchema, PlayerStatsSchema } from "./StatsSchemas";
|
||||
import { flattenedEmojiTable } from "./Util";
|
||||
|
||||
export type GameID = string;
|
||||
@@ -928,6 +928,34 @@ export const AnalyticsRecordSchema = PartialAnalyticsRecordSchema.extend({
|
||||
|
||||
export type AnalyticsRecord = z.infer<typeof AnalyticsRecordSchema>;
|
||||
|
||||
// Lenient variant for *reading* archived records. Older builds wrote records
|
||||
// under earlier schemas (username rules tightened since, clanTag and nations
|
||||
// added later, conquests became an array) while the `version` literal never
|
||||
// changed, so strict parsing rejects them wholesale. Records are trusted
|
||||
// server output, not untrusted input — tolerate the historical shapes.
|
||||
// Inferred types are identical to the strict schemas', so parsed results are
|
||||
// still AnalyticsRecord. Not for replays: those require an exact gitCommit
|
||||
// match anyway (see JoinLobbyModal.checkArchivedGame).
|
||||
const ArchivedPlayerRecordSchema = PlayerRecordSchema.extend({
|
||||
// Validated at join time under the rules of its era; the loosest era was
|
||||
// SafeString (max 1000, emoji allowed, no min), so only cap length.
|
||||
username: z.string().max(1000),
|
||||
clanTag: ClanTagSchema.catch(null).default(null), // predates clan tags
|
||||
stats: ArchivedPlayerStatsSchema, // scalar conquests
|
||||
});
|
||||
|
||||
export const ArchivedAnalyticsRecordSchema = AnalyticsRecordSchema.extend({
|
||||
info: GameEndInfoSchema.extend({
|
||||
config: GameConfigSchema.extend({
|
||||
// predates configurable nation count
|
||||
nations: GameConfigSchema.shape.nations
|
||||
.catch("default")
|
||||
.default("default"),
|
||||
}),
|
||||
players: ArchivedPlayerRecordSchema.array(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const GameRecordSchema = AnalyticsRecordSchema.extend({
|
||||
turns: TurnSchema.array(),
|
||||
});
|
||||
|
||||
@@ -127,3 +127,21 @@ export const PlayerStatsSchema = z
|
||||
})
|
||||
.optional();
|
||||
export type PlayerStats = z.infer<typeof PlayerStatsSchema>;
|
||||
|
||||
// Reading archived records: `conquests` was a single value
|
||||
// (BigIntStringSchema) before it was split into per-player-type buckets, so
|
||||
// wrap old scalars into a one-element array (index 0 = human) on read.
|
||||
export const ArchivedPlayerStatsSchema = z.preprocess((val) => {
|
||||
if (
|
||||
val !== null &&
|
||||
typeof val === "object" &&
|
||||
!Array.isArray(val) &&
|
||||
"conquests" in val
|
||||
) {
|
||||
const { conquests } = val as { conquests: unknown };
|
||||
if (conquests !== undefined && !Array.isArray(conquests)) {
|
||||
return { ...(val as Record<string, unknown>), conquests: [conquests] };
|
||||
}
|
||||
}
|
||||
return val;
|
||||
}, PlayerStatsSchema);
|
||||
|
||||
Reference in New Issue
Block a user