Skip to content

Conversation

@evanpelle
Copy link
Collaborator

@evanpelle evanpelle commented Dec 23, 2025

Description:

Integrate with crazy games SDK

Please complete the following:

  • I have added screenshots for all UI updates
  • I process any text displayed to the user through translateText() and I've added it to the en.json file
  • I have added relevant tests to the test directory
  • 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:

evan

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 23, 2025

Walkthrough

This PR integrates CrazyGames SDK across the client application. It introduces a new CrazyGamesSDK wrapper class for platform detection and lifecycle management, updates key components to report gameplay events, and replaces modal event-based closing with callback-based closing.

Changes

Cohort / File(s) Summary
CrazyGames SDK Core
src/client/CrazyGamesSDK.ts
New module exporting CrazyGamesSDK class with platform detection, lazy initialization, and guarded methods for gameplay lifecycle (start/stop), invite button management, loading states, and happy time events. Includes singleton instance.
Client Lifecycle Integration
src/client/Main.ts
Adds CrazyGamesSDK initialization and lifecycle reporting throughout app startup, navigation, and gameplay transitions. Renames hash-based join handling to URL-based with early CrazyGames invite parameter support.
Lobby Modal Integration
src/client/HostLobbyModal.ts
Integrates CrazyGamesSDK to show invite button on lobby creation and hide on close. Adds modal onClose lifecycle hook. Debounces bot slider updates with 300ms delay.
Modal Lifecycle Refactor
src/client/components/baseComponents/Modal.ts
Replaces modal-close event dispatch with optional onClose callback. Closes only if modal is open.
Gameplay Exit & Win Events
src/client/graphics/layers/GameRightSidebar.ts, src/client/graphics/layers/WinModal.ts
GameRightSidebar calls gameplayStop before exit redirect. WinModal invokes happytime on player or team win.
HTML Resource
src/client/index.html
Adds CrazyGames SDK script tag to document head.

Sequence Diagrams

sequenceDiagram
    participant App as Client App
    participant SDK as CrazyGamesSDK
    participant Platform as CrazyGames Platform
    
    rect rgba(76, 175, 80, 0.1)
    Note over App,Platform: Initialization Flow
    App->>SDK: maybeInit()
    SDK->>SDK: isOnCrazyGames()?
    alt On CrazyGames
        SDK->>SDK: await delay
        SDK->>Platform: window.CrazyGames.SDK.init()
        Platform-->>SDK: init complete
        SDK->>SDK: initialized = true
    else Not on CrazyGames
        SDK->>SDK: skip initialization
    end
    SDK-->>App: ready
    end
Loading
sequenceDiagram
    participant User as Player
    participant App as Client App
    participant SDK as CrazyGamesSDK
    participant Platform as CrazyGames Platform
    
    rect rgba(33, 150, 243, 0.1)
    Note over User,Platform: Gameplay Lifecycle
    User->>App: Start Game
    App->>SDK: gameplayStart()
    SDK->>Platform: window.CrazyGames.SDK.gameplayStart()
    Platform-->>SDK: acknowledged
    Note over User,Platform: ...gameplay in progress...
    User->>App: Win Condition
    App->>SDK: happytime()
    SDK->>Platform: window.CrazyGames.SDK.happytime()
    Platform-->>SDK: acknowledged
    end
    
    rect rgba(244, 67, 54, 0.1)
    Note over User,Platform: Exit Flow
    User->>App: Exit Game
    App->>SDK: gameplayStop()
    SDK->>Platform: window.CrazyGames.SDK.gameplayStop()
    Platform-->>SDK: acknowledged
    App->>App: Navigate away
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • #1436: Modifies same files (Main.ts, HostLobbyModal.ts) with overlapping client-side integration patterns.
  • #2608: Modifies Modal.ts class with public member additions and lifecycle behavior changes.

Suggested labels

Feature - Frontend, UI/UX

Suggested reviewers

  • scottanderson

Poem

🎮 CrazyGames unlocked, the SDK takes flight,
From lobby invites to gameplay's delight,
Happytime celebrates each victor's cheer,
Lifecycle events keep platform awareness clear! 🌟

