Skip to content

SDK ​

This SDK provides secure bidirectional communication between the game and the native clients (iOS and Android), enabling third‑party developers to invoke native capabilities.

Exposed global API name: window.GameTokSDK

Capabilities:

ActionDescription
GET_PROFILEGet the current user profile
GET_PROFILESBatch‑get user profiles (by uid array)
STORAGE_SETWrite to local key‑value storage (settings, progress, etc.)
STORAGE_GETRead from local key‑value storage (retrieve previously saved data)
ADD_SCOREReport a score (leaderboards, sharing, etc.)
GET_PERMISSION_MICRequest microphone permission (Android)
TOPUPTop up when in‑game currency is low; opens the client top‑up flow
ROUND_STARTRound start notification (call once per round start; tied to platform round/energy policies)
ROUND_ENDRound end notification (call when a round ends; round_id must match this round's ROUND_START)
SHOW_ENERGY_INSUFFICIENT_DIALOGEnergy is insufficient; notify the platform to show the energy-insufficient dialog (fire-and-forget)
onAudioSuspendListen for audio suspend event (triggered when the platform requires the game to mute)
offAudioSuspendRemove audio suspend event listener
onAudioResumeListen for audio resume event (triggered when the platform allows the game to restore sound)
offAudioResumeRemove audio resume event listener
onAutoStartGameListen for client "start game" command (same effect as clicking the in-game start button)
offAutoStartGameRemove "start game" command listener
onExitAndResetGameListen for client "exit and reset game" command (end the game and return to initial state)
offExitAndResetGameRemove "exit and reset game" command listener
REQUEST_EXITThe game's own "back to home" button was tapped; notify the platform to return home (fire-and-forget; the platform then sends EXIT_AND_RESET_GAME)

Integration examples ​

The following shows how to include the SDK on a page and call each capability.

1.1 Including the script ​

Option 1: Load the bundle via <script>

html
<script src="https://play.letskix.com/res/game/sdk-js/GameTokSDK.js"></script>
<script>
  console.log('GameTokSDK version loaded:', !!window.GameTokSDK);
</script>

Local development (not embedded in the app, debugging in a browser): After loading the SDK, call GameTokSDK.enableMock(). Promise‑based APIs will use built‑in mock data; no native client is required. Do not call this in production or store submission builds, or users will see fake data.

html
<script src="https://play.letskix.com/res/game/sdk-js/GameTokSDK.js"></script>
<script>
  GameTokSDK.enableMock();
</script>

Device testing (WebView inside the app): When you need logs, after loading the SDK call GameTokSDK.enableDebug() (returns a Promise; loads vConsole on the page and logs native bridge traffic). Do not use together with enableMock.

html
<script src="https://play.letskix.com/res/game/sdk-js/GameTokSDK.js"></script>
<script>
  GameTokSDK.enableDebug();
</script>

1.2 Usage examples ​

All examples use then / catch with Promises. On success, a unified response object is returned; on failure, the Promise rejects.

1.2.1 Get profile ​

javascript
// Example call
GameTokSDK.getProfile()
  .then(({ action, error, data }) => {
    console.log('Profile loaded:', data);
  })
  .catch((e) => {
    console.error('Failed to get profile:', e);
  });

Sample response (JSON):

json
{
  "action": "GET_PROFILE",
  "error": false,
  "data": {
    "uid": 1024780,
    "avatar": "https://game-load-sa.lobah.net/avatar/1.jpg",
    "userName": "Guest",
    "userCoins": 0,
    "level": 0,
    "gameLevel": 0,
    "rankImg": "",
    "gender": 0,
    "testAccount": false,
    "guest": true
  }
}

Key fields:

FieldTypeDescription
uidnumberUser unique ID
avatarstringAvatar URL
userNamestringUsername / display name
userCoinsnumberUser's current coin balance
levelnumberUser growth level (account system)
gameLevelnumberRank (based on game matches / ranked play); 0 means no rank (e.g., new player, has not participated in ranked)
rankImgstringRank image URL; empty string when the user has no rank
gendernumberGender enum: 0 = unknown, 1 = male, 2 = female
testAccountbooleanWhether a test account / platform review account
guestbooleanWhether a guest (not logged in to a real account)

1.2.2 Batch get user profiles ​

Use this for leaderboards, friend lists, match settlement, or any scenario that needs to fetch basic info for multiple users at once, avoiding high‑frequency looping calls to getProfile.

javascript
/**
 * Batch get user profiles
 * @param uids  Required; array of uids to query (recommended: 1–50 per call)
 */
GameTokSDK.getProfiles({ uids: [1234567, 1234568, 1234569] })
  .then(({ action, error, data }) => {
    console.log('Batch fetch succeeded:', data);
    // data is a Profile array; iterate or map by uid directly
    const map = new Map(data.map(p => [p.uid, p]));
    console.log('1234567 ->', map.get(1234567));
  })
  .catch((e) => {
    console.error('Batch fetch failed:', e);
  });

Parameters:

FieldTypeRequiredDescription
uidsnumber[]YesList of uids to query; recommended length 1–50. If empty array, non-array, or contains invalid elements (e.g. "abc", null, negative numbers, decimals), SDK directly reject(SDKError), error code INVALID_PARAMS = 1003, will not send to native

Sample response (JSON):

json
{
  "action": "GET_PROFILES",
  "error": false,
  "data": [
    {
      "uid": 1024780,
      "avatar": "https://game-load-sa.lobah.net/avatar/1.jpg",
      "userName": "Guest",
      "userCoins": 0,
      "level": 0,
      "gameLevel": 0,
      "rankImg": "",
      "gender": 0,
      "testAccount": false,
      "guest": true
    },
    {
      "uid": 1024781,
      "avatar": "https://game-load-sa.lobah.net/avatar/2.jpg",
      "userName": "Alice",
      "userCoins": 200,
      "level": 3,
      "gameLevel": 5,
      "rankImg": "https://game-load-sa.lobah.net/rank/5.png",
      "gender": 2,
      "testAccount": false,
      "guest": false
    }
  ]
}

1.2.3 Store key‑value ​

javascript
/**
 * Store a key‑value pair
 * @param key   Custom key
 * @param value Value; may be a string or an object
 */
GameTokSDK.storageSet({ key: 'settings', value: { theme: 'dark', volume: 0.8 } })
  .then(() => {
    console.log('Stored successfully');
  })
  .catch((e) => {
    console.error('Store failed:', e);
  });

Sample response (JSON):

json
{
  "action": "STORAGE_SET",
  "error": false,
  "data": null
}

1.2.4 Read key‑value ​

javascript
/**
 * Read a key‑value pair
 * @param key Predefined key to read
 */
GameTokSDK.storageGet({ key: 'settings' })
  .then((result) => {
    console.log('Read OK:', result.data.value); // object or string
  })
  .catch((e) => {
    console.error('Read failed:', e);
  });

Sample response (JSON):

json
{
  "action": "STORAGE_GET",
  "error": false,
  "data": {
    "value": {
        "theme": "dark",
        "volume": 0.8
    }
  }
}

1.2.5 Report score ​

javascript
/**
 * Depending on the game, you may report score, stage, or level.
 * For score: { score: 300, scoreType: 'score', remark: 'score' }
 * For level: { score: 1, scoreType: 'level', remark: 'level' }
 * @param score     Value to report; must be a positive integer
 * @param scoreType Category (business‑defined label for the uploaded data)
 * @param remark    Optional note (may match scoreType)
 */

GameTokSDK.addScore({ score: 10, scoreType: 'score', remark: 'score' })
  .then(() => {
    console.log('Score reported');
  })
  .catch((e) => {
    console.error('Report failed:', e);
  });

Sample response (JSON):

json
{
  "action": "ADD_SCORE",
  "error": false,
  "data": null
}

1.2.6 Microphone permission ​

javascript
/**
 * Request microphone permission (Android)
 * Use in‑game to prompt the user for mic access (e.g., voice features).
 * No return value; only triggers the native permission UI.
 */
GameTokSDK.getPermissionMic();

1.2.7 Top up (TOPUP) ​

Important: User cancel, payment failure, etc. still resolve via then. In then, check data.code for success. catch is only for SDK unavailability, timeouts, and similar errors.

javascript
/**
 * Top up
 * @param amount Amount (units as agreed with the client)
 */
GameTokSDK.topup({ amount: 100 })
  .then(({ action, error, data }) => {
    const code = data && data.code;
    if (code === 200) {
      console.log('Top‑up succeeded:', data);
    } else {
      console.warn('Top‑up incomplete or failed, code:', code, data);
    }
  })
  .catch((e) => {
    console.error('Top‑up request error (e.g., SDK unavailable):', e);
  });

Sample response (JSON, success):

json
{
  "action": "TOPUP",
  "error": false,
  "data": {
    "code": 200,
    "message": "ok"
  }
}

code values:
200 Success
401 Top-up failed (error)
402 Top-up failed (user cancelled)

1.2.8 Round start (ROUND_START) ​

Call once after a round actually starts. round_id is optional and we recommend omitting it: when omitted, the SDK generates a globally unique round ID (prefixed round_), remembers it internally, and roundEnd() automatically carries the same value. Optional timestamp (ms; defaults to now if omitted). No Promise return — fire‑and‑forget.

javascript
// Recommended: pass nothing; the SDK generates round_id and binds it to this round
GameTokSDK.roundStart();

// Compatible usage: pass your own round_id (must be globally unique per round and never reused across rounds;
// never use session-local incrementing numbers like 1, 2, 3 — they collide with past rounds after a page refresh)
GameTokSDK.roundStart({ round_id: 'round-' + crypto.randomUUID() });

When you need the current round ID (e.g. to pass to showEnergyInsufficientDialog for attribution):

javascript
const roundId = GameTokSDK.getCurrentRoundId(); // set after roundStart, null after roundEnd

If roundStart is called again without a roundEnd for the previous round, it is treated as a new round and the SDK simply overwrites the current round ID (the old round is treated as not ended normally; the platform tolerates this).

1.2.9 Round end (ROUND_END) ​

Call after the round ends (win, loss, or timeout all count as ending). round_id is optional: when omitted, the SDK automatically carries the same value as this round's roundStart; if you pass it yourself it must match the round start. Optional timestamp. No Promise return.

javascript
// Recommended: pair with roundStart() and never touch round_id
GameTokSDK.roundStart();
// ... round logic ...
GameTokSDK.roundEnd();

// Compatible usage: pass explicitly (must equal this round's roundStart round_id)
GameTokSDK.roundEnd({ round_id: myRoundId, timestamp: Date.now() });

If there is no round in progress (never called roundStart, or already called roundEnd) and no explicit round_id is passed, the SDK logs a warning and does not send to native.

1.2.10 Energy-insufficient dialog (SHOW_ENERGY_INSUFFICIENT_DIALOG) ​

When the game learns that energy is insufficient via getEnergy() / onEnergyUpdate() (see 1.2.11) (e.g. balance < cost_per_round, so a new round cannot start), call this API to ask the platform to show the energy-insufficient dialog in the game container layer (it contains guidance such as check-in for energy, go get energy, etc.). The game does not evaluate energy rules or handle the dialog UI; it only triggers it. No Promise return — fire‑and‑forget.

Note: the platform itself also shows this dialog proactively at times such as round-start charging and round-end checks; the game-side call is a supplementary trigger (typical scenario: the player taps "Start" but the game already knows energy is insufficient, so it requests the dialog instead of silently doing nothing).

javascript
/**
 * Energy-insufficient dialog
 * @param scene    Optional trigger scene (business-defined, e.g. round_start, continue)
 * @param round_id Optional associated round ID for platform attribution (use getCurrentRoundId())
 */
GameTokSDK.showEnergyInsufficientDialog({
  scene: 'round_start',
  round_id: GameTokSDK.getCurrentRoundId() || undefined,
});

// Or call without parameters
GameTokSDK.showEnergyInsufficientDialog();

KC League flow: before a match starts, the game server calls the platform server's entry-check. When it returns canStart: false, call this same API and attach the fields from the check response as-is (the fire-and-forget channel passes through arbitrary JSON fields, no SDK release needed). After the call the game stays on the cover screen and the START button becomes clickable again; if the platform wants to start the match directly after the player tops up / checks in, it will send AUTO_START_GAME — the game does not need to retry on its own.

javascript
GameTokSDK.showEnergyInsufficientDialog({
  scene: 'kc_entry_check',
  energyBalance: 0,          // balance returned by entry-check, passed through as-is
  blockReason: 'NO_ENERGY'   // enum returned by entry-check, passed through as-is
});

Sample payload (JSON):

json
{
  "action": "SHOW_ENERGY_INSUFFICIENT_DIALOG",
  "data": {
    "scene": "round_start",
    "round_id": "round-1732000000-abc123"
  }
}

1.2.11 Energy query and push (getEnergy / onEnergyUpdate) ​

Energy deduction, dialogs, and recharge are all handled by the platform. The game only needs to do two things: display the energy HUD, and grey out the "Start" button when energy is insufficient. There are two data sources:

  • getEnergy() (request-response): query once proactively during initialization;
  • onEnergyUpdate(handler) (platform push): after every energy change (round-start charge, check-in, recharge, round-end check, etc.) the platform pushes the latest value, and the game refreshes the HUD accordingly.

Both return/push the same data structure:

FieldTypeDescription
balancenumber | nullEnergy balance; null when the energy feature is disabled — the game should hide the energy UI and run with unlimited energy
charge_enabledbooleanWhether per-round deduction is enabled for this game
cost_per_roundnumberEnergy consumed per round
reasonstringReason for the change (push only): initial / round_charge / round_end_check / checkin / recharge_check
seqnumberMonotonically increasing sequence number; consumers must discard out-of-order stale values by seq (pushes are not guaranteed to arrive in order)
javascript
// Initialization: query once + register push listener
const { data } = await GameTokSDK.getEnergy();
if (data.balance === null) {
  hideEnergyHud();           // energy feature disabled; degrade to unlimited energy
} else {
  renderEnergyHud(data.balance, data.cost_per_round);
}

let lastSeq = -Infinity;
GameTokSDK.onEnergyUpdate((payload) => {
  if (payload.seq <= lastSeq) return; // discard out-of-order stale values
  lastSeq = payload.seq;
  renderEnergyHud(payload.balance, payload.cost_per_round);
  setStartButtonEnabled(payload.balance === null || payload.balance >= payload.cost_per_round);
});

Last-value sync compensation: like the audio events, onEnergyUpdate's options.sync defaults to true — if the SDK has already cached an energy value at registration time (from an earlier push or a getEnergy() response), it re-emits it once asynchronously, so late registration does not miss the current state; pass { sync: false } to disable. offEnergyUpdate / onceEnergyUpdate (same signatures as the audio events) are also provided, along with a read-only cache:

javascript
const { last, seq } = GameTokSDK.getEnergyState(); // last is the latest energy data (monotonic by seq), null when no data

Local debugging (Mock): after enableMock(), getEnergy() returns the built-in mock value { balance: 100, charge_enabled: true, cost_per_round: 8 }; you can override it with a custom handler:

javascript
GameTokSDK.enableMock({ GET_ENERGY: () => ({ balance: 3, charge_enabled: true, cost_per_round: 8, seq: Date.now() }) });

ENERGY_UPDATE pushes come from the host and are absent in Mock mode; during debugging you can dispatch one manually from the console:

javascript
window.dispatchEvent(new MessageEvent('message', { data: JSON.stringify({
  action: 'ENERGY_UPDATE',
  data: { balance: 0, charge_enabled: true, cost_per_round: 8, reason: 'round_charge', seq: Date.now() }
})}));

1.2.12 Audio event listeners (onAudioSuspend / offAudioSuspend / onAudioResume / offAudioResume) ​

When the platform requires the game to mute (e.g., the user enters a live room, a system call comes in, etc.), an audio suspend event is pushed to the game; when sound is allowed to resume, an audio resume event is pushed. The game should promptly mute/unmute all sound effects and background music upon receiving these events.

This is an event listener interface, not a Promise interface, and has no return value.

javascript
// Listen for audio suspend event
function handleAudioSuspend(payload) {
  console.log('Mute notification received:', payload);
  // Mute all sound effects and background music in the game
  myGame.muteAll();
}

GameTokSDK.onAudioSuspend(handleAudioSuspend);

// Listen for audio resume event
function handleAudioResume(payload) {
  console.log('Resume sound notification received:', payload);
  // Restore all sound effects and background music in the game
  myGame.unmuteAll();
}

GameTokSDK.onAudioResume(handleAudioResume);

Remove listeners:

javascript
// Remove audio suspend listener (pass the same function reference used when registering)
GameTokSDK.offAudioSuspend(handleAudioSuspend);

// Remove audio resume listener
GameTokSDK.offAudioResume(handleAudioResume);

options.sync parameter (state synchronization):

onAudioSuspend and onAudioResume support a second optional options parameter, where sync (default true) handles the case where the listener is registered after the event has already fired:

  • sync: true (default) — If audio is already in a suspended/resumed state when the listener is registered, the callback will be called once immediately in the next microtask, ensuring the game does not miss previous state changes.
  • sync: false — Only listens for subsequent new event pushes; no state sync on registration.
javascript
// Default sync: true — if audio is already suspended, callback fires immediately once
GameTokSDK.onAudioSuspend((payload) => {
  myGame.muteAll();
});

// Disable state sync, only listen for subsequent events
GameTokSDK.onAudioSuspend((payload) => {
  myGame.muteAll();
}, { sync: false });

Recommended usage: Register listeners as early as possible during game initialization and keep sync: true (default), so the game can correctly sync state even if the platform has already sent a mute instruction before registration.

1.2.13 Game control event listeners (onAutoStartGame / offAutoStartGame / onExitAndResetGame / offExitAndResetGame) ​

The client / shell can push two types of control commands to the game (client => H5):

  • AUTO_START_GAME: Notifies the game to start automatically, with the same effect as clicking the in-game start button. There are two trigger scenarios: (1) the player taps "Play again" in the platform leaderboard overlay and the platform sends it after verifying energy; (2) auto-start on entry — when the player enters from the mini-program poster page by tapping "Start now", the platform sends it directly once the game is ready, so it may arrive right after the game finishes loading, before the player has performed any in-game action.
  • EXIT_AND_RESET_GAME: The client notifies the game to end and return to the initial state (terminate the current round immediately, clear countdowns/animations, and return to the initial or ready-to-start screen).

⚠️ Games integrating the energy / leaderboard flow must implement both listeners: the platform's energy race-condition fallback (energy found insufficient only after the round started) relies on EXIT_AND_RESET_GAME to reclaim the round, and continuing after check-in / recharge as well as auto-start on entry rely on AUTO_START_GAME to start the round; without them the platform cannot intercept the game when energy is insufficient.

This is an event listener interface, not a Promise interface, and has no return value. AUTO_START_GAME supports one-time re-emission on late registration: if no listener exists when the command arrives (e.g. auto-start on entry arrives before game initialization completes), the SDK stores the latest one and re-emits it once asynchronously when the first onAutoStartGame is registered, then clears it — so registering the listener during initialization as usual is enough and the entry command will not be missed. EXIT_AND_RESET_GAME is a purely instantaneous command with no compensation (it is only sent mid-round, when the game has necessarily finished initializing); register the listener as early as possible during game initialization.

javascript
// Listen for "start game" command
function handleAutoStart(payload) {
  console.log('Start game command received:', payload);
  myGame.start(); // same as clicking the in-game start button
}
GameTokSDK.onAutoStartGame(handleAutoStart);

// Listen for "exit and reset game" command
function handleExitReset(payload) {
  console.log('Exit and reset command received:', payload);
  myGame.exitAndReset(); // end current round and return to initial state
}
GameTokSDK.onExitAndResetGame(handleExitReset);

Remove listeners:

javascript
// Pass the same function reference used when registering
GameTokSDK.offAutoStartGame(handleAutoStart);
GameTokSDK.offExitAndResetGame(handleExitReset);

Listen once:

javascript
GameTokSDK.onceAutoStartGame((payload) => {
  myGame.start();
});
GameTokSDK.onceExitAndResetGame((payload) => {
  myGame.exitAndReset();
});

Client push message format:

json
{ "action": "AUTO_START_GAME" }
json
{ "action": "EXIT_AND_RESET_GAME" }

1.2.14 Game's own back button (requestExit / REQUEST_EXIT) ​

If the game UI has its own "back to home / exit" button, call requestExit() when the player taps it to tell the platform "I want to go back home". The platform will then send EXIT_AND_RESET_GAME, and the game wraps up in its existing onExitAndResetGame listener (terminate the round, clear animations, return to the ready-to-start screen) — requestExit() itself does not change any round state, nor does it clear the current round_id.

This is a fire-and-forget interface: no Promise, no return value, no de-duplication (repeated taps are de-duplicated on the platform side). All parameters are optional and only used for troubleshooting correlation:

javascript
/**
 * Player taps the in-game "back to home" button
 * @param reason    Optional trigger reason; defaults to 'player_button'
 * @param state     Optional game state at tap time (e.g. 'IDLE' / 'PLAY' / 'RESULT'), troubleshooting only
 * @param timestamp Optional ms timestamp; defaults to now if omitted
 */
GameTokSDK.requestExit();

// With troubleshooting info
GameTokSDK.requestExit({ reason: 'player_button', state: 'PLAY' });

// The actual wrap-up still happens here (must be registered before integrating, see 1.2.13)
GameTokSDK.onExitAndResetGame(() => myGame.exitAndReset());

If a round is in progress (after roundStart, before roundEnd), the SDK automatically includes round_id so the platform can correlate this round; no need to pass it manually.

Sample payload (JSON):

json
{
  "action": "REQUEST_EXIT",
  "data": {
    "reason": "player_button",
    "state": "PLAY",
    "round_id": "round_xxx",
    "timestamp": 1717000000000
  }
}

Field reference:

FieldTypeDescription
reasonstringTrigger reason; defaults to player_button
statestringOptional, game state at tap time, troubleshooting only
round_idstringOptional, automatically included by the SDK when tapped mid-round
timestampnumberms timestamp; defaults to now

⚠️ Do not reset the game yourself after requestExit() — always wait for EXIT_AND_RESET_GAME to arrive before wrapping up, to avoid the platform dialog / settlement getting out of sync with the game screen.

Parameters and response shape ​

  • Typical call parameters (reference; see each API for exact fields):
    • getProfile: `` (usually no extra parameters)
    • getProfiles: { uids: number[] } (required, non-empty array; returns Profile[], same structure as getProfile.data in array form)
    • storageSet: { key: string, value: any }
    • storageGet: { key: string }
    • addScore: { score: number, scoreType: string }
    • getPermissionMic: `` (usually no extra parameters)
    • topup: { amount: number, ... } (other fields per client protocol)
    • roundStart / roundEnd: { round_id: string, timestamp?: number }
    • showEnergyInsufficientDialog: { scene?: string, round_id?: string } (optional; fire-and-forget, no return value)
    • onAudioSuspend / onAudioResume: (handler: Function, options?: { sync?: boolean })
    • offAudioSuspend / offAudioResume: (handler: Function)
    • onAutoStartGame / onExitAndResetGame: (handler: Function)
    • offAutoStartGame / offExitAndResetGame: (handler: Function)
    • requestExit: { reason?: string, state?: string, timestamp?: number } (all optional; fire-and-forget, no return value; round_id is included automatically mid-round)
  • Successful responses share this shape:
typescript
{
  action: string;      // Action name for this call (e.g. 'GET_PROFILE')
  error: false;        // false on success (on failure the Promise rejects)
  data: any;           // Native payload; fields depend on the action
}
  • Failure behavior:
    • topup: Most business outcomes (success/failure/cancel) resolve in then via data.code; catch is for SDK errors and timeouts.
    • Other Promise interfaces: On failure the Promise rejects with an Error; handle in .catch (log, retry, or show UI).

Important notes ​

Do not overwrite or remove these globals, or the SDK will break:

  • window.GameTokSDK (main SDK entry)

Example (do not do this):

javascript
// Dangerous — breaks iOS / Android bridge
window.GameTokSDK = {};
window.GameTokSDK = null;
delete window.GameTokSDK;

Full example ​

javascript
// Get user profile
GameTokSDK.getProfile({})
  .then((resp) => {
    console.log('Profile:', resp.data);
  })
  .catch((e) => {
    console.error('Failed to get profile:', e);
  });

// Batch get user profiles (for leaderboards, friend lists, etc.)
GameTokSDK.getProfiles({ uids: [10001, 10002, 10003] })
  .then(({ data }) => {
    const map = new Map(data.map(p => [p.uid, p]));
    console.log('10001 profile:', map.get(10001));
  })
  .catch((e) => {
    console.error('Batch fetch failed:', e);
  });

// Store
GameTokSDK.storageSet({ key: 'config', value: { lang: 'en' } })
  .then(() => {
    console.log('Stored successfully');
  })
  .catch((e) => {
    console.error('Store failed:', e);
  });

// Read
GameTokSDK.storageGet({ key: 'config' })
  .then((resp) => {
    console.log('Config:', resp.data.value);
  })
  .catch((e) => {
    console.error('Read failed:', e);
  });

// Score
GameTokSDK.addScore({ score: 5, scoreType: 'score' })
  .then(() => {
    console.log('Score added');
  })
  .catch((e) => {
    console.error('Score report failed:', e);
  });

// Microphone (Android, no return value)
GameTokSDK.getPermissionMic();

// Top up: check data.code in then (200 = success)
GameTokSDK.topup({ amount: 100 })
  .then(({ data }) => {
    if (data && data.code === 200) {
      console.log('Top-up succeeded', data);
    } else {
      console.log('Top-up not successful', data);
    }
  })
  .catch((e) => console.error('Top-up error', e));

// Round notifications (same round_id)
const rid = 'r-' + Date.now();
GameTokSDK.roundStart({ round_id: rid });
GameTokSDK.roundEnd({ round_id: rid });

// Audio event listeners (register during game initialization)
GameTokSDK.onAudioSuspend((payload) => {
  console.log('Mute', payload);
  myGame.muteAll();
});

GameTokSDK.onAudioResume((payload) => {
  console.log('Resume sound', payload);
  myGame.unmuteAll();
});

// Game's own "back to home" button: only notify the platform; wrap-up waits for EXIT_AND_RESET_GAME
document.getElementById('btn-home').onclick = () => GameTokSDK.requestExit({ state: 'PLAY' });

Swipe & Play Endless Game Together