2026-08-29 11:46:20 +03:00
---
name: rudder-web-sdk
2026-09-17 12:08:03 +03:00
description: Use when working with the Rudder TypeScript/JavaScript SDK (@rudder/js -sdk) — the browser/player-facing SDK for the Rudder LiveOps platform. Covers client setup, device/google/apple/custom auth, identity linking, observable kernel domains (player, catalog, storage), modules.call, resources(slug), and error handling. Load this whenever code imports from @rudder/js -sdk or RudderClient appears.
2026-08-29 11:46:20 +03:00
---
# Rudder Web SDK (@rudder/js-sdk)
Browser-facing player SDK for the Rudder LiveOps platform. Source of truth:
`liveops-web-sdk/src` (generated wire types in `src/generated` come from the
gateway's apigen — never edit them by hand).
2026-09-17 12:08:03 +03:00
2.0 kernel surface: auth, player (wallets), catalog currencies, storage,
project storage, revision sync, `client.modules.call(module, fn, args)` ,
and `client.resources(slug)` . Module-specific methods (battle pass, quests,
leaderboards, stores, inventory, remote config, scenarios) are gone.
2026-08-29 11:46:20 +03:00
## Install
Published to a private registry, 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/js-sdk
```
ESM + CJS + `.d.ts` ; `type: module` ; zero runtime dependencies.
## Init
``` ts
import { RudderClient } from '@rudder/js-sdk' ;
const client = new RudderClient ( {
baseUrl : 'https://api.rudder.build' , // required
projectKey : 'your-project-key' , // required
// tokenStore?: TokenStore — default: localStorage, in-memory fallback
// requestTimeoutMs?: number — default 10000
// syncIntervalMs?: number — revision poll, default 30000 (±20% jitter)
// onEffectError?: (error) => void — default console.error
} ) ;
```
2026-09-17 12:08:03 +03:00
Missing `baseUrl` /`projectKey` throws `RudderError` with
`code: 'sdk/invalid-options'` from the constructor.
2026-08-29 11:46:20 +03:00
2026-09-17 12:08:03 +03:00
Call `client.dispose()` on unmount/HMR to stop the sync poll and drop cached
state.
2026-08-29 11:46:20 +03:00
## Auth essentials
``` ts
await client . auth . loginWithDevice ( { region , language , nickname } ) ; // device id auto-generated/persisted
2026-09-08 16:05:00 +03:00
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:<name>'
2026-08-29 11:46:20 +03:00
client . auth . logout ( ) ;
2026-09-17 12:08:03 +03:00
client . auth . isAuthenticated ;
client . auth . onAuthStateChange ( cb ) ;
2026-08-29 11:46:20 +03:00
```
2026-09-08 16:05:00 +03:00
Linked identities are listed on the profile: `client.player.data.identities`
(`PlayerProfileIdentity { provider, subject, createdAt }` ).
2026-08-29 11:46:20 +03:00
Tokens are saved to the `TokenStore` automatically. The transport injects
`Authorization: Bearer <token>` , retries GETs, and single-flight refreshes on
401; a failed refresh clears tokens and emits `signed-out` . Details:
[reference/auth.md ](reference/auth.md ).
2026-09-17 12:08:03 +03:00
## Client surface
2026-08-29 11:46:20 +03:00
| Area | Access | Kind |
|---|---|---|
| Auth | `client.auth` | service |
| Player profile + wallets | `client.player` | observable `SyncedState<PlayerProfile>` |
2026-09-17 12:08:03 +03:00
| Catalog currencies | `client.catalog` | observable `SyncedState<Map<string, CatalogCurrency>>` |
2026-08-29 11:46:20 +03:00
| Player storage | `client.storage` + `.save(items)` / `.delete(type)` | observable + mutations |
| Project storage | `client.projectStorage` + `.save(items)` | observable + mutation |
2026-09-17 12:27:52 +03:00
| Modules | `client.modules.call(module, fn, args?)` | `CallModuleFunctionClientResponse` (`{ result }` ) |
2026-09-17 12:08:03 +03:00
| Resources | `client.resources(slug)` | `list` / `get` / `create` / `update` / `delete` |
There is no UGC, wallet service, or standalone economy service — wallets live
on `client.player.data.wallets` . Quests, stores, inventory, leaderboards,
battle pass, and remote config are installed modules: call
`client.modules.call('quests', 'list')` (etc.) and read rows with
`client.resources('items')` .
2026-08-29 11:46:20 +03:00
## Observable domains — the shared pattern
2026-09-17 12:08:03 +03:00
`player` , `catalog` , `storage` , and `projectStorage` all extend
`SyncedState<T>` :
2026-08-29 11:46:20 +03:00
- `.data: T | undefined` , `.status: 'idle' | 'loading' | 'ready' | 'error'` ,
`.error?: Error`
- `onChange(cb: (snapshot: SyncedSnapshot<T>) => void): () => void` — fires
immediately with the current snapshot; maps onto React `useSyncExternalStore`
- `load(): Promise<T>` — deduplicated, no-op when ready
- `reload(): Promise<T>` — forced refetch
2026-09-17 12:08:03 +03:00
`player` and `catalog` are warmed once at login; `storage` and
`projectStorage` load on first use. All observable domains are then kept fresh
by a revision poll every 30 s (±20% jitter); polling pauses while the tab is
hidden. Storage mutations invalidate their domain immediately. Call `reload()`
2026-08-29 11:46:20 +03:00
when you need a freshness guarantee.
## Errors
All SDK errors extend `RudderError` (optional machine-readable `code` ):
- `RudderNetworkError` — fetch failed / timeout (`error.cause` holds the original)
- `RudderHttpError` — non-2xx, carries `status` , `statusText` , `body` , `code`
- `RudderAuthError extends RudderHttpError` — 401 after failed refresh; tokens
already cleared, `signed-out` already emitted
- `RudderError` with `code: 'sdk/invalid-options'` (`SDK_ERROR_INVALID_OPTIONS` )
— constructor validation
2026-09-17 12:27:52 +03:00
The platform error codes are `forbidden` and `row_referenced`
(`src/generated/errors.ts` , not exported from the package entry); compare
`error.code === 'forbidden'` .
`client.modules.call` resolves to `{ result }` only; console output and the
kernel call log never reach players. A module business error rejects with
`RudderHttpError` whose `status` is 422 and whose `code` is the module's own
code (the body is `{ error, code, requestId }` ). Module codes are open strings
with no generated constants, so compare against the codes the module
documents. A module trap is a 500 and a timeout a 504, both without details.
2026-08-29 11:46:20 +03:00
## Reference files
- [reference/auth.md ](reference/auth.md ) — login flows, options, token store, session lifecycle
- [reference/player.md ](reference/player.md ) — profile + wallets (`PlayerProfile` , `Wallet` )
- [reference/storage.md ](reference/storage.md ) — player + project storage, limits