Fix client reconnection after page refresh (#3117)

## Description:

- Removed all code related to generating a client ID on the client. The
server now assigns the client ID and sends it to the client in lobby
messages.

## 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
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## 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:
Ryan
2026-02-09 01:10:11 +00:00
committed by GitHub
parent e7676b4260
commit 8dcc7cfb9a
15 changed files with 321 additions and 276 deletions
+31 -33
View File
@@ -56,7 +56,6 @@ export interface LobbyConfig {
serverConfig: ServerConfig;
cosmetics: PlayerCosmeticRefs;
playerName: string;
clientID: ClientID;
gameID: GameID;
turnstileToken: string | null;
// GameStartInfo only exists when playing a singleplayer game.
@@ -71,9 +70,10 @@ export function joinLobby(
onPrestart: () => void,
onJoin: () => void,
): (force?: boolean) => boolean {
console.log(
`joining lobby: gameID: ${lobbyConfig.gameID}, clientID: ${lobbyConfig.clientID}`,
);
// Mutable clientID state — assigned by server (multiplayer) or derived from gameStartInfo (singleplayer)
let clientID: ClientID | undefined;
console.log(`joining lobby: gameID: ${lobbyConfig.gameID}`);
const userSettings: UserSettings = new UserSettings();
startGame(lobbyConfig.gameID, lobbyConfig.gameStartInfo?.config ?? {});
@@ -82,23 +82,18 @@ export function joinLobby(
let currentGameRunner: ClientGameRunner | null = null;
let hasJoined = false;
const onconnect = () => {
if (hasJoined) {
console.log("rejoining game");
transport.rejoinGame(0);
} else {
hasJoined = true;
console.log(`Joining game lobby ${lobbyConfig.gameID}`);
transport.joinGame();
}
// Always send join - server will detect reconnection via persistentID
console.log(`Joining game lobby ${lobbyConfig.gameID}`);
transport.joinGame();
};
let terrainLoad: Promise<TerrainMapData> | null = null;
const onmessage = (message: ServerMessage) => {
if (message.type === "lobby_info") {
eventBus.emit(new LobbyInfoEvent(message.lobby));
// Server tells us our assigned clientID
clientID = message.myClientID;
eventBus.emit(new LobbyInfoEvent(message.lobby, message.myClientID));
return;
}
if (message.type === "prestart") {
@@ -118,11 +113,14 @@ export function joinLobby(
console.log(
`lobby: game started: ${JSON.stringify(message, replacer, 2)}`,
);
// Server tells us our assigned clientID (also sent on start for late joins)
clientID = message.myClientID;
onJoin();
// For multiplayer games, GameStartInfo is not known until game starts.
lobbyConfig.gameStartInfo = message.gameStartInfo;
createClientGame(
lobbyConfig,
clientID,
eventBus,
transport,
userSettings,
@@ -148,7 +146,7 @@ export function joinLobby(
e.message,
e.stack,
lobbyConfig.gameID,
lobbyConfig.clientID,
clientID,
true,
false,
"error_modal.connection_error",
@@ -169,7 +167,7 @@ export function joinLobby(
message.error,
message.message,
lobbyConfig.gameID,
lobbyConfig.clientID,
clientID,
true,
false,
"error_modal.connection_error",
@@ -196,6 +194,7 @@ export function joinLobby(
async function createClientGame(
lobbyConfig: LobbyConfig,
clientID: ClientID,
eventBus: EventBus,
transport: Transport,
userSettings: UserSettings,
@@ -221,16 +220,13 @@ async function createClientGame(
mapLoader,
);
}
const worker = new WorkerClient(
lobbyConfig.gameStartInfo,
lobbyConfig.clientID,
);
const worker = new WorkerClient(lobbyConfig.gameStartInfo, clientID);
await worker.initialize();
const gameView = new GameView(
worker,
config,
gameMap,
lobbyConfig.clientID,
clientID,
lobbyConfig.gameStartInfo.gameID,
lobbyConfig.gameStartInfo.players,
);
@@ -244,6 +240,7 @@ async function createClientGame(
return new ClientGameRunner(
lobbyConfig,
clientID,
eventBus,
gameRenderer,
new InputHandler(gameRenderer.uiState, canvas, eventBus),
@@ -269,6 +266,7 @@ export class ClientGameRunner {
constructor(
private lobby: LobbyConfig,
private clientID: ClientID,
private eventBus: EventBus,
private renderer: GameRenderer,
private input: InputHandler,
@@ -302,8 +300,8 @@ export class ClientGameRunner {
{
persistentID: getPersistentID(),
username: this.lobby.playerName,
clientID: this.lobby.clientID,
stats: update.allPlayersStats[this.lobby.clientID],
clientID: this.clientID,
stats: update.allPlayersStats[this.clientID],
},
];
@@ -360,7 +358,7 @@ export class ClientGameRunner {
gu.errMsg,
gu.stack ?? "missing",
this.lobby.gameStartInfo.gameID,
this.lobby.clientID,
this.clientID,
);
console.error(gu.stack);
this.stop();
@@ -422,7 +420,7 @@ export class ClientGameRunner {
"spawn_failed",
translateText("error_modal.spawn_failed.description"),
this.lobby.gameID,
this.lobby.clientID,
this.clientID,
true,
false,
translateText("error_modal.spawn_failed.title"),
@@ -459,7 +457,7 @@ export class ClientGameRunner {
`desync from server: ${JSON.stringify(message)}`,
"",
this.lobby.gameStartInfo.gameID,
this.lobby.clientID,
this.clientID,
true,
false,
"error_modal.desync_notice",
@@ -470,7 +468,7 @@ export class ClientGameRunner {
message.error,
message.message,
this.lobby.gameID,
this.lobby.clientID,
this.clientID,
true,
false,
"error_modal.connection_error",
@@ -554,7 +552,7 @@ export class ClientGameRunner {
return;
}
if (this.myPlayer === null) {
const myPlayer = this.gameView.playerByClientID(this.lobby.clientID);
const myPlayer = this.gameView.playerByClientID(this.clientID);
if (myPlayer === null) return;
this.myPlayer = myPlayer;
}
@@ -589,7 +587,7 @@ export class ClientGameRunner {
const tile = this.gameView.ref(cell.x, cell.y);
if (this.myPlayer === null) {
const myPlayer = this.gameView.playerByClientID(this.lobby.clientID);
const myPlayer = this.gameView.playerByClientID(this.clientID);
if (myPlayer === null) return;
this.myPlayer = myPlayer;
}
@@ -650,7 +648,7 @@ export class ClientGameRunner {
}
if (this.myPlayer === null) {
const myPlayer = this.gameView.playerByClientID(this.lobby.clientID);
const myPlayer = this.gameView.playerByClientID(this.clientID);
if (myPlayer === null) return;
this.myPlayer = myPlayer;
}
@@ -669,7 +667,7 @@ export class ClientGameRunner {
}
if (this.myPlayer === null) {
const myPlayer = this.gameView.playerByClientID(this.lobby.clientID);
const myPlayer = this.gameView.playerByClientID(this.clientID);
if (myPlayer === null) return;
this.myPlayer = myPlayer;
}
@@ -766,7 +764,7 @@ function showErrorModal(
error: string,
message: string | undefined,
gameID: GameID,
clientID: ClientID,
clientID: ClientID | undefined,
closable = false,
showDiscord = true,
heading = "error_modal.crashed",