@cross-deck/web 1.6.2 → 1.6.4

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
@@ -247,9 +247,9 @@ declare const CrossdeckContracts: {
247
247
  /** Filter by lifecycle status. */
248
248
  readonly withStatus: (status: ContractStatus) => readonly Contract[];
249
249
  /** Semver of the SDK release these contracts were bundled with. */
250
- readonly sdkVersion: "1.6.2";
250
+ readonly sdkVersion: "1.6.4";
251
251
  /** Fully-qualified bundle identifier — e.g. `@cross-deck/web@1.4.2`. */
252
- readonly bundledIn: "@cross-deck/web@1.6.2";
252
+ readonly bundledIn: "@cross-deck/web@1.6.4";
253
253
  /**
254
254
  * Resolve a failing test back to the contract it exercises.
255
255
  * Used by test-framework hooks (Vitest `afterEach`, XCTest
@@ -462,6 +462,7 @@ declare class CrossdeckClient {
462
462
  private verifiers;
463
463
  private verifierReporter;
464
464
  private verifierCtx;
465
+ private flushIntervalMs;
465
466
  /**
466
467
  * Boot the SDK. Idempotent — calling init twice with the same options
467
468
  * is a no-op; calling with different options replaces the previous
@@ -936,7 +937,7 @@ declare class MemoryStorage implements KeyValueStorage {
936
937
  *
937
938
  * Do NOT edit by hand — `node scripts/sync-sdk-versions.mjs`.
938
939
  */
939
- declare const SDK_VERSION = "1.6.2";
940
+ declare const SDK_VERSION = "1.6.4";
940
941
  declare const SDK_NAME = "@cross-deck/web";
941
942
 
942
943
  /**
package/dist/index.d.ts CHANGED
@@ -247,9 +247,9 @@ declare const CrossdeckContracts: {
247
247
  /** Filter by lifecycle status. */
248
248
  readonly withStatus: (status: ContractStatus) => readonly Contract[];
249
249
  /** Semver of the SDK release these contracts were bundled with. */
250
- readonly sdkVersion: "1.6.2";
250
+ readonly sdkVersion: "1.6.4";
251
251
  /** Fully-qualified bundle identifier — e.g. `@cross-deck/web@1.4.2`. */
252
- readonly bundledIn: "@cross-deck/web@1.6.2";
252
+ readonly bundledIn: "@cross-deck/web@1.6.4";
253
253
  /**
254
254
  * Resolve a failing test back to the contract it exercises.
255
255
  * Used by test-framework hooks (Vitest `afterEach`, XCTest
@@ -462,6 +462,7 @@ declare class CrossdeckClient {
462
462
  private verifiers;
463
463
  private verifierReporter;
464
464
  private verifierCtx;
465
+ private flushIntervalMs;
465
466
  /**
466
467
  * Boot the SDK. Idempotent — calling init twice with the same options
467
468
  * is a no-op; calling with different options replaces the previous
@@ -936,7 +937,7 @@ declare class MemoryStorage implements KeyValueStorage {
936
937
  *
937
938
  * Do NOT edit by hand — `node scripts/sync-sdk-versions.mjs`.
938
939
  */
939
- declare const SDK_VERSION = "1.6.2";
940
+ declare const SDK_VERSION = "1.6.4";
940
941
  declare const SDK_NAME = "@cross-deck/web";
941
942
 
942
943
  /**
package/dist/index.mjs CHANGED
@@ -64,7 +64,7 @@ function typeMapForStatus(status) {
64
64
  }
65
65
 
66
66
  // src/_version.ts
67
- var SDK_VERSION = "1.6.2";
67
+ var SDK_VERSION = "1.6.4";
68
68
  var SDK_NAME = "@cross-deck/web";
69
69
 
70
70
  // src/http.ts
@@ -2830,6 +2830,15 @@ var VerifierReporter = class {
2830
2830
  constructor(ctx) {
2831
2831
  this.ctx = ctx;
2832
2832
  this.reentrancyDepth = 0;
2833
+ /** The live verifier set — attached by the SDK right after init so the
2834
+ * emit path can schema-lock its OWN outgoing telemetry against the real
2835
+ * payload. Null until attached (boot-time reports still work without it). */
2836
+ this.verifiers = null;
2837
+ }
2838
+ /** Hand the reporter the live verifier set so `reportFail` can run the
2839
+ * `onReportContractFailure` hook on the exact payload it's about to emit. */
2840
+ attachVerifiers(verifiers) {
2841
+ this.verifiers = verifiers;
2833
2842
  }
