2.0.0: slug-based quests, scenarios and offers; environment carried by the token; regenerated models
Claude-Session: https://claude.ai/code/session_01SMCvdwDmuxoaqGgvGBLk1V
This commit is contained in:
@@ -14,8 +14,8 @@ public class AddBattlePassXpRequest
|
|||||||
[JsonProperty("runId")]
|
[JsonProperty("runId")]
|
||||||
public string? RunId { get; set; }
|
public string? RunId { get; set; }
|
||||||
|
|
||||||
[JsonProperty("scenarioId")]
|
[JsonProperty("scenarioSlug")]
|
||||||
public string? ScenarioId { get; set; }
|
public string? ScenarioSlug { get; set; }
|
||||||
|
|
||||||
[JsonProperty("source")]
|
[JsonProperty("source")]
|
||||||
public string? Source { get; set; }
|
public string? Source { get; set; }
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ public class ClaimBattlePassRewardRequest
|
|||||||
[JsonProperty("runId")]
|
[JsonProperty("runId")]
|
||||||
public string? RunId { get; set; }
|
public string? RunId { get; set; }
|
||||||
|
|
||||||
[JsonProperty("scenarioId")]
|
[JsonProperty("scenarioSlug")]
|
||||||
public string? ScenarioId { get; set; }
|
public string? ScenarioSlug { get; set; }
|
||||||
|
|
||||||
[JsonProperty("track")]
|
[JsonProperty("track")]
|
||||||
public string? Track { get; set; }
|
public string? Track { get; set; }
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ public class GetBattlePassProgressRequest
|
|||||||
[JsonProperty("nodeId")]
|
[JsonProperty("nodeId")]
|
||||||
public string? NodeId { get; set; }
|
public string? NodeId { get; set; }
|
||||||
|
|
||||||
[JsonProperty("scenarioId")]
|
[JsonProperty("scenarioSlug")]
|
||||||
public string? ScenarioId { get; set; }
|
public string? ScenarioSlug { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ public class PurchaseBattlePassPremiumRequest
|
|||||||
[JsonProperty("runId")]
|
[JsonProperty("runId")]
|
||||||
public string? RunId { get; set; }
|
public string? RunId { get; set; }
|
||||||
|
|
||||||
[JsonProperty("scenarioId")]
|
[JsonProperty("scenarioSlug")]
|
||||||
public string? ScenarioId { get; set; }
|
public string? ScenarioSlug { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,36 @@
|
|||||||
# Changelog
|
# 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
|
## 1.0.0
|
||||||
|
|
||||||
Breaking change — major bump. The local scenario engine is replaced by a
|
Breaking change — major bump. The local scenario engine is replaced by a
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace RudderSdk.Core.Models.Quests;
|
|||||||
|
|
||||||
public class ClaimQuestRequest
|
public class ClaimQuestRequest
|
||||||
{
|
{
|
||||||
[JsonProperty("questId")]
|
[JsonProperty("questSlug")]
|
||||||
public string? QuestId { get; set; }
|
public string? QuestSlug { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -7,9 +7,6 @@ namespace RudderSdk.Core.Models.Quests;
|
|||||||
|
|
||||||
public class Quest
|
public class Quest
|
||||||
{
|
{
|
||||||
[JsonProperty("id")]
|
|
||||||
public string? Id { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("name")]
|
[JsonProperty("name")]
|
||||||
public string? Name { get; set; }
|
public string? Name { get; set; }
|
||||||
|
|
||||||
@@ -19,6 +16,9 @@ public class Quest
|
|||||||
[JsonProperty("rewards")]
|
[JsonProperty("rewards")]
|
||||||
public List<Reward>? Rewards { get; set; }
|
public List<Reward>? Rewards { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("slug")]
|
||||||
|
public string? Slug { get; set; }
|
||||||
|
|
||||||
[JsonProperty("status")]
|
[JsonProperty("status")]
|
||||||
public string? Status { get; set; }
|
public string? Status { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ namespace RudderSdk.Core.Models.Quests;
|
|||||||
|
|
||||||
public class ReportQuestProgressResponse
|
public class ReportQuestProgressResponse
|
||||||
{
|
{
|
||||||
[JsonProperty("completedQuestIds")]
|
[JsonProperty("completedQuestSlugs")]
|
||||||
public List<string>? CompletedQuestIds { get; set; }
|
public List<string>? CompletedQuestSlugs { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ var quests = await client.Quests.ListAsync();
|
|||||||
foreach (var quest in quests)
|
foreach (var quest in quests)
|
||||||
{
|
{
|
||||||
if (quest.Status == "completed")
|
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
|
// 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;
|
Purchase metrics are reported automatically by store purchases;
|
||||||
`QuestMetrics.PurchaseOffer(offerId)` / `QuestMetrics.PurchaseItem(itemId)`
|
`QuestMetrics.PurchaseOffer(offerSlug)` / `QuestMetrics.PurchaseItem(itemId)`
|
||||||
name the format (`purchase.offer:<offerId>`, `purchase.item:<itemId>`) so
|
name the format (`purchase.offer:<offerSlug>`, `purchase.item:<itemId>`) so
|
||||||
quest configs and client code agree on it.
|
quest configs and client code agree on it.
|
||||||
|
|
||||||
## Sessions
|
## Sessions
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
<AssemblyName>Rudder.Core</AssemblyName>
|
<AssemblyName>Rudder.Core</AssemblyName>
|
||||||
<RootNamespace>RudderSdk.Core</RootNamespace>
|
<RootNamespace>RudderSdk.Core</RootNamespace>
|
||||||
<PackageId>Rudder.Core</PackageId>
|
<PackageId>Rudder.Core</PackageId>
|
||||||
<Version>1.0.0</Version>
|
<Version>2.0.0</Version>
|
||||||
<Authors>Rudder</Authors>
|
<Authors>Rudder</Authors>
|
||||||
<Description>Rudder LiveOps client SDK for .NET: auth, player, stores, battle pass, quests, leaderboards, inventory, remote config, scenarios and storage.</Description>
|
<Description>Rudder LiveOps client SDK for .NET: auth, player, stores, battle pass, quests, leaderboards, inventory, remote config, scenarios and storage.</Description>
|
||||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ public class HandleScenarioCallbackRequest
|
|||||||
[JsonProperty("runId")]
|
[JsonProperty("runId")]
|
||||||
public string? RunId { get; set; }
|
public string? RunId { get; set; }
|
||||||
|
|
||||||
[JsonProperty("scenarioId")]
|
[JsonProperty("scenarioSlug")]
|
||||||
public string? ScenarioId { get; set; }
|
public string? ScenarioSlug { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ public class PendingEffect
|
|||||||
[JsonProperty("runId")]
|
[JsonProperty("runId")]
|
||||||
public string RunId { get; set; } = null!;
|
public string RunId { get; set; } = null!;
|
||||||
|
|
||||||
[JsonProperty("scenarioId")]
|
[JsonProperty("scenarioSlug")]
|
||||||
public string ScenarioId { get; set; } = null!;
|
public string ScenarioSlug { get; set; } = null!;
|
||||||
|
|
||||||
[JsonProperty("type")]
|
[JsonProperty("type")]
|
||||||
public string Type { get; set; } = null!;
|
public string Type { get; set; } = null!;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public class UpdateScenarioCounterRequest
|
|||||||
[JsonProperty("runId")]
|
[JsonProperty("runId")]
|
||||||
public string? RunId { get; set; }
|
public string? RunId { get; set; }
|
||||||
|
|
||||||
[JsonProperty("scenarioId")]
|
[JsonProperty("scenarioSlug")]
|
||||||
public string? ScenarioId { get; set; }
|
public string? ScenarioSlug { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,11 +24,11 @@ public sealed class BattlePassService
|
|||||||
internal BattlePassService(RudderClient client) => _client = client;
|
internal BattlePassService(RudderClient client) => _client = client;
|
||||||
|
|
||||||
/// <summary>Reads current progress: xp, level, premium ownership, claimed tiers.</summary>
|
/// <summary>Reads current progress: xp, level, premium ownership, claimed tiers.</summary>
|
||||||
public Task<GetBattlePassProgressResponse> GetProgressAsync(string scenarioId, string nodeId, CancellationToken cancellationToken = default)
|
public Task<GetBattlePassProgressResponse> GetProgressAsync(string scenarioSlug, string nodeId, CancellationToken cancellationToken = default)
|
||||||
=> _client.SendAsync<GetBattlePassProgressRequest, GetBattlePassProgressResponse>(
|
=> _client.SendAsync<GetBattlePassProgressRequest, GetBattlePassProgressResponse>(
|
||||||
"POST",
|
"POST",
|
||||||
"/sdk/v1/battlepass/progress",
|
"/sdk/v1/battlepass/progress",
|
||||||
new GetBattlePassProgressRequest { ScenarioId = scenarioId, NodeId = nodeId },
|
new GetBattlePassProgressRequest { ScenarioSlug = scenarioSlug, NodeId = nodeId },
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
/// <summary>Credits xp and returns the new xp/level and level-up flags.</summary>
|
/// <summary>Credits xp and returns the new xp/level and level-up flags.</summary>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public sealed class BattlePassEffect
|
|||||||
public string RunId => _handle.RunId;
|
public string RunId => _handle.RunId;
|
||||||
|
|
||||||
/// <summary>Scenario id.</summary>
|
/// <summary>Scenario id.</summary>
|
||||||
public string ScenarioId => _handle.ScenarioId;
|
public string ScenarioSlug => _handle.ScenarioSlug;
|
||||||
|
|
||||||
/// <summary>Node id.</summary>
|
/// <summary>Node id.</summary>
|
||||||
public string NodeId => _handle.NodeId;
|
public string NodeId => _handle.NodeId;
|
||||||
@@ -39,13 +39,13 @@ public sealed class BattlePassEffect
|
|||||||
|
|
||||||
/// <summary>Reads current progress (xp, level, premium ownership, claimed tiers) for this node.</summary>
|
/// <summary>Reads current progress (xp, level, premium ownership, claimed tiers) for this node.</summary>
|
||||||
public Task<GetBattlePassProgressResponse> GetProgressAsync(CancellationToken cancellationToken = default)
|
public Task<GetBattlePassProgressResponse> GetProgressAsync(CancellationToken cancellationToken = default)
|
||||||
=> _battlePass.GetProgressAsync(_handle.ScenarioId, _handle.NodeId, cancellationToken);
|
=> _battlePass.GetProgressAsync(_handle.ScenarioSlug, _handle.NodeId, cancellationToken);
|
||||||
|
|
||||||
/// <summary>Credits xp from a configured source.</summary>
|
/// <summary>Credits xp from a configured source.</summary>
|
||||||
public Task<AddBattlePassXpResponse> AddXpAsync(string source, long amount, CancellationToken cancellationToken = default)
|
public Task<AddBattlePassXpResponse> AddXpAsync(string source, long amount, CancellationToken cancellationToken = default)
|
||||||
=> _battlePass.AddXpAsync(new AddBattlePassXpRequest
|
=> _battlePass.AddXpAsync(new AddBattlePassXpRequest
|
||||||
{
|
{
|
||||||
ScenarioId = _handle.ScenarioId,
|
ScenarioSlug = _handle.ScenarioSlug,
|
||||||
NodeId = _handle.NodeId,
|
NodeId = _handle.NodeId,
|
||||||
RunId = _handle.RunId,
|
RunId = _handle.RunId,
|
||||||
Source = source,
|
Source = source,
|
||||||
@@ -59,7 +59,7 @@ public sealed class BattlePassEffect
|
|||||||
public Task<ClaimBattlePassRewardResponse> ClaimRewardAsync(int level, string track, CancellationToken cancellationToken = default)
|
public Task<ClaimBattlePassRewardResponse> ClaimRewardAsync(int level, string track, CancellationToken cancellationToken = default)
|
||||||
=> _battlePass.ClaimRewardAsync(new ClaimBattlePassRewardRequest
|
=> _battlePass.ClaimRewardAsync(new ClaimBattlePassRewardRequest
|
||||||
{
|
{
|
||||||
ScenarioId = _handle.ScenarioId,
|
ScenarioSlug = _handle.ScenarioSlug,
|
||||||
NodeId = _handle.NodeId,
|
NodeId = _handle.NodeId,
|
||||||
RunId = _handle.RunId,
|
RunId = _handle.RunId,
|
||||||
Level = level,
|
Level = level,
|
||||||
@@ -71,7 +71,7 @@ public sealed class BattlePassEffect
|
|||||||
{
|
{
|
||||||
var response = await _battlePass.PurchasePremiumAsync(new PurchaseBattlePassPremiumRequest
|
var response = await _battlePass.PurchasePremiumAsync(new PurchaseBattlePassPremiumRequest
|
||||||
{
|
{
|
||||||
ScenarioId = _handle.ScenarioId,
|
ScenarioSlug = _handle.ScenarioSlug,
|
||||||
NodeId = _handle.NodeId,
|
NodeId = _handle.NodeId,
|
||||||
RunId = _handle.RunId,
|
RunId = _handle.RunId,
|
||||||
IdempotencyKey = Guid.NewGuid().ToString()
|
IdempotencyKey = Guid.NewGuid().ToString()
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public sealed class BattlePassLevelEffect
|
|||||||
public string RunId => _handle.RunId;
|
public string RunId => _handle.RunId;
|
||||||
|
|
||||||
/// <summary>Scenario id.</summary>
|
/// <summary>Scenario id.</summary>
|
||||||
public string ScenarioId => _handle.ScenarioId;
|
public string ScenarioSlug => _handle.ScenarioSlug;
|
||||||
|
|
||||||
/// <summary>Node id.</summary>
|
/// <summary>Node id.</summary>
|
||||||
public string NodeId => _handle.NodeId;
|
public string NodeId => _handle.NodeId;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public sealed class LeaderboardEffect
|
|||||||
public string RunId => _handle.RunId;
|
public string RunId => _handle.RunId;
|
||||||
|
|
||||||
/// <summary>Scenario id.</summary>
|
/// <summary>Scenario id.</summary>
|
||||||
public string ScenarioId => _handle.ScenarioId;
|
public string ScenarioSlug => _handle.ScenarioSlug;
|
||||||
|
|
||||||
/// <summary>Node id.</summary>
|
/// <summary>Node id.</summary>
|
||||||
public string NodeId => _handle.NodeId;
|
public string NodeId => _handle.NodeId;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public sealed class NotificationEffect
|
|||||||
public string RunId => _handle.RunId;
|
public string RunId => _handle.RunId;
|
||||||
|
|
||||||
/// <summary>Scenario id.</summary>
|
/// <summary>Scenario id.</summary>
|
||||||
public string ScenarioId => _handle.ScenarioId;
|
public string ScenarioSlug => _handle.ScenarioSlug;
|
||||||
|
|
||||||
/// <summary>Node id.</summary>
|
/// <summary>Node id.</summary>
|
||||||
public string NodeId => _handle.NodeId;
|
public string NodeId => _handle.NodeId;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public sealed class QuestEffect
|
|||||||
public string RunId => _handle.RunId;
|
public string RunId => _handle.RunId;
|
||||||
|
|
||||||
/// <summary>Scenario id.</summary>
|
/// <summary>Scenario id.</summary>
|
||||||
public string ScenarioId => _handle.ScenarioId;
|
public string ScenarioSlug => _handle.ScenarioSlug;
|
||||||
|
|
||||||
/// <summary>Node id.</summary>
|
/// <summary>Node id.</summary>
|
||||||
public string NodeId => _handle.NodeId;
|
public string NodeId => _handle.NodeId;
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ namespace RudderSdk.Core;
|
|||||||
/// <summary>Payload of <see cref="EffectsService.OnScenarioCompleted"/>.</summary>
|
/// <summary>Payload of <see cref="EffectsService.OnScenarioCompleted"/>.</summary>
|
||||||
public sealed class ScenarioCompletedEffect
|
public sealed class ScenarioCompletedEffect
|
||||||
{
|
{
|
||||||
internal ScenarioCompletedEffect(string runId, string scenarioId)
|
internal ScenarioCompletedEffect(string runId, string scenarioSlug)
|
||||||
{
|
{
|
||||||
RunId = runId;
|
RunId = runId;
|
||||||
ScenarioId = scenarioId;
|
ScenarioSlug = scenarioSlug;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Server-issued run id.</summary>
|
/// <summary>Server-issued run id.</summary>
|
||||||
public string RunId { get; }
|
public string RunId { get; }
|
||||||
|
|
||||||
/// <summary>Scenario id.</summary>
|
/// <summary>Scenario id.</summary>
|
||||||
public string ScenarioId { get; }
|
public string ScenarioSlug { get; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ namespace RudderSdk.Core;
|
|||||||
/// <summary>Payload of <see cref="EffectsService.OnScenarioFailed"/>.</summary>
|
/// <summary>Payload of <see cref="EffectsService.OnScenarioFailed"/>.</summary>
|
||||||
public sealed class ScenarioFailedEffect
|
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;
|
RunId = runId;
|
||||||
ScenarioId = scenarioId;
|
ScenarioSlug = scenarioSlug;
|
||||||
NodeId = nodeId;
|
NodeId = nodeId;
|
||||||
Exception = exception;
|
Exception = exception;
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@ public sealed class ScenarioFailedEffect
|
|||||||
public string RunId { get; }
|
public string RunId { get; }
|
||||||
|
|
||||||
/// <summary>Scenario id.</summary>
|
/// <summary>Scenario id.</summary>
|
||||||
public string ScenarioId { get; }
|
public string ScenarioSlug { get; }
|
||||||
|
|
||||||
/// <summary>Node the failure happened at.</summary>
|
/// <summary>Node the failure happened at.</summary>
|
||||||
public string NodeId { get; }
|
public string NodeId { get; }
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public sealed class StoreOfferEffect
|
|||||||
public string RunId => _handle.RunId;
|
public string RunId => _handle.RunId;
|
||||||
|
|
||||||
/// <summary>Scenario id.</summary>
|
/// <summary>Scenario id.</summary>
|
||||||
public string ScenarioId => _handle.ScenarioId;
|
public string ScenarioSlug => _handle.ScenarioSlug;
|
||||||
|
|
||||||
/// <summary>Node id.</summary>
|
/// <summary>Node id.</summary>
|
||||||
public string NodeId => _handle.NodeId;
|
public string NodeId => _handle.NodeId;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public sealed class WaitEffect
|
|||||||
public string RunId => _handle.RunId;
|
public string RunId => _handle.RunId;
|
||||||
|
|
||||||
/// <summary>Scenario id.</summary>
|
/// <summary>Scenario id.</summary>
|
||||||
public string ScenarioId => _handle.ScenarioId;
|
public string ScenarioSlug => _handle.ScenarioSlug;
|
||||||
|
|
||||||
/// <summary>Node id.</summary>
|
/// <summary>Node id.</summary>
|
||||||
public string NodeId => _handle.NodeId;
|
public string NodeId => _handle.NodeId;
|
||||||
|
|||||||
+11
-11
@@ -101,7 +101,7 @@ public sealed class EffectsService
|
|||||||
var key = (effect.RunId, effect.NodeId);
|
var key = (effect.RunId, effect.NodeId);
|
||||||
if (_seen.ContainsKey(key))
|
if (_seen.ContainsKey(key))
|
||||||
continue;
|
continue;
|
||||||
_seen[key] = effect.ScenarioId;
|
_seen[key] = effect.ScenarioSlug;
|
||||||
|
|
||||||
if (string.Equals(effect.Type, EffectTypes.Wait, StringComparison.Ordinal))
|
if (string.Equals(effect.Type, EffectTypes.Wait, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
@@ -126,7 +126,7 @@ public sealed class EffectsService
|
|||||||
"/sdk/v1/scenarios/callback",
|
"/sdk/v1/scenarios/callback",
|
||||||
new HandleScenarioCallbackRequest
|
new HandleScenarioCallbackRequest
|
||||||
{
|
{
|
||||||
ScenarioId = source.ScenarioId,
|
ScenarioSlug = source.ScenarioSlug,
|
||||||
NodeId = source.NodeId,
|
NodeId = source.NodeId,
|
||||||
Handle = handle,
|
Handle = handle,
|
||||||
RunId = source.RunId
|
RunId = source.RunId
|
||||||
@@ -136,13 +136,13 @@ public sealed class EffectsService
|
|||||||
ForgetWait(source.RunId, source.NodeId);
|
ForgetWait(source.RunId, source.NodeId);
|
||||||
var next = ReadEffect(response?.Effect);
|
var next = ReadEffect(response?.Effect);
|
||||||
if (next == null)
|
if (next == null)
|
||||||
Emit(OnScenarioCompleted, new ScenarioCompletedEffect(source.RunId, source.ScenarioId));
|
Emit(OnScenarioCompleted, new ScenarioCompletedEffect(source.RunId, source.ScenarioSlug));
|
||||||
else
|
else
|
||||||
Ingest(new[] { next });
|
Ingest(new[] { next });
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (IsDefinitiveRejection(ex))
|
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",
|
"/sdk/v1/scenarios/counter",
|
||||||
new UpdateScenarioCounterRequest
|
new UpdateScenarioCounterRequest
|
||||||
{
|
{
|
||||||
ScenarioId = source.ScenarioId,
|
ScenarioSlug = source.ScenarioSlug,
|
||||||
NodeId = source.NodeId,
|
NodeId = source.NodeId,
|
||||||
CounterKey = counterKey,
|
CounterKey = counterKey,
|
||||||
Amount = amount,
|
Amount = amount,
|
||||||
@@ -173,13 +173,13 @@ public sealed class EffectsService
|
|||||||
ForgetWait(source.RunId, source.NodeId);
|
ForgetWait(source.RunId, source.NodeId);
|
||||||
var next = ReadEffect(response.Effect);
|
var next = ReadEffect(response.Effect);
|
||||||
if (next == null)
|
if (next == null)
|
||||||
Emit(OnScenarioCompleted, new ScenarioCompletedEffect(source.RunId, source.ScenarioId));
|
Emit(OnScenarioCompleted, new ScenarioCompletedEffect(source.RunId, source.ScenarioSlug));
|
||||||
else
|
else
|
||||||
Ingest(new[] { next });
|
Ingest(new[] { next });
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (IsDefinitiveRejection(ex))
|
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -274,14 +274,14 @@ public sealed class EffectsService
|
|||||||
OnScenarioFailed,
|
OnScenarioFailed,
|
||||||
new ScenarioFailedEffect(
|
new ScenarioFailedEffect(
|
||||||
effect.RunId,
|
effect.RunId,
|
||||||
effect.ScenarioId,
|
effect.ScenarioSlug,
|
||||||
effect.NodeId,
|
effect.NodeId,
|
||||||
new Exception($"Unsupported scenario node type '{effect.Type}'")));
|
new Exception($"Unsupported scenario node type '{effect.Type}'")));
|
||||||
break;
|
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)
|
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<PendingEffect>? effects)
|
private void Reconcile(IEnumerable<PendingEffect>? effects)
|
||||||
@@ -424,7 +424,7 @@ internal sealed class EffectHandle
|
|||||||
|
|
||||||
public string RunId => _effect.RunId;
|
public string RunId => _effect.RunId;
|
||||||
|
|
||||||
public string ScenarioId => _effect.ScenarioId;
|
public string ScenarioSlug => _effect.ScenarioSlug;
|
||||||
|
|
||||||
public string NodeId => _effect.NodeId;
|
public string NodeId => _effect.NodeId;
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace RudderSdk.Core;
|
|||||||
public static class QuestMetrics
|
public static class QuestMetrics
|
||||||
{
|
{
|
||||||
/// <summary>Metric for purchasing a store offer; auto-reported on purchase.</summary>
|
/// <summary>Metric for purchasing a store offer; auto-reported on purchase.</summary>
|
||||||
public static string PurchaseOffer(string offerId) => $"purchase.offer:{offerId}";
|
public static string PurchaseOffer(string offerSlug) => $"purchase.offer:{offerSlug}";
|
||||||
|
|
||||||
/// <summary>Metric for purchasing a catalog item; auto-reported on purchase.</summary>
|
/// <summary>Metric for purchasing a catalog item; auto-reported on purchase.</summary>
|
||||||
public static string PurchaseItem(string itemId) => $"purchase.item:{itemId}";
|
public static string PurchaseItem(string itemId) => $"purchase.item:{itemId}";
|
||||||
|
|||||||
@@ -27,11 +27,11 @@ public sealed class QuestsService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Claims a completed quest's rewards (idempotent server-side).</summary>
|
/// <summary>Claims a completed quest's rewards (idempotent server-side).</summary>
|
||||||
public Task<ClaimQuestResponse> ClaimAsync(string questId, CancellationToken cancellationToken = default)
|
public Task<ClaimQuestResponse> ClaimAsync(string questSlug, CancellationToken cancellationToken = default)
|
||||||
=> _client.SendAsync<ClaimQuestRequest, ClaimQuestResponse>(
|
=> _client.SendAsync<ClaimQuestRequest, ClaimQuestResponse>(
|
||||||
"POST",
|
"POST",
|
||||||
"/sdk/v1/quests/claim",
|
"/sdk/v1/quests/claim",
|
||||||
new ClaimQuestRequest { QuestId = questId },
|
new ClaimQuestRequest { QuestSlug = questSlug },
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
/// <summary>Reports progress for a metric and returns the ids of quests completed by this report.</summary>
|
/// <summary>Reports progress for a metric and returns the ids of quests completed by this report.</summary>
|
||||||
@@ -43,6 +43,6 @@ public sealed class QuestsService
|
|||||||
new ReportQuestProgressRequest { Metric = metric, Amount = amount },
|
new ReportQuestProgressRequest { Metric = metric, Amount = amount },
|
||||||
cancellationToken).ConfigureAwait(false);
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
return response?.CompletedQuestIds ?? new List<string>();
|
return response?.CompletedQuestSlugs ?? new List<string>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,17 +35,17 @@ public sealed class StoresService
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Task<PurchaseOfferResponse> PurchaseAsync(
|
public Task<PurchaseOfferResponse> PurchaseAsync(
|
||||||
string storeSlug,
|
string storeSlug,
|
||||||
string offerId,
|
string offerSlug,
|
||||||
string? idempotencyKey = null,
|
string? idempotencyKey = null,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
return _client.SendAsync<PurchaseOfferRequest, PurchaseOfferResponse>(
|
return _client.SendAsync<PurchaseOfferRequest, PurchaseOfferResponse>(
|
||||||
"POST",
|
"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
|
new PurchaseOfferRequest
|
||||||
{
|
{
|
||||||
StoreSlug = storeSlug,
|
StoreSlug = storeSlug,
|
||||||
OfferId = offerId,
|
OfferSlug = offerSlug,
|
||||||
IdempotencyKey = idempotencyKey ?? Guid.NewGuid().ToString()
|
IdempotencyKey = idempotencyKey ?? Guid.NewGuid().ToString()
|
||||||
},
|
},
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ public class Offer
|
|||||||
[JsonProperty("price")]
|
[JsonProperty("price")]
|
||||||
public OfferPrice? Price { get; set; }
|
public OfferPrice? Price { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("slug")]
|
||||||
|
public string? Slug { get; set; }
|
||||||
|
|
||||||
[JsonProperty("updatedAt")]
|
[JsonProperty("updatedAt")]
|
||||||
public DateTimeOffset? UpdatedAt { get; set; }
|
public DateTimeOffset? UpdatedAt { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ public class PurchaseOfferRequest
|
|||||||
[JsonProperty("idempotencyKey")]
|
[JsonProperty("idempotencyKey")]
|
||||||
public string? IdempotencyKey { get; set; }
|
public string? IdempotencyKey { get; set; }
|
||||||
|
|
||||||
[JsonProperty("offerId")]
|
[JsonProperty("offerSlug")]
|
||||||
public string? OfferId { get; set; }
|
public string? OfferSlug { get; set; }
|
||||||
|
|
||||||
[JsonProperty("storeSlug")]
|
[JsonProperty("storeSlug")]
|
||||||
public string? StoreSlug { get; set; }
|
public string? StoreSlug { get; set; }
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ realtime websocket client in Rudder.Core.
|
|||||||
Important cross-cutting facts:
|
Important cross-cutting facts:
|
||||||
|
|
||||||
- Battle pass state is tied to a scenario battle-pass node: every
|
- 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
|
`RunId`). During a scenario run, `BattlePassEffect` supplies them — prefer
|
||||||
the effect API inside a run.
|
the effect API inside a run.
|
||||||
- Global quests (`client.Quests`) are distinct from scenario quest nodes
|
- 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
|
`RudderAuthState`: `SignedIn`, `SignedOut`. The event fires after a
|
||||||
successful login and after logout or a failed token refresh.
|
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
|
## Endpoints
|
||||||
|
|
||||||
- `POST /sdk/v1/authorization/device` — body `{key, deviceId, region,
|
- `POST /sdk/v1/authorization/device` — body `{key, deviceId, region,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Source: `Services/BattlePassService.cs`,
|
|||||||
(generated DTOs).
|
(generated DTOs).
|
||||||
|
|
||||||
Battle pass state is tied to a scenario battle-pass node, so every call
|
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
|
`Effects.OnBattlePass` hands you a `BattlePassEffect` that supplies these
|
||||||
ids — prefer it over calling the service directly.
|
ids — prefer it over calling the service directly.
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ public const string TrackPremium = "premium";
|
|||||||
|
|
||||||
// POST /sdk/v1/battlepass/progress
|
// POST /sdk/v1/battlepass/progress
|
||||||
public Task<GetBattlePassProgressResponse> GetProgressAsync(
|
public Task<GetBattlePassProgressResponse> GetProgressAsync(
|
||||||
string scenarioId, string nodeId, CancellationToken cancellationToken = default);
|
string scenarioSlug, string nodeId, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
// POST /sdk/v1/battlepass/xp
|
// POST /sdk/v1/battlepass/xp
|
||||||
public Task<AddBattlePassXpResponse> AddXpAsync(
|
public Task<AddBattlePassXpResponse> AddXpAsync(
|
||||||
@@ -40,7 +40,7 @@ public Task<PurchaseBattlePassPremiumResponse> PurchasePremiumAsync(
|
|||||||
public class GetBattlePassProgressRequest
|
public class GetBattlePassProgressRequest
|
||||||
{
|
{
|
||||||
public string? NodeId { get; set; } // "nodeId"
|
public string? NodeId { get; set; } // "nodeId"
|
||||||
public string? ScenarioId { get; set; } // "scenarioId"
|
public string? ScenarioSlug { get; set; } // "scenarioSlug"
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AddBattlePassXpRequest
|
public class AddBattlePassXpRequest
|
||||||
@@ -48,7 +48,7 @@ public class AddBattlePassXpRequest
|
|||||||
public long? Amount { get; set; } // "amount"
|
public long? Amount { get; set; } // "amount"
|
||||||
public string? NodeId { get; set; } // "nodeId"
|
public string? NodeId { get; set; } // "nodeId"
|
||||||
public string? RunId { get; set; } // "runId"
|
public string? RunId { get; set; } // "runId"
|
||||||
public string? ScenarioId { get; set; } // "scenarioId"
|
public string? ScenarioSlug { get; set; } // "scenarioSlug"
|
||||||
public string? Source { get; set; } // "source"
|
public string? Source { get; set; } // "source"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ public class ClaimBattlePassRewardRequest
|
|||||||
public int? Level { get; set; } // "level"
|
public int? Level { get; set; } // "level"
|
||||||
public string? NodeId { get; set; } // "nodeId"
|
public string? NodeId { get; set; } // "nodeId"
|
||||||
public string? RunId { get; set; } // "runId"
|
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
|
public string? Track { get; set; } // "track" — TrackFree / TrackPremium
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ public class PurchaseBattlePassPremiumRequest
|
|||||||
public string? IdempotencyKey { get; set; } // "idempotencyKey"
|
public string? IdempotencyKey { get; set; } // "idempotencyKey"
|
||||||
public string? NodeId { get; set; } // "nodeId"
|
public string? NodeId { get; set; } // "nodeId"
|
||||||
public string? RunId { get; set; } // "runId"
|
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
|
```csharp
|
||||||
public string RunId { get; }
|
public string RunId { get; }
|
||||||
public string ScenarioId { get; }
|
public string ScenarioSlug { get; }
|
||||||
public string NodeId { get; }
|
public string NodeId { get; }
|
||||||
public T Get<T>(string key, T defaultValue = default!);
|
public T Get<T>(string key, T defaultValue = default!);
|
||||||
|
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ scenarios.md.
|
|||||||
public Task<IReadOnlyList<Quest>> ListAsync(CancellationToken cancellationToken = default);
|
public Task<IReadOnlyList<Quest>> ListAsync(CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
// POST /sdk/v1/quests/claim — claims a completed quest's rewards (idempotent server-side)
|
// 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
|
// 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(
|
public Task<IReadOnlyList<string>> ReportProgressAsync(
|
||||||
string metric, long amount, CancellationToken cancellationToken = default);
|
string metric, long amount, CancellationToken cancellationToken = default);
|
||||||
```
|
```
|
||||||
@@ -27,7 +27,7 @@ public Task<IReadOnlyList<string>> ReportProgressAsync(
|
|||||||
```csharp
|
```csharp
|
||||||
public static class QuestMetrics
|
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}"
|
public static string PurchaseItem(string itemId); // "purchase.item:{itemId}"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -40,10 +40,10 @@ metric is a custom string passed to `ReportProgressAsync`.
|
|||||||
```csharp
|
```csharp
|
||||||
public class Quest
|
public class Quest
|
||||||
{
|
{
|
||||||
public string? Id { get; set; } // "id"
|
|
||||||
public string? Name { get; set; } // "name"
|
public string? Name { get; set; } // "name"
|
||||||
public List<QuestObjectiveProgress>? Objectives { get; set; }// "objectives"
|
public List<QuestObjectiveProgress>? Objectives { get; set; }// "objectives"
|
||||||
public List<Reward>? Rewards { get; set; } // "rewards"
|
public List<Reward>? Rewards { get; set; } // "rewards"
|
||||||
|
public string? Slug { get; set; } // "slug" — stable across environments
|
||||||
public string? Status { get; set; } // "status"
|
public string? Status { get; set; } // "status"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ client.Effects.OnNotification += n =>
|
|||||||
await client.Scenario.TriggerAsync("level_complete");
|
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
|
and `Get<T>(key, defaultValue)` for reading node data. Completion methods
|
||||||
have an `Async` variant (awaitable) and a fire-and-forget variant.
|
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 string Name { get; } // node data "name"
|
||||||
public IReadOnlyList<JObject> Objectives { get; } // node data "objectives"
|
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 Task ReportProgressAsync(string objectiveId, long amount = 1, CancellationToken ct = default);
|
||||||
public void ReportProgress(string objectiveId, long amount = 1);
|
public void ReportProgress(string objectiveId, long amount = 1);
|
||||||
```
|
```
|
||||||
@@ -166,7 +166,7 @@ completed counter response may carry the next `PendingEffect`.
|
|||||||
|
|
||||||
### `BattlePassEffect`
|
### `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.
|
calling `client.BattlePass` by hand. See also reference/battlepass.md.
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
@@ -199,13 +199,13 @@ configured level.
|
|||||||
public sealed class ScenarioCompletedEffect
|
public sealed class ScenarioCompletedEffect
|
||||||
{
|
{
|
||||||
public string RunId { get; }
|
public string RunId { get; }
|
||||||
public string ScenarioId { get; }
|
public string ScenarioSlug { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ScenarioFailedEffect
|
public sealed class ScenarioFailedEffect
|
||||||
{
|
{
|
||||||
public string RunId { get; }
|
public string RunId { get; }
|
||||||
public string ScenarioId { get; }
|
public string ScenarioSlug { get; }
|
||||||
public string NodeId { get; }
|
public string NodeId { get; }
|
||||||
public Exception Exception { get; }
|
public Exception Exception { get; }
|
||||||
}
|
}
|
||||||
@@ -219,7 +219,7 @@ advanced a wait, the run expired, or another device completed it.
|
|||||||
## Reliability
|
## Reliability
|
||||||
|
|
||||||
- Completion methods POST `/sdk/v1/scenarios/callback` with
|
- 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`,
|
`onPurchase`, `onDecline`, `onEnd`, `onClaim`, `onComplete`, `onLevelUp`,
|
||||||
`onPremiumPurchase`. A non-null `effect` in the response is ingested as
|
`onPremiumPurchase`. A non-null `effect` in the response is ingested as
|
||||||
the next node; a null/missing effect completes the run.
|
the next node; a null/missing effect completes the run.
|
||||||
@@ -243,7 +243,7 @@ public class PendingEffect
|
|||||||
public JToken Data { get; set; }
|
public JToken Data { get; set; }
|
||||||
public string NodeId { get; set; }
|
public string NodeId { get; set; }
|
||||||
public string RunId { get; set; }
|
public string RunId { get; set; }
|
||||||
public string ScenarioId { get; set; }
|
public string ScenarioSlug { get; set; }
|
||||||
public string Type { get; set; }
|
public string Type { get; set; }
|
||||||
public DateTimeOffset? WaitDeadline { get; set; }
|
public DateTimeOffset? WaitDeadline { get; set; }
|
||||||
}
|
}
|
||||||
@@ -257,7 +257,7 @@ public class HandleScenarioCallbackRequest
|
|||||||
public string? Handle { get; set; }
|
public string? Handle { get; set; }
|
||||||
public string? NodeId { get; set; }
|
public string? NodeId { get; set; }
|
||||||
public string? RunId { 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; } }
|
public class HandleScenarioCallbackResponse { public JToken? Effect { get; set; } }
|
||||||
|
|
||||||
@@ -267,7 +267,7 @@ public class UpdateScenarioCounterRequest
|
|||||||
public string? CounterKey { get; set; }
|
public string? CounterKey { get; set; }
|
||||||
public string? NodeId { get; set; }
|
public string? NodeId { get; set; }
|
||||||
public string? RunId { get; set; }
|
public string? RunId { get; set; }
|
||||||
public string? ScenarioId { get; set; }
|
public string? ScenarioSlug { get; set; }
|
||||||
}
|
}
|
||||||
public class UpdateScenarioCounterResponse
|
public class UpdateScenarioCounterResponse
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ public Task<IReadOnlyList<Store>> ListAsync(CancellationToken cancellationToken
|
|||||||
// GET /sdk/v1/stores/{slug}
|
// GET /sdk/v1/stores/{slug}
|
||||||
public Task<Store> GetAsync(string slug, CancellationToken cancellationToken = default);
|
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
|
// When idempotencyKey is null a random GUID is generated; pass a stable key to
|
||||||
// make retries safe.
|
// make retries safe.
|
||||||
public Task<PurchaseOfferResponse> PurchaseAsync(
|
public Task<PurchaseOfferResponse> PurchaseAsync(
|
||||||
string storeSlug, string offerId, string? idempotencyKey = null,
|
string storeSlug, string offerSlug, string? idempotencyKey = null,
|
||||||
CancellationToken cancellationToken = default);
|
CancellationToken cancellationToken = default);
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -44,6 +44,7 @@ public class Store
|
|||||||
public class Offer
|
public class Offer
|
||||||
{
|
{
|
||||||
public string? Id { get; set; } // "id"
|
public string? Id { get; set; } // "id"
|
||||||
|
public string? Slug { get; set; } // "slug" — stable across environments
|
||||||
public string? Name { get; set; } // "name"
|
public string? Name { get; set; } // "name"
|
||||||
public OfferPrice? Price { get; set; } // "price"
|
public OfferPrice? Price { get; set; } // "price"
|
||||||
public List<OfferContent>? Contents { get; set; }// "contents"
|
public List<OfferContent>? Contents { get; set; }// "contents"
|
||||||
@@ -65,5 +66,5 @@ public class PurchaseOfferResponse
|
|||||||
|
|
||||||
`PurchaseOfferResponse.Error` carries a business error string when
|
`PurchaseOfferResponse.Error` carries a business error string when
|
||||||
`Success` is false — check the flag instead of relying on exceptions alone.
|
`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.
|
quests.md.
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ public sealed class ScenarioServiceTests
|
|||||||
|
|
||||||
Assert.NotNull(completed);
|
Assert.NotNull(completed);
|
||||||
Assert.Equal("run-1", completed!.RunId);
|
Assert.Equal("run-1", completed!.RunId);
|
||||||
Assert.Equal("scenario-1", completed.ScenarioId);
|
Assert.Equal("scenario-1", completed.ScenarioSlug);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -522,7 +522,7 @@ public sealed class ScenarioServiceTests
|
|||||||
return new PendingEffect
|
return new PendingEffect
|
||||||
{
|
{
|
||||||
RunId = runId,
|
RunId = runId,
|
||||||
ScenarioId = "scenario-1",
|
ScenarioSlug = "scenario-1",
|
||||||
NodeId = nodeId,
|
NodeId = nodeId,
|
||||||
Type = type,
|
Type = type,
|
||||||
Data = data == null ? new JObject() : JObject.FromObject(data),
|
Data = data == null ? new JObject() : JObject.FromObject(data),
|
||||||
|
|||||||
Reference in New Issue
Block a user