SDK reference
The SDK is a 2.5 KB script that exposes one global, GemsArcadia. Load it from
the arcade:
<script src="https://gemsarcadia.com/sdk/v1.js"></script>Or install it with a bundler and import the same functions (ESM, with types):
import { init, insertCoin, on } from "@gems-arcadia/sdk";
await init({ host: "https://gemsarcadia.com" });This page covers SDK 1.1.0 (see the changelog;
GemsArcadia.version tells you which version is loaded). Every method returns a promise. Only init() can reject; the others always
resolve with { ok: true, ... } or { ok: false, reason }.
init(options?)#
Connects to the arcade. Call it once on load (calling it again returns the same answer).
| Option | Type | Meaning |
|---|---|---|
host | string | Arcade origin. Defaults to the origin the SDK script was loaded from. |
debug | boolean | Log every bridge message to the console. |
Resolves with:
| Field | Type | Meaning |
|---|---|---|
player.ref | string | Stable pseudonymous id (pr_...), the same across all of your games. |
player.displayName | string | A name to show in your game. |
coinsPerCredit | number | Coins one credit costs (set in the portal, 1–5). |
balance | number | The player's coins. |
testMode | boolean | true in previews and mock mode: insertions are free. |
Rejects if the arcade doesn't answer within 10 seconds.
export const ready = GemsArcadia.init().then((session) => {
const { player, coinsPerCredit, balance, testMode } = session;
ui.message.textContent =
`Hi ${player.displayName}! ${balance} coins, ` +
`${coinsPerCredit} per credit${testMode ? " (test mode)" : ""}.`;
// Coins inserted with the cabinet's own INSERT COIN button.
GemsArcadia.on("coinInserted", ({ insertionId }) => grantCredit(insertionId));
// The player switched tabs: stop the clock.
GemsArcadia.on("pause", () => (paused = true));
GemsArcadia.on("resume", () => (paused = false));
GemsArcadia.ready(); // loaded and ready to take coins
return session;
});insertCoin()#
Asks the arcade to take coinsPerCredit coins from the player for one
credit. Resolves { ok: true, insertionId, balance } when the coin went in.
Never rejects.
const REASONS = {
INSUFFICIENT_COINS: "Out of coins! Top up at the Token Booth.",
NOT_SIGNED_IN: "Log in to play.",
EMAIL_NOT_VERIFIED: "Verify your email to play.",
SESSION_EXPIRED: "Session timed out. Reload the game.",
RATE_LIMITED: "Easy there! One coin at a time.",
BUSY: "A coin is already dropping...",
};
/** Coins inserted from inside your game. insertCoin() never rejects. */
export async function insertCoin() {
ui.coinButton.disabled = true;
try {
const result = await GemsArcadia.insertCoin();
if (result.ok) grantCredit(result.insertionId);
else ui.message.textContent = REASONS[result.reason] ?? "The coin slot jammed. Try again.";
return result;
} finally {
ui.coinButton.disabled = false;
}
}
ui.coinButton.addEventListener("click", insertCoin);Failure reasons:
| Reason | What happened | Tell the player |
|---|---|---|
INSUFFICIENT_COINS | The wallet can't cover a credit. Nothing was charged. | Top up at the Token Booth |
RATE_LIMITED | More than 1 coin per 500 ms (or 60 per minute). | Wait a moment |
BUSY | A coin is already on its way in. | Nothing |
SESSION_EXPIRED | The play session ended or idled for 30 minutes. | Reload to keep playing |
NOT_SIGNED_IN | The player isn't logged in. | Log in to play |
EMAIL_NOT_VERIFIED | Players must verify their email before spending coins. | Verify your email |
TIMEOUT | No answer in 10 s. The coin may or may not have gone in: check getBalance(). | Try again |
ERROR | Anything else. | Try again |
getBalance()#
Resolves with the player's coin balance (a number). Handy after a TIMEOUT.
submitScore(score)#
Reports a final non-negative safe integer for the current session. Resolves
{ ok: true, scoreId, saved, testMode, qualifies, replayed, rank, initials, needsInitials }, or { ok: false, reason }. saved is false in a test preview
or mock mode. When a new score reaches the all-time top ten, the arcade opens
the initials picker; saved initials are offered again on future scores.
The game submits an untrusted client report. Do not use this result to grant valuable game entitlements; use the server score endpoint in server verification for trusted scores.
/** Spend a credit to start a run; false when the player must insert a coin. */
export function startRun() {
if (credits < 1 || paused) return false;
credits -= 1;
ui.credits.textContent = String(credits);
return true;
}
export async function gameOver(score) {
const result = await GemsArcadia.submitScore(Math.max(0, Math.floor(score)));
if (!result.ok && result.reason !== "UNAVAILABLE") {
console.warn("Score not saved:", result.reason);
}
return result;
}unlockAchievement(key)#
Since 1.1.0. Unlocks one of your game's achievements (defined in the
workshop's ACHIEVEMENTS tab) for the player, and the arcade plays the
ACHIEVEMENT UNLOCKED celebration. Resolves { ok: true, achievement, unlocked, newlyUnlocked, unlockedAt, testMode } or { ok: false, reason }. Idempotent:
repeats resolve with newlyUnlocked: false. Never rejects. Samples, fields and
error codes are in the Achievements API.
ready()#
Tells the arcade your game has finished loading and can take coins.
on(event, callback) and off(event, callback)#
on returns an unsubscribe function.
| Event | Payload | When |
|---|---|---|
coinInserted | { insertionId, balance } | The player used the arcade's own INSERT COIN button. Grant a credit. |
balanceChanged | { balance } | The balance changed (a coin went in, or the player topped up). |
pause | none | The player switched tabs. Freeze the game. |
resume | none | The player is back. |
mock#
GemsArcadia.mock.insertCoin(), .pause() and .resume() simulate the arcade
in mock mode. They do nothing inside the real arcade. See
testing & preview.
Under the hood#
Messages are { __ga: 1, id, type, payload }. The game posts requests (init,
insertCoin, getBalance, submitScore, unlockAchievement, ready) to its parent with the
arcade origin as targetOrigin. The arcade answers with type: "response", the
same id and a payload of { ok: true, data } or
{ ok: false, error: { code, message } }, and sends type: "event" messages
shaped { name, data }. The full walk-through is in
coin flow.