diff --git a/Packages/rudder.sdk/AGENTS.md b/Packages/rudder.sdk/AGENTS.md index 310cdf0..56d2e55 100644 --- a/Packages/rudder.sdk/AGENTS.md +++ b/Packages/rudder.sdk/AGENTS.md @@ -113,7 +113,7 @@ save format in a wrapper type. ```csharp var stores = await client.Stores.ListAsync(); -var result = await client.Stores.PurchaseAsync(storeSlug, offerId); +var result = await client.Stores.PurchaseAsync(storeSlug, offerSlug); if (result != null && result.Success != true) { /* result.Error */ } var items = await client.Inventory.GetAsync(); ``` @@ -165,7 +165,7 @@ effects through the `On*` events. ```csharp var quests = await client.Quests.ListAsync(); -await client.Quests.ClaimAsync(quest.Id); +await client.Quests.ClaimAsync(quest.Slug); var completedIds = await client.Quests.ReportProgressAsync("kills", 1); ``` @@ -175,7 +175,7 @@ Store purchases already report `purchase.offer:` / `purchase.item:`. Progress is tied to a scenario battle-pass node. Prefer `BattlePassEffect` from `Effects.OnBattlePass` (it binds scenario/node/run ids). Direct -`BattlePass.GetProgressAsync(scenarioId, nodeId)` needs those ids. Tracks: +`BattlePass.GetProgressAsync(scenarioSlug, nodeId)` needs those ids. Tracks: `BattlePassService.TrackFree` / `TrackPremium`. ## Errors diff --git a/Packages/rudder.sdk/CHANGELOG.md b/Packages/rudder.sdk/CHANGELOG.md index cfc1f0a..0443af1 100644 --- a/Packages/rudder.sdk/CHANGELOG.md +++ b/Packages/rudder.sdk/CHANGELOG.md @@ -1,5 +1,37 @@ # Changelog +## 2.0.0 + +Breaking changes, following Rudder.Core 2.0.0 and the backend environments +release. The bundled `Runtime/Plugins/Rudder.Core.dll` is rebuilt from +Rudder.Core 2.0.0. + +A project now has exactly two environments, `staging` and `prod`. The SDK key +set in `RudderConfiguration` belongs to one of them, so the environment is +resolved at login and carried inside the tokens; nothing in the API takes an +environment argument and players do not cross between environments. Tokens +saved to PlayerPrefs by 1.x have no environment claim and are rejected with +401: the refresh fails, the stored tokens are cleared and +`Auth.AuthStateChanged` fires `SignedOut`, so call `LoginWithDeviceAsync` +again on that event. + +Quests, scenarios and offers are addressed by slug instead of id, because ids +differ between staging and prod. `Quest.Id` became `Quest.Slug`, +`Quests.ClaimAsync` takes a quest slug, `Quests.ReportProgressAsync` returns +quest slugs, `Stores.PurchaseAsync` takes an offer slug and `Offer` carries a +`Slug`, `QuestMetrics.PurchaseOffer` builds its metric from the offer slug, +and every scenario-scoped effect and request exposes `ScenarioSlug` instead of +`ScenarioId` (`PendingEffect`, `ScenarioCompletedEffect`, +`ScenarioFailedEffect`, the battle pass requests, +`BattlePass.GetProgressAsync`). `Store.ScenarioId` is unchanged — it is an +authoring reference, not a player-facing one. The Store/Inventory and +Scenarios samples were updated to `offer.Slug`. + +Also inherited from Core: the effects client reconciles against every pending +poll, so a run that disappeared server-side emits `OnScenarioCompleted` +instead of lingering, and `run_not_active` is a terminal rejection that drops +the run and emits `OnScenarioFailed`. + ## 1.0.0 Breaking changes, following the Rudder.Core 1.0.0 rework: diff --git a/Packages/rudder.sdk/Runtime/Plugins/Rudder.Core.dll b/Packages/rudder.sdk/Runtime/Plugins/Rudder.Core.dll index 6bf0567..ef61475 100644 Binary files a/Packages/rudder.sdk/Runtime/Plugins/Rudder.Core.dll and b/Packages/rudder.sdk/Runtime/Plugins/Rudder.Core.dll differ diff --git a/Packages/rudder.sdk/Samples~/Scenarios/ScenariosSample.cs b/Packages/rudder.sdk/Samples~/Scenarios/ScenariosSample.cs index beb5354..dabc993 100644 --- a/Packages/rudder.sdk/Samples~/Scenarios/ScenariosSample.cs +++ b/Packages/rudder.sdk/Samples~/Scenarios/ScenariosSample.cs @@ -81,8 +81,8 @@ namespace RudderSdk.Unity.Samples if (offer == null) throw new InvalidOperationException("Store " + storeSlug + " has no offers."); - _page.Log("Stores.PurchaseAsync(" + storeSlug + ", " + offer.Id + ")"); - var response = await client.Stores.PurchaseAsync(storeSlug, offer.Id); + _page.Log("Stores.PurchaseAsync(" + storeSlug + ", " + offer.Slug + ")"); + var response = await client.Stores.PurchaseAsync(storeSlug, offer.Slug); if (response != null && response.Success != true) throw new InvalidOperationException(response.Error ?? "Purchase rejected"); diff --git a/Packages/rudder.sdk/Samples~/StoreInventory/StoreInventorySample.cs b/Packages/rudder.sdk/Samples~/StoreInventory/StoreInventorySample.cs index ec460a3..3a3ed76 100644 --- a/Packages/rudder.sdk/Samples~/StoreInventory/StoreInventorySample.cs +++ b/Packages/rudder.sdk/Samples~/StoreInventory/StoreInventorySample.cs @@ -55,11 +55,11 @@ namespace RudderSdk.Unity.Samples foreach (var offer in store.Offers) { var slug = store.Slug; - var offerId = offer.Id; - var name = string.IsNullOrEmpty(offer.Name) ? offer.Id : offer.Name; + var offerSlug = offer.Slug; + var name = string.IsNullOrEmpty(offer.Name) ? offer.Slug : offer.Name; var price = offer.Price == null ? "free" : offer.Price.Amount + " " + offer.Price.Currency; if (_page.Button("Buy " + name + " — " + price)) - _page.Run(this, () => Buy(slug, offerId, name)); + _page.Run(this, () => Buy(slug, offerSlug, name)); } } }); @@ -74,11 +74,11 @@ namespace RudderSdk.Unity.Samples _page.SetStatus(_stores.Count == 0 ? "No stores" : "Loaded " + _stores.Count + " store(s)"); } - async Task Buy(string storeSlug, string offerId, string name) + async Task Buy(string storeSlug, string offerSlug, string name) { var client = Rudder.Initialize(); - _page.Log("Stores.PurchaseAsync(" + storeSlug + ", " + offerId + ")"); - var response = await client.Stores.PurchaseAsync(storeSlug, offerId); + _page.Log("Stores.PurchaseAsync(" + storeSlug + ", " + offerSlug + ")"); + var response = await client.Stores.PurchaseAsync(storeSlug, offerSlug); if (response != null && response.Success != true) throw new InvalidOperationException(response.Error ?? "Purchase rejected"); diff --git a/Packages/rudder.sdk/package.json b/Packages/rudder.sdk/package.json index 4f09e71..eed0da8 100644 --- a/Packages/rudder.sdk/package.json +++ b/Packages/rudder.sdk/package.json @@ -1,6 +1,6 @@ { "name": "rudder.sdk", - "version": "1.0.0", + "version": "2.0.0", "displayName": "Rudder SDK", "description": "LiveOps SDK for Unity — player auth, remote config, stores, inventory, leaderboards, scenarios.", "unity": "6000.0", diff --git a/skills/rudder-unity-sdk/reference/authentication.md b/skills/rudder-unity-sdk/reference/authentication.md index 608706d..b543b0f 100644 --- a/skills/rudder-unity-sdk/reference/authentication.md +++ b/skills/rudder-unity-sdk/reference/authentication.md @@ -26,6 +26,18 @@ Auth service: Login uses the SDK device id from PlayerPrefs. Do not pass `SystemInfo.deviceUniqueIdentifier`. +## Environments + +A project has two environments, `staging` and `prod`. The SDK key in +`RudderConfiguration` belongs to one of them, so the environment is decided at +login and travels inside the tokens; no API takes an environment argument, and +a player created against a staging key does not exist in prod. Point a QA build +at the staging key and the live build at the prod key. + +Tokens stored by SDK 1.x carry no environment claim and are rejected with 401. +The refresh then fails, PlayerPrefs tokens are cleared and `AuthStateChanged` +fires `SignedOut` — call `LoginWithDeviceAsync` again. + ## Profile and wallets ```csharp diff --git a/skills/rudder-unity-sdk/reference/battlepass.md b/skills/rudder-unity-sdk/reference/battlepass.md index 516ee3f..8aa0f63 100644 --- a/skills/rudder-unity-sdk/reference/battlepass.md +++ b/skills/rudder-unity-sdk/reference/battlepass.md @@ -4,7 +4,7 @@ `Rudder.Core.dll`. There is no sample scene for this feature. Progress is tied to a scenario battle-pass node. Prefer `BattlePassEffect` -delivered by `Effects.OnBattlePass` — it binds `scenarioId`/`nodeId`/ +delivered by `Effects.OnBattlePass` — it binds `scenarioSlug`/`nodeId`/ `runId` for you. Direct `BattlePassService` calls need those ids manually. ## BattlePassEffect (preferred) @@ -24,20 +24,20 @@ client.Effects.OnBattlePass += async effect => - `Task PurchasePremiumAsync(CancellationToken = default)` — posts `onPremiumPurchase` on success - `LevelUp()` / `LevelUpAsync()` — posts `onLevelUp` - `End()` / `EndAsync()` — posts `onComplete` -- `Get(key, fallback)`, `RunId`, `ScenarioId`, `NodeId`, `Data` like other effects +- `Get(key, fallback)`, `RunId`, `ScenarioSlug`, `NodeId`, `Data` like other effects `BattlePassLevelEffect` (from `OnBattlePassLevel`) has `Claim()` / `ClaimAsync()` (posts `onComplete`) and a `Level` property. ## BattlePassService (direct) -- `Task GetProgressAsync(string scenarioId, string nodeId, CancellationToken = default)` +- `Task GetProgressAsync(string scenarioSlug, string nodeId, CancellationToken = default)` - `Task AddXpAsync(AddBattlePassXpRequest request, CancellationToken = default)` - `Task ClaimRewardAsync(ClaimBattlePassRewardRequest request, CancellationToken = default)` - `Task PurchasePremiumAsync(PurchaseBattlePassPremiumRequest request, CancellationToken = default)` - Track constants: `BattlePassService.TrackFree`, `BattlePassService.TrackPremium` (static fields). -Request models carry `ScenarioId`, `NodeId`, and (except progress) +Request models carry `ScenarioSlug`, `NodeId`, and (except progress) `RunId`; `PurchaseBattlePassPremiumRequest` also takes `IdempotencyKey`. ## Response models diff --git a/skills/rudder-unity-sdk/reference/quests.md b/skills/rudder-unity-sdk/reference/quests.md index 0e4c90f..6af974c 100644 --- a/skills/rudder-unity-sdk/reference/quests.md +++ b/skills/rudder-unity-sdk/reference/quests.md @@ -7,19 +7,19 @@ from scenario `OnQuest` nodes (`QuestEffect`, see ```csharp var quests = await client.Quests.ListAsync(); -var claim = await client.Quests.ClaimAsync(quest.Id); -var completedIds = await client.Quests.ReportProgressAsync("kills", 1); +var claim = await client.Quests.ClaimAsync(quest.Slug); +var completedSlugs = await client.Quests.ReportProgressAsync("kills", 1); ``` API: - `Task> ListAsync(CancellationToken = default)` -- `Task ClaimAsync(string questId, CancellationToken = default)` -- `Task> ReportProgressAsync(string metric, long amount, CancellationToken = default)` — returns the ids of quests completed by this report. +- `Task ClaimAsync(string questSlug, CancellationToken = default)` +- `Task> ReportProgressAsync(string metric, long amount, CancellationToken = default)` — returns the slugs of quests completed by this report. Models: -- `Quest` — `Id`, `Name`, `Status`, `Objectives` +- `Quest` — `Slug`, `Name`, `Status`, `Objectives` (`List`), `Rewards` (`List`). - `QuestObjectiveProgress` — `ObjectiveId`, `Metric`, `Current` (`long?`), `Target` (`long?`), `Completed` (`bool?`). @@ -28,6 +28,6 @@ Models: `Granted` (`List`), `Error`. Store purchases already report metrics automatically; the metric strings are -built by `QuestMetrics.PurchaseOffer(offerId)` → `purchase.offer:` +built by `QuestMetrics.PurchaseOffer(offerSlug)` → `purchase.offer:` and `QuestMetrics.PurchaseItem(itemId)` → `purchase.item:` (`RudderSdk.Core.QuestMetrics`, static). diff --git a/skills/rudder-unity-sdk/reference/scenarios.md b/skills/rudder-unity-sdk/reference/scenarios.md index 28fa417..1a3cfaa 100644 --- a/skills/rudder-unity-sdk/reference/scenarios.md +++ b/skills/rudder-unity-sdk/reference/scenarios.md @@ -27,7 +27,7 @@ are dispatched the same way. ## Events and effects -All effect types expose `RunId`, `ScenarioId`, `NodeId`, `Data` (`JObject`), +All effect types expose `RunId`, `ScenarioSlug`, `NodeId`, `Data` (`JObject`), and `T Get(string key, T defaultValue = default)` for node data. | Event | Effect | Game must | @@ -39,8 +39,8 @@ and `T Get(string key, T defaultValue = default)` for node data. | `OnLeaderboard` | `LeaderboardEffect` | `End()` / `EndAsync()`, `Claim()` / `ClaimAsync()`. `IsResolved` marks resolution. | | `OnBattlePass` | `BattlePassEffect` | Drive the effect — see [battlepass.md](battlepass.md). | | `OnBattlePassLevel` | `BattlePassLevelEffect` | `Claim()` / `ClaimAsync()` for the level reward; `Level` prop. | -| `OnScenarioCompleted` | `ScenarioCompletedEffect` | `RunId`, `ScenarioId`. Log / surface. | -| `OnScenarioFailed` | `ScenarioFailedEffect` | `RunId`, `ScenarioId`, `NodeId`, `Exception`. Log / surface. | +| `OnScenarioCompleted` | `ScenarioCompletedEffect` | `RunId`, `ScenarioSlug`. Log / surface. | +| `OnScenarioFailed` | `ScenarioFailedEffect` | `RunId`, `ScenarioSlug`, `NodeId`, `Exception`. Log / surface. | Async and sync variants are equivalent; the sync variants fire-and-forget the same server callback. The event name must match a scenario trigger diff --git a/skills/rudder-unity-sdk/reference/stores-inventory.md b/skills/rudder-unity-sdk/reference/stores-inventory.md index b656043..be81905 100644 --- a/skills/rudder-unity-sdk/reference/stores-inventory.md +++ b/skills/rudder-unity-sdk/reference/stores-inventory.md @@ -7,21 +7,21 @@ Verified against the bundled `Rudder.Core.dll` and ```csharp var stores = await client.Stores.ListAsync(); -var response = await client.Stores.PurchaseAsync(storeSlug, offerId); +var response = await client.Stores.PurchaseAsync(storeSlug, offerSlug); if (response != null && response.Success != true) throw new InvalidOperationException(response.Error ?? "Purchase rejected"); ``` - `Task> ListAsync(CancellationToken = default)` - `Task GetAsync(string slug, CancellationToken = default)` -- `Task PurchaseAsync(string storeSlug, string offerId, string idempotencyKey = null, CancellationToken = default)` — generates an idempotency key when omitted. +- `Task PurchaseAsync(string storeSlug, string offerSlug, string idempotencyKey = null, CancellationToken = default)` — generates an idempotency key when omitted. Models: - `Store` — `Id`, `Slug`, `Name`, `Description`, `Status`, `Environment`, `ProjectId`, `ScenarioId`, `Data` (`JToken`), `Offers` (`List`), `CreatedAt`, `UpdatedAt`. -- `Offer` — `Id`, `Name`, `Price` (`OfferPrice`), `Contents` +- `Offer` — `Id`, `Slug`, `Name`, `Price` (`OfferPrice`), `Contents` (`List`), `MaxPurchases` (`int?`), `CreatedAt`, `UpdatedAt`. - `OfferPrice` — `Currency`, `Amount` (`long?`). - `OfferContent` — `ItemId`, `Amount` (`long?`). @@ -30,7 +30,7 @@ Models: Check `Success != true` (nullable bool), not `!Success`. After a purchase, reload wallets (`Player.GetProfileAsync`) and inventory — there is no `onChange` callback. Purchases auto-report quest metrics -`purchase.offer:` / `purchase.item:` (see +`purchase.offer:` / `purchase.item:` (see [quests.md](quests.md)). The store slug must exist in the dashboard, else 404. ## Inventory — `client.Inventory` (`InventoryService`)