diff --git a/BattlePass/AddBattlePassXpRequest.cs b/BattlePass/AddBattlePassXpRequest.cs index 42d193c..f53f35e 100644 --- a/BattlePass/AddBattlePassXpRequest.cs +++ b/BattlePass/AddBattlePassXpRequest.cs @@ -14,8 +14,8 @@ public class AddBattlePassXpRequest [JsonProperty("runId")] public string? RunId { get; set; } - [JsonProperty("scenarioId")] - public string? ScenarioId { get; set; } + [JsonProperty("scenarioSlug")] + public string? ScenarioSlug { get; set; } [JsonProperty("source")] public string? Source { get; set; } diff --git a/BattlePass/ClaimBattlePassRewardRequest.cs b/BattlePass/ClaimBattlePassRewardRequest.cs index 51dd9fd..adfee1a 100644 --- a/BattlePass/ClaimBattlePassRewardRequest.cs +++ b/BattlePass/ClaimBattlePassRewardRequest.cs @@ -14,8 +14,8 @@ public class ClaimBattlePassRewardRequest [JsonProperty("runId")] public string? RunId { get; set; } - [JsonProperty("scenarioId")] - public string? ScenarioId { get; set; } + [JsonProperty("scenarioSlug")] + public string? ScenarioSlug { get; set; } [JsonProperty("track")] public string? Track { get; set; } diff --git a/BattlePass/GetBattlePassProgressRequest.cs b/BattlePass/GetBattlePassProgressRequest.cs index 6f633ea..426d254 100644 --- a/BattlePass/GetBattlePassProgressRequest.cs +++ b/BattlePass/GetBattlePassProgressRequest.cs @@ -8,7 +8,7 @@ public class GetBattlePassProgressRequest [JsonProperty("nodeId")] public string? NodeId { get; set; } - [JsonProperty("scenarioId")] - public string? ScenarioId { get; set; } + [JsonProperty("scenarioSlug")] + public string? ScenarioSlug { get; set; } } diff --git a/BattlePass/PurchaseBattlePassPremiumRequest.cs b/BattlePass/PurchaseBattlePassPremiumRequest.cs index 6208bf0..b360952 100644 --- a/BattlePass/PurchaseBattlePassPremiumRequest.cs +++ b/BattlePass/PurchaseBattlePassPremiumRequest.cs @@ -14,7 +14,7 @@ public class PurchaseBattlePassPremiumRequest [JsonProperty("runId")] public string? RunId { get; set; } - [JsonProperty("scenarioId")] - public string? ScenarioId { get; set; } + [JsonProperty("scenarioSlug")] + public string? ScenarioSlug { get; set; } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 3366285..4fd1108 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,36 @@ # Changelog +## 2.0.0 + +Breaking change — major bump, required by the backend environments release. +Every project now has exactly two environments, `staging` and `prod`, and the +SDK key you configure decides which one the player belongs to. The environment +never appears in the client API: it is resolved at login and carried inside the +access and refresh tokens. Tokens issued before this release have no +environment claim and are rejected with 401, so the first call after the +backend upgrade refreshes, fails, clears the stored tokens and raises +`AuthService.AuthStateChanged` with `RudderAuthState.SignedOut`. Log the player +in again with `Auth.LoginWithDeviceAsync` / `LoginWithCustomAsync`. + +Quests, scenarios and offers are addressed by their slug instead of their id, +because ids differ between staging and prod while slugs are stable. Renamed +accordingly: `Quest.Id` is now `Quest.Slug`, `QuestsService.ClaimAsync` takes a +quest slug, `ReportProgressAsync` returns the slugs of the completed quests +(`ReportQuestProgressResponse.CompletedQuestSlugs`), `StoresService.PurchaseAsync` +takes an offer slug and `Offer` carries a `Slug`, `QuestMetrics.PurchaseOffer` +builds its metric from the offer slug, and every scenario-scoped type exposes +`ScenarioSlug` instead of `ScenarioId` — `PendingEffect`, the effect objects, +`ScenarioCompletedEffect`, `ScenarioFailedEffect`, the battle pass requests and +`BattlePassService.GetProgressAsync`. Leaderboards, items and stores already +used slugs and are unchanged. + +Also shipped here, previously committed but never published: the effects client +reconciles against every `GET /sdk/v1/scenarios/pending` response, so a run that +disappears server-side (finished elsewhere, expired after a promote) now emits +`OnScenarioCompleted` instead of lingering; and `run_not_active` joins +`unknown_run` and `run_expired` as a terminal rejection that drops the run and +emits `OnScenarioFailed`. + ## 1.0.0 Breaking change — major bump. The local scenario engine is replaced by a diff --git a/Quests/ClaimQuestRequest.cs b/Quests/ClaimQuestRequest.cs index 8e11b6f..312a7db 100644 --- a/Quests/ClaimQuestRequest.cs +++ b/Quests/ClaimQuestRequest.cs @@ -5,7 +5,7 @@ namespace RudderSdk.Core.Models.Quests; public class ClaimQuestRequest { - [JsonProperty("questId")] - public string? QuestId { get; set; } + [JsonProperty("questSlug")] + public string? QuestSlug { get; set; } } diff --git a/Quests/Quest.cs b/Quests/Quest.cs index b87ce9c..e11bfd8 100644 --- a/Quests/Quest.cs +++ b/Quests/Quest.cs @@ -7,9 +7,6 @@ namespace RudderSdk.Core.Models.Quests; public class Quest { - [JsonProperty("id")] - public string? Id { get; set; } - [JsonProperty("name")] public string? Name { get; set; } @@ -19,6 +16,9 @@ public class Quest [JsonProperty("rewards")] public List? Rewards { get; set; } + [JsonProperty("slug")] + public string? Slug { get; set; } + [JsonProperty("status")] public string? Status { get; set; } diff --git a/Quests/ReportQuestProgressResponse.cs b/Quests/ReportQuestProgressResponse.cs index ef90c2e..777978a 100644 --- a/Quests/ReportQuestProgressResponse.cs +++ b/Quests/ReportQuestProgressResponse.cs @@ -6,7 +6,7 @@ namespace RudderSdk.Core.Models.Quests; public class ReportQuestProgressResponse { - [JsonProperty("completedQuestIds")] - public List? CompletedQuestIds { get; set; } + [JsonProperty("completedQuestSlugs")] + public List? CompletedQuestSlugs { get; set; } } diff --git a/README.md b/README.md index de95f95..c599e4a 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ var quests = await client.Quests.ListAsync(); foreach (var quest in quests) { if (quest.Status == "completed") - await client.Quests.ClaimAsync(quest.Id); + await client.Quests.ClaimAsync(quest.Slug); } // Custom metrics advance matching objectives server-side; the call returns @@ -82,8 +82,8 @@ var completedIds = await client.Quests.ReportProgressAsync("kills", 1); ``` Purchase metrics are reported automatically by store purchases; -`QuestMetrics.PurchaseOffer(offerId)` / `QuestMetrics.PurchaseItem(itemId)` -name the format (`purchase.offer:`, `purchase.item:`) so +`QuestMetrics.PurchaseOffer(offerSlug)` / `QuestMetrics.PurchaseItem(itemId)` +name the format (`purchase.offer:`, `purchase.item:`) so quest configs and client code agree on it. ## Sessions diff --git a/Rudder.Core.csproj b/Rudder.Core.csproj index 022768d..40e94a9 100644 --- a/Rudder.Core.csproj +++ b/Rudder.Core.csproj @@ -6,7 +6,7 @@ Rudder.Core RudderSdk.Core Rudder.Core - 1.0.0 + 2.0.0 Rudder Rudder LiveOps client SDK for .NET: auth, player, stores, battle pass, quests, leaderboards, inventory, remote config, scenarios and storage. MIT diff --git a/Scenarios/HandleScenarioCallbackRequest.cs b/Scenarios/HandleScenarioCallbackRequest.cs index 922875b..515a713 100644 --- a/Scenarios/HandleScenarioCallbackRequest.cs +++ b/Scenarios/HandleScenarioCallbackRequest.cs @@ -14,7 +14,7 @@ public class HandleScenarioCallbackRequest [JsonProperty("runId")] public string? RunId { get; set; } - [JsonProperty("scenarioId")] - public string? ScenarioId { get; set; } + [JsonProperty("scenarioSlug")] + public string? ScenarioSlug { get; set; } } diff --git a/Scenarios/PendingEffect.cs b/Scenarios/PendingEffect.cs index bac2d18..4160922 100644 --- a/Scenarios/PendingEffect.cs +++ b/Scenarios/PendingEffect.cs @@ -16,8 +16,8 @@ public class PendingEffect [JsonProperty("runId")] public string RunId { get; set; } = null!; - [JsonProperty("scenarioId")] - public string ScenarioId { get; set; } = null!; + [JsonProperty("scenarioSlug")] + public string ScenarioSlug { get; set; } = null!; [JsonProperty("type")] public string Type { get; set; } = null!; diff --git a/Scenarios/UpdateScenarioCounterRequest.cs b/Scenarios/UpdateScenarioCounterRequest.cs index 1457c0d..c36f269 100644 --- a/Scenarios/UpdateScenarioCounterRequest.cs +++ b/Scenarios/UpdateScenarioCounterRequest.cs @@ -17,7 +17,7 @@ public class UpdateScenarioCounterRequest [JsonProperty("runId")] public string? RunId { get; set; } - [JsonProperty("scenarioId")] - public string? ScenarioId { get; set; } + [JsonProperty("scenarioSlug")] + public string? ScenarioSlug { get; set; } } diff --git a/Services/BattlePassService.cs b/Services/BattlePassService.cs index 5662b32..41292c6 100644 --- a/Services/BattlePassService.cs +++ b/Services/BattlePassService.cs @@ -24,11 +24,11 @@ public sealed class BattlePassService internal BattlePassService(RudderClient client) => _client = client; /// Reads current progress: xp, level, premium ownership, claimed tiers. - public Task GetProgressAsync(string scenarioId, string nodeId, CancellationToken cancellationToken = default) + public Task GetProgressAsync(string scenarioSlug, string nodeId, CancellationToken cancellationToken = default) => _client.SendAsync( "POST", "/sdk/v1/battlepass/progress", - new GetBattlePassProgressRequest { ScenarioId = scenarioId, NodeId = nodeId }, + new GetBattlePassProgressRequest { ScenarioSlug = scenarioSlug, NodeId = nodeId }, cancellationToken); /// Credits xp and returns the new xp/level and level-up flags. diff --git a/Services/Effects/BattlePassEffect.cs b/Services/Effects/BattlePassEffect.cs index a7ce69b..5aff93c 100644 --- a/Services/Effects/BattlePassEffect.cs +++ b/Services/Effects/BattlePassEffect.cs @@ -26,7 +26,7 @@ public sealed class BattlePassEffect public string RunId => _handle.RunId; /// Scenario id. - public string ScenarioId => _handle.ScenarioId; + public string ScenarioSlug => _handle.ScenarioSlug; /// Node id. public string NodeId => _handle.NodeId; @@ -39,13 +39,13 @@ public sealed class BattlePassEffect /// Reads current progress (xp, level, premium ownership, claimed tiers) for this node. public Task GetProgressAsync(CancellationToken cancellationToken = default) - => _battlePass.GetProgressAsync(_handle.ScenarioId, _handle.NodeId, cancellationToken); + => _battlePass.GetProgressAsync(_handle.ScenarioSlug, _handle.NodeId, cancellationToken); /// Credits xp from a configured source. public Task AddXpAsync(string source, long amount, CancellationToken cancellationToken = default) => _battlePass.AddXpAsync(new AddBattlePassXpRequest { - ScenarioId = _handle.ScenarioId, + ScenarioSlug = _handle.ScenarioSlug, NodeId = _handle.NodeId, RunId = _handle.RunId, Source = source, @@ -59,7 +59,7 @@ public sealed class BattlePassEffect public Task ClaimRewardAsync(int level, string track, CancellationToken cancellationToken = default) => _battlePass.ClaimRewardAsync(new ClaimBattlePassRewardRequest { - ScenarioId = _handle.ScenarioId, + ScenarioSlug = _handle.ScenarioSlug, NodeId = _handle.NodeId, RunId = _handle.RunId, Level = level, @@ -71,7 +71,7 @@ public sealed class BattlePassEffect { var response = await _battlePass.PurchasePremiumAsync(new PurchaseBattlePassPremiumRequest { - ScenarioId = _handle.ScenarioId, + ScenarioSlug = _handle.ScenarioSlug, NodeId = _handle.NodeId, RunId = _handle.RunId, IdempotencyKey = Guid.NewGuid().ToString() diff --git a/Services/Effects/BattlePassLevelEffect.cs b/Services/Effects/BattlePassLevelEffect.cs index 0bb0507..d1975da 100644 --- a/Services/Effects/BattlePassLevelEffect.cs +++ b/Services/Effects/BattlePassLevelEffect.cs @@ -19,7 +19,7 @@ public sealed class BattlePassLevelEffect public string RunId => _handle.RunId; /// Scenario id. - public string ScenarioId => _handle.ScenarioId; + public string ScenarioSlug => _handle.ScenarioSlug; /// Node id. public string NodeId => _handle.NodeId; diff --git a/Services/Effects/LeaderboardEffect.cs b/Services/Effects/LeaderboardEffect.cs index cee667d..b64f992 100644 --- a/Services/Effects/LeaderboardEffect.cs +++ b/Services/Effects/LeaderboardEffect.cs @@ -15,7 +15,7 @@ public sealed class LeaderboardEffect public string RunId => _handle.RunId; /// Scenario id. - public string ScenarioId => _handle.ScenarioId; + public string ScenarioSlug => _handle.ScenarioSlug; /// Node id. public string NodeId => _handle.NodeId; diff --git a/Services/Effects/NotificationEffect.cs b/Services/Effects/NotificationEffect.cs index af986d1..8314441 100644 --- a/Services/Effects/NotificationEffect.cs +++ b/Services/Effects/NotificationEffect.cs @@ -15,7 +15,7 @@ public sealed class NotificationEffect public string RunId => _handle.RunId; /// Scenario id. - public string ScenarioId => _handle.ScenarioId; + public string ScenarioSlug => _handle.ScenarioSlug; /// Node id. public string NodeId => _handle.NodeId; diff --git a/Services/Effects/QuestEffect.cs b/Services/Effects/QuestEffect.cs index c3ece16..7fb6281 100644 --- a/Services/Effects/QuestEffect.cs +++ b/Services/Effects/QuestEffect.cs @@ -20,7 +20,7 @@ public sealed class QuestEffect public string RunId => _handle.RunId; /// Scenario id. - public string ScenarioId => _handle.ScenarioId; + public string ScenarioSlug => _handle.ScenarioSlug; /// Node id. public string NodeId => _handle.NodeId; diff --git a/Services/Effects/ScenarioCompletedEffect.cs b/Services/Effects/ScenarioCompletedEffect.cs index 267275b..b7fcf59 100644 --- a/Services/Effects/ScenarioCompletedEffect.cs +++ b/Services/Effects/ScenarioCompletedEffect.cs @@ -3,15 +3,15 @@ namespace RudderSdk.Core; /// Payload of . public sealed class ScenarioCompletedEffect { - internal ScenarioCompletedEffect(string runId, string scenarioId) + internal ScenarioCompletedEffect(string runId, string scenarioSlug) { RunId = runId; - ScenarioId = scenarioId; + ScenarioSlug = scenarioSlug; } /// Server-issued run id. public string RunId { get; } /// Scenario id. - public string ScenarioId { get; } + public string ScenarioSlug { get; } } diff --git a/Services/Effects/ScenarioFailedEffect.cs b/Services/Effects/ScenarioFailedEffect.cs index 2c833a7..c2e0d55 100644 --- a/Services/Effects/ScenarioFailedEffect.cs +++ b/Services/Effects/ScenarioFailedEffect.cs @@ -5,10 +5,10 @@ namespace RudderSdk.Core; /// Payload of . public sealed class ScenarioFailedEffect { - internal ScenarioFailedEffect(string runId, string scenarioId, string nodeId, Exception exception) + internal ScenarioFailedEffect(string runId, string scenarioSlug, string nodeId, Exception exception) { RunId = runId; - ScenarioId = scenarioId; + ScenarioSlug = scenarioSlug; NodeId = nodeId; Exception = exception; } @@ -17,7 +17,7 @@ public sealed class ScenarioFailedEffect public string RunId { get; } /// Scenario id. - public string ScenarioId { get; } + public string ScenarioSlug { get; } /// Node the failure happened at. public string NodeId { get; } diff --git a/Services/Effects/StoreOfferEffect.cs b/Services/Effects/StoreOfferEffect.cs index 084acc8..19a4ab4 100644 --- a/Services/Effects/StoreOfferEffect.cs +++ b/Services/Effects/StoreOfferEffect.cs @@ -15,7 +15,7 @@ public sealed class StoreOfferEffect public string RunId => _handle.RunId; /// Scenario id. - public string ScenarioId => _handle.ScenarioId; + public string ScenarioSlug => _handle.ScenarioSlug; /// Node id. public string NodeId => _handle.NodeId; diff --git a/Services/Effects/WaitEffect.cs b/Services/Effects/WaitEffect.cs index 28c7d57..6bf165d 100644 --- a/Services/Effects/WaitEffect.cs +++ b/Services/Effects/WaitEffect.cs @@ -18,7 +18,7 @@ public sealed class WaitEffect public string RunId => _handle.RunId; /// Scenario id. - public string ScenarioId => _handle.ScenarioId; + public string ScenarioSlug => _handle.ScenarioSlug; /// Node id. public string NodeId => _handle.NodeId; diff --git a/Services/EffectsService.cs b/Services/EffectsService.cs index dbb7f02..4cd0811 100644 --- a/Services/EffectsService.cs +++ b/Services/EffectsService.cs @@ -101,7 +101,7 @@ public sealed class EffectsService var key = (effect.RunId, effect.NodeId); if (_seen.ContainsKey(key)) continue; - _seen[key] = effect.ScenarioId; + _seen[key] = effect.ScenarioSlug; if (string.Equals(effect.Type, EffectTypes.Wait, StringComparison.Ordinal)) { @@ -126,7 +126,7 @@ public sealed class EffectsService "/sdk/v1/scenarios/callback", new HandleScenarioCallbackRequest { - ScenarioId = source.ScenarioId, + ScenarioSlug = source.ScenarioSlug, NodeId = source.NodeId, Handle = handle, RunId = source.RunId @@ -136,13 +136,13 @@ public sealed class EffectsService ForgetWait(source.RunId, source.NodeId); var next = ReadEffect(response?.Effect); if (next == null) - Emit(OnScenarioCompleted, new ScenarioCompletedEffect(source.RunId, source.ScenarioId)); + Emit(OnScenarioCompleted, new ScenarioCompletedEffect(source.RunId, source.ScenarioSlug)); else Ingest(new[] { next }); } catch (Exception ex) when (IsDefinitiveRejection(ex)) { - DropRun(source.RunId, source.ScenarioId, source.NodeId, ex); + DropRun(source.RunId, source.ScenarioSlug, source.NodeId, ex); } } @@ -159,7 +159,7 @@ public sealed class EffectsService "/sdk/v1/scenarios/counter", new UpdateScenarioCounterRequest { - ScenarioId = source.ScenarioId, + ScenarioSlug = source.ScenarioSlug, NodeId = source.NodeId, CounterKey = counterKey, Amount = amount, @@ -173,13 +173,13 @@ public sealed class EffectsService ForgetWait(source.RunId, source.NodeId); var next = ReadEffect(response.Effect); if (next == null) - Emit(OnScenarioCompleted, new ScenarioCompletedEffect(source.RunId, source.ScenarioId)); + Emit(OnScenarioCompleted, new ScenarioCompletedEffect(source.RunId, source.ScenarioSlug)); else Ingest(new[] { next }); } catch (Exception ex) when (IsDefinitiveRejection(ex)) { - DropRun(source.RunId, source.ScenarioId, source.NodeId, ex); + DropRun(source.RunId, source.ScenarioSlug, source.NodeId, ex); } catch (Exception ex) { @@ -274,14 +274,14 @@ public sealed class EffectsService OnScenarioFailed, new ScenarioFailedEffect( effect.RunId, - effect.ScenarioId, + effect.ScenarioSlug, effect.NodeId, new Exception($"Unsupported scenario node type '{effect.Type}'"))); break; } } - private void DropRun(string runId, string scenarioId, string nodeId, Exception exception) + private void DropRun(string runId, string scenarioSlug, string nodeId, Exception exception) { lock (_gate) { @@ -299,7 +299,7 @@ public sealed class EffectsService } } - Emit(OnScenarioFailed, new ScenarioFailedEffect(runId, scenarioId, nodeId, exception)); + Emit(OnScenarioFailed, new ScenarioFailedEffect(runId, scenarioSlug, nodeId, exception)); } private void Reconcile(IEnumerable? effects) @@ -424,7 +424,7 @@ internal sealed class EffectHandle public string RunId => _effect.RunId; - public string ScenarioId => _effect.ScenarioId; + public string ScenarioSlug => _effect.ScenarioSlug; public string NodeId => _effect.NodeId; diff --git a/Services/QuestMetrics.cs b/Services/QuestMetrics.cs index c30ade5..ef7349d 100644 --- a/Services/QuestMetrics.cs +++ b/Services/QuestMetrics.cs @@ -9,7 +9,7 @@ namespace RudderSdk.Core; public static class QuestMetrics { /// Metric for purchasing a store offer; auto-reported on purchase. - public static string PurchaseOffer(string offerId) => $"purchase.offer:{offerId}"; + public static string PurchaseOffer(string offerSlug) => $"purchase.offer:{offerSlug}"; /// Metric for purchasing a catalog item; auto-reported on purchase. public static string PurchaseItem(string itemId) => $"purchase.item:{itemId}"; diff --git a/Services/QuestsService.cs b/Services/QuestsService.cs index 9fa6080..c89c991 100644 --- a/Services/QuestsService.cs +++ b/Services/QuestsService.cs @@ -27,11 +27,11 @@ public sealed class QuestsService } /// Claims a completed quest's rewards (idempotent server-side). - public Task ClaimAsync(string questId, CancellationToken cancellationToken = default) + public Task ClaimAsync(string questSlug, CancellationToken cancellationToken = default) => _client.SendAsync( "POST", "/sdk/v1/quests/claim", - new ClaimQuestRequest { QuestId = questId }, + new ClaimQuestRequest { QuestSlug = questSlug }, cancellationToken); /// Reports progress for a metric and returns the ids of quests completed by this report. @@ -43,6 +43,6 @@ public sealed class QuestsService new ReportQuestProgressRequest { Metric = metric, Amount = amount }, cancellationToken).ConfigureAwait(false); - return response?.CompletedQuestIds ?? new List(); + return response?.CompletedQuestSlugs ?? new List(); } } diff --git a/Services/StoresService.cs b/Services/StoresService.cs index 0e366e7..93470f0 100644 --- a/Services/StoresService.cs +++ b/Services/StoresService.cs @@ -35,17 +35,17 @@ public sealed class StoresService /// public Task PurchaseAsync( string storeSlug, - string offerId, + string offerSlug, string? idempotencyKey = null, CancellationToken cancellationToken = default) { return _client.SendAsync( "POST", - "/sdk/v1/stores/" + Url.Encode(storeSlug) + "/offers/" + Url.Encode(offerId) + "/purchase", + "/sdk/v1/stores/" + Url.Encode(storeSlug) + "/offers/" + Url.Encode(offerSlug) + "/purchase", new PurchaseOfferRequest { StoreSlug = storeSlug, - OfferId = offerId, + OfferSlug = offerSlug, IdempotencyKey = idempotencyKey ?? Guid.NewGuid().ToString() }, cancellationToken); diff --git a/Stores/Offer.cs b/Stores/Offer.cs index 5f9d9e3..dc616a1 100644 --- a/Stores/Offer.cs +++ b/Stores/Offer.cs @@ -25,6 +25,9 @@ public class Offer [JsonProperty("price")] public OfferPrice? Price { get; set; } + [JsonProperty("slug")] + public string? Slug { get; set; } + [JsonProperty("updatedAt")] public DateTimeOffset? UpdatedAt { get; set; } diff --git a/Stores/PurchaseOfferRequest.cs b/Stores/PurchaseOfferRequest.cs index c72a45b..6d48627 100644 --- a/Stores/PurchaseOfferRequest.cs +++ b/Stores/PurchaseOfferRequest.cs @@ -8,8 +8,8 @@ public class PurchaseOfferRequest [JsonProperty("idempotencyKey")] public string? IdempotencyKey { get; set; } - [JsonProperty("offerId")] - public string? OfferId { get; set; } + [JsonProperty("offerSlug")] + public string? OfferSlug { get; set; } [JsonProperty("storeSlug")] public string? StoreSlug { get; set; } diff --git a/skills/rudder-csharp-sdk/SKILL.md b/skills/rudder-csharp-sdk/SKILL.md index 8fb22f1..ce85b65 100644 --- a/skills/rudder-csharp-sdk/SKILL.md +++ b/skills/rudder-csharp-sdk/SKILL.md @@ -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 diff --git a/skills/rudder-csharp-sdk/reference/auth.md b/skills/rudder-csharp-sdk/reference/auth.md index d644b7a..99ece49 100644 --- a/skills/rudder-csharp-sdk/reference/auth.md +++ b/skills/rudder-csharp-sdk/reference/auth.md @@ -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, diff --git a/skills/rudder-csharp-sdk/reference/battlepass.md b/skills/rudder-csharp-sdk/reference/battlepass.md index ad6aa86..26fe69a 100644 --- a/skills/rudder-csharp-sdk/reference/battlepass.md +++ b/skills/rudder-csharp-sdk/reference/battlepass.md @@ -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 GetProgressAsync( - string scenarioId, string nodeId, CancellationToken cancellationToken = default); + string scenarioSlug, string nodeId, CancellationToken cancellationToken = default); // POST /sdk/v1/battlepass/xp public Task AddXpAsync( @@ -40,7 +40,7 @@ public Task 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(string key, T defaultValue = default!); diff --git a/skills/rudder-csharp-sdk/reference/quests.md b/skills/rudder-csharp-sdk/reference/quests.md index 0c4f767..4444f16 100644 --- a/skills/rudder-csharp-sdk/reference/quests.md +++ b/skills/rudder-csharp-sdk/reference/quests.md @@ -14,10 +14,10 @@ scenarios.md. public Task> ListAsync(CancellationToken cancellationToken = default); // POST /sdk/v1/quests/claim — claims a completed quest's rewards (idempotent server-side) -public Task ClaimAsync(string questId, CancellationToken cancellationToken = default); +public Task 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> ReportProgressAsync( string metric, long amount, CancellationToken cancellationToken = default); ``` @@ -27,7 +27,7 @@ public Task> 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? Objectives { get; set; }// "objectives" public List? Rewards { get; set; } // "rewards" + public string? Slug { get; set; } // "slug" — stable across environments public string? Status { get; set; } // "status" } diff --git a/skills/rudder-csharp-sdk/reference/scenarios.md b/skills/rudder-csharp-sdk/reference/scenarios.md index 176e88a..4af909f 100644 --- a/skills/rudder-csharp-sdk/reference/scenarios.md +++ b/skills/rudder-csharp-sdk/reference/scenarios.md @@ -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(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 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 { diff --git a/skills/rudder-csharp-sdk/reference/stores.md b/skills/rudder-csharp-sdk/reference/stores.md index 7f175b1..3fbed95 100644 --- a/skills/rudder-csharp-sdk/reference/stores.md +++ b/skills/rudder-csharp-sdk/reference/stores.md @@ -14,11 +14,11 @@ public Task> ListAsync(CancellationToken cancellationToken // GET /sdk/v1/stores/{slug} public Task 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 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? 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. diff --git a/tests/Rudder.Core.Tests/ScenarioServiceTests.cs b/tests/Rudder.Core.Tests/ScenarioServiceTests.cs index 094e632..3a75a82 100644 --- a/tests/Rudder.Core.Tests/ScenarioServiceTests.cs +++ b/tests/Rudder.Core.Tests/ScenarioServiceTests.cs @@ -103,7 +103,7 @@ public sealed class ScenarioServiceTests Assert.NotNull(completed); Assert.Equal("run-1", completed!.RunId); - Assert.Equal("scenario-1", completed.ScenarioId); + Assert.Equal("scenario-1", completed.ScenarioSlug); } [Fact] @@ -522,7 +522,7 @@ public sealed class ScenarioServiceTests return new PendingEffect { RunId = runId, - ScenarioId = "scenario-1", + ScenarioSlug = "scenario-1", NodeId = nodeId, Type = type, Data = data == null ? new JObject() : JObject.FromObject(data),