2.0.0: slug-based quests, scenarios and offers; environment carried by the token; regenerated models
CI / check (push) Successful in 25s
CI / publish (push) Has been skipped

Claude-Session: https://claude.ai/code/session_01SMCvdwDmuxoaqGgvGBLk1V
This commit is contained in:
edmand46
2026-09-06 22:25:32 +03:00
parent 62025592ba
commit 372ab7cf7e
36 changed files with 138 additions and 90 deletions
+1 -1
View File
@@ -82,7 +82,7 @@ realtime websocket client in Rudder.Core.
Important cross-cutting facts:
- Battle pass state is tied to a scenario battle-pass node: every
`BattlePassService` call carries `ScenarioId`/`NodeId` (mutations also
`BattlePassService` call carries `ScenarioSlug`/`NodeId` (mutations also
`RunId`). During a scenario run, `BattlePassEffect` supplies them — prefer
the effect API inside a run.
- Global quests (`client.Quests`) are distinct from scenario quest nodes
@@ -24,6 +24,19 @@ public void Logout(); // drops the stored session, fires SignedOut
`RudderAuthState`: `SignedIn`, `SignedOut`. The event fires after a
successful login and after logout or a failed token refresh.
## Environments
A project has two environments, `staging` and `prod`. The SDK key configured
in `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 refresh pipeline then fails, clears the store and fires
`AuthStateChanged(SignedOut)` — log the player in again.
## Endpoints
- `POST /sdk/v1/authorization/device` — body `{key, deviceId, region,
@@ -6,7 +6,7 @@ Source: `Services/BattlePassService.cs`,
(generated DTOs).
Battle pass state is tied to a scenario battle-pass node, so every call
carries `ScenarioId`/`NodeId` (mutations also `RunId`). Inside a scenario run,
carries `ScenarioSlug`/`NodeId` (mutations also `RunId`). Inside a scenario run,
`Effects.OnBattlePass` hands you a `BattlePassEffect` that supplies these
ids — prefer it over calling the service directly.
@@ -18,7 +18,7 @@ public const string TrackPremium = "premium";
// POST /sdk/v1/battlepass/progress
public Task<GetBattlePassProgressResponse> GetProgressAsync(
string scenarioId, string nodeId, CancellationToken cancellationToken = default);
string scenarioSlug, string nodeId, CancellationToken cancellationToken = default);
// POST /sdk/v1/battlepass/xp
public Task<AddBattlePassXpResponse> AddXpAsync(
@@ -40,7 +40,7 @@ public Task<PurchaseBattlePassPremiumResponse> PurchasePremiumAsync(
public class GetBattlePassProgressRequest
{
public string? NodeId { get; set; } // "nodeId"
public string? ScenarioId { get; set; } // "scenarioId"
public string? ScenarioSlug { get; set; } // "scenarioSlug"
}
public class AddBattlePassXpRequest
@@ -48,7 +48,7 @@ public class AddBattlePassXpRequest
public long? Amount { get; set; } // "amount"
public string? NodeId { get; set; } // "nodeId"
public string? RunId { get; set; } // "runId"
public string? ScenarioId { get; set; } // "scenarioId"
public string? ScenarioSlug { get; set; } // "scenarioSlug"
public string? Source { get; set; } // "source"
}
@@ -57,7 +57,7 @@ public class ClaimBattlePassRewardRequest
public int? Level { get; set; } // "level"
public string? NodeId { get; set; } // "nodeId"
public string? RunId { get; set; } // "runId"
public string? ScenarioId { get; set; } // "scenarioId"
public string? ScenarioSlug { get; set; } // "scenarioSlug"
public string? Track { get; set; } // "track" — TrackFree / TrackPremium
}
@@ -66,7 +66,7 @@ public class PurchaseBattlePassPremiumRequest
public string? IdempotencyKey { get; set; } // "idempotencyKey"
public string? NodeId { get; set; } // "nodeId"
public string? RunId { get; set; } // "runId"
public string? ScenarioId { get; set; } // "scenarioId"
public string? ScenarioSlug { get; set; } // "scenarioSlug"
}
```
@@ -116,7 +116,7 @@ Raised via `Effects.OnBattlePass`. Bound to the node's scenario/node/run ids:
```csharp
public string RunId { get; }
public string ScenarioId { get; }
public string ScenarioSlug { get; }
public string NodeId { get; }
public T Get<T>(string key, T defaultValue = default!);
+4 -4
View File
@@ -14,10 +14,10 @@ scenarios.md.
public Task<IReadOnlyList<Quest>> ListAsync(CancellationToken cancellationToken = default);
// POST /sdk/v1/quests/claim — claims a completed quest's rewards (idempotent server-side)
public Task<ClaimQuestResponse> ClaimAsync(string questId, CancellationToken cancellationToken = default);
public Task<ClaimQuestResponse> ClaimAsync(string questSlug, CancellationToken cancellationToken = default);
// POST /sdk/v1/quests/progress — reports progress for a metric; returns the
// ids of quests completed by THIS report
// slugs of quests completed by THIS report
public Task<IReadOnlyList<string>> ReportProgressAsync(
string metric, long amount, CancellationToken cancellationToken = default);
```
@@ -27,7 +27,7 @@ public Task<IReadOnlyList<string>> ReportProgressAsync(
```csharp
public static class QuestMetrics
{
public static string PurchaseOffer(string offerId); // "purchase.offer:{offerId}"
public static string PurchaseOffer(string offerSlug); // "purchase.offer:{offerSlug}"
public static string PurchaseItem(string itemId); // "purchase.item:{itemId}"
}
```
@@ -40,10 +40,10 @@ metric is a custom string passed to `ReportProgressAsync`.
```csharp
public class Quest
{
public string? Id { get; set; } // "id"
public string? Name { get; set; } // "name"
public List<QuestObjectiveProgress>? Objectives { get; set; }// "objectives"
public List<Reward>? Rewards { get; set; } // "rewards"
public string? Slug { get; set; } // "slug" — stable across environments
public string? Status { get; set; } // "status"
}
@@ -78,7 +78,7 @@ client.Effects.OnNotification += n =>
await client.Scenario.TriggerAsync("level_complete");
```
Every effect exposes `RunId`, `ScenarioId`, `NodeId`, `Data` (`JObject`),
Every effect exposes `RunId`, `ScenarioSlug`, `NodeId`, `Data` (`JObject`),
and `Get<T>(key, defaultValue)` for reading node data. Completion methods
have an `Async` variant (awaitable) and a fire-and-forget variant.
@@ -156,7 +156,7 @@ Scenario quest node — distinct from global `client.Quests`.
public string Name { get; } // node data "name"
public IReadOnlyList<JObject> Objectives { get; } // node data "objectives"
// POST /sdk/v1/scenarios/counter {scenarioId, nodeId, runId, counterKey, amount}
// POST /sdk/v1/scenarios/counter {scenarioSlug, nodeId, runId, counterKey, amount}
public Task ReportProgressAsync(string objectiveId, long amount = 1, CancellationToken ct = default);
public void ReportProgress(string objectiveId, long amount = 1);
```
@@ -166,7 +166,7 @@ completed counter response may carry the next `PendingEffect`.
### `BattlePassEffect`
Bound to this node's `ScenarioId` / `NodeId` / `RunId` — prefer these over
Bound to this node's `ScenarioSlug` / `NodeId` / `RunId` — prefer these over
calling `client.BattlePass` by hand. See also reference/battlepass.md.
```csharp
@@ -199,13 +199,13 @@ configured level.
public sealed class ScenarioCompletedEffect
{
public string RunId { get; }
public string ScenarioId { get; }
public string ScenarioSlug { get; }
}
public sealed class ScenarioFailedEffect
{
public string RunId { get; }
public string ScenarioId { get; }
public string ScenarioSlug { get; }
public string NodeId { get; }
public Exception Exception { get; }
}
@@ -219,7 +219,7 @@ advanced a wait, the run expired, or another device completed it.
## Reliability
- 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`. A non-null `effect` in the response is ingested as
the next node; a null/missing effect completes the run.
@@ -243,7 +243,7 @@ public class PendingEffect
public JToken Data { get; set; }
public string NodeId { get; set; }
public string RunId { get; set; }
public string ScenarioId { get; set; }
public string ScenarioSlug { get; set; }
public string Type { get; set; }
public DateTimeOffset? WaitDeadline { get; set; }
}
@@ -257,7 +257,7 @@ public class HandleScenarioCallbackRequest
public string? Handle { get; set; }
public string? NodeId { get; set; }
public string? RunId { get; set; }
public string? ScenarioId { get; set; }
public string? ScenarioSlug { get; set; }
}
public class HandleScenarioCallbackResponse { public JToken? Effect { get; set; } }
@@ -267,7 +267,7 @@ public class UpdateScenarioCounterRequest
public string? CounterKey { get; set; }
public string? NodeId { get; set; }
public string? RunId { get; set; }
public string? ScenarioId { get; set; }
public string? ScenarioSlug { get; set; }
}
public class UpdateScenarioCounterResponse
{
+4 -3
View File
@@ -14,11 +14,11 @@ public Task<IReadOnlyList<Store>> ListAsync(CancellationToken cancellationToken
// GET /sdk/v1/stores/{slug}
public Task<Store> GetAsync(string slug, CancellationToken cancellationToken = default);
// POST /sdk/v1/stores/{storeSlug}/offers/{offerId}/purchase
// POST /sdk/v1/stores/{storeSlug}/offers/{offerSlug}/purchase
// When idempotencyKey is null a random GUID is generated; pass a stable key to
// make retries safe.
public Task<PurchaseOfferResponse> PurchaseAsync(
string storeSlug, string offerId, string? idempotencyKey = null,
string storeSlug, string offerSlug, string? idempotencyKey = null,
CancellationToken cancellationToken = default);
```
@@ -44,6 +44,7 @@ public class Store
public class Offer
{
public string? Id { get; set; } // "id"
public string? Slug { get; set; } // "slug" — stable across environments
public string? Name { get; set; } // "name"
public OfferPrice? Price { get; set; } // "price"
public List<OfferContent>? Contents { get; set; }// "contents"
@@ -65,5 +66,5 @@ public class PurchaseOfferResponse
`PurchaseOfferResponse.Error` carries a business error string when
`Success` is false — check the flag instead of relying on exceptions alone.
Purchases also auto-report quest metrics (`purchase.offer:{offerId}`) — see
Purchases also auto-report quest metrics (`purchase.offer:{offerSlug}`) — see
quests.md.