@cross-deck/web 1.5.6 → 1.6.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/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.5.3";
67
+ var SDK_VERSION = "1.6.1";
68
68
  var SDK_NAME = "@cross-deck/web";
69
69
 
70
70
  // src/http.ts
@@ -1413,6 +1413,8 @@ var DEFAULT_AUTO_TRACK = {
1413
1413
  errors: true
1414
1414
  };
1415
1415
  var SESSION_RESUME_THRESHOLD_MS = 30 * 60 * 1e3;
1416
+ var SESSION_STORAGE_KEY = "crossdeck:session";
1417
+ var ACTIVITY_PERSIST_THROTTLE_MS = 5e3;
1416
1418
  var EMPTY_ACQUISITION = {
1417
1419
  utm_source: "",
1418
1420
  utm_medium: "",
@@ -1428,11 +1430,13 @@ var EMPTY_ACQUISITION = {
1428
1430
  twclid: ""
1429
1431
  };
1430
1432
  var AutoTracker = class {
1431
- constructor(cfg, track) {
1433
+ constructor(cfg, track, opts) {
1432
1434
  this.cfg = cfg;
1433
1435
  this.track = track;
1434
1436
  this.session = null;
1435
1437
  this.cleanups = [];
1438
+ /** Last time we flushed lastActivityAt to storage (throttle gate). */
1439
+ this.lastPersistAt = 0;
1436
1440
  /**
1437
1441
  * Stable per-page-view identifier. Minted at every `page.viewed`
1438
1442
  * emission and attached to every subsequent event until the next
@@ -1444,6 +1448,8 @@ var AutoTracker = class {
1444
1448
  * install if `autoTrack.pageViews !== false`).
1445
1449
  */
1446
1450
  this.pageviewId = null;
1451
+ this.storage = opts?.storage ?? null;
1452
+ this.sessionKey = opts?.storageKey ?? SESSION_STORAGE_KEY;
1447
1453
  }
1448
1454
  install() {
1449
1455
  if (!isBrowserSafe()) return;
@@ -1462,6 +1468,7 @@ var AutoTracker = class {
1462
1468
  if (this.session && !this.session.endedSent) {
1463
1469
  this.emitSessionEnd();
1464
1470
  }
1471
+ this.clearStoredSession();
1465
1472
  this.session = null;
1466
1473
  }
1467
1474
  /** Exposed for tests + consumers that want to reset the session manually. */
@@ -1469,8 +1476,24 @@ var AutoTracker = class {
1469
1476
  if (this.session && !this.session.endedSent) this.emitSessionEnd();
1470
1477
  this.pageviewId = null;
1471
1478
  this.session = this.startNewSession();
1479
+ this.persistSession();
1472
1480
  this.emitSessionStart();
1473
1481
  }
1482
+ /**
1483
+ * Keep the rolling session window alive. Called by the host on EVERY
1484
+ * tracked event (auto or custom) so any activity — not just the
1485
+ * pageviews/clicks AutoTracker emits itself — pushes the 30-min idle
1486
+ * boundary forward. In-memory time is updated exactly; the storage
1487
+ * flush is throttled (pagehide forces a final flush).
1488
+ */
1489
+ markActivity() {
1490
+ if (!this.session) return;
1491
+ const now = Date.now();
1492
+ this.session.lastActivityAt = now;
1493
+ if (now - this.lastPersistAt >= ACTIVITY_PERSIST_THROTTLE_MS) {
1494
+ this.persistSession();
1495
+ }
1496
+ }
1474
1497
  /** Exposed for inspection/tests — returns the current sessionId (or null if not in a session). */
1475
1498
  get currentSessionId() {
1476
1499
  return this.session?.sessionId ?? null;
@@ -1493,26 +1516,43 @@ var AutoTracker = class {
1493
1516
  }
1494
1517
  // ---------- sessions ----------
1495
1518
  installSessionTracking() {
1496
- this.session = this.startNewSession();
1497
- this.emitSessionStart();
1519
+ const now = Date.now();
1520
+ const stored = this.readStoredSession();
1521
+ if (stored && now - stored.lastActivityAt < SESSION_RESUME_THRESHOLD_MS) {
1522
+ this.session = {
1523
+ sessionId: stored.id,
1524
+ startedAt: stored.startedAt,
1525
+ lastActivityAt: now,
1526
+ hiddenAt: null,
1527
+ endedSent: false,
1528
+ acquisition: stored.acquisition
1529
+ };
1530
+ this.persistSession();
1531
+ } else {
1532
+ this.session = this.startNewSession();
1533
+ this.persistSession();
1534
+ this.emitSessionStart();
1535
+ }
1498
1536
  const onVisChange = () => {
1499
1537
  if (!this.session) return;
1500
1538
  const doc2 = globalThis.document;
1501
1539
  if (doc2.visibilityState === "hidden") {
1502
1540
  this.session.hiddenAt = Date.now();
1541
+ this.persistSession();
1503
1542
  } else if (doc2.visibilityState === "visible") {
1504
- const hiddenFor = this.session.hiddenAt ? Date.now() - this.session.hiddenAt : 0;
1505
- if (hiddenFor >= SESSION_RESUME_THRESHOLD_MS) {
1543
+ const idleFor = Date.now() - this.session.lastActivityAt;
1544
+ if (idleFor >= SESSION_RESUME_THRESHOLD_MS) {
1506
1545
  this.emitSessionEnd();
1507
1546
  this.pageviewId = null;
1508
1547
  this.session = this.startNewSession();
1548
+ this.persistSession();
1509
1549
  this.emitSessionStart();
1510
1550
  } else {
1511
1551
  this.session.hiddenAt = null;
1512
1552
  }
1513
1553
  }
1514
1554
  };
1515
- const onPageHide = () => this.emitSessionEnd();
1555
+ const onPageHide = () => this.persistSession();
1516
1556
  const w = globalThis.window;
1517
1557
  const doc = globalThis.document;
1518
1558
  doc.addEventListener("visibilitychange", onVisChange);
@@ -1525,14 +1565,63 @@ var AutoTracker = class {
1525
1565
  });
1526
1566
  }
1527
1567
  startNewSession() {
1568
+ const now = Date.now();
1528
1569
  return {
1529
1570
  sessionId: mintSessionId(),
1530
- startedAt: Date.now(),
1571
+ startedAt: now,
1572
+ lastActivityAt: now,
1531
1573
  hiddenAt: null,
1532
1574
  endedSent: false,
1533
1575
  acquisition: captureAcquisition()
1534
1576
  };
1535
1577
  }
1578
+ /**
1579
+ * Read the persisted session continuity record. Returns null on no
1580
+ * storage, no record, malformed JSON, or a record missing its required
1581
+ * fields — every failure degrades to "no session to resume", which is
1582
+ * the safe (start-fresh) path.
1583
+ */
1584
+ readStoredSession() {
1585
+ if (!this.storage) return null;
1586
+ try {
1587
+ const raw = this.storage.getItem(this.sessionKey);
1588
+ if (!raw) return null;
1589
+ const p = JSON.parse(raw);
1590
+ if (!p || typeof p.id !== "string" || typeof p.startedAt !== "number" || typeof p.lastActivityAt !== "number") {
1591
+ return null;
1592
+ }
1593
+ return {
1594
+ id: p.id,
1595
+ startedAt: p.startedAt,
1596
+ lastActivityAt: p.lastActivityAt,
1597
+ acquisition: p.acquisition && typeof p.acquisition === "object" ? p.acquisition : EMPTY_ACQUISITION
1598
+ };
1599
+ } catch {
1600
+ return null;
1601
+ }
1602
+ }
1603
+ /** Flush the current session to storage. No-op without storage/session. */
1604
+ persistSession() {
1605
+ if (!this.storage || !this.session) return;
1606
+ this.lastPersistAt = Date.now();
1607
+ try {
1608
+ const rec = {
1609
+ id: this.session.sessionId,
1610
+ startedAt: this.session.startedAt,
1611
+ lastActivityAt: this.session.lastActivityAt,
1612
+ acquisition: this.session.acquisition
1613
+ };
1614
+ this.storage.setItem(this.sessionKey, JSON.stringify(rec));
1615
+ } catch {
1616
+ }
1617
+ }
1618
+ clearStoredSession() {
1619
+ if (!this.storage) return;
1620
+ try {
1621
+ this.storage.removeItem(this.sessionKey);
1622
+ } catch {
1623
+ }
1624
+ }
1536
1625
  emitSessionStart() {
1537
1626
  if (!this.session) return;
1538
1627
  this.track("session.started", { sessionId: this.session.sessionId });
@@ -1704,6 +1793,78 @@ function isInsidePasswordField(el) {
1704
1793
  if (el.closest('input[type="password"]')) return true;
1705
1794
  return false;
1706
1795
  }
1796
+ var INLINE_LABEL_TAGS = /* @__PURE__ */ new Set([
1797
+ "span",
1798
+ "b",
1799
+ "strong",
1800
+ "em",
1801
+ "i",
1802
+ "small",
1803
+ "mark",
1804
+ "u",
1805
+ "label",
1806
+ "abbr",
1807
+ "time",
1808
+ "bdi",
1809
+ "cite",
1810
+ "code",
1811
+ "kbd",
1812
+ "q",
1813
+ "sub",
1814
+ "sup"
1815
+ ]);
1816
+ var NON_LABEL_SUBTREES = /* @__PURE__ */ new Set(["svg", "style", "script", "noscript"]);
1817
+ var CONTAINER_MARKERS = "a[href], button, input, select, textarea, [role='button'], [role='link'], h1, h2, h3, h4, h5, h6";
1818
+ var HEADING_SELECTOR = "h1, h2, h3, h4, h5, h6";
1819
+ function collapseWs(s) {
1820
+ return s.replace(/\s+/g, " ").trim();
1821
+ }
1822
+ function boundaryText(el) {
1823
+ let out = "";
1824
+ const walk = (node) => {
1825
+ const kids = node.childNodes;
1826
+ for (let i = 0; i < kids.length; i++) {
1827
+ const child = kids[i];
1828
+ if (!child) continue;
1829
+ if (child.nodeType === 3) {
1830
+ out += child.textContent || "";
1831
+ } else if (child.nodeType === 1) {
1832
+ if (NON_LABEL_SUBTREES.has(child.tagName.toLowerCase())) continue;
1833
+ out += " ";
1834
+ walk(child);
1835
+ out += " ";
1836
+ }
1837
+ }
1838
+ };
1839
+ walk(el);
1840
+ return collapseWs(out);
1841
+ }
1842
+ function directLabel(el) {
1843
+ let out = "";
1844
+ const kids = el.childNodes;
1845
+ for (let i = 0; i < kids.length; i++) {
1846
+ const child = kids[i];
1847
+ if (!child) continue;
1848
+ if (child.nodeType === 3) {
1849
+ out += " " + (child.textContent || "");
1850
+ } else if (child.nodeType === 1 && INLINE_LABEL_TAGS.has(child.tagName.toLowerCase())) {
1851
+ out += " " + boundaryText(child);
1852
+ }
1853
+ }
1854
+ return collapseWs(out);
1855
+ }
1856
+ function visibleLabel(el) {
1857
+ const isContainer = el.querySelector(CONTAINER_MARKERS) !== null;
1858
+ if (!isContainer) return boundaryText(el);
1859
+ const direct = directLabel(el);
1860
+ if (direct) return direct;
1861
+ const heading = el.querySelector(HEADING_SELECTOR);
1862
+ if (heading) {
1863
+ const h = boundaryText(heading);
1864
+ if (h) return h;
1865
+ }
1866
+ return "";
1867
+ }
1707
1868
  function extractText(el) {
1708
1869
  const clean = (s) => s.replace(/\s+/g, " ").trim();
1709
1870
  const explicit = el.getAttribute("data-cd-track") || el.getAttribute("data-track") || el.getAttribute("data-testid");
@@ -1730,7 +1891,7 @@ function extractText(el) {
1730
1891
  const t = clean(el.value);
1731
1892
  if (t) return t;
1732
1893
  }
1733
- const text = clean(el.textContent || "");
1894
+ const text = visibleLabel(el);
1734
1895
  if (text) return text;
1735
1896
  const title = el.getAttribute("title");
1736
1897
  if (title) {
@@ -2371,7 +2532,7 @@ var BreadcrumbBuffer = class {
2371
2532
  };
2372
2533
 
2373
2534
  // src/_diagnostic-telemetry.ts
2374
- var DIAGNOSTIC_TELEMETRY_ENDPOINT = "https://api.cross-deck.com/v1/sdk/diagnostic";
2535
+ var DIAGNOSTIC_TELEMETRY_ENDPOINT = "https://api.cross-deck.com/v1/events";
2375
2536
  var DIAGNOSTIC_TELEMETRY_PUBLISHABLE_KEY = "cd_pub_live_9490e7aa029c432abf";
2376
2537
  function isDiagnosticTelemetryEnabled() {
2377
2538
  return !DIAGNOSTIC_TELEMETRY_PUBLISHABLE_KEY.startsWith(
@@ -2409,7 +2570,15 @@ function sendDiagnosticTelemetry(payload) {
2409
2570
  if (!isDiagnosticTelemetryEnabled()) return;
2410
2571
  const filtered = filterDiagnosticPayload(payload);
2411
2572
  if (Object.keys(filtered).length === 0) return;
2412
- const body = JSON.stringify(filtered);
2573
+ const body = JSON.stringify({
2574
+ events: [
2575
+ {
2576
+ name: "crossdeck.contract_failed",
2577
+ timestamp: Date.now(),
2578
+ properties: filtered
2579
+ }
2580
+ ]
2581
+ });
2413
2582
  const f = globalThis.fetch;
2414
2583
  if (typeof f !== "function") return;
2415
2584
  try {
@@ -3269,19 +3438,58 @@ var DEFAULT_ERROR_CAPTURE = {
3269
3438
  "ResizeObserver loop completed with undelivered notifications",
3270
3439
  "Non-Error promise rejection captured"
3271
3440
  // NOTE: We deliberately do NOT drop cross-origin "Script error."
3272
- // events here. They used to be silently filtered as noise, but
3273
- // silent drops are the opposite of "developer sleeps well at
3274
- // night". We now capture them with a clear label and the
3275
- // `cross_origin` tag so the dashboard surfaces them as a
3276
- // distinct, actionable category (the fix is always the same
3277
- // add `crossorigin="anonymous"` to the script tag + CORS
3278
- // headers on the script's origin). Apps that genuinely want
3279
- // them muted can re-add "Script error" to ignoreErrors via
3280
- // init config.
3441
+ // events here. The actionability principle (see denyUrls
3442
+ // comment below) draws the line at "developer cannot act":
3443
+ // cross-origin CORS opacity HAS a real, code-shaped fix (add
3444
+ // `crossorigin="anonymous"` to the script tag + CORS headers
3445
+ // on the script's origin). The dashboard surfaces these with a
3446
+ // `cross_origin` tag pointing at that fix. Apps that genuinely
3447
+ // want them muted can re-add "Script error" to ignoreErrors
3448
+ // via init config.
3281
3449
  ],
3282
3450
  allowUrls: [],
3283
3451
  denyUrls: [
3284
- // Common third-party extensions that pollute error streams.
3452
+ // The actionability principle
3453
+ // ---------------------------
3454
+ // Crossdeck's default philosophy is "classify, don't silently
3455
+ // drop" — surfacing errors the developer can fix is more useful
3456
+ // than hiding them. That's right for cross-origin "Script
3457
+ // error." (real, code-shaped CORS fix) and for plain
3458
+ // application bugs (obviously real).
3459
+ //
3460
+ // It's NOT right for events the developer cannot act on.
3461
+ // - A user's installed browser extension throwing inside
3462
+ // its own `chrome-extension://` URL: the developer can't
3463
+ // ship a fix for someone else's extension.
3464
+ // - An ad blocker preventing `googletagmanager.com` from
3465
+ // loading: the developer can't unblock the user's blocker.
3466
+ //
3467
+ // Capturing these creates a noise tab that's always non-empty
3468
+ // and never actionable, which trains the dev to ignore the
3469
+ // noise tab — and then the actionable noise (CORS opacity,
3470
+ // etc.) gets ignored along with it. Same lesson as Crossdeck's
3471
+ // "0-2 notifications a week" discipline: a signal that fires
3472
+ // constantly with nothing actionable behind it stops being a
3473
+ // signal.
3474
+ //
3475
+ // So we drop at the source for the unactionable category, and
3476
+ // keep capturing for the actionable category. Same principle
3477
+ // applied to two structurally different inputs — not a new
3478
+ // philosophy.
3479
+ //
3480
+ // The bootstrap list below is the minimum that ships in code.
3481
+ // The full, versioned list arrives at boot via /v1/config —
3482
+ // see `backend/src/lib/error-noise-deny-list.ts`. The SDK
3483
+ // applies the union of (bootstrap + remote), so a freshly-
3484
+ // installed SDK protects users immediately, and remote updates
3485
+ // (new ad-network domains, new pixel hosts) reach every
3486
+ // install without an SDK release.
3487
+ //
3488
+ // The backend Layer-2 classifier
3489
+ // (`backend/src/api/lib/noise-classifier.ts`) is the safety
3490
+ // net for events that slip past these patterns — older SDK
3491
+ // versions in the wild that haven't fetched the remote list
3492
+ // yet, or brand-new patterns the list hasn't named.
3285
3493
  /^chrome-extension:\/\//,
3286
3494
  /^moz-extension:\/\//,
3287
3495
  /^safari-extension:\/\//,
@@ -4074,7 +4282,12 @@ var CrossdeckClient = class {
4074
4282
  if (autoTrack.sessions || autoTrack.pageViews) {
4075
4283
  const tracker = new AutoTracker(
4076
4284
  autoTrack,
4077
- (name, properties) => this.track(name, properties)
4285
+ (name, properties) => this.track(name, properties),
4286
+ // Persist session continuity on the SAME adapter as identity, so
4287
+ // a visit survives full-page navigations (multi-page sites
4288
+ // re-install the SDK on every page) and honours the same consent
4289
+ // posture — MemoryStorage when identity persistence is off.
4290
+ { storage: effectiveStorage, storageKey: opts.storagePrefix + "session" }
4078
4291
  );
4079
4292
  this.state.autoTracker = tracker;
4080
4293
  tracker.install();
@@ -4539,6 +4752,12 @@ var CrossdeckClient = class {
4539
4752
  * true even before the session's first network round-trip. Returns
4540
4753
  * false only for a genuinely new install that has never completed a
4541
4754
  * getEntitlements(), or for an entitlement past its own validUntil.
4755
+ *
4756
+ * Throws `not_initialized` (CrossdeckError, type
4757
+ * `configuration_error`) if called before `Crossdeck.init()`. The
4758
+ * `useEntitlement` React hook and the Vue composable both swallow
4759
+ * this and return `false`; bare callers must guard for it (or call
4760
+ * after `init()` resolves).
4542
4761
  */
4543
4762
  isEntitled(key) {
4544
4763
  const s = this.requireStarted();
@@ -4554,8 +4773,21 @@ var CrossdeckClient = class {
4554
4773
  *
4555
4774
  * The listener is invoked AFTER the cache mutates — once after a
4556
4775
  * successful `getEntitlements()` warms it, again after `syncPurchases()`
4557
- * delivers fresh entitlements, and once on `reset()` to fire the
4558
- * empty-cache state for logout flows.
4776
+ * delivers fresh entitlements, once on `reset()` to fire the empty-
4777
+ * cache state for logout flows, AND once on `identify()` after the
4778
+ * per-user cache slot rotates and re-hydrates from device storage.
4779
+ *
4780
+ * IMPORTANT — the `identify()` fire is a TRAP if you treat it as
4781
+ * authoritative network state. `identify()` does NOT fetch entitlements;
4782
+ * it switches the per-user cache slot and rehydrates from device
4783
+ * storage (which is empty for a brand-new install, and last-known-good
4784
+ * — possibly stale — for a returning user). A listener that gates a
4785
+ * paywall on the first fire after an identity switch will read
4786
+ * `false` for a paying customer on a fresh device and let them past
4787
+ * the gate as free. The network-truth fire is the one that follows
4788
+ * the next `getEntitlements()` resolution. Either call
4789
+ * `getEntitlements()` explicitly after `identify()`, or have your
4790
+ * gating code tolerate the empty-then-populated transition.
4559
4791
  *
4560
4792
  * It is NOT invoked synchronously on subscribe. Callers that need
4561
4793
  * the current state should read it via `isEntitled()` / `listEntitlements()`
@@ -4664,6 +4896,7 @@ var CrossdeckClient = class {
4664
4896
  );
4665
4897
  }
4666
4898
  }
4899
+ s.autoTracker?.markActivity();
4667
4900
  const enriched = { ...s.deviceInfo };
4668
4901
  const sessionId = s.autoTracker?.currentSessionId;
4669
4902
  if (sessionId) enriched.sessionId = sessionId;
@@ -4709,7 +4942,7 @@ var CrossdeckClient = class {
4709
4942
  callerProperties: validation.properties,
4710
4943
  superProperties: supers,
4711
4944
  deviceProperties: s.deviceInfo,
4712
- mergedProperties: finalProperties
4945
+ mergedProperties: enriched
4713
4946
  });
4714
4947
  }
4715
4948
  if (!isError && !isWebVital) {
@@ -4918,6 +5151,25 @@ var CrossdeckClient = class {
4918
5151
  events: s.events.getStats()
4919
5152
  };
4920
5153
  }
5154
+ /**
5155
+ * The stable reference to hand Stripe at checkout so the resulting
5156
+ * purchase attributes to THIS person. Stamp it on the Checkout Session
5157
+ * as `metadata.crossdeck_ref` (see docs/connect-stripe) — the platform
5158
+ * webhook reads it back, validates it, and attaches the subscription to
5159
+ * the right customer instead of stranding it on an anonymous record.
5160
+ *
5161
+ * Returns the strongest identifier the SDK currently holds, in the same
5162
+ * precedence the server resolves by:
5163
+ * crossdeckCustomerId (cdcust_…) > developerUserId > anonymousId
5164
+ * anonymousId is always present, so this always returns a usable,
5165
+ * non-empty reference — even before identify(). Identify the user as
5166
+ * early as you can, though, so the reference is their stable identity
5167
+ * and not a per-device anon id.
5168
+ */
5169
+ getCheckoutReference() {
5170
+ const s = this.requireStarted();
5171
+ return s.identity.crossdeckCustomerId ?? s.developerUserId ?? s.identity.anonymousId;
5172
+ }
4921
5173
  // ---------- private helpers ----------
4922
5174
  requireStarted() {
4923
5175
  if (!this.state) {
@@ -5239,8 +5491,8 @@ function getErrorCode(code) {
5239
5491
  }
5240
5492
 
5241
5493
  // src/_contracts-bundled.ts
5242
- var BUNDLED_IN = "@cross-deck/web@1.5.3";
5243
- var SDK_VERSION2 = "1.5.3";
5494
+ var BUNDLED_IN = "@cross-deck/web@1.6.1";
5495
+ var SDK_VERSION2 = "1.6.1";
5244
5496
  var BUNDLED_CONTRACTS = Object.freeze([
5245
5497
  {
5246
5498
  "id": "contract-failed-payload-schema-lock",
@@ -5362,7 +5614,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5362
5614
  "legal/security/index.html#diagnostic",
5363
5615
  "legal/sdk-data/index.html#b-diagnostic"
5364
5616
  ],
5365
- "bundledIn": "@cross-deck/web@1.5.3"
5617
+ "bundledIn": "@cross-deck/web@1.6.1"
5366
5618
  },
5367
5619
  {
5368
5620
  "id": "error-envelope-shape",
@@ -5401,7 +5653,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5401
5653
  ],
5402
5654
  "registeredAt": "2026-05-26",
5403
5655
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 8 (codifies existing contract)",
5404
- "bundledIn": "@cross-deck/web@1.5.3"
5656
+ "bundledIn": "@cross-deck/web@1.6.1"
5405
5657
  },
5406
5658
  {
5407
5659
  "id": "flush-interval-parity",
@@ -5446,7 +5698,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5446
5698
  ],
5447
5699
  "registeredAt": "2026-05-26",
5448
5700
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.3",
5449
- "bundledIn": "@cross-deck/web@1.5.3"
5701
+ "bundledIn": "@cross-deck/web@1.6.1"
5450
5702
  },
5451
5703
  {
5452
5704
  "id": "idempotency-key-deterministic",
@@ -5551,7 +5803,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5551
5803
  ],
5552
5804
  "registeredAt": "2026-05-26",
5553
5805
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 2.2.a + 2.2.b + 2.2.c",
5554
- "bundledIn": "@cross-deck/web@1.5.3"
5806
+ "bundledIn": "@cross-deck/web@1.6.1"
5555
5807
  },
5556
5808
  {
5557
5809
  "id": "init-reentry-drains-prior-queue",
@@ -5578,7 +5830,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5578
5830
  ],
5579
5831
  "registeredAt": "2026-05-26",
5580
5832
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 5.5",
5581
- "bundledIn": "@cross-deck/web@1.5.3"
5833
+ "bundledIn": "@cross-deck/web@1.6.1"
5582
5834
  },
5583
5835
  {
5584
5836
  "id": "per-user-cache-isolation",
@@ -5657,7 +5909,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5657
5909
  ],
5658
5910
  "registeredAt": "2026-05-26",
5659
5911
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 1.3 (web/RN) + dogfood-gap fix (swift + android)",
5660
- "bundledIn": "@cross-deck/web@1.5.3"
5912
+ "bundledIn": "@cross-deck/web@1.6.1"
5661
5913
  },
5662
5914
  {
5663
5915
  "id": "sdk-error-codes-catalogue",
@@ -5697,7 +5949,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5697
5949
  ],
5698
5950
  "registeredAt": "2026-05-26",
5699
5951
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 6.2",
5700
- "bundledIn": "@cross-deck/web@1.5.3"
5952
+ "bundledIn": "@cross-deck/web@1.6.1"
5701
5953
  },
5702
5954
  {
5703
5955
  "id": "sync-purchases-funnel-parity",
@@ -5730,7 +5982,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5730
5982
  ],
5731
5983
  "registeredAt": "2026-05-26",
5732
5984
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.5",
5733
- "bundledIn": "@cross-deck/web@1.5.3"
5985
+ "bundledIn": "@cross-deck/web@1.6.1"
5734
5986
  }
5735
5987
  ]);
5736
5988