@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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "generatedAt": "2026-06-01T05:25:25.597Z",
3
+ "generatedAt": "2026-06-01T09:52:17.794Z",
4
4
  "sdk": "@cross-deck/web",
5
5
  "codes": [
6
6
  {
package/dist/index.cjs CHANGED
@@ -99,7 +99,7 @@ function typeMapForStatus(status) {
99
99
  }
100
100
 
101
101
  // src/_version.ts
102
- var SDK_VERSION = "1.6.2";
102
+ var SDK_VERSION = "1.6.4";
103
103
  var SDK_NAME = "@cross-deck/web";
104
104
 
105
105
  // src/http.ts
@@ -2865,6 +2865,15 @@ var VerifierReporter = class {
2865
2865
  constructor(ctx) {
2866
2866
  this.ctx = ctx;
2867
2867
  this.reentrancyDepth = 0;
2868
+ /** The live verifier set — attached by the SDK right after init so the
2869
+ * emit path can schema-lock its OWN outgoing telemetry against the real
2870
+ * payload. Null until attached (boot-time reports still work without it). */
2871
+ this.verifiers = null;
2872
+ }
2873
+ /** Hand the reporter the live verifier set so `reportFail` can run the
2874
+ * `onReportContractFailure` hook on the exact payload it's about to emit. */
2875
+ attachVerifiers(verifiers) {
2876
+ this.verifiers = verifiers;
2868
2877
  }
2869
2878
  /**
2870
2879
  * Report a verifier result. Pass `operation` for hot-path results
@@ -2893,7 +2902,7 @@ var VerifierReporter = class {
2893
2902
  if (this.reentrancyDepth > 0) return;
2894
2903
  this.reentrancyDepth += 1;
2895
2904
  try {
2896
- this.ctx.emitTelemetry({
2905
+ const payload = {
2897
2906
  contract_id: result.contractId,
2898
2907
  sdk_version: this.ctx.sdkVersion,
2899
2908
  sdk_platform: "web",
@@ -2901,7 +2910,13 @@ var VerifierReporter = class {
2901
2910
  run_context: this.ctx.runContext,
2902
2911
  run_id: this.ctx.runId,
2903
2912
  verification_phase: phase
2904
- });
2913
+ };
2914
+ if (this.verifiers) {
2915
+ runOnReportContractFailure(this.verifiers, this, this.ctx, {
2916
+ outgoingPayload: payload
2917
+ });
2918
+ }
2919
+ this.ctx.emitTelemetry(payload);
2905
2920
  } finally {
2906
2921
  this.reentrancyDepth -= 1;
2907
2922
  }
@@ -3191,9 +3206,31 @@ var VERIFIER_FLUSH_INTERVAL_PARITY = {
3191
3206
  "eventFlushIntervalMs default = 2000ms (Web/Node/RN/Swift/Android parity)",
3192
3207
  nowMs() - t0
3193
3208
  );
3209
+ },
3210
+ // Hot-path hook — on every real track() we re-affirm the LIVE configured
3211
+ // flush interval (carried on the observation), so the parity guarantee is
3212
+ // verified against the running SDK in the customer flow, not just a
3213
+ // canonical constant at boot. It can't change per-event, but observing it
3214
+ // per-event is what makes the contract genuinely runtime-verified.
3215
+ hooks: {
3216
+ onTrack(obs) {
3217
+ const t0 = nowMs();
3218
+ const CANONICAL_DEFAULT_MS = 2e3;
3219
+ const ms = obs.flushIntervalMs;
3220
+ if (typeof ms !== "number" || ms < 100 || ms > 6e4) {
3221
+ return fail(
3222
+ "flush-interval-parity",
3223
+ `live eventFlushIntervalMs=${ms} outside reasonable bounds [100, 60000]`,
3224
+ nowMs() - t0
3225
+ );
3226
+ }
3227
+ return pass(
3228
+ "flush-interval-parity",
3229
+ ms === CANONICAL_DEFAULT_MS ? "live eventFlushIntervalMs = 2000ms (canonical parity value)" : `live eventFlushIntervalMs = ${ms}ms (permitted override; canonical default 2000ms)`,
3230
+ nowMs() - t0
3231
+ );
3232
+ }
3194
3233
  }
3195
- // No hot-path hook — the flush interval is set once at start() and
3196
- // never changes per-operation.
3197
3234
  };
3198
3235
  function buildFlushIntervalVerifier(configuredIntervalMs) {
3199
3236
  return {
@@ -3320,6 +3357,26 @@ var CONTRACT_FAILED_FORBIDDEN_FIELDS = [
3320
3357
  "referrer",
3321
3358
  "deviceId"
3322
3359
  ];
3360
+ function contractFailedSchemaViolation(keys) {
3361
+ const allowed = /* @__PURE__ */ new Set([
3362
+ ...CONTRACT_FAILED_REQUIRED_FIELDS,
3363
+ ...CONTRACT_FAILED_OPTIONAL_FIELDS
3364
+ ]);
3365
+ const forbidden = new Set(CONTRACT_FAILED_FORBIDDEN_FIELDS);
3366
+ for (const required of CONTRACT_FAILED_REQUIRED_FIELDS) {
3367
+ if (!keys.includes(required)) return `missing required field: ${required}`;
3368
+ }
3369
+ for (const k of keys) {
3370
+ if (forbidden.has(k)) return `forbidden field on wire: ${k}`;
3371
+ }
3372
+ for (const k of keys) {
3373
+ if (!allowed.has(k)) {
3374
+ return `unrecognised field on wire: ${k} (not in required \u222A optional)`;
3375
+ }
3376
+ }
3377
+ return null;
3378
+ }
3379
+ 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`;
3323
3380
  var VERIFIER_CONTRACT_FAILED_PAYLOAD_SCHEMA_LOCK = {
3324
3381
  contractId: "contract-failed-payload-schema-lock",
3325
3382
  bootTest() {
@@ -3334,43 +3391,35 @@ var VERIFIER_CONTRACT_FAILED_PAYLOAD_SCHEMA_LOCK = {
3334
3391
  verification_phase: "boot"
3335
3392
  };
3336
3393
  const keys = Object.keys(syntheticPayload);
3337
- const allowed = /* @__PURE__ */ new Set([
3338
- ...CONTRACT_FAILED_REQUIRED_FIELDS,
3339
- ...CONTRACT_FAILED_OPTIONAL_FIELDS
3340
- ]);
3341
- const forbidden = new Set(CONTRACT_FAILED_FORBIDDEN_FIELDS);
3342
- for (const required of CONTRACT_FAILED_REQUIRED_FIELDS) {
3343
- if (!keys.includes(required)) {
3344
- return fail(
3345
- "contract-failed-payload-schema-lock",
3346
- `missing required field: ${required}`,
3347
- nowMs() - t0
3348
- );
3349
- }
3350
- }
3351
- for (const k of keys) {
3352
- if (forbidden.has(k)) {
3353
- return fail(
3354
- "contract-failed-payload-schema-lock",
3355
- `forbidden field on wire: ${k}`,
3356
- nowMs() - t0
3357
- );
3358
- }
3359
- }
3360
- for (const k of keys) {
3361
- if (!allowed.has(k)) {
3362
- return fail(
3363
- "contract-failed-payload-schema-lock",
3364
- `unrecognised field on wire: ${k} (not in required \u222A optional)`,
3365
- nowMs() - t0
3366
- );
3367
- }
3394
+ const violation = contractFailedSchemaViolation(keys);
3395
+ if (violation) {
3396
+ return fail("contract-failed-payload-schema-lock", violation, nowMs() - t0);
3368
3397
  }
3369
3398
  return pass(
3370
3399
  "contract-failed-payload-schema-lock",
3371
- `${keys.length} fields \u2286 required(${CONTRACT_FAILED_REQUIRED_FIELDS.length}) \u222A optional(${CONTRACT_FAILED_OPTIONAL_FIELDS.length}); ${CONTRACT_FAILED_FORBIDDEN_FIELDS.length} forbidden absent`,
3400
+ `${keys.length} ${CONTRACT_FAILED_OK_EVIDENCE}`,
3372
3401
  nowMs() - t0
3373
3402
  );
3403
+ },
3404
+ // Hot-path hook — fires on the REAL reportFail emit (VerifierReporter,
3405
+ // inside the re-entrancy guard) against the exact payload about to go on
3406
+ // the wire. This is the assertion the guard at reportFail was built in
3407
+ // anticipation of: every reliability-channel write is schema-locked in the
3408
+ // field, against real data, not just the synthetic boot mirror.
3409
+ hooks: {
3410
+ onReportContractFailure(obs) {
3411
+ const t0 = nowMs();
3412
+ const keys = Object.keys(obs.outgoingPayload);
3413
+ const violation = contractFailedSchemaViolation(keys);
3414
+ if (violation) {
3415
+ return fail("contract-failed-payload-schema-lock", violation, nowMs() - t0);
3416
+ }
3417
+ return pass(
3418
+ "contract-failed-payload-schema-lock",
3419
+ `${keys.length} ${CONTRACT_FAILED_OK_EVIDENCE}`,
3420
+ nowMs() - t0
3421
+ );
3422
+ }
3374
3423
  }
3375
3424
  };
3376
3425
  var BACKEND_WIRE_CODES = Object.freeze([
@@ -3421,6 +3470,39 @@ var VERIFIER_SDK_ERROR_CODES_CATALOGUE = {
3421
3470
  nowMs() - t0
3422
3471
  );
3423
3472
  }
3473
+ },
3474
+ // Hot-path hook — when the SDK parses a real wire error, assert the code
3475
+ // the customer actually hit carries usable remediation in the shipped
3476
+ // catalogue. Scoped to BACKEND_WIRE_CODES (the contract's set); other
3477
+ // codes are out of scope and pass. This turns the catalogue from a
3478
+ // boot-only completeness claim into a per-error field assertion: a backend
3479
+ // code that ships without remediation is caught the first time a customer
3480
+ // hits it, not only if the off-by-default boot self-test ran.
3481
+ hooks: {
3482
+ onErrorParse(obs) {
3483
+ const t0 = nowMs();
3484
+ const code = obs.errorCode;
3485
+ if (!code || !BACKEND_WIRE_CODES.includes(code)) {
3486
+ return pass(
3487
+ "sdk-error-codes-catalogue",
3488
+ `wire code "${code || "(none)"}" is out of the backend-catalogue scope`,
3489
+ nowMs() - t0
3490
+ );
3491
+ }
3492
+ const entry = getErrorCode(code);
3493
+ if (!entry || !entry.description || entry.description.trim().length === 0 || !entry.resolution || entry.resolution.trim().length === 0) {
3494
+ return fail(
3495
+ "sdk-error-codes-catalogue",
3496
+ `backend wire code "${code}" hit in the field has no description+resolution in the shipped catalogue`,
3497
+ nowMs() - t0
3498
+ );
3499
+ }
3500
+ return pass(
3501
+ "sdk-error-codes-catalogue",
3502
+ `backend wire code "${code}" carries description + resolution`,
3503
+ nowMs() - t0
3504
+ );
3505
+ }
3424
3506
  }
3425
3507
  };
3426
3508
  var STATIC_VERIFIERS = Object.freeze([
@@ -3544,6 +3626,24 @@ function runOnErrorParse(verifiers, reporter, ctx, obs) {
3544
3626
  reporter.report(result, "hot_path", "errorParse");
3545
3627
  }
3546
3628
  }
3629
+ function runOnReportContractFailure(verifiers, reporter, ctx, obs) {
3630
+ if (ctx.disableContractAssertions) return;
3631
+ for (const verifier of verifiers) {
3632
+ const hook = verifier.hooks?.onReportContractFailure;
3633
+ if (!hook) continue;
3634
+ let result;
3635
+ try {
3636
+ result = hook(obs);
3637
+ } catch (err) {
3638
+ result = fail(
3639
+ verifier.contractId,
3640
+ `hook threw: ${err.message?.slice(0, 80) ?? "unknown"}`,
3641
+ 0
3642
+ );
3643
+ }
3644
+ reporter.report(result, "hot_path", "reportContractFailure");
3645
+ }
3646
+ }
3547
3647
  function defaultDebugModeFlag() {
3548
3648
  try {
3549
3649
  if (typeof process !== "undefined" && process.env) {
@@ -3943,7 +4043,7 @@ var ErrorTracker = class {
3943
4043
  }
3944
4044
  return response;
3945
4045
  } catch (err) {
3946
- if (this.opts.isConsented() && !url.includes("api.cross-deck.com")) {
4046
+ if (this.opts.isConsented() && !url.includes("api.cross-deck.com") && !isClientNetworkNoise(url, err, w)) {
3947
4047
  this.captureHttp({
3948
4048
  url,
3949
4049
  method,
@@ -4342,6 +4442,19 @@ function isSelfRequest(requestUrl, selfHostname) {
4342
4442
  return false;
4343
4443
  }
4344
4444
  }
4445
+ function isClientNetworkNoise(requestUrl, err, w) {
4446
+ if (err instanceof Error && err.name === "AbortError") return true;
4447
+ const nav = w.navigator;
4448
+ if (nav && nav.onLine === false) return true;
4449
+ const pageHref = w.location?.href;
4450
+ const pageOrigin = w.location?.origin;
4451
+ if (!pageOrigin) return false;
4452
+ try {
4453
+ return new URL(requestUrl, pageHref ?? pageOrigin).origin === pageOrigin;
4454
+ } catch {
4455
+ return false;
4456
+ }
4457
+ }
4345
4458
 
4346
4459
  // src/crossdeck.ts
4347
4460
  var CrossdeckClient = class {
@@ -4363,6 +4476,9 @@ var CrossdeckClient = class {
4363
4476
  this.verifiers = null;
4364
4477
  this.verifierReporter = null;
4365
4478
  this.verifierCtx = null;
4479
+ // The live configured event-flush interval, surfaced to the track-path
4480
+ // verifier so flush-interval-parity validates the real cadence per event.
4481
+ this.flushIntervalMs = 2e3;
4366
4482
  }
4367
4483
  /**
4368
4484
  * Boot the SDK. Idempotent — calling init twice with the same options
@@ -4642,6 +4758,8 @@ var CrossdeckClient = class {
4642
4758
  this.verifierReporter = new VerifierReporter(this.verifierCtx);
4643
4759
  const flushVerifier = buildFlushIntervalVerifier(opts.eventFlushIntervalMs);
4644
4760
  this.verifiers = Object.freeze([...STATIC_VERIFIERS, flushVerifier]);
4761
+ this.flushIntervalMs = opts.eventFlushIntervalMs;
4762
+ this.verifierReporter.attachVerifiers(this.verifiers);
4645
4763
  if (localDevMode) {
4646
4764
  const verifyAtBootLocal = options.verifyContractsAtBoot ?? debugDefault;
4647
4765
  if (verifyAtBootLocal && this.verifierReporter) {
@@ -5216,9 +5334,7 @@ var CrossdeckClient = class {
5216
5334
  }
5217
5335
  }
5218
5336
  const supers = s.superProps.getSuperProperties();
5219
- for (const k of Object.keys(supers)) {
5220
- if (!(k in enriched)) enriched[k] = supers[k];
5221
- }
5337
+ Object.assign(enriched, supers);
5222
5338
  const groupIds = s.superProps.getGroupIds();
5223
5339
  if (Object.keys(groupIds).length > 0) {
5224
5340
  enriched.$groups = groupIds;
@@ -5238,7 +5354,8 @@ var CrossdeckClient = class {
5238
5354
  callerProperties: validation.properties,
5239
5355
  superProperties: supers,
5240
5356
  deviceProperties: s.deviceInfo,
5241
- mergedProperties: enriched
5357
+ mergedProperties: enriched,
5358
+ flushIntervalMs: this.flushIntervalMs
5242
5359
  });
5243
5360
  }
5244
5361
  if (!isError && !isWebVital) {
@@ -5583,8 +5700,8 @@ function installUnloadFlush(onUnload) {
5583
5700
  }
5584
5701
 
5585
5702
  // src/_contracts-bundled.ts
5586
- var BUNDLED_IN = "@cross-deck/web@1.6.2";
5587
- var SDK_VERSION2 = "1.6.2";
5703
+ var BUNDLED_IN = "@cross-deck/web@1.6.4";
5704
+ var SDK_VERSION2 = "1.6.4";
5588
5705
  var BUNDLED_CONTRACTS = Object.freeze([
5589
5706
  {
5590
5707
  "id": "contract-failed-payload-schema-lock",
@@ -5706,7 +5823,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5706
5823
  "legal/security/index.html#diagnostic",
5707
5824
  "legal/sdk-data/index.html#b-diagnostic"
5708
5825
  ],
5709
- "bundledIn": "@cross-deck/web@1.6.2",
5826
+ "bundledIn": "@cross-deck/web@1.6.4",
5710
5827
  "runtimeVerified": true
5711
5828
  },
5712
5829
  {
@@ -5746,7 +5863,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5746
5863
  ],
5747
5864
  "registeredAt": "2026-05-26",
5748
5865
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 8 (codifies existing contract)",
5749
- "bundledIn": "@cross-deck/web@1.6.2",
5866
+ "bundledIn": "@cross-deck/web@1.6.4",
5750
5867
  "runtimeVerified": true
5751
5868
  },
5752
5869
  {
@@ -5792,7 +5909,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5792
5909
  ],
5793
5910
  "registeredAt": "2026-05-26",
5794
5911
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.3",
5795
- "bundledIn": "@cross-deck/web@1.6.2",
5912
+ "bundledIn": "@cross-deck/web@1.6.4",
5796
5913
  "runtimeVerified": true
5797
5914
  },
5798
5915
  {
@@ -5898,7 +6015,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5898
6015
  ],
5899
6016
  "registeredAt": "2026-05-26",
5900
6017
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 2.2.a + 2.2.b + 2.2.c",
5901
- "bundledIn": "@cross-deck/web@1.6.2",
6018
+ "bundledIn": "@cross-deck/web@1.6.4",
5902
6019
  "runtimeVerified": true
5903
6020
  },
5904
6021
  {
@@ -5926,7 +6043,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5926
6043
  ],
5927
6044
  "registeredAt": "2026-05-26",
5928
6045
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 5.5",
5929
- "bundledIn": "@cross-deck/web@1.6.2",
6046
+ "bundledIn": "@cross-deck/web@1.6.4",
5930
6047
  "runtimeVerified": false
5931
6048
  },
5932
6049
  {
@@ -6006,7 +6123,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
6006
6123
  ],
6007
6124
  "registeredAt": "2026-05-26",
6008
6125
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 1.3 (web/RN) + dogfood-gap fix (swift + android)",
6009
- "bundledIn": "@cross-deck/web@1.6.2",
6126
+ "bundledIn": "@cross-deck/web@1.6.4",
6010
6127
  "runtimeVerified": true
6011
6128
  },
6012
6129
  {
@@ -6052,7 +6169,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
6052
6169
  ],
6053
6170
  "registeredAt": "2026-05-26",
6054
6171
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 6.2",
6055
- "bundledIn": "@cross-deck/web@1.6.2",
6172
+ "bundledIn": "@cross-deck/web@1.6.4",
6056
6173
  "runtimeVerified": true
6057
6174
  },
6058
6175
  {
@@ -6094,7 +6211,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
6094
6211
  ],
6095
6212
  "registeredAt": "2026-05-26",
6096
6213
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.2",
6097
- "bundledIn": "@cross-deck/web@1.6.2",
6214
+ "bundledIn": "@cross-deck/web@1.6.4",
6098
6215
  "runtimeVerified": true
6099
6216
  },
6100
6217
  {
@@ -6128,7 +6245,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
6128
6245
  ],
6129
6246
  "registeredAt": "2026-05-26",
6130
6247
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.5",
6131
- "bundledIn": "@cross-deck/web@1.6.2",
6248
+ "bundledIn": "@cross-deck/web@1.6.4",
6132
6249
  "runtimeVerified": false
6133
6250
  }
6134
6251
  ]);