@cross-deck/web 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,46 @@
2
2
 
3
3
  All notable changes to `@cross-deck/web` will be documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
4
4
 
5
+ ## [1.9.0] — 2026-06-24
6
+
7
+ **Added — read-cost cross-match (the Buckets bridge).** When
8
+ [`@cross-deck/buckets/web`](https://www.npmjs.com/package/@cross-deck/buckets) is
9
+ installed, `identify(userId)` now tells the browser collector who the session is,
10
+ so every browser database read attributes to that user; `reset()` clears it on
11
+ logout. A browser session is single-user, so the actor is session-level. Decoupled
12
+ and zero-dependency — the SDK drives a global bridge (`__crossdeckBucketsBridge__`)
13
+ and never imports Buckets; a missing collector is a silent no-op. The cross-match is
14
+ operational (like the entitlement cache), not an analytics event.
15
+
16
+ ## [1.8.0] — 2026-06-11
17
+
18
+ **PARK on version-rejection — events are held, never dropped.** A third
19
+ event-queue outcome for the day the server stops accepting an outdated event
20
+ format. Purely additive; no public API change.
21
+
22
+ **Added:**
23
+
24
+ - **PARK (HTTP `426` / `sdk_version_unsupported`).** Previously a permanent 4xx
25
+ dropped the batch. Now a version-rejection is recognised as its own outcome —
26
+ distinct from retry (transient) and drop (invalid): the data is good, only the
27
+ wire dialect is stale. The queue **holds** the events (folded to the front of
28
+ the durable localStorage queue, FIFO-capped at 1000), **hushes** (stops
29
+ flushing a known-too-old payload — no wasted battery/bandwidth), **signals**
30
+ once (one `console.warn` + a typed `sdk.parked` debug event the dashboard
31
+ reads), and **backfills** on the next page load after you upgrade. So "paused,
32
+ not lost — held on-device, resumes on upgrade" is literally true.
33
+ - **`sdk_version_unsupported`** added to the error-codes catalogue with
34
+ remediation, and `version_error` to `CrossdeckErrorType`. `CrossdeckError`
35
+ now carries `minVersion` / `surface` (parsed from the 426 body) so the PARK
36
+ message names the exact version to update to.
37
+ - New `onParked` queue callback (host → dashboard heartbeat channel).
38
+ - Bundle-size budget: UMD min 33 → 35 KB gzipped (~0.5 KB for the PARK branch +
39
+ onParked + catalogue entry + minVersion/surface). core/react/vue unchanged,
40
+ under budget.
41
+
42
+ See https://cross-deck.com/docs/sdk-event-durability/ for the full durability
43
+ contract.
44
+
5
45
  ## [1.7.0] — 2026-06-10
6
46
 
7
47
  Event Envelope v1 conformance (`backend/docs/event-envelope-spec-v1.md`). Wire-breaking change (pre-launch; free). No public API change.
package/README.md CHANGED
@@ -79,6 +79,7 @@ That's the full happy path.
79
79
  - **One identity for every device + user.** Pre-login events get an `anonymousId`. After login, `identify()` links them to your user ID through Crossdeck's identity graph. The SDK persists both so subsequent app launches resume where you left off.
80
80
  - **Synchronous entitlement reads.** `getEntitlements()` populates a local cache. `isEntitled("pro")` is a Set lookup — no network call, no waiting.
81
81
  - **Batched telemetry.** `track()` queues events in memory; the SDK flushes every 5 seconds (configurable) or when the buffer hits 20 events. Network failures re-queue the batch — events aren't lost on a flaky connection.
82
+ - **Outdated-version PARK (v1.8.0).** If the server ever stops accepting this SDK version's event format (HTTP `426` / `sdk_version_unsupported`), events are **parked, not lost**: held in the durable on-device queue (up to 1,000 events across page loads via `localStorage`), flushing hushes, one console warning names the exact version to update to, and everything delivers automatically after you upgrade. See [the durability contract](https://cross-deck.com/docs/sdk-event-durability/).
82
83
  - **Boot heartbeat.** On `init()` the SDK pings `/v1/sdk/heartbeat` so the dashboard's Apps page can show you "last seen" per install. Disable with `autoHeartbeat: false`.
83
84
  - **Stripe-style errors.** Every async method throws `CrossdeckError` with `type`, `code`, `requestId`, and `status` — same shape as Stripe's SDKs, so generic error handlers transfer.
84
85
 
@@ -167,6 +168,8 @@ If `mergePending: true`, both identifiers already pointed at different customers
167
168
 
168
169
  **Entitlement-cache isolation (v1.4.0).** Every `identify(userId)` switches the local entitlement cache to a per-user storage slot — `localStorage["crossdeck:entitlements:<sha256(userId)>"]` — and unconditionally wipes the in-memory snapshot. A user-switch on a shared device CANNOT cross-read a prior user's cached entitlements, even if the in-memory clear is somehow skipped, because the storage keys are physically separate. `reset()` then wipes every per-user slot on the device (logout-grade).
169
170
 
171
+ **Read-cost cross-match (v1.9.0).** If [`@cross-deck/buckets/web`](https://www.npmjs.com/package/@cross-deck/buckets) is installed, `identify(userId)` also tells the browser read-cost collector who the session is — so every database read this session makes attributes to that user, and you can see *which user's which feature* is driving your read bill (`npx @cross-deck/buckets`, or the dashboard). `reset()` clears it on logout. Decoupled and zero-dependency — the SDK never imports Buckets; with no collector installed it's a silent no-op. This is operational, not an analytics event.
172
+
170
173
  **Idempotency-Key (v1.4.0).** Every `syncPurchases()` derives a
171
174
  deterministic `Idempotency-Key` from the request body — same
172
175
  signed transaction in produces the same key out. The backend
@@ -204,6 +207,8 @@ window.addEventListener("beforeunload", () => {
204
207
 
205
208
  Event names match `[A-Za-z0-9_.\-:]+`, max 128 chars. Properties are JSON-serialisable, max 8 KB per event after JSON encoding.
206
209
 
210
+ An empty event name **throws synchronously** with `CrossdeckError({ code: "missing_event_name" })` — the JS SDKs reject invalid input at the call site with a typed, catchable error (the Swift SDK's fire-and-forget surface drops + debug-logs instead; same invariant — invalid input never crashes your app and never reaches the wire — platform-native signalling). Codified as the `invalid-input-rejected-natively` contract.
211
+
207
212
  In `debug: true` mode the SDK warns (one signal per call) when property keys look like PII — `email`, `password`, `token`, `secret`, `card`, `phone`. Crossdeck never strips fields automatically; the warning is so accidental leaks surface during development, not in prod logs.
208
213
 
209
214
  ### `await Crossdeck.syncPurchases(input)`
@@ -237,7 +242,7 @@ Force-flush the in-memory event queue. Useful before page unload or when shuttin
237
242
 
238
243
  ### `Crossdeck.setDebugMode(enabled)`
239
244
 
240
- Toggle the verbose debug-signal vocabulary at runtime (NorthStar §16). When enabled, the SDK emits a fixed set of `console.info` lines tagged `[crossdeck:sdk.<signal>]` for `sdk.configured`, `sdk.first_event_sent`, `sdk.no_identity`, `sdk.purchase_evidence_sent`, `sdk.environment_mismatch`, and `sdk.sensitive_property_warning`.
245
+ Toggle the verbose debug-signal vocabulary at runtime (NorthStar §16). When enabled, the SDK emits a fixed set of `console.info` lines tagged `[crossdeck:sdk.<signal>]` for `sdk.configured`, `sdk.first_event_sent`, `sdk.no_identity`, `sdk.purchase_evidence_sent`, `sdk.environment_mismatch`, `sdk.sensitive_property_warning`, and `sdk.parked` (fired once when the queue parks on an outdated-version rejection).
241
246
 
242
247
  ### `Crossdeck.diagnostics()`
243
248
 
@@ -249,7 +254,7 @@ Diagnostic snapshot — useful for development consoles and bug reports:
249
254
  anonymousId: "anon_…",
250
255
  crossdeckCustomerId: "cdcust_…" | null,
251
256
  developerUserId: "user_…" | null,
252
- sdkVersion: "0.1.0",
257
+ sdkVersion: "1.8.0",
253
258
  baseUrl: "https://api.cross-deck.com/v1",
254
259
  entitlements: { count: 2, lastUpdated: 1717891200000 },
255
260
  events: { buffered: 0, dropped: 0, inFlight: 0, lastFlushAt: 1717891200000, lastError: null },
@@ -271,8 +276,8 @@ CrossdeckContracts.byId("per-user-cache-isolation");
271
276
  CrossdeckContracts.byPillar("entitlements");
272
277
  CrossdeckContracts.withStatus("proposed");
273
278
  CrossdeckContracts.findByTestName("identify(B) makes A's entitlements unreachable from in-memory");
274
- CrossdeckContracts.sdkVersion; // "1.5.0"
275
- CrossdeckContracts.bundledIn; // "@cross-deck/web@1.5.0"
279
+ CrossdeckContracts.sdkVersion; // "1.8.0"
280
+ CrossdeckContracts.bundledIn; // "@cross-deck/web@1.8.0"
276
281
  ```
277
282
 
278
283
  The `Contract` type is exported alongside; the binary-stability promise (which fields are guaranteed across patch/minor releases) is documented inline on `src/contracts.ts` and in [`contracts/README.md`](https://github.com/VistaApps-za/crossdeck/blob/main/contracts/README.md).
@@ -373,8 +378,9 @@ Error fields:
373
378
 
374
379
  | Field | What it is |
375
380
  |---|---|
376
- | `type` | One of `authentication_error`, `permission_error`, `invalid_request_error`, `rate_limit_error`, `internal_error`, `network_error`, `configuration_error`. Same vocabulary the backend uses. |
377
- | `code` | Specific machine-readable code, e.g. `invalid_api_key`, `origin_not_allowed`, `rate_limited`, `network_error`. |
381
+ | `type` | One of `authentication_error`, `permission_error`, `invalid_request_error`, `rate_limit_error`, `internal_error`, `network_error`, `configuration_error`, `version_error`. Same vocabulary the backend uses. |
382
+ | `code` | Specific machine-readable code, e.g. `invalid_api_key`, `origin_not_allowed`, `rate_limited`, `network_error`, `sdk_version_unsupported`. |
383
+ | `minVersion`, `surface` | Set on `version_error` (HTTP 426) responses — the exact SDK version to update to. The queue PARKs on this error instead of dropping (see "Outdated-version PARK" above). |
378
384
  | `message` | Human-readable description. |
379
385
  | `requestId` | Server-issued ID. Echo it in support tickets — we'll have a one-line log entry that explains the decision. |
380
386
  | `status` | HTTP status code if the error came from an API response. |