Skip to content

v1.4.0

@VistaApps-za VistaApps-za tagged this 26 May 09:55
closed across SDK + backend. Every behavioural guarantee registered
in the monorepo's `contracts/` directory with a CI-enforced audit job.

- **`PurchaseAutoTrack` purchase durability.** `transaction.finish()`
  is now called STRICTLY inside the success branch of the backend
  sync. Pre-1.4.0 it fired regardless of outcome — a 5xx mid-process-
  death silently lost the purchase. Failed syncs persist to a new
  `PendingPurchaseQueue` (max 5 in-process retries, exp backoff
  30s/1m/5m/30m/2h).
- **Proper `appAccountToken` UUID conformance.** Derived from
  `developerUserId` via `AppAccountTokenDerivation` (UUID
  passthrough, else UUID v5 from URL namespace + `crossdeck:<id>`,
  else omit). Numeric StoreKit `originalTransactionId` now rides
  in its own dedicated wire field — pre-1.4.0 it was stuffed into
  the UUID-shaped `appAccountToken`, violating Apple's StoreKit
  contract.
- **Deterministic `Idempotency-Key` on `syncPurchases()`** — same
  JWS → same key. Cross-SDK parity oracle CI-pinned.
- **`PurchaseResult.idempotent_replay?: Bool`** — true when the
  backend replayed a cached response.
- **`purchase.completed` on every successful manual
  `syncPurchases()`** — funnel parity with auto-track.

- **`reset()` is now `async`**. Awaits identity / entitlements /
  super-properties / breadcrumbs clear before returning. New
  `isResetting` tombstone flips synchronously at entry; `isEntitled`
  honours it and returns false during the clear window — closes
  the race between a logout button firing reset() and the actor-
  internal clear completing. `resetSync()` exists for callers that
  cannot await.
- **`stop()` is now `async`**. Awaits `queue.persistAll()` and
  cancels stored boot + heartbeat Tasks. Pre-1.4.0 the Tasks ran
  fire-and-forget against actors of stopped clients. `stopSync()`
  exists for tests / deinit paths.
- **`CrossdeckErrorType.internalError` / `.configurationError`
  added; `.apiError` / `.unknown` deprecated** with `@available(*,
  deprecated, renamed:)`. Backend's `ApiErrorType` never emitted
  `"api_error"` or `"unknown_error"` on the wire — native pattern-
  matching on the deprecated cases only matched the SDK-synthesised
  fallback, never a real backend envelope. Use `.internalError`
  for 5xx responses.

- **`NSNotificationCenter` observer cleanup in `stop()`.** Pre-1.4.0
  every start→stop→start cycle leaked N orphan observers; each
  subsequent didEnterBackground fired N stacked queue.flush() against
  dead Crossdecks. Stored tokens, removed via
  `uninstallLifecycleObservers()`.
- **`ErrorCapture.shared.uninstall()` called in `stop()`.** Pre-1.4.0
  the global exception handler retained queue/identity/consent/
  breadcrumb actors of the stopped client; next uncaught exception
  shipped through dead actors.
- **Super-property merge order matches Web/Node/RN** — device <
  super < caller. Pre-1.4.0 Swift had it inverted (super < device <
  caller, so device clobbered super-properties).
- **Default event-queue flush interval is now 2000ms** (was 5000ms)
  — cross-SDK parity.
Assets 2
Loading