2.0.0: changelog and skill docs for slugs and environments
Claude-Session: https://claude.ai/code/session_01SMCvdwDmuxoaqGgvGBLk1V
This commit is contained in:
@@ -50,6 +50,19 @@ On success both tokens are saved to the client's `TokenStore`, the runtime
|
||||
starts (domains warmed, scenario engine restored, `player_login` event fired),
|
||||
and the state flips to `'signed-in'`.
|
||||
|
||||
## Environments
|
||||
|
||||
A project has two environments, `staging` and `prod`. The SDK key you pass as
|
||||
`RudderClientOptions.projectKey` belongs to one of them, so the environment is
|
||||
resolved at login and carried inside the access and refresh tokens; nothing in
|
||||
the client API takes an environment argument, and a player created in one
|
||||
environment is invisible in the other. Content released only to `staging` is
|
||||
empty for a `prod` key and vice versa.
|
||||
|
||||
Tokens issued before SDK 2.0.0 carry no environment claim and are rejected with
|
||||
401. The transport's refresh then fails, clears the token store and emits
|
||||
`'signed-out'` — log the player in again.
|
||||
|
||||
## Auth state
|
||||
|
||||
```ts
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
`BattlePassService` (source: `src/battlepass/BattlePassService.ts`).
|
||||
Call-and-response access to battle pass endpoints. Battle pass state is tied to
|
||||
a **scenario battle pass node**, so calls carry `scenarioId` + `nodeId` (plus
|
||||
a **scenario battle pass node**, so calls carry `scenarioSlug` + `nodeId` (plus
|
||||
`runId` for mutating calls). NOT observable — re-fetch progress explicitly
|
||||
after a mutation.
|
||||
|
||||
@@ -14,7 +14,7 @@ you already know the scenario/node/run identifiers.
|
||||
## Methods
|
||||
|
||||
```ts
|
||||
getProgress(scenarioId: string, nodeId: string): Promise<GetBattlePassProgressResponse>
|
||||
getProgress(scenarioSlug: string, nodeId: string): Promise<GetBattlePassProgressResponse>
|
||||
addXp(request: AddBattlePassXpRequest): Promise<AddBattlePassXpResponse>
|
||||
claimReward(request: ClaimBattlePassRewardRequest): Promise<ClaimBattlePassRewardResponse>
|
||||
purchasePremium(request: PurchaseBattlePassPremiumRequest): Promise<PurchaseBattlePassPremiumResponse>
|
||||
@@ -27,7 +27,7 @@ interface AddBattlePassXpRequest {
|
||||
amount?: number;
|
||||
nodeId?: string;
|
||||
runId?: string;
|
||||
scenarioId?: string;
|
||||
scenarioSlug?: string;
|
||||
source?: string; // configured XP source
|
||||
}
|
||||
interface AddBattlePassXpResponse {
|
||||
@@ -41,7 +41,7 @@ interface ClaimBattlePassRewardRequest {
|
||||
level?: number;
|
||||
nodeId?: string;
|
||||
runId?: string;
|
||||
scenarioId?: string;
|
||||
scenarioSlug?: string;
|
||||
track?: 'free' | 'premium';
|
||||
}
|
||||
interface ClaimBattlePassRewardResponse {
|
||||
@@ -62,7 +62,7 @@ interface PurchaseBattlePassPremiumRequest {
|
||||
idempotencyKey?: string;
|
||||
nodeId?: string;
|
||||
runId?: string;
|
||||
scenarioId?: string;
|
||||
scenarioSlug?: string;
|
||||
}
|
||||
interface PurchaseBattlePassPremiumResponse {
|
||||
error?: string;
|
||||
@@ -80,5 +80,5 @@ interface PurchaseBattlePassPremiumResponse {
|
||||
in the response and the typed error `code` (`RudderErrorCodes`).
|
||||
- `purchasePremium` charges the player's wallet; idempotent; pass your own
|
||||
`idempotencyKey` for safe retries.
|
||||
- Prefer the `onBattlePass` effect session, which binds `scenarioId` /
|
||||
- Prefer the `onBattlePass` effect session, which binds `scenarioSlug` /
|
||||
`nodeId` / `runId` and posts scenario callbacks for you.
|
||||
|
||||
@@ -11,15 +11,15 @@ Distinct from scenario quest nodes, which advance through
|
||||
|
||||
```ts
|
||||
list(): Promise<Quest[]>
|
||||
claim(questId: string): Promise<ClaimQuestResponse>
|
||||
reportProgress(metric: string, amount: number): Promise<string[]> // ids of quests completed by this report
|
||||
claim(questSlug: string): Promise<ClaimQuestResponse>
|
||||
reportProgress(metric: string, amount: number): Promise<string[]> // slugs of quests completed by this report
|
||||
```
|
||||
|
||||
## Types
|
||||
|
||||
```ts
|
||||
interface Quest {
|
||||
id?: string;
|
||||
slug?: string; // stable across environments — what claim() takes
|
||||
name?: string;
|
||||
objectives?: QuestObjectiveProgress[];
|
||||
rewards?: Reward[];
|
||||
@@ -47,12 +47,12 @@ interface ClaimQuestResponse {
|
||||
```ts
|
||||
const quests = await client.quests.list();
|
||||
for (const quest of quests) {
|
||||
if (quest.status === 'completed' && quest.id) {
|
||||
const res = await client.quests.claim(quest.id);
|
||||
if (quest.status === 'completed' && quest.slug) {
|
||||
const res = await client.quests.claim(quest.slug);
|
||||
// res.success / res.alreadyClaimed / res.granted
|
||||
}
|
||||
}
|
||||
const completedIds = await client.quests.reportProgress('kills', 1);
|
||||
const completedSlugs = await client.quests.reportProgress('kills', 1);
|
||||
```
|
||||
|
||||
- `claim` is idempotent server-side; check `success` / `alreadyClaimed` /
|
||||
|
||||
@@ -122,7 +122,7 @@ counter response may carry the next `PendingEffect`.
|
||||
}
|
||||
```
|
||||
|
||||
These wrap `client.battlePass` with the run's `scenarioId`/`nodeId`/`runId`
|
||||
These wrap `client.battlePass` with the run's `scenarioSlug`/`nodeId`/`runId`
|
||||
already bound — prefer them over calling the service manually.
|
||||
|
||||
### `BattlePassLevelEffect` — a `battlepass_level` node (single claimable tier)
|
||||
@@ -134,9 +134,9 @@ already bound — prefer them over calling the service manually.
|
||||
### Run lifecycle effects
|
||||
|
||||
```ts
|
||||
interface ScenarioCompletedEffect { readonly runId: string; readonly scenarioId: string }
|
||||
interface ScenarioCompletedEffect { readonly runId: string; readonly scenarioSlug: string }
|
||||
interface ScenarioFailedEffect {
|
||||
readonly runId: string; readonly scenarioId: string; readonly nodeId: string;
|
||||
readonly runId: string; readonly scenarioSlug: string; readonly nodeId: string;
|
||||
readonly error: Error;
|
||||
}
|
||||
```
|
||||
@@ -154,7 +154,7 @@ not delivered to the client.
|
||||
## Reliability notes
|
||||
|
||||
- Completion methods POST `/sdk/v1/scenarios/callback` with
|
||||
`{scenarioId, runId, nodeId, handle}` using the handles `output`,
|
||||
`{scenarioSlug, runId, nodeId, handle}` using the handles `output`,
|
||||
`onPurchase`, `onDecline`, `onEnd`, `onClaim`, `onComplete`, `onLevelUp`,
|
||||
`onPremiumPurchase`.
|
||||
- Transient callback failures (network error, 5xx) leave the effect active
|
||||
|
||||
@@ -13,7 +13,7 @@ client.stores.onChange(cb);
|
||||
await client.stores.load() / .reload();
|
||||
|
||||
// Direct purchase (bypassing handles):
|
||||
await client.stores.purchase(storeSlug, offerId, options?);
|
||||
await client.stores.purchase(storeSlug, offerSlug, options?);
|
||||
```
|
||||
|
||||
## Handles
|
||||
@@ -28,7 +28,8 @@ class ShopHandle {
|
||||
}
|
||||
|
||||
class OfferHandle {
|
||||
readonly id: string;
|
||||
readonly id: string; // authoring id, differs between staging and prod
|
||||
readonly slug: string; // stable across environments — what purchases use
|
||||
readonly name?: string;
|
||||
readonly price?: OfferPrice; // { amount?: number; currency?: string }
|
||||
readonly contents: OfferContent[]; // { amount?: number; itemId?: string }[]
|
||||
@@ -48,15 +49,15 @@ interface PurchaseOfferResponse {
|
||||
|
||||
## Purchase semantics
|
||||
|
||||
- `stores.purchase(storeSlug, offerId, options?)` and `offer.buy(options?)` are
|
||||
the same executor; handles just bind the slug/id.
|
||||
- `stores.purchase(storeSlug, offerSlug, options?)` and `offer.buy(options?)` are
|
||||
the same executor; handles just bind the store slug and the offer slug.
|
||||
- When `options.idempotencyKey` is omitted the SDK generates
|
||||
`crypto.randomUUID()` per call — safe retries require passing your own key.
|
||||
- On success the executor invalidates `player`, `inventory`, and `stores`, so
|
||||
subscribers observe fresh wallet/inventory/store data immediately.
|
||||
- Check `response.success` / `response.error` — a failed purchase is a resolved
|
||||
response, not necessarily a thrown error.
|
||||
- Purchase metrics (`purchase.offer:<offerId>`, `purchase.item:<itemId>`) are
|
||||
- Purchase metrics (`purchase.offer:<offerSlug>`, `purchase.item:<itemId>`) are
|
||||
reported to quests automatically server-side — do not report them manually
|
||||
(see `reference/quests.md`).
|
||||
|
||||
@@ -71,10 +72,11 @@ interface Store {
|
||||
}
|
||||
interface Offer {
|
||||
contents?: OfferContent[]; createdAt?: string; id?: string;
|
||||
maxPurchases?: number; name?: string; price?: OfferPrice; updatedAt?: string;
|
||||
maxPurchases?: number; name?: string; price?: OfferPrice; slug?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
```
|
||||
|
||||
`ShopHandle`/`OfferHandle` throw a plain `Error` if constructed from a store
|
||||
without `slug` / an offer without `id` — in practice the domain only builds
|
||||
handles from server data that has both.
|
||||
without `slug` / an offer without `id` or `slug` — in practice the domain only
|
||||
builds handles from server data that has all of them.
|
||||
|
||||
Reference in New Issue
Block a user