Fix bigint serialization error (#916)

## Description:

- JSON serialization, bigint to string handling
- JSON deserialization, string to bigint handling

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
- [x] I understand that submitting code with bugs that could have been
caught through manual testing blocks releases and new features for all
contributors

---------

Co-authored-by: Scott Anderson <662325+scottanderson@users.noreply.github.com>
This commit is contained in:
Scott Anderson
2025-05-28 13:33:20 -04:00
committed by GitHub
parent 7926267791
commit 40932b9f5f
7 changed files with 28 additions and 16 deletions
+2 -1
View File
@@ -1,5 +1,6 @@
import { consolex } from "../core/Consolex";
import { GameConfig, GameID, GameRecord } from "../core/Schemas";
import { replacer } from "../core/Util";
export interface LocalStatsData {
[key: GameID]: {
@@ -19,7 +20,7 @@ function getStats(): LocalStatsData {
function save(stats: LocalStatsData) {
// To execute asynchronously
setTimeout(
() => localStorage.setItem("game-records", JSON.stringify(stats)),
() => localStorage.setItem("game-records", JSON.stringify(stats, replacer)),
0,
);
}
+7 -4
View File
@@ -11,7 +11,7 @@ import {
ServerStartGameMessageSchema,
Turn,
} from "../core/Schemas";
import { createGameRecord, decompressGameRecord } from "../core/Util";
import { createGameRecord, decompressGameRecord, replacer } from "../core/Util";
import { LobbyConfig } from "./ClientGameRunner";
import { getPersistentID } from "./Main";
@@ -199,9 +199,12 @@ export class LocalServer {
record.turns = [];
}
// For unload events, sendBeacon is the only reliable method
const blob = new Blob([JSON.stringify(GameRecordSchema.parse(record))], {
type: "application/json",
});
const blob = new Blob(
[JSON.stringify(GameRecordSchema.parse(record), replacer)],
{
type: "application/json",
},
);
const workerPath = this.lobbyConfig.serverConfig.workerPath(
this.lobbyConfig.gameStartInfo.gameID,
);
+2 -1
View File
@@ -23,6 +23,7 @@ import {
ServerMessageSchema,
Winner,
} from "../core/Schemas";
import { replacer } from "../core/Util";
import { LobbyConfig } from "./ClientGameRunner";
import { LocalServer } from "./LocalServer";
@@ -536,7 +537,7 @@ export class Transport {
winner: event.winner,
allPlayersStats: event.allPlayersStats,
} satisfies ClientSendWinnerMessage;
this.sendMsg(JSON.stringify(msg));
this.sendMsg(JSON.stringify(msg, replacer));
} else {
console.log(
"WebSocket is not open. Current state:",
+8 -2
View File
@@ -84,13 +84,19 @@ export const OTHER_INDEX_DESTROY = 1; // Structures and warships destroyed
export const OTHER_INDEX_CAPTURE = 2; // Structures captured
export const OTHER_INDEX_LOST = 3; // Structures/warships destroyed/captured by others
const AtLeastOneNumberSchema = z.bigint().array().min(1);
const BigIntStringSchema = z.preprocess((val) => {
if (typeof val === "string" && /^\d+$/.test(val)) return BigInt(val);
if (typeof val === "bigint") return val;
return val;
}, z.bigint());
const AtLeastOneNumberSchema = BigIntStringSchema.array().min(1);
export type AtLeastOneNumber = z.infer<typeof AtLeastOneNumberSchema>;
export const PlayerStatsSchema = z
.object({
attacks: AtLeastOneNumberSchema.optional(),
betrayals: z.bigint().positive().optional(),
betrayals: BigIntStringSchema.optional(),
boats: z.record(BoatUnitSchema, AtLeastOneNumberSchema).optional(),
bombs: z.record(BombUnitSchema, AtLeastOneNumberSchema).optional(),
gold: AtLeastOneNumberSchema.optional(),
+7
View File
@@ -308,3 +308,10 @@ export const emojiTable: string[][] = [
];
// 2d to 1d array
export const flattenedEmojiTable: string[] = emojiTable.flat();
/**
* JSON.stringify replacer function that converts bigint values to strings.
*/
export function replacer(_key: string, value: any): any {
return typeof value === "bigint" ? value.toString() : value;
}
+1 -7
View File
@@ -1,6 +1,7 @@
import { S3 } from "@aws-sdk/client-s3";
import { getServerConfigFromServer } from "../core/configuration/ConfigLoader";
import { AnalyticsRecord, GameID, GameRecord } from "../core/Schemas";
import { replacer } from "../core/Util";
import { logger } from "./Logger";
const config = getServerConfigFromServer();
@@ -147,10 +148,3 @@ export async function gameRecordExists(gameId: GameID): Promise<boolean> {
return false;
}
}
/**
* JSON.stringify replacer function that converts bigint values to strings.
*/
export function replacer(_key: string, value: any): any {
return typeof value === "bigint" ? value.toString() : value;
}
+1 -1
View File
@@ -7,7 +7,7 @@ import {
} from "../src/core/game/Game";
import { Stats } from "../src/core/game/Stats";
import { StatsImpl } from "../src/core/game/StatsImpl";
import { replacer } from "../src/server/Archive";
import { replacer } from "../src/core/Util";
import { setup } from "./util/Setup";
let stats: Stats;