@usions/sdk
v2.19.0
Published
Usion Mini App SDK for iframe games and services
Maintainers
Readme
@usions/sdk
Client SDK for building mini-apps and games on the Usion platform.
Provides access to user info, storage, wallet, chat, sessions, and real-time multiplayer game features.
Documentation
- Guides — first game in 30 min, the reliability contract, netcode, money, cloud saves
- API reference (generated from the type declarations)
- Platform behavior spec — delivery guarantees, quotas, security model
- CHANGELOG · versioning policy
- Local development:
@usions/devkit— build and chaos-test without a Usion account
Install
npm install @usions/sdkOr load via script tag:
<script src="https://usions.com/usion-sdk.js"></script>Quick Start
import Usion from '@usions/sdk';
Usion.init((config) => {
console.log('User:', Usion.user.getName());
console.log('Room:', config.roomId);
});Modules
User
Usion.user.getId() // User ID
Usion.user.getName() // Display name
Usion.user.getAvatar() // Avatar URL
Usion.user.getToken() // Auth JWT
Usion.user.getProfile() // Promise<{ id, name, avatar }>Storage (persistent, per-user, per-service)
await Usion.storage.set('highscore', 1500)
await Usion.storage.get('highscore') // 1500
await Usion.storage.remove('highscore')
await Usion.storage.keys() // ['key1', 'key2']
await Usion.storage.clear()Wallet
await Usion.wallet.getBalance() // 500
await Usion.wallet.hasCredits(100) // true
await Usion.wallet.requestPayment(50, 'Power-up')
Usion.wallet.onBalanceChange((balance) => { ... })Session (ephemeral)
Usion.session.getId()
Usion.session.getData('key')
Usion.session.setData('key', 'value')
Usion.session.clear()Chat
await Usion.chat.sendMessage(recipientId, 'Hello!')
await Usion.chat.createPersonalChat(peerUserId)Game (Multiplayer)
Platform Mode (relay through Usion backend)
await Usion.game.connect()
await Usion.game.join(roomId)
// Send actions (validated, sequenced)
await Usion.game.action('move', { cell: 4 })
// Send real-time updates (fire-and-forget)
Usion.game.realtime('position', { x: 100, y: 200 })Direct Mode (connect to game creator's server)
await Usion.game.connectDirect()
await Usion.game.join(roomId)Event Handlers
Usion.game.onPlayerJoined((data) => { ... })
Usion.game.onPlayerLeft((data) => { ... })
Usion.game.onStateUpdate((data) => { ... })
Usion.game.onAction((data) => { ... })
Usion.game.onRealtime((data) => { ... })
Usion.game.onGameFinished((data) => { ... })
Usion.game.onGameRestarted((data) => { ... })
Usion.game.onError((data) => { ... })
Usion.game.onDisconnect((reason) => { ... })
Usion.game.onReconnect((attempt) => { ... })
Usion.game.onPlayerConnection((d) => { ... }) // d.state: 'connected' | 'reconnecting' | 'gone'Reliability contract (read this before shipping multiplayer)
The platform recovers from connection blips for you — your game just has to follow four rules so both screens always agree:
- Apply moves only in
onAction— never optimistically on send. Every action (including your own) is echoed back with the authoritativesequence; the SDK deduplicates, so each action is delivered exactly once even across reconnect replays. - Turn-based? Pass
{ nextTurn }on every move and trustcurrent_turnfrom the join result /onSyncinstead of deriving the turn locally:await Usion.game.action('move', { cell: 4 }, { nextTurn: opponentId }) - Handle
onDisconnect/onReconnect— pause input and show a "reconnecting…" hint. The platform rejoins the room and replays missed actions automatically. UseonPlayerConnectionfor the opponent's status; don't end the match on'reconnecting'(a ~15s grace window applies), treat'gone'as the opponent having left. - Authority checkpoints with
setState— the room authority (player_ids[0]) should checkpoint at meaningful transitions so a rejoining client restores instantly (delivered asgame_statein the join result andonSync; max 64 KB):await Usion.game.setState(fullGameState)
Other
Usion.game.requestSync(lastSequence) // Recover missed actions
Usion.game.requestRematch()
Usion.game.forfeit()
Usion.game.disconnect()
Usion.game.isConnected() // booleanNetcode (low-latency realtime toolkit)
Zero-dependency, transport-agnostic helpers for smooth, lossless multiplayer.
They work across all connection modes (platform / direct / proxy) and both
platforms. Adopt incrementally. Full guide: docs/realtime-netcode.md.
// 1. Snapshot interpolation — smooth rendering, absorbs jitter & packet loss
const interp = Usion.game.createInterpolation({ serverFps: 20 });
Usion.game.onRealtime((m) => { if (m.action_type === 'state') interp.add(m.action_data); });
function render() {
const players = interp.calc('x y angle(deg)'); // entities at "now - bufferMs"
if (players) draw(players);
requestAnimationFrame(render);
}
// 2. Sequence-guarded, delta-compressed snapshot sender (cuts bandwidth + flood)
const snap = Usion.game.createSnapshotSender({ hz: 20, channel: 'state', precision: 2 });
loop(() => snap.send(world));
// receiver (drops stale/out-of-order frames; never patches the wrong base):
const rx = Usion.game.createSnapshotReceiver();
let world = {};
Usion.game.onRealtime((m) => { if (m.action_type === 'state') world = rx.receive(m.action_data); });
// 3. Client-side prediction + reconciliation + smooth error blending
const p = Usion.game.createPredictor({ apply: (s, i) => move(s, i), initialState, smooth: { keys: 'x y' } });
const { seq } = p.predict(input); // apply locally, send seq
Usion.game.onStateUpdate((s) => p.reconcile(s.game_state.me, s.game_state.ackSeq));
function render() { draw(p.view()); requestAnimationFrame(render); } // blended, no snaps
// 4. WebRTC peer-to-peer (UDP) — escapes TCP head-of-line blocking
const mesh = Usion.game.createMesh({
role: amHost ? 'host' : 'guest',
iceServers: Usion.netcode.MeshConnection.iceServers({ turn: 'turn:host:3478', turnUsername: 'u', turnCredential: 'p' }),
});
mesh.onMessage = (d) => applyRemote(d);
await mesh.start();
mesh.send({ x, y }); // unreliable + unordered + sequenced (drops stale)
mesh.sendReliable({ event }); // ordered, must-arrive
// 4b. N-player mesh
const net = Usion.game.createMeshNetwork();
await net.setRoster(allPlayerIds);
net.onMessage = (peerId, d) => applyRemote(peerId, d);
net.broadcast({ x, y });
// 4c. WebTransport (HTTP/3) — lowest-latency client-server (no TCP HOL blocking)
const wt = Usion.game.createWebTransport({ url: 'https://game.example.com:4433/wt' });
wt.onMessage = (m, ch) => { if (ch === 'datagram') applyState(m); };
await wt.connect();
wt.send(stateBytes); // UDP-like datagram (unreliable, sequenced)
wt.sendReliable({ event }); // reliable stream
// (server: use the open-source @fails-components/webtransport Node HTTP/3 server)
// 4d. Declarative replication — mutate a plain object, it auto-syncs (host)
const world = Usion.game.replicate({ players: [] }, { channel: 'world', hz: 20, precision: 2 });
world.state.players.push({ id: myId, x: 0, y: 0 }); // just mutate it
// client:
const remote = Usion.game.replica({ channel: 'world', interpolate: { keys: 'x y', group: 'players' } });
function render() { draw(remote.view()); requestAnimationFrame(render); }
// 4e. Test under a bad network locally (latency / jitter / loss)
Usion.game.simulateNetwork({ latencyMs: 120, jitterMs: 30, lossPct: 5 });
Usion.game.simulateNetwork(null); // off
// 4f. Deterministic lockstep (inputs-only sim + free replays) — MOBA/RTS
const ls = Usion.game.createLockstep({ playerId: myId, players, step: (frame, inputs) => sim(frame, inputs) });
gameLoop(() => { ls.submit(localInput); ls.tick(); });
// replay later: Usion.netcode.Lockstep.replay(ls.getReplay(), sim);
// 4g. Server-side lag compensation (server rewind) — fair hits across pings
const lag = Usion.game.createLagCompensator({ historyMs: 1000 });
serverTick(() => lag.record(allEntities)); // each tick
function onShot(shooter, ray) { const past = lag.rewindForClient(shooter.rtt, shooter.interpMs); resolveHit(ray, past); }
// 5. Telemetry, delta + binary helpers
await Usion.game.ping(); Usion.game.getRtt(); // ms
const d = Usion.game.diff(prev, next); const next2 = Usion.game.patch(prev, d);
const bytes = Usion.game.encode(state); const back = Usion.game.decode(bytes);Also available directly: Usion.netcode.{ SnapshotInterpolation, Predictor,
Coalescer, PingMeter, MeshConnection, MeshNetwork, diff, patch, quantize,
encode, decode }.
Utility
Usion.requestPayment(amount, reason)
Usion.share('image', { imageUrl, text })
Usion.exit() // Close the mini-app
Usion.log(message) // Debug logLeaderboard (opt-in per game)
Enable on the service (leaderboard: { enabled: true, order: 'desc' }), then:
await Usion.leaderboard.submit(1500, { level: 3 }); // best score is kept
const friends = await Usion.leaderboard.friends(); // people you've messaged + you
const top = await Usion.leaderboard.top({ limit: 20 });// global top
const me = await Usion.leaderboard.me(); // { score, rank, total }
// entry: { user_id, name, avatar, score, rank, is_me, metadata }friends() is scoped to users you've chatted with (your conversations) plus
yourself. Works in standalone and embedded games (rides the backend channel).
Matchmaking (play with online strangers)
Pair players who don't know each other into a game:
const match = await Usion.matchmaking.find(); // resolves when matched
// match = { roomId, players, serviceId }
await Usion.game.connect();
await Usion.game.join(match.roomId);
// while waiting you can stop:
Usion.matchmaking.cancel();
// or react to matches:
Usion.matchmaking.onMatch(({ roomId, players }) => { /* ... */ });find({ size }) controls party size (default 2). Works standalone and
embedded (rides the backend channel). Pair this with Usion.lobby for
invite-a-friend, and Usion.matchmaking for quick-match with strangers.
TypeScript
Full type declarations included:
import Usion from '@usions/sdk';
import type { UsionConfig, GameModule, PlayerJoinedData } from '@usions/sdk';License
MIT
