Files
rudder-js-sdk/skills/rudder-web-sdk/reference/scenarios.md
T
edmand46 8753239fbd
CI / check (push) Successful in 55s
CI / publish (push) Has been skipped
Add agent skill (SKILL.md + per-domain reference)
2026-08-29 11:46:20 +03:00

5.1 KiB

Scenarios + effects — client.effects

Scenarios are server-authored node graphs (configured in the dashboard) that run per player. The SDK's scenario runtime is deliberately not part of the public client surface: scenario nodes surface as typed effects through client.effects (source: src/effects/EffectsCenter.ts), and the engine itself loads lazily on first login.

Lifecycle

  • The runtime starts after login: scenario state is restored from persistence, then the login event ('player_login' by default) is fired to trigger event-driven scenarios.
  • Runs persist to IndexedDB by default, so waits and pending nodes survive page reloads. On restore, each run is reconciled with the server (unknown_run / expired runs are dropped).
  • logout() / dispose() clear all runs and persisted state.

Runtime options (RudderClientOptions.runtime)

runtime?: {
  // Scenario plan persistence. undefined = default IndexedDB store (created
  // lazily); null = disable persistence entirely; or pass a PlanStateStore.
  planStateStore?: PlanStateStore | null;

  // Scenario event fired automatically after login.
  // undefined = 'player_login'; null = fire nothing.
  loginEvent?: string | null;
}

Subscribing

Every on* method takes a handler (effect) => void | Promise<void> and returns an unsubscribe function. Errors thrown inside handlers go to the onEffectError client option (default console.error).

const off = client.effects.onNotification(async (n) => {
  showToast(n.title, n.message);
  await n.done();   // always resolve the session or the run stalls
});

Effect types

interface Effects {
  onNotification(handler): EffectUnsubscribe;
  onStoreOffer(handler): EffectUnsubscribe;
  onLeaderboard(handler): EffectUnsubscribe;
  onConfigChanged(handler): EffectUnsubscribe;
  onWait(handler): EffectUnsubscribe;
  onScenarioCompleted(handler): EffectUnsubscribe;
  onScenarioFailed(handler): EffectUnsubscribe;
  onQuest(handler): EffectUnsubscribe;
  onBattlePass(handler): EffectUnsubscribe;
  onBattlePassLevel(handler): EffectUnsubscribe;
}

NotificationEffect — a notification node became active

{ readonly title: string; readonly message: string; done(): Promise<void> }

StoreOfferEffect — a store node became active

{
  readonly store: ShopHandle;
  readonly offers: readonly OfferHandle[];
  readonly message?: string;
  buy(offer: OfferHandle, options?: BuyOptions): Promise<PurchaseOfferResponse>;
  dismiss(): Promise<void>;   // declines the offer and advances the run
}

LeaderboardEffect — a leaderboard node became active

{ end(): Promise<void>; rewardClaimed(): Promise<void> }

ConfigChangedEffect — a remote_config_override node patched config

{ readonly key: string }   // client.remoteConfig.get(key) already returns the override

WaitEffect — a wait node became active

{ readonly deadlineUtc: Date }   // run resumes automatically at the deadline

QuestEffect — a scenario quest node became active

{
  readonly name: string;
  readonly objectives: ReadonlyArray<Record<string, unknown>>;
  reportProgress(objectiveId: string, amount?: number): Promise<void>;
}

The node auto-completes server-side once every objective is satisfied; the SDK crosses the onComplete boundary itself when the server reports completion.

BattlePassEffect — a battle pass node became active

{
  getProgress(): Promise<GetBattlePassProgressResponse>;
  addXp(source: string, amount: number): Promise<AddBattlePassXpResponse>;
  claimReward(level: number, track?: 'free' | 'premium'): Promise<ClaimBattlePassRewardResponse>;
  purchasePremium(): Promise<PurchaseBattlePassPremiumResponse>;
  levelUp(): Promise<void>;
  end(): Promise<void>;
}

These wrap client.battlePass with the run's scenarioId/nodeId/runId already bound — prefer them over calling the service manually.

BattlePassLevelEffect — a battlepass_level node (single claimable tier)

{ readonly level: number; claim(): Promise<void> }

Run lifecycle effects

interface ScenarioCompletedEffect { readonly runId: string; readonly scenarioId: string }
interface ScenarioFailedEffect {
  readonly runId: string; readonly scenarioId: string; readonly nodeId: string;
  readonly error: Error;   // transport gave up, or unsupported node type
}

Always subscribe to onScenarioFailed — otherwise run failures surface only as console warnings.

Supported node types

wait, remote_config_override, notification, store, leaderboard, quest, battlepass, battlepass_level. Any other node type fails the run (surfaced via onScenarioFailed).

Reliability notes

  • Node completion is idempotent client-side (completed handles are tracked).
  • Transient boundary failures (network error, 5xx) leave the node active for retry on reconnect; terminal server errors fail the run.
  • Server scenario errors use the typed codes run_expired, run_not_active, node_not_active, scenario_not_active, unknown_run, early_completion, objectives_incomplete, level_not_reached, forbidden (RudderErrorCodes).