@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/dist/index.d.mts CHANGED
@@ -489,8 +489,8 @@ declare const CrossdeckContracts: {
489
489
  readonly byId: (id: string) => Contract | undefined;
490
490
  readonly byPillar: (pillar: ContractPillar) => readonly Contract[];
491
491
  readonly withStatus: (status: ContractStatus) => readonly Contract[];
492
- readonly sdkVersion: "1.5.0";
493
- readonly bundledIn: "@cross-deck/react-native@1.5.0";
492
+ readonly sdkVersion: "1.5.3";
493
+ readonly bundledIn: "@cross-deck/react-native@1.5.3";
494
494
  /**
495
495
  * Resolve a failing test back to the contract it exercises.
496
496
  * Used by test-framework hooks to find the contract id of a
@@ -499,9 +499,22 @@ declare const CrossdeckContracts: {
499
499
  */
500
500
  readonly findByTestName: (name: string) => Contract | undefined;
501
501
  };
502
- /** Input to {@link Crossdeck.reportContractFailure}. */
502
+ /**
503
+ * Input to {@link Crossdeck.reportContractFailure}.
504
+ *
505
+ * SCHEMA-LOCK: this interface's field set is exhaustively named. No
506
+ * free-form `extra: Record<string, unknown>` — the schema-lock
507
+ * contract at
508
+ * `contracts/diagnostics/contract-failed-payload-schema-lock.json`
509
+ * forbids unbounded fields.
510
+ */
503
511
  interface ContractFailureInput {
504
512
  contractId: string;
513
+ /**
514
+ * Short categorical-ish label — the SDK convention is to keep
515
+ * this under 128 chars and stable across runs. Never an
516
+ * end-user-supplied string.
517
+ */
505
518
  failureReason: string;
506
519
  runContext: "ci" | "dogfood" | "customer-app";
507
520
  runId: string;
@@ -509,7 +522,11 @@ interface ContractFailureInput {
509
522
  file: string;
510
523
  name: string;
511
524
  };
512
- extra?: Record<string, unknown>;
525
+ /**
526
+ * Optional coarse device class, e.g. "ios-phone", "android-tablet".
527
+ * A categorical bucket, not a device identifier.
528
+ */
529
+ deviceClass?: string;
513
530
  }
514
531
 
515
532
  /**
@@ -763,10 +780,14 @@ declare class CrossdeckClient {
763
780
  * synchronously.
764
781
  */
765
782
  /**
766
- * Emit `crossdeck.contract_failed` with the canonical property
767
- * shape. Same wire shape every Crossdeck SDK uses for contract
768
- * verification telemetry see `contracts/README.md`. No new
769
- * endpoint, goes through the standard track() pipeline.
783
+ * Emit `crossdeck.contract_failed` to the Crossdeck reliability
784
+ * endpoint single-fire, one-way, never visible in the customer's
785
+ * dashboard. Goes over a dedicated HTTP path with the reliability
786
+ * publishable key embedded at build time; the customer's track()
787
+ * pipeline never carries `crossdeck.*` events. This is the
788
+ * independent-controller flow described in Privacy Policy §6
789
+ * ("Flow B"). The wire shape is fixed by the schema-lock contract
790
+ * at `contracts/diagnostics/contract-failed-payload-schema-lock.json`.
770
791
  */
771
792
  reportContractFailure(input: ContractFailureInput): void;
772
793
  track(name: string, properties?: EventProperties): void;
@@ -991,7 +1012,7 @@ declare class AsyncStorageAdapter implements KeyValueStorage {
991
1012
  *
992
1013
  * Do NOT edit by hand — `node scripts/sync-sdk-versions.mjs`.
993
1014
  */
994
- declare const SDK_VERSION = "1.4.2";
1015
+ declare const SDK_VERSION = "1.5.3";
995
1016
  declare const SDK_NAME = "@cross-deck/react-native";
996
1017
 
997
1018
  /**
package/dist/index.d.ts CHANGED
@@ -489,8 +489,8 @@ declare const CrossdeckContracts: {
489
489
  readonly byId: (id: string) => Contract | undefined;
490
490
  readonly byPillar: (pillar: ContractPillar) => readonly Contract[];
491
491
  readonly withStatus: (status: ContractStatus) => readonly Contract[];
492
- readonly sdkVersion: "1.5.0";
493
- readonly bundledIn: "@cross-deck/react-native@1.5.0";
492
+ readonly sdkVersion: "1.5.3";
493
+ readonly bundledIn: "@cross-deck/react-native@1.5.3";
494
494
  /**
495
495
  * Resolve a failing test back to the contract it exercises.
496
496
  * Used by test-framework hooks to find the contract id of a
@@ -499,9 +499,22 @@ declare const CrossdeckContracts: {
499
499
  */
500
500
  readonly findByTestName: (name: string) => Contract | undefined;
501
501
  };
