mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-18 16:22:41 +00:00
Cherry pick PR 1836 "reloading the page during a game should reload with the same ClientID"
Co-authored-by: Danny Asmussen <woodydrn@gmail.com>
This commit is contained in:
committed by
variablevince
parent
6a78494404
commit
14d10307a9
@@ -2,7 +2,7 @@ import { LitElement, html } from "lit";
|
||||
import { customElement, query, state } from "lit/decorators.js";
|
||||
import { translateText } from "../client/Utils";
|
||||
import { GameInfo, GameRecordSchema } from "../core/Schemas";
|
||||
import { generateID } from "../core/Util";
|
||||
import { getClientID } from "../core/Util";
|
||||
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
|
||||
import { JoinLobbyEvent } from "./Main";
|
||||
import "./components/baseComponents/Button";
|
||||
@@ -218,7 +218,7 @@ export class JoinPrivateLobbyModal extends LitElement {
|
||||
new CustomEvent("join-lobby", {
|
||||
detail: {
|
||||
gameID: lobbyId,
|
||||
clientID: generateID(),
|
||||
clientID: getClientID(lobbyId),
|
||||
} as JoinLobbyEvent,
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
@@ -290,7 +290,7 @@ export class JoinPrivateLobbyModal extends LitElement {
|
||||
detail: {
|
||||
gameID: lobbyId,
|
||||
gameRecord: parsed.data,
|
||||
clientID: generateID(),
|
||||
clientID: getClientID(lobbyId),
|
||||
} as JoinLobbyEvent,
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
|
||||
+4
-2
@@ -1,7 +1,9 @@
|
||||
import version from "../../resources/version.txt";
|
||||
import { UserMeResponse } from "../core/ApiSchemas";
|
||||
import { ID } from "../core/BaseSchemas";
|
||||
import { EventBus } from "../core/EventBus";
|
||||
import { GameRecord, GameStartInfo, ID } from "../core/Schemas";
|
||||
import { GameRecord, GameStartInfo } from "../core/Schemas";
|
||||
import { getClientID } from "../core/Util";
|
||||
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
|
||||
import { UserSettings } from "../core/game/UserSettings";
|
||||
import "./AccountModal";
|
||||
@@ -501,7 +503,7 @@ class Client {
|
||||
},
|
||||
playerName: this.usernameInput?.getCurrentUsername() ?? "",
|
||||
token: getPlayToken(),
|
||||
clientID: lobby.clientID,
|
||||
clientID: getClientID(lobby.gameID),
|
||||
gameStartInfo: lobby.gameStartInfo ?? lobby.gameRecord?.info,
|
||||
gameRecord: lobby.gameRecord,
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@ import { customElement, state } from "lit/decorators.js";
|
||||
import { renderDuration, translateText } from "../client/Utils";
|
||||
import { GameMapType, GameMode } from "../core/game/Game";
|
||||
import { GameID, GameInfo } from "../core/Schemas";
|
||||
import { generateID } from "../core/Util";
|
||||
import { getClientID } from "../core/Util";
|
||||
import { JoinLobbyEvent } from "./Main";
|
||||
import { terrainMapFileLoader } from "./TerrainMapFileLoader";
|
||||
|
||||
@@ -211,7 +211,7 @@ export class PublicLobby extends LitElement {
|
||||
new CustomEvent("join-lobby", {
|
||||
detail: {
|
||||
gameID: lobby.gameID,
|
||||
clientID: generateID(),
|
||||
clientID: getClientID(lobby.gameID),
|
||||
} as JoinLobbyEvent,
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from "../core/game/Game";
|
||||
import { UserSettings } from "../core/game/UserSettings";
|
||||
import { TeamCountConfig } from "../core/Schemas";
|
||||
import { generateID } from "../core/Util";
|
||||
import { generateID, getClientID } from "../core/Util";
|
||||
import "./components/baseComponents/Button";
|
||||
import "./components/baseComponents/Modal";
|
||||
import "./components/Difficulties";
|
||||
@@ -525,8 +525,8 @@ export class SinglePlayerModal extends LitElement {
|
||||
console.log(
|
||||
`Starting single player game with map: ${GameMapType[this.selectedMap as keyof typeof GameMapType]}${this.useRandomMap ? " (Randomly selected)" : ""}`,
|
||||
);
|
||||
const clientID = generateID();
|
||||
const gameID = generateID();
|
||||
const clientID = getClientID(gameID);
|
||||
|
||||
const usernameInput = document.querySelector(
|
||||
"username-input",
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
// This file contains shared schemas
|
||||
import { z } from "zod";
|
||||
|
||||
export const ID = z
|
||||
.string()
|
||||
.regex(/^[a-zA-Z0-9]+$/)
|
||||
.length(8);
|
||||
+1
-4
@@ -1,6 +1,7 @@
|
||||
import { z } from "zod";
|
||||
import quickChatData from "../../resources/QuickChat.json" with { type: "json" };
|
||||
import countries from "../client/data/countries.json" with { type: "json" };
|
||||
import { ID } from "./BaseSchemas";
|
||||
import {
|
||||
ColorPaletteSchema,
|
||||
PatternDataSchema,
|
||||
@@ -200,10 +201,6 @@ const EmojiSchema = z
|
||||
.number()
|
||||
.nonnegative()
|
||||
.max(flattenedEmojiTable.length - 1);
|
||||
export const ID = z
|
||||
.string()
|
||||
.regex(/^[a-zA-Z0-9]+$/)
|
||||
.length(8);
|
||||
|
||||
export const AllPlayersStatsSchema = z.record(ID, PlayerStatsSchema);
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import DOMPurify from "dompurify";
|
||||
import { customAlphabet } from "nanoid";
|
||||
import { ID } from "./BaseSchemas";
|
||||
import { Cell, Unit } from "./game/Game";
|
||||
import { GameMap, TileRef } from "./game/GameMap";
|
||||
import {
|
||||
ClientID,
|
||||
GameConfig,
|
||||
GameID,
|
||||
GameRecord,
|
||||
@@ -254,6 +256,24 @@ export function generateID(): GameID {
|
||||
return nanoid();
|
||||
}
|
||||
|
||||
export function getClientID(gameID: GameID): ClientID {
|
||||
const cachedGame = localStorage.getItem("game_id");
|
||||
const cachedClient = localStorage.getItem("client_id");
|
||||
|
||||
if (
|
||||
gameID === cachedGame &&
|
||||
cachedClient &&
|
||||
ID.safeParse(cachedClient).success
|
||||
)
|
||||
return cachedClient;
|
||||
|
||||
const clientId = generateID();
|
||||
localStorage.setItem("game_id", gameID);
|
||||
localStorage.setItem("client_id", clientId);
|
||||
|
||||
return clientId;
|
||||
}
|
||||
|
||||
export function toInt(num: number): bigint {
|
||||
if (num === Infinity) {
|
||||
return BigInt(Number.MAX_SAFE_INTEGER);
|
||||
|
||||
@@ -4,8 +4,9 @@ import rateLimit from "express-rate-limit";
|
||||
import http from "http";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { ID } from "../core/BaseSchemas";
|
||||
import { getServerConfigFromServer } from "../core/configuration/ConfigLoader";
|
||||
import { GameInfo, ID } from "../core/Schemas";
|
||||
import { GameInfo } from "../core/Schemas";
|
||||
import { generateID } from "../core/Util";
|
||||
import { logger } from "./Logger";
|
||||
import { MapPlaylist } from "./MapPlaylist";
|
||||
|
||||
@@ -7,12 +7,12 @@ import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { WebSocket, WebSocketServer } from "ws";
|
||||
import { z } from "zod";
|
||||
import { ID } from "../core/BaseSchemas";
|
||||
import { getServerConfigFromServer } from "../core/configuration/ConfigLoader";
|
||||
import { GameType } from "../core/game/Game";
|
||||
import {
|
||||
ClientMessageSchema,
|
||||
GameID,
|
||||
ID,
|
||||
PartialGameRecordSchema,
|
||||
ServerErrorMessage,
|
||||
} from "../core/Schemas";
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { getClientID } from "../../src/core/Util";
|
||||
|
||||
describe("Util", () => {
|
||||
class InMemoryLocalStorage {
|
||||
private readonly store = new Map<string, string>();
|
||||
getItem(key: string): string | null {
|
||||
return this.store.has(key) ? this.store.get(key)! : null;
|
||||
}
|
||||
setItem(key: string, value: string): void {
|
||||
this.store.set(key, String(value));
|
||||
}
|
||||
removeItem(key: string): void {
|
||||
this.store.delete(key);
|
||||
}
|
||||
clear(): void {
|
||||
this.store.clear();
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
(globalThis as any).localStorage = new InMemoryLocalStorage();
|
||||
});
|
||||
|
||||
test("creates and persists a new client", () => {
|
||||
expect((globalThis as any).localStorage.getItem("client_id")).toBeNull();
|
||||
|
||||
const id = getClientID("testGameID");
|
||||
|
||||
expect(typeof id).toBe("string");
|
||||
expect(id).toMatch(/^[0-9a-zA-Z]{8}$/);
|
||||
|
||||
const stored = (globalThis as any).localStorage.getItem("client_id");
|
||||
expect(stored).toBe(id);
|
||||
});
|
||||
|
||||
test("creates two games and make sure only last one is updated", () => {
|
||||
const id1 = getClientID("testGameID1");
|
||||
const id2 = getClientID("testGameID2");
|
||||
|
||||
expect(id1).not.toBe(id2);
|
||||
|
||||
const stored = (globalThis as any).localStorage.getItem("client_id");
|
||||
expect(stored).toBe(id2);
|
||||
});
|
||||
|
||||
test("creates two games with same game id, make sure the id stays the same", () => {
|
||||
const id1 = getClientID("testGameID1");
|
||||
const id2 = getClientID("testGameID1");
|
||||
|
||||
expect(id1).toBe(id2);
|
||||
|
||||
const stored = (globalThis as any).localStorage.getItem("client_id");
|
||||
expect(stored).toBe(id1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user