mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-25 02:27:14 +00:00
fix: survive host modal close during listed-lobby auto-start (#4514)
Follow-up to the listed-lobby auto-start feature (`2a82b01e3`, on main). ## Bug When a listed lobby auto-started (and in some paths on manual start), the game began server-side but a player got ejected back to the menu before it loaded. Root cause: `BaseModal.close()` navigates via `showPage()`, which **force-closes whichever page-modal is currently visible**. During the game-start transition that is the *other* lobby modal — closing the join modal first cascade-closed the host's modal with `leaveLobbyOnClose` still armed (host disconnected → host-left teardown killed the game); reversing the order just moved the victim to the joiner. No close order fixes both sides. ## Fix - **Disarm both lobby modals before closing either** in the prestart transition (`disarmLeaveOnClose()` on `HostLobbyModal` and `JoinLobbyModal`), so no cascade order can disconnect anyone mid game-start. - Both modals re-arm `leaveLobbyOnClose` in `onOpen` instead of `onClose`'s state reset, so once disarmed no later cascade close can re-arm it until the modal is reopened. - `HostLobbyModal.closeWithoutLeaving()` (pattern `JoinLobbyModal` already used), called explicitly instead of the generic modal-close loop. - **Server hardening:** `handleClientDisconnect` bails on `hasStarted()` (which includes prestart) instead of raw `_hasStarted`, so host socket churn during the lobby → game transition can never tear down a starting game regardless of client behavior. Regression test included. ## Verification Headless end-to-end (two browser contexts, host + joiner, `leave-lobby` stack-trace tripwire armed on both pages): - **Manual start:** host and joiner both receive prestart/start and enter the game; zero `leave-lobby` dispatches. - **Auto-start:** same, with nobody interacting after listing. Also confirmed manually in a real browser. The auto-start deadline was temporarily 30s during testing and is restored to 5 minutes in the final commit. Full suite green (1843 + 161), tsc/eslint/prettier clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -338,6 +338,10 @@ export class JoinLobbyModal extends BaseModal {
|
||||
}
|
||||
|
||||
protected onOpen(args?: Record<string, unknown>): void {
|
||||
// Re-armed here (not in onClose's reset) so that once
|
||||
// disarmLeaveOnClose() runs, no close cascade can re-arm it and
|
||||
// disconnect the player mid game-start.
|
||||
this.leaveLobbyOnClose = true;
|
||||
void this.hostedLobbySocket.start();
|
||||
const lobbyId = typeof args?.lobbyId === "string" ? args.lobbyId : "";
|
||||
const lobbyInfo = args?.lobbyInfo as GameInfo | PublicGameInfo | undefined;
|
||||
@@ -453,7 +457,6 @@ export class JoinLobbyModal extends BaseModal {
|
||||
this.serverTimeOffset = 0;
|
||||
this.lobbyCreatorClientID = null;
|
||||
this.isConnecting = true;
|
||||
this.leaveLobbyOnClose = true;
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
@@ -474,8 +477,17 @@ export class JoinLobbyModal extends BaseModal {
|
||||
this.close();
|
||||
}
|
||||
|
||||
public closeWithoutLeaving() {
|
||||
// Closing this modal is part of the game-start transition, not the player
|
||||
// leaving. Kept separate from closeWithoutLeaving because closing ANY
|
||||
// page-modal navigates via showPage, which force-closes the currently
|
||||
// visible page — so all lobby modals must be disarmed before any of them
|
||||
// is closed.
|
||||
public disarmLeaveOnClose() {
|
||||
this.leaveLobbyOnClose = false;
|
||||
}
|
||||
|
||||
public closeWithoutLeaving() {
|
||||
this.disarmLeaveOnClose();
|
||||
this.close();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user