502
- /** Input to {@link Crossdeck.reportContractFailure}. */
502
+ /**
503
+ * Input to {@link Crossdeck.reportContractFailure}.
504
+ *
505
+ * SCHEMA-LOCK: this interface's field set is exhaustively named. No
506
+ * free-form `extra: Record<string, unknown>` — the schema-lock
507
+ * contract at
508
+ * `contracts/diagnostics/contract-failed-payload-schema-lock.json`
509
+ * forbids unbounded fields.
510
+ */
503
511
  interface ContractFailureInput {
504
512
  contractId: string;
513
+ /**
514
+ * Short categorical-ish label — the SDK convention is to keep
515
+ * this under 128 chars and stable across runs. Never an
516
+ * end-user-supplied string.
517
+ */
505
518
  failureReason: string;
506
519
  runContext: "ci" | "dogfood" | "customer-app";
507
520
  runId: string;
@@ -509,7 +522,11 @@ interface ContractFailureInput {
509
522
  file: string;
510
523
  name: string;
511
524
  };
512
- extra?: Record<string, unknown>;
525
+ /**
526
+ * Optional coarse device class, e.g. "ios-phone", "android-tablet".
527
+ * A categorical bucket, not a device identifier.
528
+ */
529
+ deviceClass?: string;
513
530
  }
514
531
 
515
532
  /**
@@ -763,10 +780,14 @@ declare class CrossdeckClient {
763
780
  * synchronously.
764
781
  */
765
782
  /**
766
- * Emit `crossdeck.contract_failed` with the canonical property
767
- * shape. Same wire shape every Crossdeck SDK uses for contract
768
- * verification telemetry see `contracts/README.md`. No new
769
- * endpoint, goes through the standard track() pipeline.
783
+ * Emit `crossdeck.contract_failed` to the Crossdeck reliability
784
+ * endpoint single-fire, one-way, never visible in the customer's
785
+ * dashboard. Goes over a dedicated HTTP path with the reliability
786
+ * publishable key embedded at build time; the customer's track()
787
+ * pipeline never carries `crossdeck.*` events. This is the
788
+ * independent-controller flow described in Privacy Policy §6
789
+ * ("Flow B"). The wire shape is fixed by the schema-lock contract
790
+ * at `contracts/diagnostics/contract-failed-payload-schema-lock.json`.
770
791
  */
771
792
  reportContractFailure(input: ContractFailureInput): void;
772
793
  track(name: string, properties?: EventProperties): void;
