@cross-deck/react-native 1.5.0 → 1.5.3
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 +51 -0
- package/README.md +54 -0
- package/dist/contracts.json +133 -11
- package/dist/index.cjs +196 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +30 -9
- package/dist/index.d.ts +30 -9
- package/dist/index.mjs +196 -22
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,57 @@ 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
|
+
|
|
7
58
|
## [1.4.2] — 2026-05-26
|
|
8
59
|
|
|
9
60
|
Patch — wire `bundleId` + `packageName` (per-platform identity-
|
package/README.md
CHANGED
|
@@ -157,6 +157,60 @@ Crossdeck.diagnostics();
|
|
|
157
157
|
// }
|
|
158
158
|
```
|
|
159
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
|
+
|
|
160
214
|
## Documentation
|
|
161
215
|
|
|
162
216
|
- [Full SDK reference](https://cross-deck.com/docs/react-native-sdk)
|
package/dist/contracts.json
CHANGED
|
@@ -1,11 +1,133 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"generatedAt": "2026-05-
|
|
3
|
+
"generatedAt": "2026-05-27T15:25:43.497Z",
|
|
4
4
|
"sdk": "@cross-deck/react-native",
|
|
5
|
-
"sdkVersion": "1.5.
|
|
6
|
-
"bundledIn": "@cross-deck/react-native@1.5.
|
|
7
|
-
"count":
|
|
5
|
+
"sdkVersion": "1.5.3",
|
|
6
|
+
"bundledIn": "@cross-deck/react-native@1.5.3",
|
|
7
|
+
"count": 8,
|
|
8
8
|
"contracts": [
|
|
9
|
+
{
|
|
10
|
+
"id": "contract-failed-payload-schema-lock",
|
|
11
|
+
"pillar": "diagnostics",
|
|
12
|
+
"status": "enforced",
|
|
13
|
+
"claim": "The `crossdeck.contract_failed` event payload contains ONLY the named diagnostic fields and never any end-user personal data. The wire shape is fixed — adding a new field requires (1) a pull request that updates this contract's `allowedFields` set, (2) a Privacy Policy §6 amendment, and (3) the Customer Disclosure Template / SDK Data Collection Reference §B updates. Per-SDK assertion tests enforce the field set on every release. The `verification_phase` field is a categorical bucket — values are restricted to `boot` (the SDK self-test ran on Crossdeck.start) or `hot_path` (a verifier observed a real customer-triggered operation). The categorical nature is what preserves the diagnostic-only-not-personal classification. This is the structural guarantee that backs the independent-controller lawful basis in the Privacy Policy: the payload remains diagnostic-only, not personal, so the legitimate-interest analysis stays valid as the SDK evolves.",
|
|
14
|
+
"appliesTo": [
|
|
15
|
+
"web",
|
|
16
|
+
"node",
|
|
17
|
+
"swift",
|
|
18
|
+
"android",
|
|
19
|
+
"react-native"
|
|
20
|
+
],
|
|
21
|
+
"allowedFields": {
|
|
22
|
+
"required": [
|
|
23
|
+
"contract_id",
|
|
24
|
+
"sdk_version",
|
|
25
|
+
"sdk_platform",
|
|
26
|
+
"failure_reason",
|
|
27
|
+
"run_context",
|
|
28
|
+
"run_id"
|
|
29
|
+
],
|
|
30
|
+
"optional": [
|
|
31
|
+
"test_file",
|
|
32
|
+
"test_name",
|
|
33
|
+
"device_class",
|
|
34
|
+
"verification_phase"
|
|
35
|
+
],
|
|
36
|
+
"forbidden": [
|
|
37
|
+
"anonymousId",
|
|
38
|
+
"developerUserId",
|
|
39
|
+
"crossdeckCustomerId",
|
|
40
|
+
"email",
|
|
41
|
+
"ip",
|
|
42
|
+
"user_agent",
|
|
43
|
+
"message",
|
|
44
|
+
"stack",
|
|
45
|
+
"stack_trace",
|
|
46
|
+
"frames",
|
|
47
|
+
"exception_message",
|
|
48
|
+
"url",
|
|
49
|
+
"path",
|
|
50
|
+
"screen",
|
|
51
|
+
"title",
|
|
52
|
+
"label",
|
|
53
|
+
"text",
|
|
54
|
+
"ariaLabel",
|
|
55
|
+
"accessibilityLabel",
|
|
56
|
+
"contentDescription",
|
|
57
|
+
"session_id",
|
|
58
|
+
"sessionId"
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
"transport": "Telemetry is single-fire to the Crossdeck reliability endpoint only — NOT the customer's appId. The customer's track() pipeline never carries `crossdeck.*` events; the customer's dashboard never shows individual contract failures. Operational telemetry flows one-way to the Crossdeck operations team for SDK reliability purposes (legitimate interest, independent-controller flow per Privacy Policy §6). The reliability endpoint is hardcoded at SDK build time; the publishable key for the reliability project is embedded as a constant and rejects writes that don't match the schema.",
|
|
62
|
+
"codeRef": [
|
|
63
|
+
"sdks/web/src/crossdeck.ts",
|
|
64
|
+
"sdks/node/src/crossdeck-server.ts",
|
|
65
|
+
"sdks/swift/Sources/Crossdeck/Crossdeck.swift",
|
|
66
|
+
"sdks/swift/Sources/Crossdeck/_DiagnosticTelemetry.swift",
|
|
67
|
+
"sdks/android/crossdeck/src/main/kotlin/com/crossdeck/Crossdeck.kt",
|
|
68
|
+
"sdks/android/crossdeck/src/main/kotlin/com/crossdeck/_DiagnosticTelemetry.kt",
|
|
69
|
+
"sdks/react-native/src/crossdeck.ts",
|
|
70
|
+
"backend/src/api/v1-sdk-diagnostic.ts",
|
|
71
|
+
"sdks/web/src/_diagnostic-telemetry.ts",
|
|
72
|
+
"sdks/node/src/_diagnostic-telemetry.ts",
|
|
73
|
+
"sdks/react-native/src/_diagnostic-telemetry.ts"
|
|
74
|
+
],
|
|
75
|
+
"testRef": [
|
|
76
|
+
{
|
|
77
|
+
"file": "sdks/web/tests/contract-failed-schema-lock.test.ts",
|
|
78
|
+
"name": "reportContractFailure payload conforms to schema-lock"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"file": "sdks/node/tests/contract-failed-schema-lock.test.ts",
|
|
82
|
+
"name": "reportContractFailure payload conforms to schema-lock"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"file": "sdks/swift/Tests/CrossdeckTests/ContractFailedSchemaLockTests.swift",
|
|
86
|
+
"name": "test_reportContractFailure_payloadFieldsAreInAllowList"
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"file": "sdks/swift/Tests/CrossdeckTests/ContractFailedSchemaLockTests.swift",
|
|
90
|
+
"name": "test_reportContractFailure_doesNotEnterCustomerTrackPipeline"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"file": "sdks/android/crossdeck/src/test/kotlin/com/crossdeck/ContractFailedSchemaLockTest.kt",
|
|
94
|
+
"name": "reportContractFailure payload conforms to schema-lock"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"file": "sdks/android/crossdeck/src/test/kotlin/com/crossdeck/ContractFailedSchemaLockTest.kt",
|
|
98
|
+
"name": "reportContractFailure does not enter customer track pipeline"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"file": "sdks/react-native/tests/contract-failed-schema-lock.test.ts",
|
|
102
|
+
"name": "reportContractFailure payload conforms to schema-lock"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"file": "backend/tests/unit/v1-sdk-diagnostic.test.ts",
|
|
106
|
+
"name": "forbidden fields are enumerated in the schema-lock contract"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"file": "backend/tests/unit/v1-sdk-diagnostic.test.ts",
|
|
110
|
+
"name": "required fields are enumerated in the schema-lock contract"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"file": "backend/tests/unit/v1-sdk-diagnostic.test.ts",
|
|
114
|
+
"name": "regression guard: never returns a raw IP"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"file": "backend/tests/unit/v1-sdk-diagnostic.test.ts",
|
|
118
|
+
"name": "verification_phase is in the optional field set"
|
|
119
|
+
}
|
|
120
|
+
],
|
|
121
|
+
"registeredAt": "2026-05-27",
|
|
122
|
+
"firstRegisteredIn": "Diagnostic telemetry single-fire + schema-lock — independent-controller flow",
|
|
123
|
+
"privacyReferences": [
|
|
124
|
+
"legal/privacy/index.html#sdk-diagnostic",
|
|
125
|
+
"legal/customer-disclosure/index.html#flow-b",
|
|
126
|
+
"legal/security/index.html#diagnostic",
|
|
127
|
+
"legal/sdk-data/index.html#b-diagnostic"
|
|
128
|
+
],
|
|
129
|
+
"bundledIn": "@cross-deck/react-native@1.5.3"
|
|
130
|
+
},
|
|
9
131
|
{
|
|
10
132
|
"id": "error-envelope-shape",
|
|
11
133
|
"pillar": "errors",
|
|
@@ -43,7 +165,7 @@
|
|
|
43
165
|
],
|
|
44
166
|
"registeredAt": "2026-05-26",
|
|
45
167
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 — phase 8 (codifies existing contract)",
|
|
46
|
-
"bundledIn": "@cross-deck/react-native@1.5.
|
|
168
|
+
"bundledIn": "@cross-deck/react-native@1.5.3"
|
|
47
169
|
},
|
|
48
170
|
{
|
|
49
171
|
"id": "flush-interval-parity",
|
|
@@ -88,7 +210,7 @@
|
|
|
88
210
|
],
|
|
89
211
|
"registeredAt": "2026-05-26",
|
|
90
212
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 — phase 3.3",
|
|
91
|
-
"bundledIn": "@cross-deck/react-native@1.5.
|
|
213
|
+
"bundledIn": "@cross-deck/react-native@1.5.3"
|
|
92
214
|
},
|
|
93
215
|
{
|
|
94
216
|
"id": "idempotency-key-deterministic",
|
|
@@ -193,7 +315,7 @@
|
|
|
193
315
|
],
|
|
194
316
|
"registeredAt": "2026-05-26",
|
|
195
317
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 — phase 2.2.a + 2.2.b + 2.2.c",
|
|
196
|
-
"bundledIn": "@cross-deck/react-native@1.5.
|
|
318
|
+
"bundledIn": "@cross-deck/react-native@1.5.3"
|
|
197
319
|
},
|
|
198
320
|
{
|
|
199
321
|
"id": "init-reentry-drains-prior-queue",
|
|
@@ -220,7 +342,7 @@
|
|
|
220
342
|
],
|
|
221
343
|
"registeredAt": "2026-05-26",
|
|
222
344
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 — phase 5.5",
|
|
223
|
-
"bundledIn": "@cross-deck/react-native@1.5.
|
|
345
|
+
"bundledIn": "@cross-deck/react-native@1.5.3"
|
|
224
346
|
},
|
|
225
347
|
{
|
|
226
348
|
"id": "per-user-cache-isolation",
|
|
@@ -299,7 +421,7 @@
|
|
|
299
421
|
],
|
|
300
422
|
"registeredAt": "2026-05-26",
|
|
301
423
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 — phase 1.3 (web/RN) + dogfood-gap fix (swift + android)",
|
|
302
|
-
"bundledIn": "@cross-deck/react-native@1.5.
|
|
424
|
+
"bundledIn": "@cross-deck/react-native@1.5.3"
|
|
303
425
|
},
|
|
304
426
|
{
|
|
305
427
|
"id": "rn-session-id-enrichment",
|
|
@@ -332,7 +454,7 @@
|
|
|
332
454
|
],
|
|
333
455
|
"registeredAt": "2026-05-26",
|
|
334
456
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 — phase 3.4",
|
|
335
|
-
"bundledIn": "@cross-deck/react-native@1.5.
|
|
457
|
+
"bundledIn": "@cross-deck/react-native@1.5.3"
|
|
336
458
|
},
|
|
337
459
|
{
|
|
338
460
|
"id": "sync-purchases-funnel-parity",
|
|
@@ -365,7 +487,7 @@
|
|
|
365
487
|
],
|
|
366
488
|
"registeredAt": "2026-05-26",
|
|
367
489
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 — phase 3.5",
|
|
368
|
-
"bundledIn": "@cross-deck/react-native@1.5.
|
|
490
|
+
"bundledIn": "@cross-deck/react-native@1.5.3"
|
|
369
491
|
}
|
|
370
492
|
]
|
|
371
493
|
}
|
package/dist/index.cjs
CHANGED
|
@@ -101,7 +101,7 @@ function typeMapForStatus(status) {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
// src/_version.ts
|
|
104
|
-
var SDK_VERSION = "1.
|
|
104
|
+
var SDK_VERSION = "1.5.3";
|
|
105
105
|
var SDK_NAME = "@cross-deck/react-native";
|
|
106
106
|
|
|
107
107
|
// src/http.ts
|
|
@@ -1722,6 +1722,56 @@ var BreadcrumbBuffer = class {
|
|
|
1722
1722
|
}
|
|
1723
1723
|
};
|
|
1724
1724
|
|
|
1725
|
+
// src/_diagnostic-telemetry.ts
|
|
1726
|
+
var DIAGNOSTIC_TELEMETRY_ENDPOINT = "https://api.cross-deck.com/v1/sdk/diagnostic";
|
|
1727
|
+
var DIAGNOSTIC_TELEMETRY_PUBLISHABLE_KEY = "cd_pub_live_9490e7aa029c432abf";
|
|
1728
|
+
function isDiagnosticTelemetryEnabled() {
|
|
1729
|
+
return !DIAGNOSTIC_TELEMETRY_PUBLISHABLE_KEY.startsWith(
|
|
1730
|
+
"cd_pub_RELIABILITY_PLACEHOLDER"
|
|
1731
|
+
);
|
|
1732
|
+
}
|
|
1733
|
+
var DIAGNOSTIC_TELEMETRY_ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
1734
|
+
"contract_id",
|
|
1735
|
+
"sdk_version",
|
|
1736
|
+
"sdk_platform",
|
|
1737
|
+
"failure_reason",
|
|
1738
|
+
"run_context",
|
|
1739
|
+
"run_id",
|
|
1740
|
+
"test_file",
|
|
1741
|
+
"test_name",
|
|
1742
|
+
"device_class"
|
|
1743
|
+
]);
|
|
1744
|
+
function filterDiagnosticPayload(payload) {
|
|
1745
|
+
const filtered = {};
|
|
1746
|
+
for (const [k, v] of Object.entries(payload)) {
|
|
1747
|
+
if (DIAGNOSTIC_TELEMETRY_ALLOWED_KEYS.has(k) && typeof v === "string") {
|
|
1748
|
+
filtered[k] = v;
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
return filtered;
|
|
1752
|
+
}
|
|
1753
|
+
function sendDiagnosticTelemetry(payload) {
|
|
1754
|
+
if (!isDiagnosticTelemetryEnabled()) return;
|
|
1755
|
+
const filtered = filterDiagnosticPayload(payload);
|
|
1756
|
+
if (Object.keys(filtered).length === 0) return;
|
|
1757
|
+
const body = JSON.stringify(filtered);
|
|
1758
|
+
const f = globalThis.fetch;
|
|
1759
|
+
if (typeof f !== "function") return;
|
|
1760
|
+
try {
|
|
1761
|
+
void f(DIAGNOSTIC_TELEMETRY_ENDPOINT, {
|
|
1762
|
+
method: "POST",
|
|
1763
|
+
headers: {
|
|
1764
|
+
"Content-Type": "application/json",
|
|
1765
|
+
Authorization: `Bearer ${DIAGNOSTIC_TELEMETRY_PUBLISHABLE_KEY}`,
|
|
1766
|
+
"Crossdeck-Sdk-Version": `${SDK_NAME}@${SDK_VERSION}`
|
|
1767
|
+
},
|
|
1768
|
+
body
|
|
1769
|
+
}).catch(() => {
|
|
1770
|
+
});
|
|
1771
|
+
} catch {
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1725
1775
|
// src/stack-parser.ts
|
|
1726
1776
|
function parseStack(stack) {
|
|
1727
1777
|
if (!stack || typeof stack !== "string") return [];
|
|
@@ -2654,13 +2704,17 @@ var CrossdeckClient = class {
|
|
|
2654
2704
|
* synchronously.
|
|
2655
2705
|
*/
|
|
2656
2706
|
/**
|
|
2657
|
-
* Emit `crossdeck.contract_failed`
|
|
2658
|
-
*
|
|
2659
|
-
*
|
|
2660
|
-
*
|
|
2707
|
+
* Emit `crossdeck.contract_failed` to the Crossdeck reliability
|
|
2708
|
+
* endpoint — single-fire, one-way, never visible in the customer's
|
|
2709
|
+
* dashboard. Goes over a dedicated HTTP path with the reliability
|
|
2710
|
+
* publishable key embedded at build time; the customer's track()
|
|
2711
|
+
* pipeline never carries `crossdeck.*` events. This is the
|
|
2712
|
+
* independent-controller flow described in Privacy Policy §6
|
|
2713
|
+
* ("Flow B"). The wire shape is fixed by the schema-lock contract
|
|
2714
|
+
* at `contracts/diagnostics/contract-failed-payload-schema-lock.json`.
|
|
2661
2715
|
*/
|
|
2662
2716
|
reportContractFailure(input) {
|
|
2663
|
-
const
|
|
2717
|
+
const payload = {
|
|
2664
2718
|
contract_id: input.contractId,
|
|
2665
2719
|
sdk_version: SDK_VERSION,
|
|
2666
2720
|
sdk_platform: "react-native",
|
|
@@ -2669,15 +2723,13 @@ var CrossdeckClient = class {
|
|
|
2669
2723
|
run_id: input.runId
|
|
2670
2724
|
};
|
|
2671
2725
|
if (input.testRef) {
|
|
2672
|
-
|
|
2673
|
-
|
|
2726
|
+
payload.test_file = input.testRef.file;
|
|
2727
|
+
payload.test_name = input.testRef.name;
|
|
2674
2728
|
}
|
|
2675
|
-
if (input.
|
|
2676
|
-
|
|
2677
|
-
if (props[k] === void 0) props[k] = v;
|
|
2678
|
-
}
|
|
2729
|
+
if (input.deviceClass) {
|
|
2730
|
+
payload.device_class = input.deviceClass;
|
|
2679
2731
|
}
|
|
2680
|
-
|
|
2732
|
+
sendDiagnosticTelemetry(payload);
|
|
2681
2733
|
}
|
|
2682
2734
|
track(name, properties) {
|
|
2683
2735
|
const s = this.requireStarted();
|
|
@@ -3033,9 +3085,131 @@ function detectPlatform() {
|
|
|
3033
3085
|
}
|
|
3034
3086
|
|
|
3035
3087
|
// src/_contracts-bundled.ts
|
|
3036
|
-
var BUNDLED_IN = "@cross-deck/react-native@1.5.
|
|
3037
|
-
var SDK_VERSION2 = "1.5.
|
|
3088
|
+
var BUNDLED_IN = "@cross-deck/react-native@1.5.3";
|
|
3089
|
+
var SDK_VERSION2 = "1.5.3";
|
|
3038
3090
|
var BUNDLED_CONTRACTS = Object.freeze([
|
|
3091
|
+
{
|
|
3092
|
+
"id": "contract-failed-payload-schema-lock",
|
|
3093
|
+
"pillar": "diagnostics",
|
|
3094
|
+
"status": "enforced",
|
|
3095
|
+
"claim": "The `crossdeck.contract_failed` event payload contains ONLY the named diagnostic fields and never any end-user personal data. The wire shape is fixed \u2014 adding a new field requires (1) a pull request that updates this contract's `allowedFields` set, (2) a Privacy Policy \xA76 amendment, and (3) the Customer Disclosure Template / SDK Data Collection Reference \xA7B updates. Per-SDK assertion tests enforce the field set on every release. The `verification_phase` field is a categorical bucket \u2014 values are restricted to `boot` (the SDK self-test ran on Crossdeck.start) or `hot_path` (a verifier observed a real customer-triggered operation). The categorical nature is what preserves the diagnostic-only-not-personal classification. This is the structural guarantee that backs the independent-controller lawful basis in the Privacy Policy: the payload remains diagnostic-only, not personal, so the legitimate-interest analysis stays valid as the SDK evolves.",
|
|
3096
|
+
"appliesTo": [
|
|
3097
|
+
"web",
|
|
3098
|
+
"node",
|
|
3099
|
+
"swift",
|
|
3100
|
+
"android",
|
|
3101
|
+
"react-native"
|
|
3102
|
+
],
|
|
3103
|
+
"allowedFields": {
|
|
3104
|
+
"required": [
|
|
3105
|
+
"contract_id",
|
|
3106
|
+
"sdk_version",
|
|
3107
|
+
"sdk_platform",
|
|
3108
|
+
"failure_reason",
|
|
3109
|
+
"run_context",
|
|
3110
|
+
"run_id"
|
|
3111
|
+
],
|
|
3112
|
+
"optional": [
|
|
3113
|
+
"test_file",
|
|
3114
|
+
"test_name",
|
|
3115
|
+
"device_class",
|
|
3116
|
+
"verification_phase"
|
|
3117
|
+
],
|
|
3118
|
+
"forbidden": [
|
|
3119
|
+
"anonymousId",
|
|
3120
|
+
"developerUserId",
|
|
3121
|
+
"crossdeckCustomerId",
|
|
3122
|
+
"email",
|
|
3123
|
+
"ip",
|
|
3124
|
+
"user_agent",
|
|
3125
|
+
"message",
|
|
3126
|
+
"stack",
|
|
3127
|
+
"stack_trace",
|
|
3128
|
+
"frames",
|
|
3129
|
+
"exception_message",
|
|
3130
|
+
"url",
|
|
3131
|
+
"path",
|
|
3132
|
+
"screen",
|
|
3133
|
+
"title",
|
|
3134
|
+
"label",
|
|
3135
|
+
"text",
|
|
3136
|
+
"ariaLabel",
|
|
3137
|
+
"accessibilityLabel",
|
|
3138
|
+
"contentDescription",
|
|
3139
|
+
"session_id",
|
|
3140
|
+
"sessionId"
|
|
3141
|
+
]
|
|
3142
|
+
},
|
|
3143
|
+
"transport": "Telemetry is single-fire to the Crossdeck reliability endpoint only \u2014 NOT the customer's appId. The customer's track() pipeline never carries `crossdeck.*` events; the customer's dashboard never shows individual contract failures. Operational telemetry flows one-way to the Crossdeck operations team for SDK reliability purposes (legitimate interest, independent-controller flow per Privacy Policy \xA76). The reliability endpoint is hardcoded at SDK build time; the publishable key for the reliability project is embedded as a constant and rejects writes that don't match the schema.",
|
|
3144
|
+
"codeRef": [
|
|
3145
|
+
"sdks/web/src/crossdeck.ts",
|
|
3146
|
+
"sdks/node/src/crossdeck-server.ts",
|
|
3147
|
+
"sdks/swift/Sources/Crossdeck/Crossdeck.swift",
|
|
3148
|
+
"sdks/swift/Sources/Crossdeck/_DiagnosticTelemetry.swift",
|
|
3149
|
+
"sdks/android/crossdeck/src/main/kotlin/com/crossdeck/Crossdeck.kt",
|
|
3150
|
+
"sdks/android/crossdeck/src/main/kotlin/com/crossdeck/_DiagnosticTelemetry.kt",
|
|
3151
|
+
"sdks/react-native/src/crossdeck.ts",
|
|
3152
|
+
"backend/src/api/v1-sdk-diagnostic.ts",
|
|
3153
|
+
"sdks/web/src/_diagnostic-telemetry.ts",
|
|
3154
|
+
"sdks/node/src/_diagnostic-telemetry.ts",
|
|
3155
|
+
"sdks/react-native/src/_diagnostic-telemetry.ts"
|
|
3156
|
+
],
|
|
3157
|
+
"testRef": [
|
|
3158
|
+
{
|
|
3159
|
+
"file": "sdks/web/tests/contract-failed-schema-lock.test.ts",
|
|
3160
|
+
"name": "reportContractFailure payload conforms to schema-lock"
|
|
3161
|
+
},
|
|
3162
|
+
{
|
|
3163
|
+
"file": "sdks/node/tests/contract-failed-schema-lock.test.ts",
|
|
3164
|
+
"name": "reportContractFailure payload conforms to schema-lock"
|
|
3165
|
+
},
|
|
3166
|
+
{
|
|
3167
|
+
"file": "sdks/swift/Tests/CrossdeckTests/ContractFailedSchemaLockTests.swift",
|
|
3168
|
+
"name": "test_reportContractFailure_payloadFieldsAreInAllowList"
|
|
3169
|
+
},
|
|
3170
|
+
{
|
|
3171
|
+
"file": "sdks/swift/Tests/CrossdeckTests/ContractFailedSchemaLockTests.swift",
|
|
3172
|
+
"name": "test_reportContractFailure_doesNotEnterCustomerTrackPipeline"
|
|
3173
|
+
},
|
|
3174
|
+
{
|
|
3175
|
+
"file": "sdks/android/crossdeck/src/test/kotlin/com/crossdeck/ContractFailedSchemaLockTest.kt",
|
|
3176
|
+
"name": "reportContractFailure payload conforms to schema-lock"
|
|
3177
|
+
},
|
|
3178
|
+
{
|
|
3179
|
+
"file": "sdks/android/crossdeck/src/test/kotlin/com/crossdeck/ContractFailedSchemaLockTest.kt",
|
|
3180
|
+
"name": "reportContractFailure does not enter customer track pipeline"
|
|
3181
|
+
},
|
|
3182
|
+
{
|
|
3183
|
+
"file": "sdks/react-native/tests/contract-failed-schema-lock.test.ts",
|
|
3184
|
+
"name": "reportContractFailure payload conforms to schema-lock"
|
|
3185
|
+
},
|
|
3186
|
+
{
|
|
3187
|
+
"file": "backend/tests/unit/v1-sdk-diagnostic.test.ts",
|
|
3188
|
+
"name": "forbidden fields are enumerated in the schema-lock contract"
|
|
3189
|
+
},
|
|
3190
|
+
{
|
|
3191
|
+
"file": "backend/tests/unit/v1-sdk-diagnostic.test.ts",
|
|
3192
|
+
"name": "required fields are enumerated in the schema-lock contract"
|
|
3193
|
+
},
|
|
3194
|
+
{
|
|
3195
|
+
"file": "backend/tests/unit/v1-sdk-diagnostic.test.ts",
|
|
3196
|
+
"name": "regression guard: never returns a raw IP"
|
|
3197
|
+
},
|
|
3198
|
+
{
|
|
3199
|
+
"file": "backend/tests/unit/v1-sdk-diagnostic.test.ts",
|
|
3200
|
+
"name": "verification_phase is in the optional field set"
|
|
3201
|
+
}
|
|
3202
|
+
],
|
|
3203
|
+
"registeredAt": "2026-05-27",
|
|
3204
|
+
"firstRegisteredIn": "Diagnostic telemetry single-fire + schema-lock \u2014 independent-controller flow",
|
|
3205
|
+
"privacyReferences": [
|
|
3206
|
+
"legal/privacy/index.html#sdk-diagnostic",
|
|
3207
|
+
"legal/customer-disclosure/index.html#flow-b",
|
|
3208
|
+
"legal/security/index.html#diagnostic",
|
|
3209
|
+
"legal/sdk-data/index.html#b-diagnostic"
|
|
3210
|
+
],
|
|
3211
|
+
"bundledIn": "@cross-deck/react-native@1.5.3"
|
|
3212
|
+
},
|
|
3039
3213
|
{
|
|
3040
3214
|
"id": "error-envelope-shape",
|
|
3041
3215
|
"pillar": "errors",
|
|
@@ -3073,7 +3247,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
3073
3247
|
],
|
|
3074
3248
|
"registeredAt": "2026-05-26",
|
|
3075
3249
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 8 (codifies existing contract)",
|
|
3076
|
-
"bundledIn": "@cross-deck/react-native@1.5.
|
|
3250
|
+
"bundledIn": "@cross-deck/react-native@1.5.3"
|
|
3077
3251
|
},
|
|
3078
3252
|
{
|
|
3079
3253
|
"id": "flush-interval-parity",
|
|
@@ -3118,7 +3292,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
3118
3292
|
],
|
|
3119
3293
|
"registeredAt": "2026-05-26",
|
|
3120
3294
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.3",
|
|
3121
|
-
"bundledIn": "@cross-deck/react-native@1.5.
|
|
3295
|
+
"bundledIn": "@cross-deck/react-native@1.5.3"
|
|
3122
3296
|
},
|
|
3123
3297
|
{
|
|
3124
3298
|
"id": "idempotency-key-deterministic",
|
|
@@ -3223,7 +3397,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
3223
3397
|
],
|
|
3224
3398
|
"registeredAt": "2026-05-26",
|
|
3225
3399
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 2.2.a + 2.2.b + 2.2.c",
|
|
3226
|
-
"bundledIn": "@cross-deck/react-native@1.5.
|
|
3400
|
+
"bundledIn": "@cross-deck/react-native@1.5.3"
|
|
3227
3401
|
},
|
|
3228
3402
|
{
|
|
3229
3403
|
"id": "init-reentry-drains-prior-queue",
|
|
@@ -3250,7 +3424,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
3250
3424
|
],
|
|
3251
3425
|
"registeredAt": "2026-05-26",
|
|
3252
3426
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 5.5",
|
|
3253
|
-
"bundledIn": "@cross-deck/react-native@1.5.
|
|
3427
|
+
"bundledIn": "@cross-deck/react-native@1.5.3"
|
|
3254
3428
|
},
|
|
3255
3429
|
{
|
|
3256
3430
|
"id": "per-user-cache-isolation",
|
|
@@ -3329,7 +3503,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
3329
3503
|
],
|
|
3330
3504
|
"registeredAt": "2026-05-26",
|
|
3331
3505
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 1.3 (web/RN) + dogfood-gap fix (swift + android)",
|
|
3332
|
-
"bundledIn": "@cross-deck/react-native@1.5.
|
|
3506
|
+
"bundledIn": "@cross-deck/react-native@1.5.3"
|
|
3333
3507
|
},
|
|
3334
3508
|
{
|
|
3335
3509
|
"id": "rn-session-id-enrichment",
|
|
@@ -3362,7 +3536,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
3362
3536
|
],
|
|
3363
3537
|
"registeredAt": "2026-05-26",
|
|
3364
3538
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.4",
|
|
3365
|
-
"bundledIn": "@cross-deck/react-native@1.5.
|
|
3539
|
+
"bundledIn": "@cross-deck/react-native@1.5.3"
|
|
3366
3540
|
},
|
|
3367
3541
|
{
|
|
3368
3542
|
"id": "sync-purchases-funnel-parity",
|
|
@@ -3395,7 +3569,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
3395
3569
|
],
|
|
3396
3570
|
"registeredAt": "2026-05-26",
|
|
3397
3571
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.5",
|
|
3398
|
-
"bundledIn": "@cross-deck/react-native@1.5.
|
|
3572
|
+
"bundledIn": "@cross-deck/react-native@1.5.3"
|
|
3399
3573
|
}
|
|
3400
3574
|
]);
|
|
3401
3575
|
|