AI-agent context

A drop-in briefing your AI assistant can read to use @gamee/sdk correctly on the first try.

TL;DR#

@gamee/sdk is a typed, promise-based JavaScript bridge between an HTML5 game and the GAMEE platform (web iframe, iOS WKWebView, Android WebView). The wire protocol is identical across all three hosts; the SDK picks the right transport at boot. Games declare capabilities at init() time; gated methods reject synchronously if the capability is missing.

import { gamee } from '@gamee/sdk';

await gamee.init({ capabilities: ['saveState', 'rewardedAds'] });
gamee.gameReady();
gamee.on('start', () => game.run());
gamee.on('pause', () => game.pause());

Glossary#

TermMeaning
GameThe HTML5 app written with @gamee/sdk.
PlatformThe GAMEE app that runs the game (web/iOS/Android). Older docs may call this the host or client; we use platform everywhere (matches GameInitResponse.platform, the emulator UI, and the bridge log).
BridgeThe transport layer the SDK picks at boot — WebBridge (postMessage), IosBridge (webkit.messageHandlers.callbackHandler), AndroidBridge (window._toDevice).
RequestGame → platform RPC. Returns a Promise.
ResponsePlatform’s reply to a specific request. Carries data or error.
EventPlatform → game push. Game subscribes via gamee.on(name, handler). SDK auto-acks.
SignalA fire-and-forget request the SDK sends without tracking the platform’s ack (e.g. updateScore). Resolves immediately.
CapabilityA string in init({ capabilities: [...] }) that gates feature methods.

Imports#

// Default singleton — what most games use:
import { gamee } from '@gamee/sdk';

// Fresh instance — for tests or multi-game setups:
import { createSdk } from '@gamee/sdk';
const sdk = createSdk({ allowedOrigins: ['https://games.gameeapp.com'] });

// Errors:
import { GameeError } from '@gamee/sdk';

// Types you'll commonly need:
import type {
  Capability,
  Platform,
  Environment,
  GameContext,
  InitOptions,
  GameInitResponse,
  MissionData,
  GameOverData,
  PurchaseDetails,
  PurchaseResult,
  RewardedLoadResult,
  RewardedResult,
  GameeEvents,
  GameeEventName,
  GameeErrorCode,
  PlatformBridge,
  BridgeListener,
  SdkOptions,
} from '@gamee/sdk';

IIFE script-tag: <script src="…/gamee-sdk.iife.js"></script> — exposes window.Gamee with Gamee.gamee, Gamee.createSdk, Gamee.GameeError.

Public API — every method#

Returns Promise<T> unless noted. Cap = capability required at init.

Lifecycle#

MethodArgsReturnsCapNotes
init{ capabilities: Capability[] }GameInitResponseCall once per SDK instance; second call rejects INVALID_STATE. Validates capability list synchronously. Sends SDK version on the wire.
gameReadyvoid (signal)Tells the platform first frame is rendered.
gameStartvoid (signal)A run is starting.
gameOver{ saveStateData?, rewardIds?, metadata? }?void (signal)Run ended. saveStateData and metadata accept object | string; objects auto-stringify.
requestPausevoid (signal)Game-initiated pause (e.g. opened in-game menu). Distinct from the platform-pushed pause event.
disposevoid (sync)Release resources. Pending data-returning requests reject INVALID_STATE.

Save state#

MethodArgsCapNotes
gameSaveStatedata: object | stringsaveStatePass an object (auto-stringified) or a pre-stringified JSON string. Wire method is saveState.

Score & missions#

MethodArgsCapValidation
updateScorenumber | { score, playTime?, checksum?, metadata? }score must be finite; playTime ≥ 0 and finite (if provided); checksum non-empty string (if provided). score is Math.trunc()-ed. Wire payload omits keys you didn’t pass.
updateMissionProgressprogress: numbermissionsprogress must be a finite number in [0, 100].

Commerce#

MethodArgsReturnsCapValidation
purchaseItemWithGems{ gemsCost, itemName }{ success }gemsgemsCost ≥ 0, itemName non-empty string.

Rewarded ads#

MethodArgsReturnsCap
loadRewardedVideo{ videoLoaded: boolean }rewardedAds
showRewardedVideo{ videoPlayed: boolean }rewardedAds

Analytics#

MethodArgsCapValidation
logEventstring | { name, value? }logEventsname non-empty, ≤ 24 chars; value (optional) ≤ 160 chars. Wire payload omits eventValue when value not provided.

Introspection#

MemberTypeNotes
gamee.versionstringSDK version sent on the wire with init.
gamee.getPlatform()PlatformReturns the bridge’s platform string: 'web' | 'mobile_web' | 'ios' | 'android'.
gamee.initResponseGameInitResponse | nullThe init payload, or null before init.

