mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 11:20:45 +00:00
Handle confirmation on popstate event if player is active in a game (#2777)
Please merge it into v29, since in that version the back navigation out of a game is currently **broken** after switching from hash-based to path-based routing via #2740 ## Description: Protect against players accidentally leaving an active game by pressing the browser back button. Uses the same confirmation dialog as the game exit button. Partially handles issue #1877 (protects against back button, not closing tab or editing the URL directly). <img width="861" height="373" alt="image" src="https://github.com/user-attachments/assets/167cc137-6df3-44a7-a594-91ffd904857d" /> Partial credit to PR #2141 ## 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: deshack_82603
This commit is contained in:
committed by
GitHub
parent
2fcca8ee26
commit
b1d63533d5
@@ -69,7 +69,7 @@ export function joinLobby(
|
||||
lobbyConfig: LobbyConfig,
|
||||
onPrestart: () => void,
|
||||
onJoin: () => void,
|
||||
): () => void {
|
||||
): (force?: boolean) => boolean {
|
||||
console.log(
|
||||
`joining lobby: gameID: ${lobbyConfig.gameID}, clientID: ${lobbyConfig.clientID}`,
|
||||
);
|
||||
@@ -79,6 +79,8 @@ export function joinLobby(
|
||||
|
||||
const transport = new Transport(lobbyConfig, eventBus);
|
||||
|
||||
let currentGameRunner: ClientGameRunner | null = null;
|
||||
|
||||
let hasJoined = false;
|
||||
|
||||
const onconnect = () => {
|
||||
@@ -122,9 +124,15 @@ export function joinLobby(
|
||||
terrainLoad,
|
||||
terrainMapFileLoader,
|
||||
)
|
||||
.then((r) => r.start())
|
||||
.then((r) => {
|
||||
currentGameRunner = r;
|
||||
r.start();
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error("error creating client game", e);
|
||||
|
||||
currentGameRunner = null;
|
||||
|
||||
const startingModal = document.querySelector(
|
||||
"game-starting-modal",
|
||||
) as HTMLElement;
|
||||
@@ -165,9 +173,19 @@ export function joinLobby(
|
||||
}
|
||||
};
|
||||
transport.connect(onconnect, onmessage);
|
||||
return () => {
|
||||
return (force: boolean = false) => {
|
||||
if (!force && currentGameRunner?.shouldPreventWindowClose()) {
|
||||
console.log("Player is active, prevent leaving game");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log("leaving game");
|
||||
|
||||
currentGameRunner = null;
|
||||
transport.leaveGame();
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -256,6 +274,21 @@ export class ClientGameRunner {
|
||||
this.lastMessageTime = Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether window closing should be prevented.
|
||||
*
|
||||
* Used to show a confirmation dialog when the user attempts to close
|
||||
* the window or navigate away during an active game session.
|
||||
*
|
||||
* @returns {boolean} `true` if the window close should be prevented
|
||||
* (when the player is alive in the game), `false` otherwise
|
||||
* (when the player is not alive or doesn't exist)
|
||||
*/
|
||||
public shouldPreventWindowClose(): boolean {
|
||||
// Show confirmation dialog if player is alive in the game
|
||||
return !!this.myPlayer?.isAlive();
|
||||
}
|
||||
|
||||
private async saveGame(update: WinUpdate) {
|
||||
if (this.myPlayer === null) {
|
||||
return;
|
||||
|
||||
+42
-16
@@ -208,9 +208,11 @@ export interface JoinLobbyEvent {
|
||||
}
|
||||
|
||||
class Client {
|
||||
private gameStop: (() => void) | null = null;
|
||||
private gameStop: ((force?: boolean) => boolean) | null = null;
|
||||
private eventBus: EventBus = new EventBus();
|
||||
|
||||
private currentUrl: string | null = null;
|
||||
|
||||
private usernameInput: UsernameInput | null = null;
|
||||
private flagInput: FlagInput | null = null;
|
||||
|
||||
@@ -277,7 +279,7 @@ class Client {
|
||||
window.addEventListener("beforeunload", async () => {
|
||||
console.log("Browser is closing");
|
||||
if (this.gameStop !== null) {
|
||||
this.gameStop();
|
||||
this.gameStop(true);
|
||||
await crazyGamesSDK.gameplayStop();
|
||||
}
|
||||
});
|
||||
@@ -583,15 +585,7 @@ class Client {
|
||||
// Attempt to join lobby
|
||||
this.handleUrl();
|
||||
|
||||
let preventHashUpdate = false;
|
||||
|
||||
const onHashUpdate = () => {
|
||||
// Prevent double-handling when both popstate and hashchange fire
|
||||
if (preventHashUpdate) {
|
||||
preventHashUpdate = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset the UI to its initial state
|
||||
this.joinModal?.close();
|
||||
if (this.gameStop !== null) {
|
||||
@@ -602,11 +596,39 @@ class Client {
|
||||
this.handleUrl();
|
||||
};
|
||||
|
||||
const onPopState = () => {
|
||||
if (this.currentUrl !== null && this.gameStop !== null) {
|
||||
console.info("Game is active");
|
||||
|
||||
if (!this.gameStop()) {
|
||||
console.info("Player is active, ask before leaving game");
|
||||
|
||||
const isConfirmed = confirm(
|
||||
translateText("help_modal.exit_confirmation"),
|
||||
);
|
||||
|
||||
if (!isConfirmed) {
|
||||
// Rollback navigator history
|
||||
history.pushState(null, "", this.currentUrl);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.info("Player is not active, leave the game immediately");
|
||||
|
||||
crazyGamesSDK.gameplayStop().then(() => {
|
||||
// redirect to the home page
|
||||
window.location.href = "/";
|
||||
});
|
||||
} else {
|
||||
console.info("Game not active, handle hash update");
|
||||
|
||||
onHashUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
// Handle browser navigation & manual hash edits
|
||||
window.addEventListener("popstate", () => {
|
||||
preventHashUpdate = true;
|
||||
onHashUpdate();
|
||||
});
|
||||
window.addEventListener("popstate", onPopState);
|
||||
window.addEventListener("hashchange", onHashUpdate);
|
||||
window.addEventListener("join-changed", onHashUpdate);
|
||||
|
||||
@@ -738,7 +760,7 @@ class Client {
|
||||
console.log(`joining lobby ${lobby.gameID}`);
|
||||
if (this.gameStop !== null) {
|
||||
console.log("joining lobby, stopping existing game");
|
||||
this.gameStop();
|
||||
this.gameStop(true);
|
||||
document.body.classList.remove("in-game");
|
||||
}
|
||||
const config = await getServerConfigFromClient();
|
||||
@@ -844,6 +866,9 @@ class Client {
|
||||
"",
|
||||
`/${config.workerPath(lobby.gameID)}/game/${lobby.gameID}?live`,
|
||||
);
|
||||
|
||||
// Store current URL for popstate confirmation
|
||||
this.currentUrl = window.location.href;
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -865,8 +890,9 @@ class Client {
|
||||
return;
|
||||
}
|
||||
console.log("leaving lobby, cancelling game");
|
||||
this.gameStop();
|
||||
this.gameStop(true);
|
||||
this.gameStop = null;
|
||||
this.currentUrl = null;
|
||||
|
||||
document.body.classList.remove("in-game");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user