@cross-deck/react-native 1.0.0 → 1.5.1
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 +97 -0
- package/README.md +67 -0
- package/dist/contracts.json +493 -0
- package/dist/index.cjs +1051 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +177 -2
- package/dist/index.d.ts +177 -2
- package/dist/index.mjs +1057 -35
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,103 @@ All notable changes to `@cross-deck/react-native` will be documented
|
|
|
4
4
|
here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [1.5.1] — 2026-05-27
|
|
8
|
+
|
|
9
|
+
`crossdeck.contract_failed` is now single-fire to a dedicated
|
|
10
|
+
reliability endpoint instead of the customer's `track()` pipeline.
|
|
11
|
+
Independent-controller flow per Privacy Policy §6; schema-locked by
|
|
12
|
+
`contracts/diagnostics/contract-failed-payload-schema-lock.json`.
|
|
13
|
+
`ContractFailureInput.extra` removed (schema-lock forbids unbounded
|
|
14
|
+
fields); `ContractFailureInput.deviceClass` added.
|
|
15
|
+
|
|
16
|
+
## [1.5.0] — 2026-05-26
|
|
17
|
+
|
|
18
|
+
Minor — `CrossdeckContracts` + `reportContractFailure(...)` ship as a
|
|
19
|
+
new public surface on every SDK simultaneously. Additive only; no
|
|
20
|
+
behavioural change to existing APIs.
|
|
21
|
+
|
|
22
|
+
**Added:**
|
|
23
|
+
|
|
24
|
+
- **`CrossdeckContracts` namespace** — typed access to the bank-grade
|
|
25
|
+
contract registry. Methods: `all()`, `allIncludingHistorical()`,
|
|
26
|
+
`byId(id)`, `byPillar(pillar)`, `withStatus(status)`,
|
|
27
|
+
`findByTestName(name)`. Properties: `sdkVersion`, `bundledIn`
|
|
28
|
+
(e.g. `"@cross-deck/react-native@1.5.0"`).
|
|
29
|
+
- **`Contract` type + `ContractPillar` / `ContractStatus` /
|
|
30
|
+
`ContractAppliesTo` unions + `ContractTestRef` + `ContractFailureInput`
|
|
31
|
+
interfaces** exported from the top-level entry. Treated as
|
|
32
|
+
binary-stable.
|
|
33
|
+
- **`Crossdeck.reportContractFailure(input)` method** — fires a typed
|
|
34
|
+
`crossdeck.contract_failed` event through the standard `track()`
|
|
35
|
+
pipeline when a contract test asserts and fails. Wire properties:
|
|
36
|
+
`contract_id`, `sdk_version` (auto-stamped), `sdk_platform`
|
|
37
|
+
(auto-stamped to `"react-native"`), `failure_reason`, `run_context`
|
|
38
|
+
(`ci` | `dogfood` | `customer-app`), `run_id`, plus optional
|
|
39
|
+
`test_file` / `test_name`.
|
|
40
|
+
|
|
41
|
+
**Fixed:**
|
|
42
|
+
|
|
43
|
+
- Pre-hydration `track()` calls now correctly snapshot the
|
|
44
|
+
call-time `sessionId` and thread it through the deferred
|
|
45
|
+
enrichment body. Previously, two `track()` calls separated by
|
|
46
|
+
`setSessionId(...)` BEFORE hydration completed would both pick up
|
|
47
|
+
whatever `sessionId` was current at hydration resolution — silently
|
|
48
|
+
rewriting the first event with the second event's state. This
|
|
49
|
+
contract is RN-specific (Web/Node have no hydration window).
|
|
50
|
+
|
|
51
|
+
**Changed:**
|
|
52
|
+
|
|
53
|
+
- Contract registry source files migrated to camelCase keys
|
|
54
|
+
(`appliesTo`, `codeRef`, `testRef`, `registeredAt`,
|
|
55
|
+
`firstRegisteredIn`). The bundled `contracts.json` sidecar uses
|
|
56
|
+
the new keys; `bundledIn` is build-stamped, never in source.
|
|
57
|
+
|
|
58
|
+
## [1.4.2] — 2026-05-26
|
|
59
|
+
|
|
60
|
+
Patch — wire `bundleId` + `packageName` (per-platform identity-
|
|
61
|
+
lock fields declared on `CrossdeckOptions` since v1.3.0) into
|
|
62
|
+
the `InternalState` opts merge. tsc accepted the missing fields
|
|
63
|
+
in monorepo CI because the monorepo test workflow doesn't lint
|
|
64
|
+
the RN SDK — only the Web SDK gets type-checked. The public
|
|
65
|
+
crossdeck-react-native publish workflow DOES run `npm run lint`
|
|
66
|
+
and aborted with TS2322. Fix: default both to empty string in
|
|
67
|
+
the opts initialiser (HTTP layer skips the header when empty;
|
|
68
|
+
backend rejects with bundle_id_not_allowed /
|
|
69
|
+
package_name_not_allowed at first request if the project
|
|
70
|
+
requires the lock — intentional fail-closed). v1.4.1 was
|
|
71
|
+
tagged on crossdeck-react-native but never reached npm.
|
|
72
|
+
**No SDK code changes vs v1.4.0 / v1.4.1**.
|
|
73
|
+
|
|
74
|
+
## [1.4.1] — 2026-05-26
|
|
75
|
+
|
|
76
|
+
Patch — add automated npm publish workflow to the public
|
|
77
|
+
`crossdeck-react-native` repo so future `vX.Y.Z` tag pushes
|
|
78
|
+
auto-publish to npm via OIDC Trusted Publishing (matches the
|
|
79
|
+
existing `crossdeck-web` pattern). No SDK code changes vs v1.4.0.
|
|
80
|
+
|
|
81
|
+
**Operator note:** npmjs.com Trusted Publisher rule must be
|
|
82
|
+
configured for `crossdeck-react-native` (owner: VistaApps-za,
|
|
83
|
+
workflow: publish.yml) before the OIDC publish succeeds. First
|
|
84
|
+
publish after this lands will fail with an auth error if the
|
|
85
|
+
rule is missing — that's the prompt to configure it.
|
|
86
|
+
|
|
87
|
+
## [1.4.0] — 2026-05-26
|
|
88
|
+
|
|
89
|
+
**Bank-grade reconciliation release.** Joined the v1.4.0 release line with the rest of the Crossdeck SDK suite. 6-pillar KPMG-style audit closed; every behavioural guarantee registered in the monorepo's `contracts/` directory with a CI-enforced audit job.
|
|
90
|
+
|
|
91
|
+
### Added
|
|
92
|
+
|
|
93
|
+
- **Per-user entitlement cache isolation.** Storage key is now `crossdeck:entitlements:<sha256(userId)>` — a user-switch on a shared device cannot physically read prior user's cached entitlements even if the in-memory clear is somehow skipped. `reset()` wipes EVERY per-user slot via the persisted index. New pure-JS SHA-256 helper.
|
|
94
|
+
- **Deterministic `Idempotency-Key` on `syncPurchases()`** — same JWS/purchaseToken → same key. Cross-SDK parity oracle CI-pinned.
|
|
95
|
+
- **`PurchaseResult.idempotent_replay?: boolean`** — true when the backend replayed a cached response.
|
|
96
|
+
- **`purchase.completed` event on every successful `syncPurchases()`** — funnel parity with native auto-track.
|
|
97
|
+
- **`setSessionId(sessionId: string | null)`** — host-driven session lifecycle. Call from your AppState change listener so every `track()` event carries the `sessionId` property — funnel parity with the web SDK.
|
|
98
|
+
|
|
99
|
+
### Changed
|
|
100
|
+
|
|
101
|
+
- **`init()` re-entry now drains the prior `EventQueue`'s pending timer** before swapping `this.state`. Pre-1.4.0 the timer fired AFTER the state swap, sending old-init events under new-init identity.
|
|
102
|
+
- **Default event-queue flush interval is now 2000ms** (was 5000ms) — cross-SDK parity.
|
|
103
|
+
|
|
7
104
|
## [1.0.0] — 2026-05-24
|
|
8
105
|
|
|
9
106
|
First public release. Built bank-grade from day one — every audit
|
package/README.md
CHANGED
|
@@ -56,10 +56,23 @@ Every Crossdeck SDK ships these patterns by default:
|
|
|
56
56
|
customer reads as Pro on the FIRST `isEntitled()` after `init()`,
|
|
57
57
|
even on a cold launch with no network. A Crossdeck outage can
|
|
58
58
|
never fail a paying customer down to free.
|
|
59
|
+
- **Per-user cache isolation (v1.4.0).** Every `identify(userId)`
|
|
60
|
+
switches the entitlement cache to a per-user AsyncStorage slot
|
|
61
|
+
(`crossdeck:entitlements:<sha256(userId)>`) and unconditionally
|
|
62
|
+
wipes the in-memory snapshot. A user-switch on a shared device
|
|
63
|
+
CANNOT cross-read a prior user's cache, even if the in-memory
|
|
64
|
+
clear is somehow skipped — the storage keys are physically
|
|
65
|
+
separate. `reset()` then wipes every per-user slot on the device
|
|
66
|
+
(logout-grade).
|
|
59
67
|
- **Queue durability + Stripe-style Idempotency-Key reuse.** Events
|
|
60
68
|
spliced for a flush persist to AsyncStorage with the in-flight
|
|
61
69
|
batch attached, so an app crash mid-flight replays the batch on
|
|
62
70
|
the next launch. Backend dedupes on `(projectId, eventId)`.
|
|
71
|
+
- **Deterministic Idempotency-Key on `syncPurchases` (v1.4.0).**
|
|
72
|
+
Same signed transaction → same key → backend short-circuits
|
|
73
|
+
with `idempotent_replay: true` on retry. A network blip or app
|
|
74
|
+
crash mid-flight that re-fires the same purchase never
|
|
75
|
+
double-processes.
|
|
63
76
|
- **4xx hard-stop.** Permanent failures (401 key revoked, 400/422
|
|
64
77
|
schema, 403 permission, 404 endpoint) drop the batch + fire
|
|
65
78
|
`onPermanentFailure` + `console.error` regardless of debug mode.
|
|
@@ -144,6 +157,60 @@ Crossdeck.diagnostics();
|
|
|
144
157
|
// }
|
|
145
158
|
```
|
|
146
159
|
|
|
160
|
+
## Bank-grade contracts
|
|
161
|
+
|
|
162
|
+
The SDK ships its own contracts registry — every behavioural guarantee the SDK makes (per-user cache isolation, deterministic Idempotency-Key, queue durability, etc.) lives in `contracts/**/*.json` at the monorepo root and is **bundled into every release**. The customer's lockfile pins SDK code + contracts atomically — drift between what the SDK does and what it claims is structurally impossible. See [`contracts/README.md`](https://github.com/VistaApps-za/crossdeck/blob/main/contracts/README.md) for the full architecture.
|
|
163
|
+
|
|
164
|
+
### `CrossdeckContracts` — typed access to the bundled registry
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
import { CrossdeckContracts } from "@cross-deck/react-native";
|
|
168
|
+
|
|
169
|
+
CrossdeckContracts.all(); // enforced contracts only
|
|
170
|
+
CrossdeckContracts.allIncludingHistorical(); // + proposed + retired
|
|
171
|
+
CrossdeckContracts.byId("per-user-cache-isolation");
|
|
172
|
+
CrossdeckContracts.byPillar("entitlements");
|
|
173
|
+
CrossdeckContracts.withStatus("proposed");
|
|
174
|
+
CrossdeckContracts.findByTestName("identify(B) makes A's entitlements unreachable from in-memory");
|
|
175
|
+
CrossdeckContracts.sdkVersion; // "1.5.0"
|
|
176
|
+
CrossdeckContracts.bundledIn; // "@cross-deck/react-native@1.5.0"
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
The `Contract` type is exported alongside; the binary-stability promise is documented in [`contracts/README.md`](https://github.com/VistaApps-za/crossdeck/blob/main/contracts/README.md).
|
|
180
|
+
|
|
181
|
+
### `Crossdeck.reportContractFailure(input)` — surface contract test failures
|
|
182
|
+
|
|
183
|
+
When a contract test asserts and fails — in your CI, a dogfood run, or a customer integration test — fire a typed `crossdeck.contract_failed` event over the **Crossdeck reliability channel**. This is one-way operational telemetry to the Crossdeck operations team (Privacy Policy §6, "Flow B"); it never enters your `track()` pipeline, never shows in your dashboard, never bills against your event quota. The wire shape is schema-locked at [`contracts/diagnostics/contract-failed-payload-schema-lock.json`](https://github.com/VistaApps-za/crossdeck/blob/main/contracts/diagnostics/contract-failed-payload-schema-lock.json):
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
Crossdeck.reportContractFailure({
|
|
187
|
+
contractId: "per-user-cache-isolation",
|
|
188
|
+
failureReason: "expected isolation across user switch, got cross-read",
|
|
189
|
+
runContext: __DEV__ ? "dogfood" : "ci",
|
|
190
|
+
runId: process.env.GITHUB_RUN_ID ?? Date.now().toString(36),
|
|
191
|
+
testRef: {
|
|
192
|
+
file: "tests/entitlement-cache-isolation.test.ts",
|
|
193
|
+
name: "identify(B) makes A's entitlements unreachable from in-memory",
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
No new endpoint, no special ingest path — the event lands in the same pipeline every other `track()` call does. It surfaces immediately in the dashboard's live event feed, the breakdown chart (group by `contract_id`, `sdk_platform`), and any alert rule with `event = crossdeck.contract_failed`.
|
|
199
|
+
|
|
200
|
+
Properties stamped on the wire:
|
|
201
|
+
|
|
202
|
+
| Property | Source |
|
|
203
|
+
|----------|--------|
|
|
204
|
+
| `contract_id` | caller |
|
|
205
|
+
| `sdk_version`, `sdk_platform` | auto-stamped (`@cross-deck/react-native` ships `sdk_platform: "react-native"`) |
|
|
206
|
+
| `failure_reason`, `run_context`, `run_id` | caller |
|
|
207
|
+
| `test_file`, `test_name` | set when `testRef` is provided |
|
|
208
|
+
| `device_class` | optional, set by caller (categorical bucket — e.g. `"ios-phone"`, `"android-tablet"`) |
|
|
209
|
+
|
|
210
|
+
The wire shape is schema-locked at [`contracts/diagnostics/contract-failed-payload-schema-lock.json`](https://github.com/VistaApps-za/crossdeck/blob/main/contracts/diagnostics/contract-failed-payload-schema-lock.json); per-SDK assertion tests gate it on every release. Free-form `extra` keys are not accepted — adding a field requires an amendment to the schema-lock contract first.
|
|
211
|
+
|
|
212
|
+
For per-test-framework hooks see [`contracts/README.md` § Reporting contract failures](https://github.com/VistaApps-za/crossdeck/blob/main/contracts/README.md#reporting-contract-failures-back-to-crossdeck).
|
|
213
|
+
|
|
147
214
|
## Documentation
|
|
148
215
|
|
|
149
216
|
- [Full SDK reference](https://cross-deck.com/docs/react-native-sdk)
|