Files
rudder-js-sdk/skills/rudder-web-sdk/SKILL.md
T
edmand46 4ab406c5cd
CI / check (push) Successful in 16s
CI / publish (push) Has been skipped
docs: module call response, error codes, remove scenarios
2026-09-17 12:27:52 +03:00

6.2 KiB

name, description
name description
rudder-web-sdk 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.

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).

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.

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/
npm install @rudder/js-sdk

ESM + CJS + .d.ts; type: module; zero runtime dependencies.

Init

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
});

Missing baseUrl/projectKey throws RudderError with code: 'sdk/invalid-options' from the constructor.

Call client.dispose() on unmount/HMR to stop the sync poll and drop cached state.

Auth essentials

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:<name>'
client.auth.logout();
client.auth.isAuthenticated;
client.auth.onAuthStateChange(cb);

Linked identities are listed on the profile: client.player.data.identities (PlayerProfileIdentity { provider, subject, createdAt }).

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.

Client surface

Area Access Kind
Auth client.auth service
Player profile + wallets client.player observable SyncedState<PlayerProfile>
Catalog currencies client.catalog observable SyncedState<Map<string, CatalogCurrency>>
Player storage client.storage + .save(items) / .delete(type) observable + mutations
Project storage client.projectStorage + .save(items) observable + mutation
Modules client.modules.call(module, fn, args?) CallModuleFunctionClientResponse ({ result })
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').

Observable domains — the shared pattern

player, catalog, storage, and projectStorage all extend SyncedState<T>:

  • .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

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() 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

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.

Reference files