2.0.0: Rudder.Core 2.0.0 DLL, samples and skill docs on slugs and environments
Claude-Session: https://claude.ai/code/session_01SMCvdwDmuxoaqGgvGBLk1V
This commit is contained in:
@@ -113,7 +113,7 @@ save format in a wrapper type.
|
|||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
var stores = await client.Stores.ListAsync();
|
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 */ }
|
if (result != null && result.Success != true) { /* result.Error */ }
|
||||||
var items = await client.Inventory.GetAsync();
|
var items = await client.Inventory.GetAsync();
|
||||||
```
|
```
|
||||||
@@ -165,7 +165,7 @@ effects through the `On*` events.
|
|||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
var quests = await client.Quests.ListAsync();
|
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);
|
var completedIds = await client.Quests.ReportProgressAsync("kills", 1);
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -175,7 +175,7 @@ Store purchases already report `purchase.offer:<id>` / `purchase.item:<id>`.
|
|||||||
|
|
||||||
Progress is tied to a scenario battle-pass node. Prefer `BattlePassEffect`
|
Progress is tied to a scenario battle-pass node. Prefer `BattlePassEffect`
|
||||||
from `Effects.OnBattlePass` (it binds scenario/node/run ids). Direct
|
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`.
|
`BattlePassService.TrackFree` / `TrackPremium`.
|
||||||
|
|
||||||
## Errors
|
## Errors
|
||||||
|
|||||||
@@ -1,5 +1,37 @@
|
|||||||
# Changelog
|
# 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
|
## 1.0.0
|
||||||
|
|
||||||
Breaking changes, following the Rudder.Core 1.0.0 rework:
|
Breaking changes, following the Rudder.Core 1.0.0 rework:
|
||||||
|
|||||||
Binary file not shown.
@@ -81,8 +81,8 @@ namespace RudderSdk.Unity.Samples
|
|||||||
if (offer == null)
|
if (offer == null)
|
||||||
throw new InvalidOperationException("Store " + storeSlug + " has no offers.");
|
throw new InvalidOperationException("Store " + storeSlug + " has no offers.");
|
||||||
|
|
||||||
_page.Log("Stores.PurchaseAsync(" + storeSlug + ", " + offer.Id + ")");
|
_page.Log("Stores.PurchaseAsync(" + storeSlug + ", " + offer.Slug + ")");
|
||||||
var response = await client.Stores.PurchaseAsync(storeSlug, offer.Id);
|
var response = await client.Stores.PurchaseAsync(storeSlug, offer.Slug);
|
||||||
if (response != null && response.Success != true)
|
if (response != null && response.Success != true)
|
||||||
throw new InvalidOperationException(response.Error ?? "Purchase rejected");
|
throw new InvalidOperationException(response.Error ?? "Purchase rejected");
|
||||||
|
|
||||||
|
|||||||
@@ -55,11 +55,11 @@ namespace RudderSdk.Unity.Samples
|
|||||||
foreach (var offer in store.Offers)
|
foreach (var offer in store.Offers)
|
||||||
{
|
{
|
||||||
var slug = store.Slug;
|
var slug = store.Slug;
|
||||||
var offerId = offer.Id;
|
var offerSlug = offer.Slug;
|
||||||
var name = string.IsNullOrEmpty(offer.Name) ? offer.Id : offer.Name;
|
var name = string.IsNullOrEmpty(offer.Name) ? offer.Slug : offer.Name;
|
||||||
var price = offer.Price == null ? "free" : offer.Price.Amount + " " + offer.Price.Currency;
|
var price = offer.Price == null ? "free" : offer.Price.Amount + " " + offer.Price.Currency;
|
||||||
if (_page.Button("Buy " + name + " — " + price))
|
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)");
|
_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();
|
var client = Rudder.Initialize();
|
||||||
_page.Log("Stores.PurchaseAsync(" + storeSlug + ", " + offerId + ")");
|
_page.Log("Stores.PurchaseAsync(" + storeSlug + ", " + offerSlug + ")");
|
||||||
var response = await client.Stores.PurchaseAsync(storeSlug, offerId);
|
var response = await client.Stores.PurchaseAsync(storeSlug, offerSlug);
|
||||||
if (response != null && response.Success != true)
|
if (response != null && response.Success != true)
|
||||||
throw new InvalidOperationException(response.Error ?? "Purchase rejected");
|
throw new InvalidOperationException(response.Error ?? "Purchase rejected");
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "rudder.sdk",
|
"name": "rudder.sdk",
|
||||||
"version": "1.0.0",
|
"version": "2.0.0",
|
||||||
"displayName": "Rudder SDK",
|
"displayName": "Rudder SDK",
|
||||||
"description": "LiveOps SDK for Unity — player auth, remote config, stores, inventory, leaderboards, scenarios.",
|
"description": "LiveOps SDK for Unity — player auth, remote config, stores, inventory, leaderboards, scenarios.",
|
||||||
"unity": "6000.0",
|
"unity": "6000.0",
|
||||||
|
|||||||
@@ -26,6 +26,18 @@ Auth service:
|
|||||||
Login uses the SDK device id from PlayerPrefs. Do not pass
|
Login uses the SDK device id from PlayerPrefs. Do not pass
|
||||||
`SystemInfo.deviceUniqueIdentifier`.
|
`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
|
## Profile and wallets
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
`Rudder.Core.dll`. There is no sample scene for this feature.
|
`Rudder.Core.dll`. There is no sample scene for this feature.
|
||||||
|
|
||||||
Progress is tied to a scenario battle-pass node. Prefer `BattlePassEffect`
|
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.
|
`runId` for you. Direct `BattlePassService` calls need those ids manually.
|
||||||
|
|
||||||
## BattlePassEffect (preferred)
|
## BattlePassEffect (preferred)
|
||||||
@@ -24,20 +24,20 @@ client.Effects.OnBattlePass += async effect =>
|
|||||||
- `Task<PurchaseBattlePassPremiumResponse> PurchasePremiumAsync(CancellationToken = default)` — posts `onPremiumPurchase` on success
|
- `Task<PurchaseBattlePassPremiumResponse> PurchasePremiumAsync(CancellationToken = default)` — posts `onPremiumPurchase` on success
|
||||||
- `LevelUp()` / `LevelUpAsync()` — posts `onLevelUp`
|
- `LevelUp()` / `LevelUpAsync()` — posts `onLevelUp`
|
||||||
- `End()` / `EndAsync()` — posts `onComplete`
|
- `End()` / `EndAsync()` — posts `onComplete`
|
||||||
- `Get<T>(key, fallback)`, `RunId`, `ScenarioId`, `NodeId`, `Data` like other effects
|
- `Get<T>(key, fallback)`, `RunId`, `ScenarioSlug`, `NodeId`, `Data` like other effects
|
||||||
|
|
||||||
`BattlePassLevelEffect` (from `OnBattlePassLevel`) has `Claim()` /
|
`BattlePassLevelEffect` (from `OnBattlePassLevel`) has `Claim()` /
|
||||||
`ClaimAsync()` (posts `onComplete`) and a `Level` property.
|
`ClaimAsync()` (posts `onComplete`) and a `Level` property.
|
||||||
|
|
||||||
## BattlePassService (direct)
|
## BattlePassService (direct)
|
||||||
|
|
||||||
- `Task<GetBattlePassProgressResponse> GetProgressAsync(string scenarioId, string nodeId, CancellationToken = default)`
|
- `Task<GetBattlePassProgressResponse> GetProgressAsync(string scenarioSlug, string nodeId, CancellationToken = default)`
|
||||||
- `Task<AddBattlePassXpResponse> AddXpAsync(AddBattlePassXpRequest request, CancellationToken = default)`
|
- `Task<AddBattlePassXpResponse> AddXpAsync(AddBattlePassXpRequest request, CancellationToken = default)`
|
||||||
- `Task<ClaimBattlePassRewardResponse> ClaimRewardAsync(ClaimBattlePassRewardRequest request, CancellationToken = default)`
|
- `Task<ClaimBattlePassRewardResponse> ClaimRewardAsync(ClaimBattlePassRewardRequest request, CancellationToken = default)`
|
||||||
- `Task<PurchaseBattlePassPremiumResponse> PurchasePremiumAsync(PurchaseBattlePassPremiumRequest request, CancellationToken = default)`
|
- `Task<PurchaseBattlePassPremiumResponse> PurchasePremiumAsync(PurchaseBattlePassPremiumRequest request, CancellationToken = default)`
|
||||||
- Track constants: `BattlePassService.TrackFree`, `BattlePassService.TrackPremium` (static fields).
|
- 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`.
|
`RunId`; `PurchaseBattlePassPremiumRequest` also takes `IdempotencyKey`.
|
||||||
|
|
||||||
## Response models
|
## Response models
|
||||||
|
|||||||
@@ -7,19 +7,19 @@ from scenario `OnQuest` nodes (`QuestEffect`, see
|
|||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
var quests = await client.Quests.ListAsync();
|
var quests = await client.Quests.ListAsync();
|
||||||
var claim = await client.Quests.ClaimAsync(quest.Id);
|
var claim = await client.Quests.ClaimAsync(quest.Slug);
|
||||||
var completedIds = await client.Quests.ReportProgressAsync("kills", 1);
|
var completedSlugs = await client.Quests.ReportProgressAsync("kills", 1);
|
||||||
```
|
```
|
||||||
|
|
||||||
API:
|
API:
|
||||||
|
|
||||||
- `Task<IReadOnlyList<Quest>> ListAsync(CancellationToken = default)`
|
- `Task<IReadOnlyList<Quest>> ListAsync(CancellationToken = default)`
|
||||||
- `Task<ClaimQuestResponse> ClaimAsync(string questId, CancellationToken = default)`
|
- `Task<ClaimQuestResponse> ClaimAsync(string questSlug, CancellationToken = default)`
|
||||||
- `Task<IReadOnlyList<string>> ReportProgressAsync(string metric, long amount, CancellationToken = default)` — returns the ids of quests completed by this report.
|
- `Task<IReadOnlyList<string>> ReportProgressAsync(string metric, long amount, CancellationToken = default)` — returns the slugs of quests completed by this report.
|
||||||
|
|
||||||
Models:
|
Models:
|
||||||
|
|
||||||
- `Quest` — `Id`, `Name`, `Status`, `Objectives`
|
- `Quest` — `Slug`, `Name`, `Status`, `Objectives`
|
||||||
(`List<QuestObjectiveProgress>`), `Rewards` (`List<Reward>`).
|
(`List<QuestObjectiveProgress>`), `Rewards` (`List<Reward>`).
|
||||||
- `QuestObjectiveProgress` — `ObjectiveId`, `Metric`, `Current` (`long?`),
|
- `QuestObjectiveProgress` — `ObjectiveId`, `Metric`, `Current` (`long?`),
|
||||||
`Target` (`long?`), `Completed` (`bool?`).
|
`Target` (`long?`), `Completed` (`bool?`).
|
||||||
@@ -28,6 +28,6 @@ Models:
|
|||||||
`Granted` (`List<Reward>`), `Error`.
|
`Granted` (`List<Reward>`), `Error`.
|
||||||
|
|
||||||
Store purchases already report metrics automatically; the metric strings are
|
Store purchases already report metrics automatically; the metric strings are
|
||||||
built by `QuestMetrics.PurchaseOffer(offerId)` → `purchase.offer:<offerId>`
|
built by `QuestMetrics.PurchaseOffer(offerSlug)` → `purchase.offer:<offerSlug>`
|
||||||
and `QuestMetrics.PurchaseItem(itemId)` → `purchase.item:<itemId>`
|
and `QuestMetrics.PurchaseItem(itemId)` → `purchase.item:<itemId>`
|
||||||
(`RudderSdk.Core.QuestMetrics`, static).
|
(`RudderSdk.Core.QuestMetrics`, static).
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ are dispatched the same way.
|
|||||||
|
|
||||||
## Events and effects
|
## Events and effects
|
||||||
|
|
||||||
All effect types expose `RunId`, `ScenarioId`, `NodeId`, `Data` (`JObject`),
|
All effect types expose `RunId`, `ScenarioSlug`, `NodeId`, `Data` (`JObject`),
|
||||||
and `T Get<T>(string key, T defaultValue = default)` for node data.
|
and `T Get<T>(string key, T defaultValue = default)` for node data.
|
||||||
|
|
||||||
| Event | Effect | Game must |
|
| Event | Effect | Game must |
|
||||||
@@ -39,8 +39,8 @@ and `T Get<T>(string key, T defaultValue = default)` for node data.
|
|||||||
| `OnLeaderboard` | `LeaderboardEffect` | `End()` / `EndAsync()`, `Claim()` / `ClaimAsync()`. `IsResolved` marks resolution. |
|
| `OnLeaderboard` | `LeaderboardEffect` | `End()` / `EndAsync()`, `Claim()` / `ClaimAsync()`. `IsResolved` marks resolution. |
|
||||||
| `OnBattlePass` | `BattlePassEffect` | Drive the effect — see [battlepass.md](battlepass.md). |
|
| `OnBattlePass` | `BattlePassEffect` | Drive the effect — see [battlepass.md](battlepass.md). |
|
||||||
| `OnBattlePassLevel` | `BattlePassLevelEffect` | `Claim()` / `ClaimAsync()` for the level reward; `Level` prop. |
|
| `OnBattlePassLevel` | `BattlePassLevelEffect` | `Claim()` / `ClaimAsync()` for the level reward; `Level` prop. |
|
||||||
| `OnScenarioCompleted` | `ScenarioCompletedEffect` | `RunId`, `ScenarioId`. Log / surface. |
|
| `OnScenarioCompleted` | `ScenarioCompletedEffect` | `RunId`, `ScenarioSlug`. Log / surface. |
|
||||||
| `OnScenarioFailed` | `ScenarioFailedEffect` | `RunId`, `ScenarioId`, `NodeId`, `Exception`. Log / surface. |
|
| `OnScenarioFailed` | `ScenarioFailedEffect` | `RunId`, `ScenarioSlug`, `NodeId`, `Exception`. Log / surface. |
|
||||||
|
|
||||||
Async and sync variants are equivalent; the sync variants fire-and-forget
|
Async and sync variants are equivalent; the sync variants fire-and-forget
|
||||||
the same server callback. The event name must match a scenario trigger
|
the same server callback. The event name must match a scenario trigger
|
||||||
|
|||||||
@@ -7,21 +7,21 @@ Verified against the bundled `Rudder.Core.dll` and
|
|||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
var stores = await client.Stores.ListAsync();
|
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)
|
if (response != null && response.Success != true)
|
||||||
throw new InvalidOperationException(response.Error ?? "Purchase rejected");
|
throw new InvalidOperationException(response.Error ?? "Purchase rejected");
|
||||||
```
|
```
|
||||||
|
|
||||||
- `Task<IReadOnlyList<Store>> ListAsync(CancellationToken = default)`
|
- `Task<IReadOnlyList<Store>> ListAsync(CancellationToken = default)`
|
||||||
- `Task<Store> GetAsync(string slug, CancellationToken = default)`
|
- `Task<Store> GetAsync(string slug, CancellationToken = default)`
|
||||||
- `Task<PurchaseOfferResponse> PurchaseAsync(string storeSlug, string offerId, string idempotencyKey = null, CancellationToken = default)` — generates an idempotency key when omitted.
|
- `Task<PurchaseOfferResponse> PurchaseAsync(string storeSlug, string offerSlug, string idempotencyKey = null, CancellationToken = default)` — generates an idempotency key when omitted.
|
||||||
|
|
||||||
Models:
|
Models:
|
||||||
|
|
||||||
- `Store` — `Id`, `Slug`, `Name`, `Description`, `Status`, `Environment`,
|
- `Store` — `Id`, `Slug`, `Name`, `Description`, `Status`, `Environment`,
|
||||||
`ProjectId`, `ScenarioId`, `Data` (`JToken`), `Offers` (`List<Offer>`),
|
`ProjectId`, `ScenarioId`, `Data` (`JToken`), `Offers` (`List<Offer>`),
|
||||||
`CreatedAt`, `UpdatedAt`.
|
`CreatedAt`, `UpdatedAt`.
|
||||||
- `Offer` — `Id`, `Name`, `Price` (`OfferPrice`), `Contents`
|
- `Offer` — `Id`, `Slug`, `Name`, `Price` (`OfferPrice`), `Contents`
|
||||||
(`List<OfferContent>`), `MaxPurchases` (`int?`), `CreatedAt`, `UpdatedAt`.
|
(`List<OfferContent>`), `MaxPurchases` (`int?`), `CreatedAt`, `UpdatedAt`.
|
||||||
- `OfferPrice` — `Currency`, `Amount` (`long?`).
|
- `OfferPrice` — `Currency`, `Amount` (`long?`).
|
||||||
- `OfferContent` — `ItemId`, `Amount` (`long?`).
|
- `OfferContent` — `ItemId`, `Amount` (`long?`).
|
||||||
@@ -30,7 +30,7 @@ Models:
|
|||||||
Check `Success != true` (nullable bool), not `!Success`. After a purchase,
|
Check `Success != true` (nullable bool), not `!Success`. After a purchase,
|
||||||
reload wallets (`Player.GetProfileAsync`) and inventory — there is no
|
reload wallets (`Player.GetProfileAsync`) and inventory — there is no
|
||||||
`onChange` callback. Purchases auto-report quest metrics
|
`onChange` callback. Purchases auto-report quest metrics
|
||||||
`purchase.offer:<offerId>` / `purchase.item:<itemId>` (see
|
`purchase.offer:<offerSlug>` / `purchase.item:<itemId>` (see
|
||||||
[quests.md](quests.md)). The store slug must exist in the dashboard, else 404.
|
[quests.md](quests.md)). The store slug must exist in the dashboard, else 404.
|
||||||
|
|
||||||
## Inventory — `client.Inventory` (`InventoryService`)
|
## Inventory — `client.Inventory` (`InventoryService`)
|
||||||
|
|||||||
Reference in New Issue
Block a user