Pre-merge checks

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'crazy games integrations' directly summarizes the main objective of adding Crazy Games SDK integration throughout the codebase.
Description check ✅ Passed The description states 'Integrate with crazy games SDK' which aligns with the changeset that adds comprehensive Crazy Games SDK integration across multiple client modules.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@evanpelle evanpelle changed the title crazy games crazy games integrations Dec 23, 2025
@evanpelle evanpelle marked this pull request as ready for review December 23, 2025 02:02
@evanpelle evanpelle requested a review from a team as a code owner December 23, 2025 02:02
@evanpelle evanpelle added this to the v28 milestone Dec 23, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/client/components/baseComponents/Modal.ts (1)

11-11: Consider using @state() or a plain class property for the callback.

@property({ type: Function }) works but the type: Function converter doesn't do much for callbacks. A simpler pattern is to use a plain public property or @state() since this callback is set programmatically, not via HTML attribute.

🔎 Suggested simplification
-  @property({ type: Function }) onClose?: () => void;
+  public onClose?: () => void;
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2973b83 and 7000231.

📒 Files selected for processing (7)
  • src/client/CrazyGamesSDK.ts
  • src/client/HostLobbyModal.ts
  • src/client/Main.ts
  • src/client/components/baseComponents/Modal.ts
  • src/client/graphics/layers/GameRightSidebar.ts
  • src/client/graphics/layers/WinModal.ts
  • src/client/index.html
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-06-09T02:20:43.637Z
Learnt from: VariableVince
Repo: openfrontio/OpenFrontIO PR: 1110
File: src/client/Main.ts:293-295
Timestamp: 2025-06-09T02:20:43.637Z
Learning: In src/client/Main.ts, during game start in the handleJoinLobby callback, UI elements are hidden using direct DOM manipulation with classList.add("hidden") for consistency. This includes modals, buttons, and error divs. The codebase follows this pattern rather than using component APIs for hiding elements during game transitions.

Applied to files:

  • src/client/graphics/layers/WinModal.ts
  • src/client/Main.ts
  • src/client/HostLobbyModal.ts
  • src/client/graphics/layers/GameRightSidebar.ts
📚 Learning: 2025-10-18T11:00:57.142Z
Learnt from: NewYearNewPhil
Repo: openfrontio/OpenFrontIO PR: 2230
File: src/client/graphics/GameRenderer.ts:269-277
Timestamp: 2025-10-18T11:00:57.142Z
Learning: In src/client/graphics/GameRenderer.ts, the GameRecapCapture implementation does not use setCaptureRenderEnabled on layers. Instead, it uses RecapCaptureSurface.capture() to render capture layers (TerrainLayer, TerritoryLayer, RailroadLayer, StructureIconsLayer, UnitLayer) directly to an off-screen canvas without requiring layer-level capture mode methods.

Applied to files:

  • src/client/graphics/layers/WinModal.ts
  • src/client/graphics/layers/GameRightSidebar.ts
📚 Learning: 2025-10-08T17:14:49.369Z
Learnt from: Foorack
Repo: openfrontio/OpenFrontIO PR: 2141
File: src/client/ClientGameRunner.ts:228-234
Timestamp: 2025-10-08T17:14:49.369Z
Learning: In `ClientGameRunner.ts`, the `myPlayer` field is always set when `shouldPreventWindowClose()` is called, so the null check in that method is sufficient without needing to fetch it again from `gameView.playerByClientID()`.

Applied to files:

  • src/client/Main.ts
📚 Learning: 2025-10-21T20:06:04.823Z
Learnt from: Saphereye
Repo: openfrontio/OpenFrontIO PR: 2233
File: src/client/HostLobbyModal.ts:891-891
Timestamp: 2025-10-21T20:06:04.823Z
Learning: For the HumansVsNations game mode in `src/client/HostLobbyModal.ts` and related files, the implementation strategy is to generate all nations and adjust their strength for balancing, rather than limiting lobby size based on the number of available nations on the map.

Applied to files:

  • src/client/HostLobbyModal.ts
🧬 Code graph analysis (4)
src/client/graphics/layers/WinModal.ts (1)
src/client/CrazyGamesSDK.ts (1)
  • crazyGamesSDK (205-205)
src/client/Main.ts (2)
src/client/CrazyGamesSDK.ts (1)
  • crazyGamesSDK (205-205)
src/core/Schemas.ts (1)
  • ID (205-208)
src/client/HostLobbyModal.ts (1)
src/client/CrazyGamesSDK.ts (1)
  • crazyGamesSDK (205-205)
