--- name: rudder-web-sdk description: Use when working with the Rudder TypeScript/JavaScript client SDK (@rudder/sdk) for web games. Covers createClient, device/google/apple/custom auth, identity linking, state objects (player with balances and items, catalog, storage), module calls through clients generated by `rudder client generate`, idempotent retries, and error handling. Load this whenever code imports from @rudder/sdk or a generated rudder.modules.ts. --- # Rudder Web SDK (@rudder/sdk) Client SDK for web games on the Rudder LiveOps platform. Source of truth: `liveops-web-sdk/src` (wire types in `src/generated` come from `liveops-backend/apigen`; never edit them by hand). 0.1.0 covers only the kernel: auth, player, catalog, storage, module calls and internal revision sync. Game features are installed modules, reached through a typed client generated from their manifests. There is no `resources(slug)` and no `projectStorage`; game-facing configuration comes from modules such as `remote_config`. ## Install Published to the Gitea registry at hub.rudder.build, not npmjs. Point the `@rudder` scope at it in `.npmrc` (anonymous read, no token): ``` @rudder:registry=https://hub.rudder.build/api/packages/rudder/npm/ ``` ```bash npm install @rudder/sdk ``` ESM + CJS + `.d.ts`; `type: module`; zero runtime dependencies. ## Init ```ts import { createClient } from '@rudder/sdk'; const client = createClient({ projectKey: 'your-project-key', // required; selects staging or prod // baseUrl?: string — default 'https://api.rudder.build' // tokenStore?: TokenStore — default: localStorage, in-memory fallback // appVersion?: string — sent with platform 'web' on login/refresh // requestTimeoutMs?: number — default 10000 // syncIntervalMs?: number — revision poll, default 30000 (±20% jitter) }); ``` A missing `projectKey` throws `RudderError` with `code: 'sdk/invalid-options'`. Call `client.dispose()` on unmount/HMR to stop the sync poll and drop cached state. ## Auth essentials ```ts await client.auth.loginWithDevice({ region, language, nickname }); // device id auto-generated/persisted await client.auth.loginWithGoogle({ idToken, region, language, nickname }); await client.auth.loginWithApple({ idToken, region, language, nickname }); await client.auth.loginWithCustom({ customData, provider?, region, language, nickname }); await client.auth.linkWithGoogle(idToken); await client.auth.linkWithApple(idToken); await client.auth.linkWithCustom({ customData, provider? }); await client.auth.unlinkIdentity(provider); // 'device' | 'google' | 'apple' | 'custom' | 'custom:' client.auth.logout(); client.auth.isAuthenticated; client.auth.onAuthStateChange(cb); ``` Tokens are saved to the `TokenStore` automatically. The transport injects `Authorization: Bearer ` and single-flight refreshes on 401; a failed refresh clears tokens and emits `signed-out`. Details: [reference/auth.md](reference/auth.md). ## Client surface | Area | Access | Kind | |---|---|---| | Auth | `client.auth` | service | | Player | `client.player` (`player`, `identities`, `balances`, `items`) | `SyncedState` | | Catalog currencies | `client.catalog` | `SyncedState>` | | Player storage | `client.storage` + `.save(type, data)` / `.delete(type)` | `SyncedState` + mutations | | Modules | `client.modules.call(module, fn, args?, { moduleVersion? })` | resolves to `result` as `T` | ## Module calls through generated clients ```bash RUDDER_URL=... RUDDER_TOKEN=... RUDDER_PROJECT=... \ rudder client generate --lang ts --out src/rudder.modules.ts [--environment staging|prod] ``` ```ts import { modules } from './rudder.modules'; const m = modules(client); await m.leaderboards.submit({ slug: 'weekly', score: 120 }); const top = await m.leaderboards.top({ slug: 'weekly', limit: 10 }); ``` The generated file is a factory over `client.modules.call`, with argument and result types, and a `Error` code union per function. Regenerate and commit it when installed modules change. Details: [reference/modules.md](reference/modules.md). ## State objects: the shared pattern `player`, `catalog` and `storage` extend `SyncedState`: - `.value: T | undefined`, `.status: 'idle' | 'loading' | 'ready' | 'error'`, `.error?: Error` - `onChange(cb: (snapshot: SyncedSnapshot) => void): () => void`: fires immediately with `{ status, value, error }`; maps onto React `useSyncExternalStore` - `load(): Promise`: deduplicated, no-op when ready - `reload(): Promise`: forced refetch `player` and `catalog` are warmed at login; `storage` loads on first use. A revision poll (`GET /sdk/v1/sync`, keys `profile`, `catalog`, `storage`) runs every 30 s (±20% jitter), pauses while the tab is hidden, and reloads the states whose revision grew. Storage mutations invalidate immediately. Call `player.reload()` after a module call that changes balances or items when you need fresh values right away. ## Errors All SDK errors extend `RudderError` (optional machine-readable `code`): - `RudderNetworkError`: fetch failed or timed out (`error.cause` holds the original) - `RudderHttpError`: non-2xx, carries `status`, `statusText`, `body`, `code` - `RudderAuthError extends RudderHttpError`: 401 after a failed refresh; tokens already cleared, `signed-out` already emitted - `RudderModuleError extends RudderHttpError`: a module call returned 422 `{ error, code, requestId }`; exposes `code: C`, `message`, `requestId` - `RudderError` with `code: 'sdk/invalid-options'` (`SDK_ERROR_INVALID_OPTIONS`) Compare `error.code` with string literals. A module trap is a 500 and a timeout a 504, both without details. ## Reference files - [reference/auth.md](reference/auth.md): login flows, options, token store, session lifecycle - [reference/player.md](reference/player.md): profile, balances, items - [reference/storage.md](reference/storage.md): player storage, limits - [reference/modules.md](reference/modules.md): generated clients, `modules.call`, idempotency, retries, errors