@cinadmin/game-sdk
v0.1.0-alpha.0
Published
Official runtime SDK for CinAdmin AR games (TypeScript/JS). Token-exchange auth, discover/join, and the v2 runtime WebSocket.
Maintainers
Readme
@cinadmin/game-sdk
The official runtime SDK for CinAdmin AR games (TypeScript / JavaScript). The
TS twin of the Unity SDK (CinAdmin.GameSdk) — same surface, same contract.
It handles the three things a runtime title needs at showtime:
- Auth — exchanges your short-lived runtime token and re-mints it transparently.
- Discover / join — finds and joins the session running in a venue.
- The v2 WebSocket — live scores, game messages, player join/leave, with an offline queue that flushes on reconnect.
This package is the runtime client. For registering plays / managing your catalogue use the admin SDK (
@cinadmin/sdk).
Install
npm install @cinadmin/game-sdkAuth model (read this first)
There are two tokens. Keep them straight:
| Token | What it is | Where it lives |
| ---------------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| Build token (cinadm_sdk_*, ~90-day) | Your GP identity. Issued once in the portal (Settings → SDK access, one-shot reveal). | Your backend only. Never in an APK, never in the browser, never in this SDK. |
| Runtime session token (ES256 JWT, ~20-min) | The Bearer on every runtime call. | Minted by exchanging the build token at POST /api/v1/sdk/session-token. This SDK uses it. |
The SDK takes a tokenProvider callback: a function that returns a fresh
runtime token. You implement the exchange in your own broker (so the build
token never reaches the client), and the SDK calls your callback whenever it needs
a token — first use, near expiry, or when the server reports the token expired.
const client = new CinAdminGameClient({
baseUrl: 'https://api.cinadmin.com',
webSocketUrl: 'wss://ws-v2.cinadmin.com',
tokenProvider: async () => {
// Call YOUR backend, which holds the cinadm_sdk_* build token and exchanges
// it at POST /api/v1/sdk/session-token. Return the minted runtime token.
const res = await fetch('https://your-broker.example/runtime-token', { method: 'POST' })
const { token, expiresAt } = await res.json()
return { token, expiresAt } // expiresAt = ISO-8601
},
})The SDK refuses a cinadm_sdk_* token from tokenProvider (HC-01 guardrail) —
if you see TOKEN_UNAVAILABLE complaining about a build token, your broker is
returning the wrong one.
Quickstart
import { CinAdminGameClient } from '@cinadmin/game-sdk'
const client = new CinAdminGameClient({ baseUrl, webSocketUrl, tokenProvider })
// 1. Find the session running on this screen.
const [session] = await client.discoverSessions({ venueId: 'venue-123' })
// 2. Join it.
await client.joinSession(session.sessionId!)
// 3. Go live.
client.on('scoreUpdate', e => console.log(`${e.playerId} → ${e.score}`))
client.on('playerJoined', e => console.log(`${e.playerId} joined seat ${e.seat}`))
client.on('connectionState', s => console.log('ws:', s))
client.on('error', e => console.warn(e.error.code, e.error.message))
await client.openSessionWebSocket(session.sessionId!)
await client.sendScoreUpdate('player-1', 4200, 'the-human-reality')
// 4. Tear down.
await client.closeWebSocket()
client.dispose()Node usage
In Node there is no global WebSocket (< Node 22) — pass a factory backed by the
ws package, and (on Node < 18) a fetch:
import WebSocket from 'ws'
new CinAdminGameClient({
baseUrl,
webSocketUrl,
tokenProvider,
webSocketFactory: url => new WebSocket(url) as never,
})Offline-first
Outbound frames (sendScoreUpdate, sendGameMessage, …) sent while the socket is
down are queued in an in-memory store and flushed FIFO on the next connect — a
transient drop never loses data and never throws into your game loop. For
cross-restart durability supply your own offlineStore (an IOfflineStore over
IndexedDB / the filesystem).
For scores accumulated entirely offline, upload them when you reconnect with the
idempotent batch — a re-flush of the same clientResultId is a server no-op,
so the leaderboard never double-counts:
await client.submitResultsBatch(sessionId, [
{ sessionPlayerId: 'sp-1', score: 1200, gameName: 'g', clientResultId: 'cr-7f3a' },
])
// → { accepted, duplicates, total }Errors
Every call throws a typed CinAdminError with a stable .code (see ErrorCodes):
FORBIDDEN (cross-tenant), CONFLICT (session closed/not open),
RUNTIME_TOKEN_EXPIRED (auto-re-minted once for you), UPGRADE_REQUIRED (your SDK
is below the supported floor — upgrade), NOT_IMPLEMENTED (a contract path not yet
shipped). Background/WS failures arrive on the error event, not as throws.
Contract
Every REST path and WS frame this client uses is defined in
@cinadmin/game-sdk-contract (openapi.yaml +
ws-v2.schema.json). The contract test pins the client to it.
License
UNLICENSED — © SSEL. Distributed for use by CinAdmin platform partners.
