2026-08-12 14:04:55 +03:00
|
|
|
# Changelog
|
|
|
|
|
|
2026-09-06 22:25:32 +03:00
|
|
|
## 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`.
|
|
|
|
|
|
2026-09-04 15:37:00 +03:00
|
|
|
## 1.0.0
|
2026-09-04 14:18:05 +03:00
|
|
|
|
|
|
|
|
Breaking change — major bump. The local scenario engine is replaced by a
|
|
|
|
|
server-driven effects client; the realtime websocket client is removed.
|
|
|
|
|
|
|
|
|
|
### Removed
|
|
|
|
|
|
|
|
|
|
- Local scenario runtime: `PlanRun`, `ExecutionPlan` walking, node sessions
|
|
|
|
|
(`NotificationSession`, `StoreOfferSession`, `LeaderboardSession`,
|
|
|
|
|
`WaitSession`, `QuestSession`, `BattlePassSession`,
|
|
|
|
|
`BattlePassLevelSession`, `ConfigChangedSession`), `RespondAsync` /
|
|
|
|
|
`Respond`, `RestoreAsync`, `Clear`, `ActiveRuns`, `IsRunning`,
|
|
|
|
|
`CurrentNodeId`, `OnConfigChanged`.
|
|
|
|
|
- `RudderClientOptions.PlanStateStore` / `IPlanStateStore` and
|
|
|
|
|
`IPlanScheduler` — no local plan persistence or delayed plan work.
|
|
|
|
|
- `RealtimeService` (`client.Realtime`), `RudderClientOptions.RealtimeUrl` /
|
|
|
|
|
`RealtimeTransportFactory`, `IRealtimeTransport` /
|
|
|
|
|
`IRealtimeTransportFactory`.
|
|
|
|
|
|
|
|
|
|
### Changed (breaking)
|
|
|
|
|
|
|
|
|
|
- `client.Scenario` is a trigger only: `TriggerAsync(eventName)` returns
|
|
|
|
|
`Task` (not started plans). Returned pending effects are ingested into
|
|
|
|
|
`client.Effects`.
|
|
|
|
|
- Subscribe to `client.Effects.On*` instead of `client.Scenario.On*`.
|
|
|
|
|
Sessions are now effect objects (`NotificationEffect`, `StoreOfferEffect`,
|
|
|
|
|
…). Complete them with the methods on the effect (`DoneAsync`,
|
|
|
|
|
`PurchaseAsync`/`DeclineAsync`, `EndAsync`/`ClaimAsync`, …).
|
|
|
|
|
- `client.Update(deltaTime)` is still required every frame — it now pumps
|
|
|
|
|
the effects client (30s `GET /sdk/v1/scenarios/pending` heartbeat +
|
|
|
|
|
wait-deadline checks), not a local DAG or a websocket.
|
|
|
|
|
- After login (or when constructed with a stored access token), the next
|
|
|
|
|
`Update` fetches pending effects. There is no `loginEvent` option; trigger
|
|
|
|
|
login-gated scenarios yourself.
|
|
|
|
|
|
|
|
|
|
### Migration
|
|
|
|
|
|
|
|
|
|
| Before | After |
|
|
|
|
|
|---|---|
|
|
|
|
|
| `client.Scenario.OnNotification` (and other `On*`) | `client.Effects.OnNotification` (same event names) |
|
|
|
|
|
| `NotificationSession` / `StoreOfferSession` / … | `NotificationEffect` / `StoreOfferEffect` / … |
|
|
|
|
|
| `session.CompleteAsync()` / `RespondAsync("output")` | `effect.DoneAsync()` (and the matching method on each effect) |
|
|
|
|
|
| `RudderClientOptions.PlanStateStore` + `RestoreAsync` | gone — server owns run state; pending fetch after login + heartbeat |
|
|
|
|
|
| `client.Realtime` | gone |
|
|
|
|
|
| `client.Update(deltaTime)` | still required (effects pump) |
|
|
|
|
|
|
2026-08-29 11:33:01 +03:00
|
|
|
## 0.4.0
|
|
|
|
|
|
|
|
|
|
- Generated DTOs live under `Models/` (`RudderSdk.Core.Models`). Wire fields
|
|
|
|
|
are nullable. `ListQuestsRequest` is gone; `Quests.ListAsync` POSTs with
|
|
|
|
|
no body. `BoundaryNode.WaitDeadline` is `DateTimeOffset?`.
|
|
|
|
|
- `LeaderboardSession.ClaimAsync` / `Claim` complete the leaderboard node
|
|
|
|
|
with handle `onClaim`. The server matches live rank to an authored place.
|
|
|
|
|
- `RewardClaimedAsync` / `RewardClaimed` are obsolete aliases and will be
|
|
|
|
|
removed in the next SDK version.
|
|
|
|
|
- `rank_not_eligible` on Claim leaves the session open (retry Claim or End);
|
|
|
|
|
it no longer fails the run.
|
|
|
|
|
|
2026-08-19 17:48:58 +03:00
|
|
|
## 0.3.0
|
|
|
|
|
|
|
|
|
|
### Removed
|
|
|
|
|
|
|
|
|
|
- UGC surface: `UgcService` (`client.Ugc`), the generated
|
|
|
|
|
`RudderSdk.Core.Models.Ugc` DTOs, `IUploadTransport` and
|
|
|
|
|
`RudderClientOptions.UploadTransport`. The server no longer exposes the UGC
|
|
|
|
|
endpoints.
|
|
|
|
|
|
|
|
|
|
### Changed (breaking)
|
|
|
|
|
|
|
|
|
|
- `BattlePassService` simple overloads sent empty scenario/node ids and always
|
|
|
|
|
failed server-side. Battle pass state is tied to a scenario battle pass
|
|
|
|
|
node, so the public API now mirrors the web SDK: `GetProgressAsync(scenarioId,
|
|
|
|
|
nodeId)` and the request-DTO overloads `AddXpAsync`, `ClaimRewardAsync`,
|
|
|
|
|
`PurchasePremiumAsync` (carrying `ScenarioId`/`NodeId`/`RunId`) are public;
|
|
|
|
|
the internal request overloads are gone.
|
|
|
|
|
- `BattlePassSession` now exposes the bound battle pass operations
|
|
|
|
|
(`GetProgressAsync`, `AddXpAsync(source, amount)`, `ClaimRewardAsync(level,
|
|
|
|
|
track)`, `PurchasePremiumAsync()` — crosses `onPremiumPurchase` on success),
|
|
|
|
|
mirroring the web SDK session.
|
|
|
|
|
- `BattlePassLevelSession.CompleteAsync`/`Complete` renamed to
|
|
|
|
|
`ClaimAsync`/`Claim`; new `Level` property reads the `levelNumber` node data
|
|
|
|
|
key.
|
|
|
|
|
- Unsupported scenario node types now fail the run (`OnScenarioFailed`)
|
|
|
|
|
instead of stalling silently.
|
|
|
|
|
- Scenario counter updates no longer read a continuation plan from the
|
|
|
|
|
response (the server stopped returning one); when the server reports the
|
|
|
|
|
objective completed, the node crosses `onComplete`.
|
|
|
|
|
|
|
|
|
|
### Fixed
|
|
|
|
|
|
|
|
|
|
- A failed scenario counter update no longer fails the run; the error is
|
|
|
|
|
logged and the node stays active.
|
|
|
|
|
|
2026-08-12 14:04:55 +03:00
|
|
|
## 0.2.0
|
|
|
|
|
|
|
|
|
|
### Added
|
|
|
|
|
|
|
|
|
|
- `QuestsService` (`client.Quests`) with `ListAsync` / `ClaimAsync`.
|
|
|
|
|
- `AuthService.LoginWithDeviceAsync(region, language, nickname?)` and
|
|
|
|
|
`AuthService.RefreshAsync()`; `AuthStateChanged` event
|
|
|
|
|
(`RudderAuthState.SignedIn` / `SignedOut`).
|
|
|
|
|
- Session pipeline: single-flight token refresh plus one transparent
|
|
|
|
|
retry-on-401 for every API call; failed refresh clears tokens and reports
|
|
|
|
|
`SignedOut`.
|
|
|
|
|
- `HttpClientTransport` — default `System.Net.Http.HttpClient`-based
|
|
|
|
|
transport. `RudderClientOptions.Transport` is now optional, as are
|
|
|
|
|
`TokenStore` (`InMemoryTokenStore`) and `DeviceIdProvider`
|
|
|
|
|
(`GuidDeviceIdProvider`).
|
|
|
|
|
- Typed exceptions: `RudderAuthException` (401), `RudderNotFoundException`
|
|
|
|
|
(404), `RudderRateLimitException` (429), `RudderNetworkException`
|
|
|
|
|
(network/timeout). All carry `RequestId` from the server error payload.
|
|
|
|
|
- `StorageService.ListAllAsync` and `UgcService.ListAllAsync`
|
|
|
|
|
(`IAsyncEnumerable<T>` cursor pagination helpers).
|
|
|
|
|
- `RudderLogLevel` enum; `IRudderLogger.Log` now takes a level.
|
|
|
|
|
- Package metadata (`Rudder.Core`, MIT) and XML documentation.
|
|
|
|
|
|
|
|
|
|
### Changed (breaking)
|
|
|
|
|
|
|
|
|
|
- Generated models moved to domain folders/namespaces
|
|
|
|
|
(`RudderSdk.Core.Models.<Domain>`); the flat `RudderSdk.Core.DTO`
|
|
|
|
|
namespace is gone. `Battlepass*` → `BattlePass*`, `SdkQuest` → `Quest`.
|
|
|
|
|
- `BattlepassService` → `BattlePassService`, `client.Battlepass` →
|
|
|
|
|
`client.BattlePass`.
|
|
|
|
|
- `client.Scenarios` → `client.Scenario`.
|
|
|
|
|
- `ScenarioService.SendAsync` → `TriggerAsync`, now returning the started
|
|
|
|
|
`PlanRun`s.
|
|
|
|
|
- Scenario events reduced to the canonical set: `OnNotification`,
|
|
|
|
|
`OnStoreOffer`, `OnLeaderboard`, `OnConfigChanged`, `OnWait`, `OnQuest`,
|
|
|
|
|
`OnBattlePass`, `OnBattlePassLevel`, `OnScenarioCompleted`,
|
|
|
|
|
`OnScenarioFailed`. Legacy events (`OnShowNotification`, `OnShowStore`,
|
|
|
|
|
`OnQuestEvent`, `OnBattlepassEvent`, `OnLeaderboardEvent`, `OnNodeEvent`,
|
|
|
|
|
`OnNode`, `OnCompleted`, `OnStore`, `OnRemoteConfigOverride`,
|
|
|
|
|
`OnRunCompleted`, `OnRunFailed`) are removed, along with the
|
|
|
|
|
`Services/Scenarios/Legacy` folder.
|
|
|
|
|
- Session types renamed: `StoreSession` → `StoreOfferSession`
|
|
|
|
|
(`BuyAsync`/`Buy` → `PurchaseAsync`/`Purchase`),
|
|
|
|
|
`RemoteConfigOverrideSession` → `ConfigChangedSession`,
|
|
|
|
|
`BattlepassSession` → `BattlePassSession`,
|
|
|
|
|
`BattlepassLevelSession` → `BattlePassLevelSession`,
|
|
|
|
|
`ScenarioRunFailedEvent` → `ScenarioFailedEvent`.
|
|
|
|
|
- `StoresService.BuyAsync` → `PurchaseAsync(storeSlug, offerId,
|
|
|
|
|
idempotencyKey = null)`; a null key is replaced with a generated GUID.
|
|
|
|
|
- `InventoryService.GetAsync` returns `IReadOnlyList<PlayerInventoryItem>`.
|
|
|
|
|
- `StoresService.ListAsync` returns `IReadOnlyList<Store>`.
|
|
|
|
|
- `QuestsService.ListAsync` returns `IReadOnlyList<Quest>`.
|
|
|
|
|
- `UgcService.GetDownloadUrlAsync` returns non-null `string` and throws
|
|
|
|
|
`InvalidOperationException` when the server returns no URL.
|
|
|
|
|
- `BattlePassService` simple overloads (`AddXpAsync(long amount)`,
|
|
|
|
|
`ClaimRewardAsync(level, track)`, `GetProgressAsync()`,
|
|
|
|
|
`PurchasePremiumAsync(idempotencyKey?)`); the request-DTO overloads are
|
|
|
|
|
internal for the scenario runtime.
|
|
|
|
|
- `LeaderboardsService.GetRankingAsync`/`SubmitScoreAsync` are internal;
|
|
|
|
|
use the `FindBySlug(slug)` handle (`SubmitAsync`, `ListAsync`).
|
|
|
|
|
- `AuthService.LoginViaDeviceAsync` removed; use `LoginWithDeviceAsync`.
|
|
|
|
|
- `ScenarioService.Restore()` (sync-over-async) removed; use `RestoreAsync`.
|
|
|
|
|
- `RealtimeService.ConnectAsync` and `IRealtimeTransport.ConnectAsync` take
|
|
|
|
|
`TimeSpan` timeouts instead of `int timeoutSeconds`.
|
|
|
|
|
- `IRudderTransport.SendAsync` takes a `string? accessToken` instead of
|
|
|
|
|
`RequestOptions`; `RequestOptions` is removed and `EmptyResponse` is
|
|
|
|
|
internal.
|
|
|
|
|
- `RudderClient` getters `Options`, `Transport`, `TokenStore`,
|
|
|
|
|
`DeviceIdProvider` are internal.
|
|
|
|
|
- `RudderApiException.StatusCode` is `int` (was `long`).
|
|
|
|
|
- `IRudderLogger.Log(string)` → `Log(RudderLogLevel level, string message)`.
|
|
|
|
|
- Nullable reference types enabled (`<Nullable>enable</Nullable>`).
|
|
|
|
|
- Remote config cache now includes every config except an explicit
|
|
|
|
|
`"active": false` (a missing flag means active).
|