# Changelog ## Unreleased 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) | ## 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. ## 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. ## 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` 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.`); 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`. - `StoresService.ListAsync` returns `IReadOnlyList`. - `QuestsService.ListAsync` returns `IReadOnlyList`. - `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 (`enable`). - Remote config cache now includes every config except an explicit `"active": false` (a missing flag means active).