Events#

gamee.on(event, handler) // returns an unsubscribe function
gamee.off(event, handler?) // drop one handler, or all if no fn passed
EventPayloadWhen
start{ resetState?, gameSeed? }New run. May fire multiple times (replay, restart).
pauseundefinedPlatform paused (backgrounded, system interruption). Stop loop + audio.
resumeundefinedOK to resume.
muteundefinedAudio toggled off.
unmuteundefinedAudio toggled on.
submitundefinedPlatform asks the game to call gameOver now.
useExtraLifeundefinedPlayer redeemed an extra life — resume from end state.
avatarAnimation{ animationType }Play a one-shot animation on the in-game avatar.
avatarUpdate{ avatar }Avatar data changed.
miningEventUpdate{ miningEvent }Mining feature payload changed.

Listeners that throw are caught and logged; one bad handler can’t starve the others. Subscribe before calling gameReady() or you may miss the first start.

Capabilities#

type Capability = 'saveState' | 'rewardedAds' | 'logEvents' | 'missions' | 'gems';

Calling a gated method without its capability throws CAPABILITY_MISSING synchronously, before anything reaches the wire. The error is a GameeError with .code, .method, .message.

Removed (relative to legacy gamee-js): replay, ghostMode, socialData, playerData, platformExtraLife (useExtraLife event still exists, just ungated).

GameInitResponse#

{
  environment: 'production' | 'development';
  platform: 'web' | 'mobile_web' | 'ios' | 'android';
  country: string;       // ISO 3166-1 alpha-2 ('US', 'CZ', …)
  locale: string;        // BCP-47 ('en-US', 'cs-CZ', …)
  gameContext: 'normal' | 'battle' | 'mission';
  sound: boolean;
  noAds?: boolean;
  saveState?: string;     // previously saved state (stringified JSON the game wrote)
  initData?: string;      // deep-link payload (stringified JSON)
  missionData?: MissionData; // present when gameContext === 'mission'
}

The SDK validates that environment, platform, country, locale, gameContext, sound are present. Missing fields cause the init Promise to reject with BRIDGE_ERROR.

Errors#

Every failure mode is a GameeError with one of seven code values:

CodeWhen
VALIDATIONBad input that the SDK validated locally (logEvent over 160 chars, updateMissionProgress outside [0, 100], unknown capability, etc.).
CAPABILITY_MISSINGGated method called without the capability.
BRIDGE_TIMEOUTPlatform did not respond within requestTimeoutMs (default 30 000 ms). Only data-returning methods can hit this.
BRIDGE_ERRORPlatform returned an explicit error response, or returned a malformed payload (e.g. init missing required fields).
BRIDGE_OVERFLOWMore than maxPendingRequests concurrent data-returning calls (default 256). Oldest pending is rejected.
INVALID_STATEinit() called twice, or any method called after dispose().
NOT_SUPPORTEDMethod exists but the platform can’t honor it. Reserved for future use.

GameeError has .name === 'GameeError', .code, .method?, .message, .cause?. Branch on .code, never on .message.

try {
  await gamee.gameSaveState({ level: 3 });
} catch (err) {
  if (err instanceof GameeError && err.code === 'CAPABILITY_MISSING') {
    // declare 'saveState' in init()
  }
}

Behavior gotchas#

  • Signal methods (gameReady, gameStart, gameOver, gameSaveState, updateScore, updateMissionProgress, requestPause, logEvent) return Promise<void> but resolve immediately — they don’t wait for the platform’s ack. Validation errors surface as Promise rejections. If you don’t await or .catch(), they become unhandled-rejection events.
  • Data-returning methods (purchaseItemWithGems, loadRewardedVideo, showRewardedVideo) DO wait for the platform’s reply and can hit BRIDGE_TIMEOUT / BRIDGE_ERROR.
  • init() is single-shot. Second call → INVALID_STATE. Use createSdk() for a fresh instance in tests.
  • Subscribe before gameReady() or you’ll miss the first start event.
  • saveState is a string on the wire. The SDK auto-stringifies objects. The platform sends it back via initResponse.saveState exactly as it was saved.
  • updateScore truncates non-integer scores to Math.trunc(score).
  • messageId is wire-internal — games never see it.

SDK options (createSdk(opts))#

interface SdkOptions {
  allowedOrigins?: readonly string[] | '*'; // postMessage origin allowlist for the web bridge. Default: '*' with first-use warning.
  requestTimeoutMs?: number; // default 30_000
  maxPendingRequests?: number; // default 256 (data-returning calls only)
  silentMode?: boolean; // suppress runtime warnings
  bridge?: PlatformBridge; // inject a custom bridge (used by emulator + MockBridge)
}