src/client/graphics/layers/GameRightSidebar.ts (1)
src/client/CrazyGamesSDK.ts (1)
  • crazyGamesSDK (205-205)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Deploy to openfront.dev
🔇 Additional comments (22)
src/client/components/baseComponents/Modal.ts (1)

78-83: Good guard against double-close.

The check prevents redundant callback invocations and state toggles. Implementation is clean.

src/client/index.html (1)

486-489: Formatting-only change — no concerns.

The onclick handler behavior is unchanged.

src/client/graphics/layers/WinModal.ts (2)

19-19: Import looks good.

Singleton import aligns with the SDK module pattern used across the codebase.


297-297: Happy-time triggers on win — good integration.

Both team and individual win paths call happytime(). The fire-and-forget pattern is fine since errors are handled internally by the SDK wrapper.

Also applies to: 320-320

src/client/Main.ts (6)

119-119: maybeInit() is async but not awaited.

The SDK might not be initialized when early code paths try to use it. This is likely acceptable since isReady() guards all SDK methods, but consider awaiting or documenting the intent.


166-171: async in beforeunload handler — browser may not wait.

Browsers typically ignore async work in beforeunload. The await crazyGamesSDK.gameplayStop() may not complete before the page unloads. This is a known limitation and acceptable here since the SDK handles it gracefully, but be aware it's best-effort.


386-395: CrazyGames invite flow looks correct.

Early-exit when invite param is valid prevents fallthrough to hash-based join. The ID.safeParse validation aligns with the schema at src/core/Schemas.ts (8-char alphanumeric).


563-564: Loading start reported — good.

Matches the expected lifecycle: loading → gameplay.


584-588: Loading stop and gameplay start with error handling — good.

Errors are logged but don't block game start.


605-610: Gameplay stop on leave with error handling — good.

Same pattern as start, consistent error handling.

src/client/HostLobbyModal.ts (5)

36-40: Type extension for modal element looks good.

Adding onClose to the type matches the new Modal.ts contract.


612-614: Invite button shown after lobby creation — correct placement.

The check with isOnCrazyGames() ensures the button only appears on the CrazyGames platform.


629-631: onClose callback wiring — clean pattern.

Ensures close() is called regardless of how the modal is dismissed.


637-638: Invite button hidden on close — good cleanup.

The SDK method guards against being called when not on CrazyGames.


67-67: Good debounce for bots slider.

Prevents excessive API calls while user drags the slider. The 300ms delay is reasonable. Timer cleanup in close() (lines 646-649) prevents stale calls.

Also applies to: 682-690

src/client/CrazyGamesSDK.ts (7)

1-22: Clean global type augmentation.

The declare global block properly extends the Window interface with the CrazyGames SDK surface. Types match the SDK v3 API.


28-41: Platform detection is reasonable.

The iframe check with cross-origin fallback to document.referrer handles the common cases. Note that referrer can be spoofed, but for feature-gating (not security), this is acceptable.


47-77: Polling for SDK availability is pragmatic.

100 attempts at 100ms intervals gives a 10-second window for the script to load. The error handling and early returns are clean.


79-114: Gameplay lifecycle methods are well-guarded.

Both gameplayStart and gameplayStop check isReady() and track isGameplayActive to prevent duplicate calls. Error handling logs but doesn't throw, allowing callers to fire-and-forget.


155-174: showInviteButton returns the invite link — good.

The return value can be useful for logging or display, and null is returned on error.


189-202: getInviteGameId retrieves the invite param correctly.

Uses the SDK's getInviteParam method. Null handling is consistent with other methods.


204-205: Singleton export is the right pattern here.

Single shared instance ensures consistent state tracking across the app.

coderabbitai[bot]
coderabbitai bot previously approved these changes Dec 23, 2025
@iiamlewis iiamlewis added Backend Server-side features and systems - lobbies, matchmaking, accounts, APIs, etc. Devops Infrastructure, CI/CD, dependencies, and maintenance. Feature labels Dec 23, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/client/Main.ts (1)

602-603: Consider awaiting gameplayStop for consistency.

gameplayStop() is called without await, meaning it fires and forgets. If this is intentional for performance, consider adding a comment. Otherwise, make handleLeaveLobby async and await the call.