2834
2843
  /**
2835
2844
  * Report a verifier result. Pass `operation` for hot-path results
@@ -2858,7 +2867,7 @@ var VerifierReporter = class {
2858
2867
  if (this.reentrancyDepth > 0) return;
2859
2868
  this.reentrancyDepth += 1;
2860
2869
  try {
2861
- this.ctx.emitTelemetry({
2870
+ const payload = {
2862
2871
  contract_id: result.contractId,
2863
2872
  sdk_version: this.ctx.sdkVersion,
2864
2873
  sdk_platform: "web",
@@ -2866,7 +2875,13 @@ var VerifierReporter = class {
2866
2875
  run_context: this.ctx.runContext,
2867
2876
  run_id: this.ctx.runId,
2868
2877
  verification_phase: phase
2869
- });
2878
+ };
2879
+ if (this.verifiers) {
2880
+ runOnReportContractFailure(this.verifiers, this, this.ctx, {
2881
+ outgoingPayload: payload
2882
+ });
2883
+ }
2884
+ this.ctx.emitTelemetry(payload);
2870
2885
  } finally {
2871
2886
  this.reentrancyDepth -= 1;
2872
2887
  }
@@ -3156,9 +3171,31 @@ var VERIFIER_FLUSH_INTERVAL_PARITY = {
3156
3171
  "eventFlushIntervalMs default = 2000ms (Web/Node/RN/Swift/Android parity)",
3157
3172
  nowMs() - t0
3158
3173
  );
3174
+ },
3175
+ // Hot-path hook — on every real track() we re-affirm the LIVE configured
3176
+ // flush interval (carried on the observation), so the parity guarantee is
3177
+ // verified against the running SDK in the customer flow, not just a
3178
+ // canonical constant at boot. It can't change per-event, but observing it
3179
+ // per-event is what makes the contract genuinely runtime-verified.
3180
+ hooks: {
3181
+ onTrack(obs) {
3182
+ const t0 = nowMs();
3183
+ const CANONICAL_DEFAULT_MS = 2e3;
3184
+ const ms = obs.flushIntervalMs;
3185
+ if (typeof ms !== "number" || ms < 100 || ms > 6e4) {
3186
+ return fail(
3187
+ "flush-interval-parity",
3188
+ `live eventFlushIntervalMs=${ms} outside reasonable bounds [100, 60000]`,
3189
+ nowMs() - t0
3190
+ );
3191
+ }
3192
+ return pass(
3193
+ "flush-interval-parity",
3194
+ ms === CANONICAL_DEFAULT_MS ? "live eventFlushIntervalMs = 2000ms (canonical parity value)" : `live eventFlushIntervalMs = ${ms}ms (permitted override; canonical default 2000ms)`,
3195
+ nowMs() - t0
3196
+ );
3197
+ }
3159
3198
  }
3160
- // No hot-path hook — the flush interval is set once at start() and
3161
- // never changes per-operation.
3162
3199
  };
3163
3200
  function buildFlushIntervalVerifier(configuredIntervalMs) {
3164
3201
  return {
@@ -3285,6 +3322,26 @@ var CONTRACT_FAILED_FORBIDDEN_FIELDS = [
3285
3322
  "referrer",
3286
3323
  "deviceId"
3287
3324
  ];
3325
+ function contractFailedSchemaViolation(keys) {
3326
+ const allowed = /* @__PURE__ */ new Set([
3327
+ ...CONTRACT_FAILED_REQUIRED_FIELDS,
3328
+ ...CONTRACT_FAILED_OPTIONAL_FIELDS
3329
+ ]);
3330
+ const forbidden = new Set(CONTRACT_FAILED_FORBIDDEN_FIELDS);
3331
+ for (const required of CONTRACT_FAILED_REQUIRED_FIELDS) {
3332
+ if (!keys.includes(required)) return `missing required field: ${required}`;
3333
+ }
3334
+ for (const k of keys) {
3335
+ if (forbidden.has(k)) return `forbidden field on wire: ${k}`;
3336
+ }
3337
+ for (const k of keys) {
3338
+ if (!allowed.has(k)) {
3339
+ return `unrecognised field on wire: ${k} (not in required \u222A optional)`;
3340
+ }
3341
+ }
3342
+ return null;
3343
+ }
3344
+ var CONTRACT_FAILED_OK_EVIDENCE = `fields \u2286 required(${CONTRACT_FAILED_REQUIRED_FIELDS.length}) \u222A optional(${CONTRACT_FAILED_OPTIONAL_FIELDS.length}); ${CONTRACT_FAILED_FORBIDDEN_FIELDS.length} forbidden absent`;
3288
3345
  var VERIFIER_CONTRACT_FAILED_PAYLOAD_SCHEMA_LOCK = {
3289
3346
  contractId: "contract-failed-payload-schema-lock",
3290
3347
  bootTest() {
@@ -3299,43 +3356,35 @@ var VERIFIER_CONTRACT_FAILED_PAYLOAD_SCHEMA_LOCK = {
3299
3356
  verification_phase: "boot"
3300
3357
  };
3301
3358
  const keys = Object.keys(syntheticPayload);
3302
- const allowed = /* @__PURE__ */ new Set([
3303
- ...CONTRACT_FAILED_REQUIRED_FIELDS,
3304
- ...CONTRACT_FAILED_OPTIONAL_FIELDS
3305
- ]);
3306
- const forbidden = new Set(CONTRACT_FAILED_FORBIDDEN_FIELDS);
3307
- for (const required of CONTRACT_FAILED_REQUIRED_FIELDS) {
3308
- if (!keys.includes(required)) {
3309
- return fail(
3310
- "contract-failed-payload-schema-lock",
3311
- `missing required field: ${required}`,
3312
- nowMs() - t0
3313
- );
3314
- }
3315
- }
3316
- for (const k of keys) {
3317
- if (forbidden.has(k)) {
3318
- return fail(
3319
- "contract-failed-payload-schema-lock",
3320
- `forbidden field on wire: ${k}`,
3321
- nowMs() - t0
3322
- );
3323
- }
3324
- }
3325
- for (const k of keys) {
3326
- if (!allowed.has(k)) {
3327
- return fail(
3328
- "contract-failed-payload-schema-lock",
3329
- `unrecognised field on wire: ${k} (not in required \u222A optional)`,
3330
- nowMs() - t0
3331
- );
3332
- }
3359
+ const violation = contractFailedSchemaViolation(keys);
3360
+ if (violation) {
3361
+ return fail("contract-failed-payload-schema-lock", violation, nowMs() - t0);
3333
3362
  }
3334
3363
  return pass(
3335
3364
  "contract-failed-payload-schema-lock",
3336
- `${keys.length} fields \u2286 required(${CONTRACT_FAILED_REQUIRED_FIELDS.length}) \u222A optional(${CONTRACT_FAILED_OPTIONAL_FIELDS.length}); ${CONTRACT_FAILED_FORBIDDEN_FIELDS.length} forbidden absent`,
3365
+ `${keys.length} ${CONTRACT_FAILED_OK_EVIDENCE}`,
3337
3366
  nowMs() - t0
3338
3367
  );
3368
+ },
3369
+ // Hot-path hook — fires on the REAL reportFail emit (VerifierReporter,
3370
+ // inside the re-entrancy guard) against the exact payload about to go on
3371
+ // the wire. This is the assertion the guard at reportFail was built in
3372
+ // anticipation of: every reliability-channel write is schema-locked in the
3373
+ // field, against real data, not just the synthetic boot mirror.
3374
+ hooks: {
3375
+ onReportContractFailure(obs) {
3376
+ const t0 = nowMs();
3377
+ const keys = Object.keys(obs.outgoingPayload);
3378
+ const violation = contractFailedSchemaViolation(keys);
3379
+ if (violation) {
3380
+ return fail("contract-failed-payload-schema-lock", violation, nowMs() - t0);
3381
+ }
3382
+ return pass(
3383
+ "contract-failed-payload-schema-lock",
3384
+ `${keys.length} ${CONTRACT_FAILED_OK_EVIDENCE}`,
3385
+ nowMs() - t0
3386
+ );
3387
+ }
3339
3388
  }
3340
3389
  };
3341
3390
  var BACKEND_WIRE_CODES = Object.freeze([
@@ -3386,6 +3435,39 @@ var VERIFIER_SDK_ERROR_CODES_CATALOGUE = {
3386
3435
  nowMs() - t0
3387
3436
  );
3388
3437
  }
3438
+ },
3439
+ // Hot-path hook — when the SDK parses a real wire error, assert the code
3440
+ // the customer actually hit carries usable remediation in the shipped
3441
+ // catalogue. Scoped to BACKEND_WIRE_CODES (the contract's set); other
3442
+ // codes are out of scope and pass. This turns the catalogue from a
3443
+ // boot-only completeness claim into a per-error field assertion: a backend
3444
+ // code that ships without remediation is caught the first time a customer
3445
+ // hits it, not only if the off-by-default boot self-test ran.
3446
+ hooks: {
3447
+ onErrorParse(obs) {
3448
+ const t0 = nowMs();
3449
+ const code = obs.errorCode;
3450
+ if (!code || !BACKEND_WIRE_CODES.includes(code)) {
3451
+ return pass(
3452
+ "sdk-error-codes-catalogue",
3453
+ `wire code "${code || "(none)"}" is out of the backend-catalogue scope`,
3454
+ nowMs() - t0
3455
+ );
3456
+ }
3457
+ const entry = getErrorCode(code);
3458
+ if (!entry || !entry.description || entry.description.trim().length === 0 || !entry.resolution || entry.resolution.trim().length === 0) {
3459
+ return fail(
3460
+ "sdk-error-codes-catalogue",
3461
+ `backend wire code "${code}" hit in the field has no description+resolution in the shipped catalogue`,
3462
+ nowMs() - t0
3463
+ );
3464
+ }
3465
+ return pass(
3466
+ "sdk-error-codes-catalogue",
3467
+ `backend wire code "${code}" carries description + resolution`,
3468
+ nowMs() - t0
3469
+ );
3470
+ }
3389
3471
  }
3390
3472
  };
