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