@@ -991,7 +1012,7 @@ declare class AsyncStorageAdapter implements KeyValueStorage {
991
1012
  *
992
1013
  * Do NOT edit by hand — `node scripts/sync-sdk-versions.mjs`.
993
1014
  */
994
- declare const SDK_VERSION = "1.4.2";
1015
+ declare const SDK_VERSION = "1.5.3";
995
1016
  declare const SDK_NAME = "@cross-deck/react-native";
996
1017
 
997
1018
  /**
package/dist/index.mjs CHANGED
@@ -71,7 +71,7 @@ function typeMapForStatus(status) {
71
71
  }
72
72
 
73
73
  // src/_version.ts
74
- var SDK_VERSION = "1.4.2";
74
+ var SDK_VERSION = "1.5.3";
75
75
  var SDK_NAME = "@cross-deck/react-native";
76
76
 
77
77
  // src/http.ts
@@ -1692,6 +1692,56 @@ var BreadcrumbBuffer = class {
1692
1692
  }
1693
1693
  };
1694
1694
 
1695
+ // src/_diagnostic-telemetry.ts
1696
+ var DIAGNOSTIC_TELEMETRY_ENDPOINT = "https://api.cross-deck.com/v1/sdk/diagnostic";
1697
+ var DIAGNOSTIC_TELEMETRY_PUBLISHABLE_KEY = "cd_pub_live_9490e7aa029c432abf";
1698
+ function isDiagnosticTelemetryEnabled() {
1699
+ return !DIAGNOSTIC_TELEMETRY_PUBLISHABLE_KEY.startsWith(
1700
+ "cd_pub_RELIABILITY_PLACEHOLDER"
1701
+ );
1702
+ }
1703
+ var DIAGNOSTIC_TELEMETRY_ALLOWED_KEYS = /* @__PURE__ */ new Set([
1704
+ "contract_id",
1705
+ "sdk_version",
1706
+ "sdk_platform",
1707
+ "failure_reason",
1708
+ "run_context",
1709
+ "run_id",
1710
+ "test_file",
1711
+ "test_name",
1712
+ "device_class"
1713
+ ]);
1714
+ function filterDiagnosticPayload(payload) {
1715
+ const filtered = {};
1716
+ for (const [k, v] of Object.entries(payload)) {
1717
+ if (DIAGNOSTIC_TELEMETRY_ALLOWED_KEYS.has(k) && typeof v === "string") {
1718
+ filtered[k] = v;
1719
+ }
1720
+ }
1721
+ return filtered;
1722
+ }
1723
+ function sendDiagnosticTelemetry(payload) {
1724
+ if (!isDiagnosticTelemetryEnabled()) return;
1725
+ const filtered = filterDiagnosticPayload(payload);
1726
+ if (Object.keys(filtered).length === 0) return;
1727
+ const body = JSON.stringify(filtered);
1728
+ const f = globalThis.fetch;
1729
+ if (typeof f !== "function") return;
1730
+ try {
1731
+ void f(DIAGNOSTIC_TELEMETRY_ENDPOINT, {
1732
+ method: "POST",
1733
+ headers: {
1734
+ "Content-Type": "application/json",
1735
+ Authorization: `Bearer ${DIAGNOSTIC_TELEMETRY_PUBLISHABLE_KEY}`,
1736
+ "Crossdeck-Sdk-Version": `${SDK_NAME}@${SDK_VERSION}`
1737
+ },
1738
+ body
1739
+ }).catch(() => {
1740
+ });
1741
+ } catch {
1742
+ }
1743
+ }
1744
+
1695
1745
  // src/stack-parser.ts
1696
1746
  function parseStack(stack) {
1697
1747
  if (!stack || typeof stack !== "string") return [];
@@ -2624,13 +2674,17 @@ var CrossdeckClient = class {
2624
2674
  * synchronously.
2625
2675
  */
2626
2676
  /**
2627
- * Emit `crossdeck.contract_failed` with the canonical property
2628
- * shape. Same wire shape every Crossdeck SDK uses for contract
2629
- * verification telemetry see `contracts/README.md`. No new
2630
- * endpoint, goes through the standard track() pipeline.
2677
+ * Emit `crossdeck.contract_failed` to the Crossdeck reliability
2678
+ * endpoint single-fire, one-way, never visible in the customer's
2679
+ * dashboard. Goes over a dedicated HTTP path with the reliability
2680
+ * publishable key embedded at build time; the customer's track()
2681
+ * pipeline never carries `crossdeck.*` events. This is the
2682
+ * independent-controller flow described in Privacy Policy §6
2683
+ * ("Flow B"). The wire shape is fixed by the schema-lock contract
2684
+ * at `contracts/diagnostics/contract-failed-payload-schema-lock.json`.
2631
2685
  */
2632
2686
  reportContractFailure(input) {
2633
- const props = {
2687
+ const payload = {
2634
2688
  contract_id: input.contractId,
2635
2689
  sdk_version: SDK_VERSION,
2636
2690
  sdk_platform: "react-native",
@@ -2639,15 +2693,13 @@ var CrossdeckClient = class {
2639
2693
  run_id: input.runId
2640
2694
  };
2641
2695
  if (input.testRef) {
2642
- props.test_file = input.testRef.file;
2643
- props.test_name = input.testRef.name;
2696
+ payload.test_file = input.testRef.file;
2697
+ payload.test_name = input.testRef.name;
2644
2698
  }
2645
- if (input.extra) {
2646
- for (const [k, v] of Object.entries(input.extra)) {
2647
- if (props[k] === void 0) props[k] = v;
2648
- }
2699
+ if (input.deviceClass) {
2700
+ payload.device_class = input.deviceClass;
2649
2701
  }
2650
- this.track("crossdeck.contract_failed", props);
2702
+ sendDiagnosticTelemetry(payload);
2651
2703
  }
2652
2704
  track(name, properties) {
2653
2705
  const s = this.requireStarted();
@@ -3003,9 +3055,131 @@ function detectPlatform() {
3003
3055
  }
3004
3056
 
3005
3057
  // src/_contracts-bundled.ts
3006
- var BUNDLED_IN = "@cross-deck/react-native@1.5.0";
3007
- var SDK_VERSION2 = "1.5.0";
3058
+ var BUNDLED_IN = "@cross-deck/react-native@1.5.3";
3059
+ var SDK_VERSION2 = "1.5.3";
3008
3060
  var BUNDLED_CONTRACTS = Object.freeze([
3061
+ {
3062
+ "id": "contract-failed-payload-schema-lock",
3063
+ "pillar": "diagnostics",
3064
+ "status": "enforced",
3065
+ "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.",
3066
+ "appliesTo": [
3067
+ "web",
3068
+ "node",
3069
+ "swift",
3070
+ "android",
3071
+ "react-native"
3072
+ ],
3073
+ "allowedFields": {
3074
+ "required": [
3075
+ "contract_id",
3076
+ "sdk_version",
3077
+ "sdk_platform",
3078
+ "failure_reason",
3079
+ "run_context",
3080
+ "run_id"
3081
+ ],
3082
+ "optional": [
3083
+ "test_file",
3084
+ "test_name",
3085
+ "device_class",
3086
+ "verification_phase"
3087
+ ],
3088
+ "forbidden": [
3089
+ "anonymousId",
3090
+ "developerUserId",
3091
+ "crossdeckCustomerId",
3092
+ "email",
3093
+ "ip",
3094
+ "user_agent",
3095
+ "message",
3096
+ "stack",
3097
+ "stack_trace",
3098
+ "frames",
3099
+ "exception_message",
3100
+ "url",
3101
+ "path",
3102
+ "screen",
3103
+ "title",
3104
+ "label",
3105
+ "text",
3106
+ "ariaLabel",
3107
+ "accessibilityLabel",
3108
+ "contentDescription",
3109
+ "session_id",
3110
+ "sessionId"
3111
+ ]
3112
+ },
3113
+ "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.",
3114
+ "codeRef": [
3115
+ "sdks/web/src/crossdeck.ts",
3116
+ "sdks/node/src/crossdeck-server.ts",
3117
+ "sdks/swift/Sources/Crossdeck/Crossdeck.swift",
3118
+ "sdks/swift/Sources/Crossdeck/_DiagnosticTelemetry.swift",
3119
+ "sdks/android/crossdeck/src/main/kotlin/com/crossdeck/Crossdeck.kt",
3120
+ "sdks/android/crossdeck/src/main/kotlin/com/crossdeck/_DiagnosticTelemetry.kt",
3121
+ "sdks/react-native/src/crossdeck.ts",
3122
+ "backend/src/api/v1-sdk-diagnostic.ts",
3123
+ "sdks/web/src/_diagnostic-telemetry.ts",
3124
+ "sdks/node/src/_diagnostic-telemetry.ts",
3125
+ "sdks/react-native/src/_diagnostic-telemetry.ts"
3126
+ ],
3127
+ "testRef": [
3128
+ {
3129
+ "file": "sdks/web/tests/contract-failed-schema-lock.test.ts",
3130
+ "name": "reportContractFailure payload conforms to schema-lock"
3131
+ },
3132
+ {
3133
+ "file": "sdks/node/tests/contract-failed-schema-lock.test.ts",
3134
+ "name": "reportContractFailure payload conforms to schema-lock"
3135
+ },
3136
+ {
3137
+ "file": "sdks/swift/Tests/CrossdeckTests/ContractFailedSchemaLockTests.swift",
3138
+ "name": "test_reportContractFailure_payloadFieldsAreInAllowList"
3139
+ },
3140
+ {
3141
+ "file": "sdks/swift/Tests/CrossdeckTests/ContractFailedSchemaLockTests.swift",
3142
+ "name": "test_reportContractFailure_doesNotEnterCustomerTrackPipeline"
3143
+ },
3144
+ {
3145
+ "file": "sdks/android/crossdeck/src/test/kotlin/com/crossdeck/ContractFailedSchemaLockTest.kt",
3146
+ "name": "reportContractFailure payload conforms to schema-lock"
3147
+ },
3148
+ {
3149
+ "file": "sdks/android/crossdeck/src/test/kotlin/com/crossdeck/ContractFailedSchemaLockTest.kt",
3150
+ "name": "reportContractFailure does not enter customer track pipeline"
3151
+ },
3152
+ {
3153
+ "file": "sdks/react-native/tests/contract-failed-schema-lock.test.ts",
3154
+ "name": "reportContractFailure payload conforms to schema-lock"
3155
+ },
3156
+ {
3157
+ "file": "backend/tests/unit/v1-sdk-diagnostic.test.ts",
3158
+ "name": "forbidden fields are enumerated in the schema-lock contract"
3159
+ },
3160
+ {
3161
+ "file": "backend/tests/unit/v1-sdk-diagnostic.test.ts",
3162
+ "name": "required fields are enumerated in the schema-lock contract"
3163
+ },
3164
+ {
3165
+ "file": "backend/tests/unit/v1-sdk-diagnostic.test.ts",
3166
+ "name": "regression guard: never returns a raw IP"
3167
+ },
3168
+ {
3169
+ "file": "backend/tests/unit/v1-sdk-diagnostic.test.ts",
3170
+ "name": "verification_phase is in the optional field set"
3171
+ }
3172
+ ],
3173
+ "registeredAt": "2026-05-27",
3174
+ "firstRegisteredIn": "Diagnostic telemetry single-fire + schema-lock \u2014 independent-controller flow",
3175
+ "privacyReferences": [
3176
+ "legal/privacy/index.html#sdk-diagnostic",
3177
+ "legal/customer-disclosure/index.html#flow-b",
3178
+ "legal/security/index.html#diagnostic",
3179
+ "legal/sdk-data/index.html#b-diagnostic"
3180
+ ],
3181
+ "bundledIn": "@cross-deck/react-native@1.5.3"
3182
+ },
3009
3183
  {
3010
3184
  "id": "error-envelope-shape",
3011
3185
  "pillar": "errors",
@@ -3043,7 +3217,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
3043
3217
  ],
3044
3218
  "registeredAt": "2026-05-26",
3045
3219
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 8 (codifies existing contract)",
3046
- "bundledIn": "@cross-deck/react-native@1.5.0"
3220
+ "bundledIn": "@cross-deck/react-native@1.5.3"
3047
3221
  },
3048
3222
  {
3049
3223
  "id": "flush-interval-parity",
@@ -3088,7 +3262,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
3088
3262
  ],
3089
3263
  "registeredAt": "2026-05-26",
3090
3264
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.3",
3091
- "bundledIn": "@cross-deck/react-native@1.5.0"
3265
+ "bundledIn": "@cross-deck/react-native@1.5.3"
3092
3266
  },
3093
3267
  {
3094
3268
  "id": "idempotency-key-deterministic",
@@ -3193,7 +3367,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
3193
3367
  ],
3194
3368
  "registeredAt": "2026-05-26",
3195
3369
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 2.2.a + 2.2.b + 2.2.c",
3196
- "bundledIn": "@cross-deck/react-native@1.5.0"
3370
+ "bundledIn": "@cross-deck/react-native@1.5.3"
3197
3371
  },
3198
3372
  {
3199
3373
  "id": "init-reentry-drains-prior-queue",
@@ -3220,7 +3394,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
3220
3394
  ],
3221
3395
  "registeredAt": "2026-05-26",
3222
3396
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 5.5",
3223
- "bundledIn": "@cross-deck/react-native@1.5.0"
3397
+ "bundledIn": "@cross-deck/react-native@1.5.3"
3224
3398
  },
3225
3399
  {
3226
3400
  "id": "per-user-cache-isolation",
@@ -3299,7 +3473,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
3299
3473
  ],
3300
3474
  "registeredAt": "2026-05-26",
3301
3475
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 1.3 (web/RN) + dogfood-gap fix (swift + android)",
3302
- "bundledIn": "@cross-deck/react-native@1.5.0"
3476
+ "bundledIn": "@cross-deck/react-native@1.5.3"
3303
3477
  },
3304
3478
  {
3305
3479
  "id": "rn-session-id-enrichment",
@@ -3332,7 +3506,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
3332
3506
  ],
3333
3507
  "registeredAt": "2026-05-26",
3334
3508
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.4",
3335
- "bundledIn": "@cross-deck/react-native@1.5.0"
3509
+ "bundledIn": "@cross-deck/react-native@1.5.3"
3336
3510
  },
3337
3511
  {
3338
3512
  "id": "sync-purchases-funnel-parity",
@@ -3365,7 +3539,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
3365
3539
  ],
3366
3540
  "registeredAt": "2026-05-26",
3367
3541
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.5",
3368
- "bundledIn": "@cross-deck/react-native@1.5.0"
3542
+ "bundledIn": "@cross-deck/react-native@1.5.3"
3369
3543
  }
3370
3544
  ]);
3371
3545