Wire format#

One envelope shape, both directions:

// Game → Platform request
{ "request":  { "method": "init", "messageId": 0, "data": { "version": "1.0.0", "capabilities": { "saveState": true } } } }

// Platform → Game response (data) — replies to a request
{ "response": { "messageId": 0, "data": { /* … */ } } }

// Platform → Game response (error)
{ "response": { "messageId": 0, "error": { "code": "…", "message": "…" } } }

// Platform → Game event (looks like a request; SDK auto-acks)
{ "request": { "method": "pause", "messageId": 7, "data": null } }

messageId is a non-negative integer (number) that increments by 1 per outbound message within a single SDK instance (starts at 0). Wire-compatible with legacy gamee-js clients. It correlates request ↔ response. init’s wire data.capabilities is an object map ({ saveState: true }), not an array — game-facing API uses arrays for ergonomics, the SDK transforms.

@gamee/sdk-test-utils#

Sister package shipping MockBridge for game test suites. Inject it instead of the real bridge.

import { describe, expect, test, beforeEach } from 'vitest';
import { createSdk, type GameeSDK } from '@gamee/sdk';
import { MockBridge } from '@gamee/sdk-test-utils';

let mock: MockBridge;
let sdk: GameeSDK;

beforeEach(() => {
  mock = new MockBridge();
  sdk = createSdk({ bridge: mock });
});

test('updateScore reaches the wire with the right shape', async () => {
  await sdk.init({ capabilities: [] });
  sdk.updateScore({ score: 420, playTime: 12, checksum: 'sum' });
  expect(mock.requests.find((r) => r.method === 'updateScore')?.data).toMatchObject({
    score: 420,
    currentPlayTime: 12,
    gameChecksum: 'sum',
  });
});

MockBridge API#

MemberPurpose
new MockBridge(platform?)Construct. platform defaults to 'web'; pass 'ios' / 'android' for tests that branch on it.
mock.requestsEvery recorded outbound request: { method, messageId, data }[].
mock.ackedmessageIds of platform events the SDK acked.
mock.expect(method).respondWith(data)Reply to the next request matching method with data.
mock.expect(method).rejectWith({ code, message })Reply with an error.
mock.expect(method).leavePending()Don’t auto-respond; settle later via mock.respondTo(method, data).
mock.respondTo(method, data)Manually settle the most recent matching pending request.
mock.emit(name, payload, messageId?)Push a platform → game event. Auto-acked.
mock.defaultResolutionWhat happens to unexpected requests: 'ack-null' (default) | 'leave-pending' | 'error'.
mock.reset()Clear requests and unconsumed expectations.
mock.dispose()Detach. Safe to call repeatedly.

Common test recipes#

// 1. Platform replies with success.
mock.expect('showRewardedVideo').respondWith({ videoPlayed: true });
const r = await sdk.showRewardedVideo();
expect(r.videoPlayed).toBe(true);

// 2. Platform rejects.
mock.expect('purchaseItemWithGems').rejectWith({ code: 'BRIDGE_ERROR', message: 'insufficient gems' });
await expect(sdk.purchaseItemWithGems({ gemsCost: 99, itemName: 'X' })).rejects.toThrow('insufficient gems');

// 3. Capability gate is sync.
expect(() => sdk.gameSaveState({ x: 1 })).toThrow(/CAPABILITY_MISSING|saveState/);

// 4. Drive a platform-initiated event.
let paused = false;
sdk.on('pause', () => {
  paused = true;
});
mock.emit('pause');
expect(paused).toBe(true);

// 5. Force a timeout.
sdk = createSdk({ bridge: mock, requestTimeoutMs: 50 });
await sdk.init({ capabilities: ['rewardedAds'] });
mock.expect('showRewardedVideo').leavePending();
await expect(sdk.showRewardedVideo()).rejects.toMatchObject({ code: 'BRIDGE_TIMEOUT' });

Quick reference for code generation#

When generating game code that uses this SDK, default to:

  1. await gamee.init({ capabilities: [...] }) — list every capability the game’s logic touches; otherwise gated calls throw.
  2. gamee.on(event, handler) before gamee.gameReady().
  3. Synchronous-feeling fire-and-forget for telemetry: gamee.updateScore(...), gamee.logEvent(...) — no need to await.
  4. await for data-returning calls: loadRewardedVideo, showRewardedVideo, purchaseItemWithGems.
  5. Error handling: try { await … } catch (err) { if (err instanceof GameeError) … } with err.code branching.
  6. Tests: inject MockBridge via createSdk({ bridge: mock }). Never use the singleton in tests.

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.