🔎 Optional: Make handleLeaveLobby async
- private async handleLeaveLobby(/* event: CustomEvent */) {
+ private async handleLeaveLobby(/* event: CustomEvent */) {
    if (this.gameStop === null) {
      return;
    }
    console.log("leaving lobby, cancelling game");
    this.gameStop();
    this.gameStop = null;
 
-   crazyGamesSDK.gameplayStop();
+   await crazyGamesSDK.gameplayStop();
 
    this.gutterAds.hide();
src/client/HostLobbyModal.ts (1)

627-629: Circular callback pattern works but could be clearer.

The onClose callback invokes this.close(), which then calls modalEl.close() again. The guard in Modal.ts (line 79) prevents infinite recursion by checking isModalOpen, but this flow is subtle and could confuse future maintainers.

Consider refactoring to avoid the circular call:

🔎 Alternative: Separate cleanup logic
  public open() {
    // ... existing code ...
    this.modalEl?.open();
-   this.modalEl.onClose = () => {
-     this.close();
-   };
+   this.modalEl.onClose = () => {
+     this.cleanup();
+   };
    this.playersInterval = setInterval(() => this.pollPlayers(), 1000);
    this.loadNationCount();
  }

  public close() {
-   console.log("Closing host lobby modal");
-   crazyGamesSDK.hideInviteButton();
    this.modalEl?.close();
+   this.cleanup();
+  }
+
+  private cleanup() {
+   console.log("Closing host lobby modal");
+   crazyGamesSDK.hideInviteButton();
-   this.modalEl?.close();
    this.copySuccess = false;
    if (this.playersInterval) {
      clearInterval(this.playersInterval);
      this.playersInterval = null;
    }
    // Clear any pending bot updates
    if (this.botsUpdateTimer !== null) {
      clearTimeout(this.botsUpdateTimer);
      this.botsUpdateTimer = null;
    }
  }

This separates cleanup logic from modal state management, making the flow more explicit.

Also applies to: 634-637

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7000231 and f7e069d.

📒 Files selected for processing (8)
  • README.md
  • src/client/CrazyGamesSDK.ts
  • src/client/HostLobbyModal.ts
  • src/client/Main.ts
  • src/client/components/baseComponents/Modal.ts
  • src/client/graphics/layers/GameRightSidebar.ts
  • src/client/graphics/layers/WinModal.ts
  • src/client/index.html
💤 Files with no reviewable changes (1)
  • README.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/client/graphics/layers/WinModal.ts
  • src/client/graphics/layers/GameRightSidebar.ts
  • src/client/CrazyGamesSDK.ts
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: VariableVince
Repo: openfrontio/OpenFrontIO PR: 1110
File: src/client/Main.ts:293-295
Timestamp: 2025-06-09T02:20:43.637Z
Learning: In src/client/Main.ts, during game start in the handleJoinLobby callback, UI elements are hidden using direct DOM manipulation with classList.add("hidden") for consistency. This includes modals, buttons, and error divs. The codebase follows this pattern rather than using component APIs for hiding elements during game transitions.
📚 Learning: 2025-06-09T02:20:43.637Z
Learnt from: VariableVince
Repo: openfrontio/OpenFrontIO PR: 1110
File: src/client/Main.ts:293-295
Timestamp: 2025-06-09T02:20:43.637Z
Learning: In src/client/Main.ts, during game start in the handleJoinLobby callback, UI elements are hidden using direct DOM manipulation with classList.add("hidden") for consistency. This includes modals, buttons, and error divs. The codebase follows this pattern rather than using component APIs for hiding elements during game transitions.

Applied to files:

  • src/client/Main.ts
  • src/client/HostLobbyModal.ts
📚 Learning: 2025-10-08T17:14:49.369Z
Learnt from: Foorack
Repo: openfrontio/OpenFrontIO PR: 2141
File: src/client/ClientGameRunner.ts:228-234
Timestamp: 2025-10-08T17:14:49.369Z
Learning: In `ClientGameRunner.ts`, the `myPlayer` field is always set when `shouldPreventWindowClose()` is called, so the null check in that method is sufficient without needing to fetch it again from `gameView.playerByClientID()`.

Applied to files:

  • src/client/Main.ts
📚 Learning: 2025-10-21T20:06:04.823Z
Learnt from: Saphereye
Repo: openfrontio/OpenFrontIO PR: 2233
File: src/client/HostLobbyModal.ts:891-891
Timestamp: 2025-10-21T20:06:04.823Z
Learning: For the HumansVsNations game mode in `src/client/HostLobbyModal.ts` and related files, the implementation strategy is to generate all nations and adjust their strength for balancing, rather than limiting lobby size based on the number of available nations on the map.

Applied to files:

  • src/client/HostLobbyModal.ts
🧬 Code graph analysis (2)
src/client/Main.ts (2)
src/client/CrazyGamesSDK.ts (1)
  • crazyGamesSDK (205-205)
src/core/Schemas.ts (1)
  • ID (205-208)
src/client/HostLobbyModal.ts (1)
src/client/CrazyGamesSDK.ts (1)
  • crazyGamesSDK (205-205)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Deploy to openfront.dev
🔇 Additional comments (9)
src/client/index.html (1)

133-137: LGTM! Async attribute added as suggested.

The CrazyGames SDK script now loads asynchronously, preventing page parsing blockage. This addresses the previous review comment and aligns with the SDK initialization pattern in CrazyGamesSDK.ts that polls for readiness.

src/client/components/baseComponents/Modal.ts (2)

11-11: Clean callback property addition.

The optional onClose callback provides a straightforward hook for parent components to react to modal closure.


78-83: Guard prevents duplicate calls.

The isModalOpen guard ensures onClose is only invoked once per close action, preventing duplicate cleanup when the modal is already closed.

src/client/Main.ts (4)

119-119: SDK initialized early.

Calling maybeInit() during client initialization ensures the SDK is ready before any lobby operations occur.


166-172: Best-effort cleanup on page unload.

The await in beforeunload provides best-effort cleanup but may not complete if the browser terminates the page quickly. This is acceptable for this use case.


386-395: CrazyGames invites take priority.

The URL handling correctly prioritizes CrazyGames invite parameters over hash-based joins, with proper ID validation using the ID schema from Schemas.ts.


563-563: Lifecycle reporting integrated.

The SDK lifecycle calls (loadingStart, loadingStop, gameplayStart) are placed at appropriate points in the join flow, coordinating with UI transitions.

Also applies to: 583-584

src/client/HostLobbyModal.ts (2)

612-612: Invite button shown after lobby creation.

showInviteButton is correctly called after lobbyId is set, ensuring the SDK has the necessary context.


680-688: Debouncing reduces server load.

The 300ms debounce on bot count changes prevents excessive putGameConfig calls while the user drags the slider. This is a good optimization.

coderabbitai[bot]
coderabbitai bot previously approved these changes Dec 23, 2025
@evanpelle evanpelle merged commit a810e0a into v28 Dec 23, 2025
9 of 11 checks passed
@evanpelle evanpelle deleted the evan-cg-28 branch December 23, 2025 17:11
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/client/components/baseComponents/Modal.ts (1)

11-11: Consider using @state() for internal callback instead of @property().

The onClose callback is set programmatically by parent components, not via HTML attributes. Using @property({ type: Function }) attempts attribute reflection which doesn't work well for functions. Consider using @state() or a plain class property instead.

🔎 Suggested change
-  @property({ type: Function }) onClose?: () => void;
+  public onClose?: () => void;
src/client/HostLobbyModal.ts (1)

609-629: Potential issue: showInviteButton called before modal is opened.

The showInviteButton(this.lobbyId) is called in the first .then() block, but modalEl.open() is called after both .then() blocks. Consider reordering so the modal is visible when the invite button appears.

Also, the chained .then() blocks run in sequence but don't depend on each other - they could be combined for clarity.

🔎 Suggested refactor
     createLobby(this.lobbyCreatorClientID)
       .then((lobby) => {
         this.lobbyId = lobby.gameID;
         crazyGamesSDK.showInviteButton(this.lobbyId);
-      })
-      .then(() => {
         this.dispatchEvent(
           new CustomEvent("join-lobby", {
             detail: {
               gameID: this.lobbyId,
               clientID: this.lobbyCreatorClientID,
             } as JoinLobbyEvent,
             bubbles: true,
             composed: true,
           }),
         );
       });
-    this.modalEl?.open();
+    this.modalEl.open();
src/client/CrazyGamesSDK.ts (1)

129-153: Consider tracking loading state to prevent mismatched start/stop.

Unlike gameplay, loading calls have no state tracking. If loadingStart() is called twice, or loadingStop() is called without a prior start, the SDK behavior is unknown.

🔎 Suggested improvement
   private initialized = false;
   private isGameplayActive = false;
+  private isLoading = false;

   loadingStart(): void {
     if (!this.isReady()) {
       return;
     }
+
+    if (this.isLoading) {
+      console.warn("Loading already started");
+      return;
+    }

     try {
       window.CrazyGames!.SDK.game.loadingStart();
+      this.isLoading = true;
       console.log("CrazyGames: loading started");
     } catch (error) {
       console.error("Failed to report loading start:", error);
     }
   }

   loadingStop(): void {
     if (!this.isReady()) {
       return;
     }
+
+    if (!this.isLoading) {
+      return;
+    }

     try {
       window.CrazyGames!.SDK.game.loadingStop();
+      this.isLoading = false;
       console.log("CrazyGames: loading stopped");
     } catch (error) {
       console.error("Failed to report loading stop:", error);
     }
   }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f7e069d and 2af124f.

📒 Files selected for processing (7)
  • src/client/CrazyGamesSDK.ts
  • src/client/HostLobbyModal.ts
  • src/client/Main.ts
  • src/client/components/baseComponents/Modal.ts
  • src/client/graphics/layers/GameRightSidebar.ts
  • src/client/graphics/layers/WinModal.ts
  • src/client/index.html
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/client/graphics/layers/GameRightSidebar.ts
  • src/client/index.html
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-06-09T02:20:43.637Z
Learnt from: VariableVince
Repo: openfrontio/OpenFrontIO PR: 1110
File: src/client/Main.ts:293-295
Timestamp: 2025-06-09T02:20:43.637Z
Learning: In src/client/Main.ts, during game start in the handleJoinLobby callback, UI elements are hidden using direct DOM manipulation with classList.add("hidden") for consistency. This includes modals, buttons, and error divs. The codebase follows this pattern rather than using component APIs for hiding elements during game transitions.

Applied to files:

  • src/client/Main.ts
  • src/client/HostLobbyModal.ts
  • src/client/graphics/layers/WinModal.ts
📚 Learning: 2025-10-08T17:14:49.369Z
Learnt from: Foorack
Repo: openfrontio/OpenFrontIO PR: 2141
File: src/client/ClientGameRunner.ts:228-234
Timestamp: 2025-10-08T17:14:49.369Z
Learning: In `ClientGameRunner.ts`, the `myPlayer` field is always set when `shouldPreventWindowClose()` is called, so the null check in that method is sufficient without needing to fetch it again from `gameView.playerByClientID()`.

Applied to files:

  • src/client/Main.ts
📚 Learning: 2025-10-21T20:06:04.823Z
Learnt from: Saphereye
Repo: openfrontio/OpenFrontIO PR: 2233
File: src/client/HostLobbyModal.ts:891-891
Timestamp: 2025-10-21T20:06:04.823Z
Learning: For the HumansVsNations game mode in `src/client/HostLobbyModal.ts` and related files, the implementation strategy is to generate all nations and adjust their strength for balancing, rather than limiting lobby size based on the number of available nations on the map.

Applied to files:

  • src/client/HostLobbyModal.ts
📚 Learning: 2025-10-18T11:00:57.142Z
Learnt from: NewYearNewPhil
Repo: openfrontio/OpenFrontIO PR: 2230
File: src/client/graphics/GameRenderer.ts:269-277
Timestamp: 2025-10-18T11:00:57.142Z
Learning: In src/client/graphics/GameRenderer.ts, the GameRecapCapture implementation does not use setCaptureRenderEnabled on layers. Instead, it uses RecapCaptureSurface.capture() to render capture layers (TerrainLayer, TerritoryLayer, RailroadLayer, StructureIconsLayer, UnitLayer) directly to an off-screen canvas without requiring layer-level capture mode methods.

Applied to files:

  • src/client/graphics/layers/WinModal.ts
🔇 Additional comments (18)
src/client/components/baseComponents/Modal.ts (1)

78-83: LGTM!

The close guard prevents redundant callbacks and the callback-based approach is cleaner than event dispatch for this use case.

src/client/graphics/layers/WinModal.ts (2)

19-19: LGTM!

Import of crazyGamesSDK is correctly placed with other imports.


292-320: LGTM!

The happytime() calls are correctly placed in both win paths (team win and individual player win). The SDK wrapper handles platform detection internally.

src/client/Main.ts (4)

386-395: LGTM!

CrazyGames invite handling is correctly prioritized before hash-based join logic. The early return is appropriate since CrazyGames environments don't use URL hashes for invites.


563-564: LGTM!

The loadingStart() call is correctly placed before showing the game starting modal.


583-585: LGTM!

Correct sequence: loadingStop() followed by gameplayStart() when the game actually begins.


602-604: LGTM!

The gameplayStop() call is correctly placed when leaving the lobby.

src/client/HostLobbyModal.ts (4)

30-30: LGTM!

Import placed correctly with other imports.


36-40: LGTM!

Type augmentation for modalEl correctly includes the optional onClose callback.


670-689: LGTM!

Good debounce implementation for the bots slider. The 300ms delay reduces unnecessary API calls during rapid slider adjustments, and cleanup on modal close prevents stale updates.


634-648: LGTM!

The close() method correctly cleans up the invite button, polling interval, and pending bots update timer.

src/client/CrazyGamesSDK.ts (7)

1-22: LGTM!

Clean global type declaration for the CrazyGames SDK. The interface structure matches the expected SDK API.


47-77: LGTM!

The initialization pattern with polling (up to 10 seconds) is consistent with other SDK loading in the codebase. The guard against double-initialization is correct.


79-114: LGTM!

The gameplay state tracking prevents duplicate SDK calls. The isGameplayActive flag ensures proper pairing of start/stop events.


116-127: LGTM!

Simple wrapper for happytime() with proper readiness check and error handling.


155-202: LGTM!

Invite button methods are well-structured with proper error handling and return values.


205-205: LGTM!

Singleton pattern is appropriate here since the SDK instance should be shared across the application.


28-41: Use CrazyGames.SDK.environment or CrazyGames.SDK.platform instead of custom iframe detection.

The current implementation relies on custom heuristics (iframe check + parent URL + referrer fallback), but CrazyGames official guidance recommends using the SDK's built-in platform detection APIs. The document.referrer fallback is especially problematic—it can be stripped or empty due to Referrer-Policy headers, iframe referrerPolicy attributes, or sandboxing. Use postMessage for reliable cross-origin parent identification, or better yet, rely on CrazyGames.SDK.platform where available.

Comment on lines +166 to 172
window.addEventListener("beforeunload", async () => {
console.log("Browser is closing");
if (this.gameStop !== null) {
this.gameStop();
await crazyGamesSDK.gameplayStop();
}
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Async callback in beforeunload may not complete.

The await crazyGamesSDK.gameplayStop() inside beforeunload handler is unlikely to complete before the browser closes the page. The beforeunload event does not wait for async operations.

🔎 Suggested fix
-    window.addEventListener("beforeunload", async () => {
+    window.addEventListener("beforeunload", () => {
       console.log("Browser is closing");
       if (this.gameStop !== null) {
         this.gameStop();
-        await crazyGamesSDK.gameplayStop();
+        // Fire-and-forget - beforeunload doesn't wait for async
+        crazyGamesSDK.gameplayStop();
       }
     });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
window.addEventListener("beforeunload", async () => {
console.log("Browser is closing");
if (this.gameStop !== null) {
this.gameStop();
await crazyGamesSDK.gameplayStop();
}
});
window.addEventListener("beforeunload", () => {
console.log("Browser is closing");
if (this.gameStop !== null) {
this.gameStop();
// Fire-and-forget - beforeunload doesn't wait for async
crazyGamesSDK.gameplayStop();
}
});
🤖 Prompt for AI Agents
In src/client/Main.ts around lines 166 to 172, the beforeunload handler uses an
async await (await crazyGamesSDK.gameplayStop()) which the browser will not wait
for; replace the async approach with a synchronous/guaranteed delivery method:
call crazyGamesSDK.gameplayStop() without awaiting and send any final telemetry
via navigator.sendBeacon or move the gameplayStop invocation to
pagehide/visibilitychange (or trigger it earlier when the user initiates exit)
so the stop signal is reliably delivered before unload.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Backend Server-side features and systems - lobbies, matchmaking, accounts, APIs, etc. Devops Infrastructure, CI/CD, dependencies, and maintenance. Feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants