1491b5e357
- engine/ (DagWalker, sessions, IndexedDbPlanStore) deleted; server owns the graph - trigger/callback/counter carry PendingEffect; GET /sdk/v1/scenarios/pending polled (30s jittered heartbeat paused on hidden tab + wait-deadline timers) - client.effects public API unchanged (done/buy/dismiss/end/claim/battlepass/quest) - planStateStore removed; loginEvent kept; reconcile() makes server the source of truth - generated models regenerated; skills/README/CHANGELOG updated
167 lines
5.5 KiB
Markdown
167 lines
5.5 KiB
Markdown
# Scenarios + effects — `client.effects`
|
|
|
|
Scenarios are server-authored node graphs (configured in the dashboard) that
|
|
run per player. The server executes the graph. The SDK is a thin effects
|
|
client: it sends trigger events, turns `PendingEffect` payloads into typed
|
|
`client.effects` handlers, posts callbacks when the game completes an
|
|
effect, and polls for pending effects. The runtime is deliberately **not**
|
|
part of the public client surface (source: `src/effects/EffectsCenter.ts`)
|
|
and loads lazily on first login.
|
|
|
|
## Lifecycle
|
|
|
|
- After login the SDK fetches `GET /sdk/v1/scenarios/pending` (replacing any
|
|
local restore), then fires the login event (`'player_login'` by default).
|
|
- A heartbeat polls pending every ~30s (±20% jitter), paused while
|
|
`document.hidden`. Each effect with a `waitDeadline` also schedules a
|
|
local timer so waits fire on time.
|
|
- An effect with an already-active `(runId, nodeId)` is not re-emitted.
|
|
- `logout()` / `dispose()` stop the poll, cancel wait timers, and drop
|
|
active effects. There is no local plan persistence.
|
|
|
|
### Runtime options (`RudderClientOptions.runtime`)
|
|
|
|
```ts
|
|
runtime?: {
|
|
// 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`).
|
|
|
|
```ts
|
|
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
|
|
|
|
```ts
|
|
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;
|
|
}
|
|
```
|
|
|
|
`onConfigChanged` remains on the public surface but scenario
|
|
`remote_config_override` nodes no longer reach the client — the server
|
|
applies them. Patched values show up through `client.remoteConfig` after
|
|
sync/reload.
|
|
|
|
### `NotificationEffect` — a notification node became active
|
|
|
|
```ts
|
|
{ readonly title: string; readonly message: string; done(): Promise<void> }
|
|
```
|
|
|
|
### `StoreOfferEffect` — a store node became active
|
|
|
|
```ts
|
|
{
|
|
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
|
|
|
|
```ts
|
|
{ end(): Promise<void>; claim(): Promise<void>; rewardClaimed(): Promise<void> }
|
|
```
|
|
|
|
`rewardClaimed()` is a deprecated alias for `claim()`.
|
|
|
|
### `WaitEffect` — a wait node became active
|
|
|
|
```ts
|
|
{ readonly deadlineUtc: Date } // SDK polls pending at the deadline; the server advances the run
|
|
```
|
|
|
|
### `QuestEffect` — a scenario quest node became active
|
|
|
|
```ts
|
|
{
|
|
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
|
|
counter response may carry the next `PendingEffect`.
|
|
|
|
### `BattlePassEffect` — a battle pass node became active
|
|
|
|
```ts
|
|
{
|
|
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)
|
|
|
|
```ts
|
|
{ readonly level: number; claim(): Promise<void> }
|
|
```
|
|
|
|
### Run lifecycle effects
|
|
|
|
```ts
|
|
interface ScenarioCompletedEffect { readonly runId: string; readonly scenarioId: string }
|
|
interface ScenarioFailedEffect {
|
|
readonly runId: string; readonly scenarioId: string; readonly nodeId: string;
|
|
readonly error: Error;
|
|
}
|
|
```
|
|
|
|
Always subscribe to `onScenarioFailed` — otherwise run failures surface only
|
|
as console warnings.
|
|
|
|
## Supported node types
|
|
|
|
`wait`, `notification`, `store`, `leaderboard`, `quest`, `battlepass`,
|
|
`battlepass_level`. Any other node type fails the run (surfaced via
|
|
`onScenarioFailed`). `remote_config_override` is applied server-side and is
|
|
not delivered to the client.
|
|
|
|
## Reliability notes
|
|
|
|
- Completion methods POST `/sdk/v1/scenarios/callback` with
|
|
`{scenarioId, runId, nodeId, handle}` using the handles `output`,
|
|
`onPurchase`, `onDecline`, `onEnd`, `onClaim`, `onComplete`, `onLevelUp`,
|
|
`onPremiumPurchase`.
|
|
- Transient callback failures (network error, 5xx) leave the effect active
|
|
for retry; `unknown_run` / `run_expired` drop that run's effects and fire
|
|
`onScenarioFailed`.
|
|
- 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`).
|