3391
3473
  var STATIC_VERIFIERS = Object.freeze([
@@ -3509,6 +3591,24 @@ function runOnErrorParse(verifiers, reporter, ctx, obs) {
3509
3591
  reporter.report(result, "hot_path", "errorParse");
3510
3592
  }
3511
3593
  }
3594
+ function runOnReportContractFailure(verifiers, reporter, ctx, obs) {
3595
+ if (ctx.disableContractAssertions) return;
3596
+ for (const verifier of verifiers) {
3597
+ const hook = verifier.hooks?.onReportContractFailure;
3598
+ if (!hook) continue;
3599
+ let result;
3600
+ try {
3601
+ result = hook(obs);
3602
+ } catch (err) {
3603
+ result = fail(
3604
+ verifier.contractId,
3605
+ `hook threw: ${err.message?.slice(0, 80) ?? "unknown"}`,
3606
+ 0
3607
+ );
3608
+ }
3609
+ reporter.report(result, "hot_path", "reportContractFailure");
3610
+ }
3611
+ }
3512
3612
  function defaultDebugModeFlag() {
3513
3613
  try {
3514
3614
  if (typeof process !== "undefined" && process.env) {
@@ -3908,7 +4008,7 @@ var ErrorTracker = class {
3908
4008
  }
3909
4009
  return response;
3910
4010
  } catch (err) {
3911
- if (this.opts.isConsented() && !url.includes("api.cross-deck.com")) {
4011
+ if (this.opts.isConsented() && !url.includes("api.cross-deck.com") && !isClientNetworkNoise(url, err, w)) {
3912
4012
  this.captureHttp({
3913
4013
  url,
3914
4014
  method,
@@ -4307,6 +4407,19 @@ function isSelfRequest(requestUrl, selfHostname) {
4307
4407
  return false;
4308
4408
  }
4309
4409
  }
4410
+ function isClientNetworkNoise(requestUrl, err, w) {
4411
+ if (err instanceof Error && err.name === "AbortError") return true;
4412
+ const nav = w.navigator;
4413
+ if (nav && nav.onLine === false) return true;
4414
+ const pageHref = w.location?.href;
4415
+ const pageOrigin = w.location?.origin;
4416
+ if (!pageOrigin) return false;
4417
+ try {
4418
+ return new URL(requestUrl, pageHref ?? pageOrigin).origin === pageOrigin;
4419
+ } catch {
4420
+ return false;
4421
+ }
4422
+ }
4310
4423
 
4311
4424
  // src/crossdeck.ts
4312
4425
  var CrossdeckClient = class {
@@ -4328,6 +4441,9 @@ var CrossdeckClient = class {
4328
4441
  this.verifiers = null;
4329
4442
  this.verifierReporter = null;
4330
4443
  this.verifierCtx = null;
4444
+ // The live configured event-flush interval, surfaced to the track-path
4445
+ // verifier so flush-interval-parity validates the real cadence per event.
4446
+ this.flushIntervalMs = 2e3;
4331
4447
  }
4332
4448
  /**
4333
4449
  * Boot the SDK. Idempotent — calling init twice with the same options
@@ -4607,6 +4723,8 @@ var CrossdeckClient = class {
4607
4723
  this.verifierReporter = new VerifierReporter(this.verifierCtx);
4608
4724
  const flushVerifier = buildFlushIntervalVerifier(opts.eventFlushIntervalMs);
4609
4725
  this.verifiers = Object.freeze([...STATIC_VERIFIERS, flushVerifier]);
4726
+ this.flushIntervalMs = opts.eventFlushIntervalMs;
4727
+ this.verifierReporter.attachVerifiers(this.verifiers);
4610
4728
  if (localDevMode) {
4611
4729
  const verifyAtBootLocal = options.verifyContractsAtBoot ?? debugDefault;
4612
4730
  if (verifyAtBootLocal && this.verifierReporter) {
@@ -5181,9 +5299,7 @@ var CrossdeckClient = class {
5181
5299
  }
5182
5300
  }
5183
5301
  const supers = s.superProps.getSuperProperties();
5184
- for (const k of Object.keys(supers)) {
5185
- if (!(k in enriched)) enriched[k] = supers[k];
5186
- }
5302
+ Object.assign(enriched, supers);
5187
5303
  const groupIds = s.superProps.getGroupIds();
5188
5304
  if (Object.keys(groupIds).length > 0) {
5189
5305
  enriched.$groups = groupIds;
@@ -5203,7 +5319,8 @@ var CrossdeckClient = class {
5203
5319
  callerProperties: validation.properties,
5204
5320
  superProperties: supers,
5205
5321
  deviceProperties: s.deviceInfo,
5206
- mergedProperties: enriched
5322
+ mergedProperties: enriched,
5323
+ flushIntervalMs: this.flushIntervalMs
5207
5324
  });
5208
5325
  }
5209
5326
  if (!isError && !isWebVital) {
@@ -5548,8 +5665,8 @@ function installUnloadFlush(onUnload) {
5548
5665
  }
5549
5666
 
5550
5667
  // src/_contracts-bundled.ts
5551
- var BUNDLED_IN = "@cross-deck/web@1.6.2";
5552
- var SDK_VERSION2 = "1.6.2";
5668
+ var BUNDLED_IN = "@cross-deck/web@1.6.4";
5669
+ var SDK_VERSION2 = "1.6.4";
5553
5670
  var BUNDLED_CONTRACTS = Object.freeze([
5554
5671
  {
5555
5672
  "id": "contract-failed-payload-schema-lock",
@@ -5671,7 +5788,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5671
5788
  "legal/security/index.html#diagnostic",
5672
5789
  "legal/sdk-data/index.html#b-diagnostic"
5673
5790
  ],
5674
- "bundledIn": "@cross-deck/web@1.6.2",
5791
+ "bundledIn": "@cross-deck/web@1.6.4",
5675
5792
  "runtimeVerified": true
5676
5793
  },
5677
5794
  {
@@ -5711,7 +5828,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5711
5828
  ],
5712
5829
  "registeredAt": "2026-05-26",
5713
5830
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 8 (codifies existing contract)",
5714
- "bundledIn": "@cross-deck/web@1.6.2",
5831
+ "bundledIn": "@cross-deck/web@1.6.4",
5715
5832
  "runtimeVerified": true
5716
5833
  },
5717
5834
  {
@@ -5757,7 +5874,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5757
5874
  ],
5758
5875
  "registeredAt": "2026-05-26",
5759
5876
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.3",
5760
- "bundledIn": "@cross-deck/web@1.6.2",
5877
+ "bundledIn": "@cross-deck/web@1.6.4",
5761
5878
  "runtimeVerified": true
5762
5879
  },
5763
5880
  {
@@ -5863,7 +5980,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5863
5980
  ],
5864
5981
  "registeredAt": "2026-05-26",
5865
5982
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 2.2.a + 2.2.b + 2.2.c",
5866
- "bundledIn": "@cross-deck/web@1.6.2",
5983
+ "bundledIn": "@cross-deck/web@1.6.4",
5867
5984
  "runtimeVerified": true
5868
5985
  },
5869
5986
  {
@@ -5891,7 +6008,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5891
6008
  ],
5892
6009
  "registeredAt": "2026-05-26",
5893
6010
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 5.5",
5894
- "bundledIn": "@cross-deck/web@1.6.2",
6011
+ "bundledIn": "@cross-deck/web@1.6.4",
5895
6012
  "runtimeVerified": false
5896
6013
  },
5897
6014
  {
@@ -5971,7 +6088,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5971
6088
  ],
5972
6089
  "registeredAt": "2026-05-26",
5973
6090
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 1.3 (web/RN) + dogfood-gap fix (swift + android)",
5974
- "bundledIn": "@cross-deck/web@1.6.2",
6091
+ "bundledIn": "@cross-deck/web@1.6.4",
5975
6092
  "runtimeVerified": true
5976
6093
  },
5977
6094
  {
@@ -6017,7 +6134,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
6017
6134
  ],
6018
6135
  "registeredAt": "2026-05-26",
6019
6136
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 6.2",
6020
- "bundledIn": "@cross-deck/web@1.6.2",
6137
+ "bundledIn": "@cross-deck/web@1.6.4",
6021
6138
  "runtimeVerified": true
6022
6139
  },
6023
6140
  {
@@ -6059,7 +6176,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
6059
6176
  ],
6060
6177
  "registeredAt": "2026-05-26",
6061
6178
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.2",
6062
- "bundledIn": "@cross-deck/web@1.6.2",
6179
+ "bundledIn": "@cross-deck/web@1.6.4",
6063
6180
  "runtimeVerified": true
6064
6181
  },
6065
6182
  {
@@ -6093,7 +6210,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
6093
6210
  ],
6094
6211
  "registeredAt": "2026-05-26",
6095
6212
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.5",
6096
- "bundledIn": "@cross-deck/web@1.6.2",
6213
+ "bundledIn": "@cross-deck/web@1.6.4",
6097
6214
  "runtimeVerified": false
6098
6215
  }
6099
6216
  ]);