fix(crazygames): guest username on logout, hide fullscreen, in-game pop-ups#4538
Conversation
…op-ups Addresses four CrazyGames platform requests: - Username reverts to a fresh guest (AnonXXX) name when the player logs out of their CrazyGames account, instead of keeping the account name. - Hide our in-game fullscreen button on CrazyGames (the frame provides one). - Replace native browser confirm()/alert() dialogs shown during gameplay with an in-game pop-up (new InGameModal): exit-game confirmation, kick confirmation, host-left, and connection-refused. - Report gameplayStop to CrazyGames whenever the Settings menu or a pop-up is open (previously only when it also paused the sim), resuming on close. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Instead of a hand-rolled modal, the showInGameConfirm/showInGameAlert helpers now drive the existing <confirm-dialog> component so the styling stays consistent with the rest of the app. Keeps the promise-based API (callable from non-Lit contexts) and the gameplayStop/Start reporting. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
WalkthroughThis PR replaces browser dialogs with in-game modals, updates several client flows to await them, and changes CrazyGames gameplay, username storage, and fullscreen behavior. ChangesIn-game modal and CrazyGames flow updates
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant UIComponent
participant InGameModal
participant CrazyGamesSDK
participant ConfirmDialog
UIComponent->>InGameModal: showInGameConfirm(message) / showInGameAlert(message)
InGameModal->>CrazyGamesSDK: gameplayStop()
InGameModal->>ConfirmDialog: render dialog
ConfirmDialog-->>InGameModal: confirm / cancel
InGameModal->>CrazyGamesSDK: gameplayStart()
InGameModal-->>UIComponent: resolve true / false
Possibly related PRs
Suggested labels: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/client/hud/layers/GraphicsSettingsModal.ts (1)
169-183: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract shared
pauseGamelogic to avoid duplication withSettingsModal.ts.The
pauseGamemethod is now identical to the one inSettingsModal.ts(lines 110-124). Both implement the same CrazyGames +PauseGameIntentEventgating. Extracting a shared helper prevents future divergence if the pause-reporting logic changes.The logic itself is correct:
gameplayStop()fires unconditionally on open,gameplayStart()fires on close unless the game was already paused, andPauseGameIntentEventremains gated byshouldPause && !wasPausedWhenOpened. The CrazyGames SDK's internalisGameplayActive/isReady()guards make the unconditional calls safe.♻️ Proposed extraction into a shared helper
+// e.g. in a shared module imported by both modals +export function pauseGameForModal( + pause: boolean, + shouldPause: boolean, + wasPausedWhenOpened: boolean, + eventBus: EventBus, +) { + if (pause) { + crazyGamesSDK.gameplayStop(); + } else if (!wasPausedWhenOpened) { + crazyGamesSDK.gameplayStart(); + } + if (shouldPause && !wasPausedWhenOpened) { + eventBus.emit(new PauseGameIntentEvent(pause)); + } +}Then in both modals:
private pauseGame(pause: boolean) { - // CrazyGames: report gameplay as stopped whenever this settings menu is open, - // and resumed when it closes — unless the game was already paused when opened. - if (pause) { - crazyGamesSDK.gameplayStop(); - } else if (!this.wasPausedWhenOpened) { - crazyGamesSDK.gameplayStart(); - } - - // Only pause the simulation itself when we own the pause (singleplayer or - // lobby creator). - if (this.shouldPause && !this.wasPausedWhenOpened) { - this.eventBus.emit(new PauseGameIntentEvent(pause)); - } + pauseGameForModal(pause, this.shouldPause, this.wasPausedWhenOpened, this.eventBus); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/client/hud/layers/GraphicsSettingsModal.ts` around lines 169 - 183, Extract the duplicated pause-reporting logic from GraphicsSettingsModal.pauseGame and the matching SettingsModal method into a shared helper so both modals use the same CrazyGames and PauseGameIntentEvent gating. Move the common behavior into a reusable function or utility that takes the pause state plus the modal-specific flags such as shouldPause and wasPausedWhenOpened, then have both pauseGame methods delegate to it to prevent future divergence.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/client/InGameModal.ts`:
- Around line 16-32: showDialog() currently resumes gameplay unconditionally,
which can override other active pause states. Update the flow in
InGameModal.showDialog so gameplayStart() is only called through the shared
pause-management mechanism used across SettingsModal, GraphicsSettingsModal,
GameRightSidebar, and WinModal, rather than directly from this dialog. Ensure
the dialog only releases its own stop request and let the centralized
counter/state decide when gameplay can actually restart.
In `@src/client/Transport.ts`:
- Line 391: The alert text in Transport.showInGameAlert is user-visible but
currently hardcoded as `connection refused: ${event.reason}` instead of going
through translateText(). Update the alert call to use a translation key and pass
event.reason as the interpolated value, following the existing localization
pattern used in src/client/**/* and keeping the logic in Transport or the
surrounding connection-handling code.
In `@src/client/UsernameInput.ts`:
- Around line 101-119: The CrazyGames auth initialization in UsernameInput can
race with a logout and overwrite the guest state after the user has already
logged out. Add a small request/version guard around the getUsername() promise
and the addAuthListener callback so only the latest auth state updates
baseUsername, and make sure the null branch can still reset to genAnonUsername
even if the initial login state has not been marked yet.
---
Nitpick comments:
In `@src/client/hud/layers/GraphicsSettingsModal.ts`:
- Around line 169-183: Extract the duplicated pause-reporting logic from
GraphicsSettingsModal.pauseGame and the matching SettingsModal method into a
shared helper so both modals use the same CrazyGames and PauseGameIntentEvent
gating. Move the common behavior into a reusable function or utility that takes
the pause state plus the modal-specific flags such as shouldPause and
wasPausedWhenOpened, then have both pauseGame methods delegate to it to prevent
future divergence.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 8adc37ba-94d6-4241-bbdc-dfabdae9525f
📒 Files selected for processing (9)
src/client/ClientGameRunner.tssrc/client/InGameModal.tssrc/client/Main.tssrc/client/Transport.tssrc/client/UsernameInput.tssrc/client/hud/layers/GameRightSidebar.tssrc/client/hud/layers/GraphicsSettingsModal.tssrc/client/hud/layers/PlayerModerationModal.tssrc/client/hud/layers/SettingsModal.ts
| function showDialog(props: Partial<ConfirmDialog>): Promise<boolean> { | ||
| const dialog = document.createElement("confirm-dialog") as ConfirmDialog; | ||
| Object.assign(dialog, props); | ||
|
|
||
| crazyGamesSDK.gameplayStop(); | ||
| document.body.appendChild(dialog); | ||
|
|
||
| return new Promise<boolean>((resolve) => { | ||
| const done = (result: boolean) => { | ||
| dialog.remove(); | ||
| crazyGamesSDK.gameplayStart(); | ||
| resolve(result); | ||
| }; | ||
| dialog.addEventListener("confirm", () => done(true)); | ||
| dialog.addEventListener("cancel", () => done(false)); | ||
| }); | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check whether CrazyGames SDK gameplayStart/gameplayStop use a counter or boolean
rg -n "gameplayStart\|gameplayStop" src/client/CrazyGamesSDK.ts -A5 -B2
# Check if SettingsModal/GraphicsSettingsModal also call gameplayStop/gameplayStart
rg -n "gameplayStart\|gameplayStop" src/client/hud/layers/SettingsModal.ts src/client/hud/layers/GraphicsSettingsModal.ts -A3 -B1Repository: openfrontio/OpenFrontIO
Length of output: 161
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find all gameplayStart/gameplayStop usages
rg -n "gameplayStart|gameplayStop" src -S
# Locate the modal/dialog files involved
fd -a "InGameModal.ts|SettingsModal.ts|GraphicsSettingsModal.ts|CrazyGamesSDK.ts" srcRepository: openfrontio/OpenFrontIO
Length of output: 1892
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Read the relevant sections with line numbers
sed -n '1,120p' src/client/InGameModal.ts
printf '\n--- CrazyGamesSDK ---\n'
sed -n '170,235p' src/client/CrazyGamesSDK.ts
printf '\n--- SettingsModal ---\n'
sed -n '90,130p' src/client/hud/layers/SettingsModal.ts
printf '\n--- GraphicsSettingsModal ---\n'
sed -n '155,190p' src/client/hud/layers/GraphicsSettingsModal.ts
printf '\n--- WinModal ---\n'
sed -n '210,240p' src/client/hud/layers/WinModal.ts
printf '\n--- GameRightSidebar ---\n'
sed -n '175,215p' src/client/hud/layers/GameRightSidebar.tsRepository: openfrontio/OpenFrontIO
Length of output: 7838
Avoid resuming gameplay from this dialog alone
showDialog() can still call gameplayStart() while another UI flow is keeping gameplay stopped. This code is only safe if all gameplayStop()/gameplayStart() calls go through one shared counter or centralized pause state; a local counter here will not cover SettingsModal, GraphicsSettingsModal, GameRightSidebar, or WinModal.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/client/InGameModal.ts` around lines 16 - 32, showDialog() currently
resumes gameplay unconditionally, which can override other active pause states.
Update the flow in InGameModal.showDialog so gameplayStart() is only called
through the shared pause-management mechanism used across SettingsModal,
GraphicsSettingsModal, GameRightSidebar, and WinModal, rather than directly from
this dialog. Ensure the dialog only releases its own stop request and let the
centralized counter/state decide when gameplay can actually restart.
| crazyGamesSDK.getUsername().then((username) => { | ||
| if (username) { | ||
| this.crazyGamesLoggedIn = true; | ||
| this.baseUsername = username; | ||
| this.validateAndStore(); | ||
| } | ||
| }); | ||
| crazyGamesSDK.addAuthListener((user) => { | ||
| if (user) { | ||
| this.crazyGamesLoggedIn = true; | ||
| this.baseUsername = user.username; | ||
| this.validateAndStore(); | ||
| } else if (this.crazyGamesLoggedIn) { | ||
| // Logged out of CrazyGames — revert to a fresh guest username. | ||
| this.crazyGamesLoggedIn = false; | ||
| this.baseUsername = genAnonUsername(); | ||
| this.validateAndStore(); | ||
| } | ||
| }); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect CrazyGamesSDK.getUsername to check whether it returns a live or snapshot username.
ast-grep outline src/client/CrazyGamesSDK.ts --match getUsername --view expandedRepository: openfrontio/OpenFrontIO
Length of output: 203
🏁 Script executed:
#!/bin/bash
set -euo pipefail
git ls-files 'src/client/**/*' | sed -n '1,200p'
printf '\n--- search CrazyGames symbols ---\n'
rg -n --hidden --glob 'src/client/**/*.{ts,tsx}' 'CrazyGamesSDK|getUsername|addAuthListener|crazyGamesLoggedIn|baseUsername' src/client
printf '\n--- outline UsernameInput ---\n'
ast-grep outline src/client/UsernameInput.ts --view expandedRepository: openfrontio/OpenFrontIO
Length of output: 13797
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find the CrazyGames SDK implementation and any related auth methods.
fd -a 'CrazyGamesSDK' src/client || true
rg -n --hidden --glob 'src/client/**/*.{ts,tsx}' 'class .*CrazyGames|function .*getUsername|getUsername\(|addAuthListener\(' src/clientRepository: openfrontio/OpenFrontIO
Length of output: 1072
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- CrazyGamesSDK.ts (120-170) ---'
sed -n '120,170p' src/client/CrazyGamesSDK.ts | cat -n
printf '\n%s\n' '--- UsernameInput.ts (96-120) ---'
sed -n '96,120p' src/client/UsernameInput.ts | cat -nRepository: openfrontio/OpenFrontIO
Length of output: 2728
🌐 Web query:
CrazyGames SDK user.getUser semantics returns current user at call time documentation
💡 Result:
The CrazyGames SDK method user.getUser is designed to retrieve the user currently logged into the CrazyGames platform at the time the function is called [1][2]. Official documentation explicitly defines this method as the way to retrieve the "currently logged in" user [1][2]. If no user is logged in at the moment the method is invoked, it returns null [1][2]. Because the method is asynchronous (typically returning a Promise or requiring a callback), it fetches the up-to-date session state from the platform at that exact call time [1][2].
Citations:
Guard the async CrazyGames auth init against logout races. getUsername() can still write a logged-in name after a logout event if that logout lands before the promise settles, and the null auth event is skipped while crazyGamesLoggedIn is still false. Add a small version/request check before updating baseUsername.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/client/UsernameInput.ts` around lines 101 - 119, The CrazyGames auth
initialization in UsernameInput can race with a logout and overwrite the guest
state after the user has already logged out. Add a small request/version guard
around the getUsername() promise and the addAuthListener callback so only the
latest auth state updates baseUsername, and make sure the null branch can still
reset to genAnonUsername even if the initial login state has not been marked
yet.
…op-ups (#4538) Addresses four requests from the CrazyGames platform team. Supersedes #4520 (closed when its branch was renamed to `crazygames`). ## 1. Username reverts to guest on CrazyGames logout When a player logged into their CrazyGames account and then logged out, the username stayed the CrazyGames name. It now reverts to a fresh guest (`AnonXXX`) name. A `crazyGamesLoggedIn` flag guards this so it only fires on a real login→logout transition (not on the initial "not logged in" state, which would otherwise wipe a stored name). ## 2. Fullscreen button hidden on CrazyGames CrazyGames provides its own fullscreen control in the game frame, so our in-game fullscreen button is now hidden when running on CrazyGames (and unchanged everywhere else). ## 3. Native browser prompts → in-game pop-ups The `showInGameConfirm()` / `showInGameAlert()` helpers (promise-based, callable from non-Lit contexts like WebSocket/popstate handlers) drive the existing shared `<confirm-dialog>` component, so styling stays consistent with the rest of the app. Every native `confirm()`/`alert()` shown **during gameplay** now uses it: - Exit-game confirmation (right sidebar + browser-back path) - Kick-player confirmation - Host-left notice (dispatches leave-lobby only after dismiss, preserving the old blocking UX) - Connection-refused notice (also removes a stale `// TODO: make this a modal`) ## 4. `gameplayStop` when Settings menu / pop-up is open - The pop-up helpers report `gameplayStop()` while shown and `gameplayStart()` on dismiss. - Settings + Graphics Settings modals now report `gameplayStop` whenever the menu is open — previously it only fired when the modal *also* paused the sim (singleplayer / lobby-creator), so regular multiplayer players never triggered it. Also fixes graphics-settings so gameplay resumes correctly on close for those players. ## Scope Per request, this covers **in-game dialogs only**. The main-menu native alerts (store/checkout, account, subscriptions, friends, login) were intentionally left as-is. ## Testing - `tsc --noEmit`, `eslint`, and `prettier --check` all pass. - Verified the confirm and alert pop-ups render correctly in the running app (shared `<confirm-dialog>`, danger/warning variants), resolve their promises, and tear down their portal. - CrazyGames-iframe-only behaviors (username revert, `gameplayStop`, fullscreen hiding) are verified by code inspection — they require running inside the actual CrazyGames frame. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses four requests from the CrazyGames platform team.
Supersedes #4520 (closed when its branch was renamed to
crazygames).1. Username reverts to guest on CrazyGames logout
When a player logged into their CrazyGames account and then logged out, the username stayed the CrazyGames name. It now reverts to a fresh guest (
AnonXXX) name. AcrazyGamesLoggedInflag guards this so it only fires on a real login→logout transition (not on the initial "not logged in" state, which would otherwise wipe a stored name).2. Fullscreen button hidden on CrazyGames
CrazyGames provides its own fullscreen control in the game frame, so our in-game fullscreen button is now hidden when running on CrazyGames (and unchanged everywhere else).
3. Native browser prompts → in-game pop-ups
The
showInGameConfirm()/showInGameAlert()helpers (promise-based, callable from non-Lit contexts like WebSocket/popstate handlers) drive the existing shared<confirm-dialog>component, so styling stays consistent with the rest of the app. Every nativeconfirm()/alert()shown during gameplay now uses it:// TODO: make this a modal)4.
gameplayStopwhen Settings menu / pop-up is opengameplayStop()while shown andgameplayStart()on dismiss.gameplayStopwhenever the menu is open — previously it only fired when the modal also paused the sim (singleplayer / lobby-creator), so regular multiplayer players never triggered it. Also fixes graphics-settings so gameplay resumes correctly on close for those players.Scope
Per request, this covers in-game dialogs only. The main-menu native alerts (store/checkout, account, subscriptions, friends, login) were intentionally left as-is.
Testing
tsc --noEmit,eslint, andprettier --checkall pass.<confirm-dialog>, danger/warning variants), resolve their promises, and tear down their portal.gameplayStop, fullscreen hiding) are verified by code inspection — they require running inside the actual CrazyGames frame.🤖 Generated with Claude Code