Architecture

How the SDK, the bridge and the GAMEE platform actually talk.

This page explains what flows on the wire and where each piece lives. Read it once and the rest of the SDK should make sense.

System components#

  • HTML5 game — the code you wrote. Calls gamee.init(), updateScore(), subscribes to gamee.on('pause', …).
  • GAMEE SDK — the typed public surface plus a platform-specific bridge picked at boot.
  • Platform — gamee.com on web, the iOS app, or the Android app. Embeds the game inside an iframe, WKWebView, or WebView.

The SDK abstracts the transport so you write the same code for all three platforms.

End-to-end session trace#

A real game’s lifetime on the wire, traced top-to-bottom.

Request
The game asks the platform to do something. Travels game → platform.
Response
The platform's reply to a specific request. Travels platform → game.
Event
The platform pushes a notification to the game. Travels platform → game, no reply expected.

The first three steps are the handshake — every game does this. Everything after is your game’s choice. start / pause / resume are the only events the platform reliably fires; the rest are opt-in features wired through capabilities.

Platform detection#

createBridge() decides which transport to use the moment the SDK is constructed. Rules from bridge/detect.ts:

User agent containsIn an iframe?Detected platform
gamee/X.Y.Zandroid
iPhone / iPad / iPodyesios
Mobi/Android/iPhone…mobile_web
anything elseweb

mobile_web and web both use the postMessage bridge; ios and android each use a native bridge.

You can override detection by injecting a bridge yourself (createSdk({ bridge: myBridge })) — that’s how the emulator and MockBridge work.

The wire format#

There is one envelope that flows in both directions:

{ "request":  { "method": "init", "messageId": 0, "data": { ... } } }
{ "response": { "messageId": 0, "data": { ... } } }
{ "response": { "messageId": 0, "error": { "code": "...", "message": "..." } } }

messageId is a non-negative integer that increments by 1 per outbound message within a single SDK instance (starts at 0). Wire-compatible with legacy gamee-js clients.

request is what an RPC looks like. response is the matching ack. The messageId ties them together — the SDK uses it to resolve the right Promise on your await.

The same envelope shape carries platform → game events. The platform sends a request, the SDK auto-acks it, and then dispatches the matching gamee.on('pause', …) listener. The ack just tells the platform “got it, my game is alive.”

Four traffic patterns#

a) Init handshake#

Game → SDK      : await gamee.init({ capabilities: ['saveState'] })
SDK → Platform  : { request: { method: 'init', messageId: 0,
                               data: { version, capabilities: { saveState: true } } } }
Platform → SDK  : { response: { messageId: 0,
                                data: { platform, locale, country, sound, ... } } }
SDK → Game      : Promise resolves with the GameInitResponse

init() may only be called once per SDK instance. A second call throws INVALID_STATE. Use createSdk() if you need a fresh instance (e.g. tests).

b) Fire-and-forget signal (updateScore, gameReady, gameOver, …)#

Game → SDK      : gamee.updateScore({ score: 123, playTime: 4.5, checksum: 'sum' })
SDK → Platform  : { request: { method: 'updateScore', messageId: 1, data: {…} } }
                     ↑ no Promise tracked, no timeout, platform ack is ignored

Signal calls do not occupy a slot in the pending registry, do not arm a timeout, and the returned Promise resolves immediately. Safe to call every frame.

c) Data-returning request (showRewardedVideo, purchaseItemWithGems, …)#

Game → SDK      : const r = await gamee.showRewardedVideo()
SDK → Platform  : { request: { method: 'showRewardedVideo', messageId: 2, data: null } }

(...time passes; user watches ad...)

Platform → SDK  : { response: { messageId: 2, data: { videoPlayed: true } } }
SDK → Game      : Promise resolves with { videoPlayed: true }

If the platform never responds within requestTimeoutMs (default 30 s), the Promise rejects with BRIDGE_TIMEOUT.

d) Platform → game event (pause, resume, start, …)#

Platform → SDK  : { request: { method: 'pause', messageId: 7, data: null } }
SDK → Platform  : { response: { messageId: 7 } }     ← auto-ack
SDK → Game      : every gamee.on('pause', …) handler fires

Listeners can throw without breaking dispatch — errors are caught and logged to console.error.

The PendingRegistry#

Every data-returning request is parked in an internal PendingRegistry keyed by messageId until the platform responds. Two safety nets:

  • TimeoutrequestTimeoutMs (default 30 000 ms) rejects forgotten requests with BRIDGE_TIMEOUT.
  • Overflow capmaxPendingRequests (default 256) rejects the oldest pending request with BRIDGE_OVERFLOW to make room. Only data-returning methods count; signals are not tracked.

Both are tunable via SdkOptions.

Why this matters in practice#

  1. The platform is authoritative. init() resolves with the platform’s view of the world: locale, country, save state, mission. Don’t hard-code these — read them from the resolved value every session.
  2. Capabilities gate at the SDK boundary. Calling a gated method without the matching capability throws CAPABILITY_MISSING synchronously, before anything hits the wire.
  3. Errors are typed. Every rejection is a GameeError. Branch on err.code. See error handling.
  4. Events fire after init. The platform won’t dispatch start, pause, etc. until the handshake succeeds.
  5. init() is one-shot. Use createSdk() for repeated init in tests.

Watching the wire#

Open the emulator, point it at any game URL, and the Bridge traffic panel logs every byte. Toggle the verbose switch and each row gets a plain-English annotation. That’s the fastest way to internalize the model.

Common pitfalls#

  • “My events don’t fire” — your game has not called init() yet, or has not yet awaited it.
  • “My save doesn’t persist” — you forgot to declare the saveState capability in init({ capabilities: [...] }).
  • init timed out” — the platform did not respond in 30 s. Confirm the game is loaded inside a real GAMEE platform, or use the emulator / MockBridge.
  • “Random fields are missing in init data” — the platform only sends fields that are relevant. missionData is only present when the user opened the game from a mission card; saveState is only present if a save exists.

Switch to a desktop

The GAMEE SDK emulator and docs are built for screens wider than 1280 px. Open this page on a laptop or desktop browser to get the full experience.

If you're already on a wide screen, drag your window wider and this banner will disappear.