@rudder/js-sdk

LiveOps Web SDK for browser games — device auth, observable player state, catalog currencies, storage, generic resources, and module function calls.

Install

The package is published to a private registry, not npmjs. Point the @rudder scope at it in your project's .npmrc:

@rudder:registry=https://hub.rudder.build/api/packages/rudder/npm/

Then install as usual (anonymous read access, no token needed):

npm install @rudder/js-sdk

Quickstart

import { RudderClient } from '@rudder/js-sdk';

const client = new RudderClient({
  baseUrl: 'https://api.rudder.build',
  projectKey: 'your-project-key',
  // tokenStore is optional: defaults to localStorage with an in-memory
  // fallback where localStorage is unavailable (SSR, private mode).
  appVersion: '1.4.2', // optional; sent with platform 'web' on login and refresh
});

await client.auth.loginWithDevice(); // region 'global', language 'en'
// or: await client.auth.loginWithDevice({ region: 'eu', language: 'de', nickname: 'Bob' });

const profile = client.player.data;
const gold = profile?.wallets?.find((w) => w.currency === 'gold')?.balance;

const items = await client.resources('items').list();
const result = await client.modules.call('quests', 'list');

client.dispose();

Surface

Area Access
Auth client.auth.loginWithDevice(), client.auth.loginWithCustom({ customData }), client.auth.logout(), client.auth.isAuthenticated, client.auth.onAuthStateChange(cb)
Player / wallets client.player (observable)
Catalog currencies client.catalog (observable)
Storage client.storage (observable) + .save(items) / .delete(type)
Project storage client.projectStorage (observable) + .save(items)
Modules client.modules.call(module, fn, args?)
Resources client.resources(slug) → list / get / create / update / delete

Game-design features (quests, stores, inventory, leaderboards, battle pass, remote config) live in installed modules. Call them through client.modules.call and read their rows through client.resources(slug).

Error handling

All SDK errors extend RudderError and carry an optional machine-readable code:

import {
  RudderError,
  RudderNetworkError,
  RudderHttpError,
  RudderAuthError,
} from '@rudder/js-sdk';

try {
  await client.player.reload();
} catch (error) {
  if (error instanceof RudderAuthError) {
    // 401 — session expired and the refresh failed; the client already
    // cleared tokens and emitted 'signed-out' via onAuthStateChange.
    showLogin();
  } else if (error instanceof RudderHttpError) {
    console.error(error.status, error.code, error.body);
  } else if (error instanceof RudderNetworkError) {
    // fetch failed / timeout — the underlying error is in error.cause
    console.error('offline?', error.cause);
  }
}

A module business error rejects with RudderHttpError (status 422) whose code is the module's own open string code. The platform codes are forbidden and row_referenced.

Invalid client options (missing baseUrl / projectKey) throw RudderError with code: 'sdk/invalid-options' from the constructor.

Track the session lifecycle without polling the token store:

client.auth.onAuthStateChange((state) => {
  // fires immediately with the current state, then on every change
  setSignedIn(state === 'signed-in');
});

Data liveness model

Observable domains (player, catalog, storage, projectStorage) are warmed at login (player and catalog) or on first use, then kept fresh by a revision poll: the client asks the gateway for entity revisions every 30 s (±20% jitter) and reloads only the entities whose revision grew. Polling pauses while the tab is hidden and resumes on visibilitychange. Storage writes invalidate the affected domain immediately. Expect data to be eventually consistent within one poll interval; call reload() when you need a guarantee.

React recipe

SyncedState.onChange fires immediately with the current snapshot, which maps straight onto useSyncExternalStore:

import { useSyncExternalStore } from 'react';

function useSynced<T>(state: { onChange(cb: () => void): () => void; data: T | undefined }) {
  return useSyncExternalStore(
    (onStoreChange) => state.onChange(() => onStoreChange()),
    () => state.data,
  );
}

function Wallet() {
  const profile = useSynced(client.player);
  return <div>{profile?.wallets?.map((w) => `${w.currency}: ${w.balance}`).join(', ')}</div>;
}

Development

npm run typecheck   # tsc over src and tests
npm test            # unit + local e2e (jsdom)
npm run build       # tsup → dist (ESM + CJS + d.ts)
npm run test:e2e-prod   # drives a seeded project on the live gateway

Generated wire types (src/generated) come from the gateway's apigen; run npm run generate to refresh them. Do not edit them by hand.

S
Description
No description provided
Readme 746 KiB
Languages
TypeScript 100%