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#
| Term | Meaning |
|---|---|
| Game | The HTML5 app written with @gamee/sdk. |
| Platform | The 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). |
| Bridge | The transport layer the SDK picks at boot — WebBridge (postMessage), IosBridge (webkit.messageHandlers.callbackHandler), AndroidBridge (window._toDevice). |
| Request | Game → platform RPC. Returns a Promise. |
| Response | Platform’s reply to a specific request. Carries data or error. |
| Event | Platform → game push. Game subscribes via gamee.on(name, handler). SDK auto-acks. |
| Signal | A fire-and-forget request the SDK sends without tracking the platform’s ack (e.g. updateScore). Resolves immediately. |
| Capability | A 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#
| Method | Args | Returns | Cap | Notes |
|---|---|---|---|---|
init | { capabilities: Capability[] } | GameInitResponse | — | Call once per SDK instance; second call rejects INVALID_STATE. Validates capability list synchronously. Sends SDK version on the wire. |
gameReady | — | void (signal) | — | Tells the platform first frame is rendered. |
gameStart | — | void (signal) | — | A run is starting. |
gameOver | { saveStateData?, rewardIds?, metadata? }? | void (signal) | — | Run ended. saveStateData and metadata accept object | string; objects auto-stringify. |
requestPause | — | void (signal) | — | Game-initiated pause (e.g. opened in-game menu). Distinct from the platform-pushed pause event. |
dispose | — | void (sync) | — | Release resources. Pending data-returning requests reject INVALID_STATE. |
Save state#
| Method | Args | Cap | Notes |
|---|---|---|---|
gameSaveState | data: object | string | saveState | Pass an object (auto-stringified) or a pre-stringified JSON string. Wire method is saveState. |
Score & missions#
| Method | Args | Cap | Validation |
|---|---|---|---|
updateScore | number | { 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. |
updateMissionProgress | progress: number | missions | progress must be a finite number in [0, 100]. |
Commerce#
| Method | Args | Returns | Cap | Validation |
|---|---|---|---|---|
purchaseItemWithGems | { gemsCost, itemName } | { success } | gems | gemsCost ≥ 0, itemName non-empty string. |
Rewarded ads#
| Method | Args | Returns | Cap |
|---|---|---|---|
loadRewardedVideo | — | { videoLoaded: boolean } | rewardedAds |
showRewardedVideo | — | { videoPlayed: boolean } | rewardedAds |
Analytics#
| Method | Args | Cap | Validation |
|---|---|---|---|
logEvent | string | { name, value? } | logEvents | name non-empty, ≤ 24 chars; value (optional) ≤ 160 chars. Wire payload omits eventValue when value not provided. |
Introspection#
| Member | Type | Notes |
|---|---|---|
gamee.version | string | SDK version sent on the wire with init. |
gamee.getPlatform() | Platform | Returns the bridge’s platform string: 'web' | 'mobile_web' | 'ios' | 'android'. |
gamee.initResponse | GameInitResponse | null | The 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
| Event | Payload | When |
|---|---|---|
start | { resetState?, gameSeed? } | New run. May fire multiple times (replay, restart). |
pause | undefined | Platform paused (backgrounded, system interruption). Stop loop + audio. |
resume | undefined | OK to resume. |
mute | undefined | Audio toggled off. |
unmute | undefined | Audio toggled on. |
submit | undefined | Platform asks the game to call gameOver now. |
useExtraLife | undefined | Player 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:
| Code | When |
|---|---|
VALIDATION | Bad input that the SDK validated locally (logEvent over 160 chars, updateMissionProgress outside [0, 100], unknown capability, etc.). |
CAPABILITY_MISSING | Gated method called without the capability. |
BRIDGE_TIMEOUT | Platform did not respond within requestTimeoutMs (default 30 000 ms). Only data-returning methods can hit this. |
BRIDGE_ERROR | Platform returned an explicit error response, or returned a malformed payload (e.g. init missing required fields). |
BRIDGE_OVERFLOW | More than maxPendingRequests concurrent data-returning calls (default 256). Oldest pending is rejected. |
INVALID_STATE | init() called twice, or any method called after dispose(). |
NOT_SUPPORTED | Method 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) returnPromise<void>but resolve immediately — they don’t wait for the platform’s ack. Validation errors surface as Promise rejections. If you don’tawaitor.catch(), they become unhandled-rejection events. - Data-returning methods (
purchaseItemWithGems,loadRewardedVideo,showRewardedVideo) DO wait for the platform’s reply and can hitBRIDGE_TIMEOUT/BRIDGE_ERROR. init()is single-shot. Second call →INVALID_STATE. UsecreateSdk()for a fresh instance in tests.- Subscribe before
gameReady()or you’ll miss the firststartevent. saveStateis a string on the wire. The SDK auto-stringifies objects. The platform sends it back viainitResponse.saveStateexactly as it was saved.updateScoretruncates non-integer scores toMath.trunc(score).messageIdis 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#
| Member | Purpose |
|---|---|
new MockBridge(platform?) | Construct. platform defaults to 'web'; pass 'ios' / 'android' for tests that branch on it. |
mock.requests | Every recorded outbound request: { method, messageId, data }[]. |
mock.acked | messageIds 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.defaultResolution | What 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:
await gamee.init({ capabilities: [...] })— list every capability the game’s logic touches; otherwise gated calls throw.gamee.on(event, handler)beforegamee.gameReady().- Synchronous-feeling fire-and-forget for telemetry:
gamee.updateScore(...),gamee.logEvent(...)— no need toawait. awaitfor data-returning calls:loadRewardedVideo,showRewardedVideo,purchaseItemWithGems.- Error handling:
try { await … } catch (err) { if (err instanceof GameeError) … }witherr.codebranching. - Tests: inject
MockBridgeviacreateSdk({ bridge: mock }). Never use the singleton in tests.