@cross-deck/web 1.5.7 → 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/CHANGELOG.md +49 -0
- package/README.md +6 -4
- package/dist/contracts.json +11 -11
- package/dist/crossdeck.umd.min.js +2 -2
- package/dist/crossdeck.umd.min.js.map +1 -1
- package/dist/error-codes.json +1 -1
- package/dist/index.cjs +276 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +40 -5
- package/dist/index.d.ts +40 -5
- package/dist/index.mjs +276 -32
- package/dist/index.mjs.map +1 -1
- package/dist/react.cjs +266 -22
- package/dist/react.cjs.map +1 -1
- package/dist/react.mjs +266 -22
- package/dist/react.mjs.map +1 -1
- package/dist/vue.cjs +266 -22
- package/dist/vue.cjs.map +1 -1
- package/dist/vue.mjs +266 -22
- package/dist/vue.mjs.map +1 -1
- package/package.json +1 -1
package/dist/error-codes.json
CHANGED
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.
|
|
102
|
+
var SDK_VERSION = "1.6.1";
|
|
103
103
|
var SDK_NAME = "@cross-deck/web";
|
|
104
104
|
|
|
105
105
|
// src/http.ts
|
|
@@ -1448,6 +1448,8 @@ var DEFAULT_AUTO_TRACK = {
|
|
|
1448
1448
|
errors: true
|
|
1449
1449
|
};
|
|
1450
1450
|
var SESSION_RESUME_THRESHOLD_MS = 30 * 60 * 1e3;
|
|
1451
|
+
var SESSION_STORAGE_KEY = "crossdeck:session";
|
|
1452
|
+
var ACTIVITY_PERSIST_THROTTLE_MS = 5e3;
|
|
1451
1453
|
var EMPTY_ACQUISITION = {
|
|
1452
1454
|
utm_source: "",
|
|
1453
1455
|
utm_medium: "",
|
|
@@ -1463,11 +1465,13 @@ var EMPTY_ACQUISITION = {
|
|
|
1463
1465
|
twclid: ""
|
|
1464
1466
|
};
|
|
1465
1467
|
var AutoTracker = class {
|
|
1466
|
-
constructor(cfg, track) {
|
|
1468
|
+
constructor(cfg, track, opts) {
|
|
1467
1469
|
this.cfg = cfg;
|
|
1468
1470
|
this.track = track;
|
|
1469
1471
|
this.session = null;
|
|
1470
1472
|
this.cleanups = [];
|
|
1473
|
+
/** Last time we flushed lastActivityAt to storage (throttle gate). */
|
|
1474
|
+
this.lastPersistAt = 0;
|
|
1471
1475
|
/**
|
|
1472
1476
|
* Stable per-page-view identifier. Minted at every `page.viewed`
|
|
1473
1477
|
* emission and attached to every subsequent event until the next
|
|
@@ -1479,6 +1483,8 @@ var AutoTracker = class {
|
|
|
1479
1483
|
* install if `autoTrack.pageViews !== false`).
|
|
1480
1484
|
*/
|
|
1481
1485
|
this.pageviewId = null;
|
|
1486
|
+
this.storage = opts?.storage ?? null;
|
|
1487
|
+
this.sessionKey = opts?.storageKey ?? SESSION_STORAGE_KEY;
|
|
1482
1488
|
}
|
|
1483
1489
|
install() {
|
|
1484
1490
|
if (!isBrowserSafe()) return;
|
|
@@ -1497,6 +1503,7 @@ var AutoTracker = class {
|
|
|
1497
1503
|
if (this.session && !this.session.endedSent) {
|
|
1498
1504
|
this.emitSessionEnd();
|
|
1499
1505
|
}
|
|
1506
|
+
this.clearStoredSession();
|
|
1500
1507
|
this.session = null;
|
|
1501
1508
|
}
|
|
1502
1509
|
/** Exposed for tests + consumers that want to reset the session manually. */
|
|
@@ -1504,8 +1511,24 @@ var AutoTracker = class {
|
|
|
1504
1511
|
if (this.session && !this.session.endedSent) this.emitSessionEnd();
|
|
1505
1512
|
this.pageviewId = null;
|
|
1506
1513
|
this.session = this.startNewSession();
|
|
1514
|
+
this.persistSession();
|
|
1507
1515
|
this.emitSessionStart();
|
|
1508
1516
|
}
|
|
1517
|
+
/**
|
|
1518
|
+
* Keep the rolling session window alive. Called by the host on EVERY
|
|
1519
|
+
* tracked event (auto or custom) so any activity — not just the
|
|
1520
|
+
* pageviews/clicks AutoTracker emits itself — pushes the 30-min idle
|
|
1521
|
+
* boundary forward. In-memory time is updated exactly; the storage
|
|
1522
|
+
* flush is throttled (pagehide forces a final flush).
|
|
1523
|
+
*/
|
|
1524
|
+
markActivity() {
|
|
1525
|
+
if (!this.session) return;
|
|
1526
|
+
const now = Date.now();
|
|
1527
|
+
this.session.lastActivityAt = now;
|
|
1528
|
+
if (now - this.lastPersistAt >= ACTIVITY_PERSIST_THROTTLE_MS) {
|
|
1529
|
+
this.persistSession();
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1509
1532
|
/** Exposed for inspection/tests — returns the current sessionId (or null if not in a session). */
|
|
1510
1533
|
get currentSessionId() {
|
|
1511
1534
|
return this.session?.sessionId ?? null;
|
|
@@ -1528,26 +1551,43 @@ var AutoTracker = class {
|
|
|
1528
1551
|
}
|
|
1529
1552
|
// ---------- sessions ----------
|
|
1530
1553
|
installSessionTracking() {
|
|
1531
|
-
|
|
1532
|
-
this.
|
|
1554
|
+
const now = Date.now();
|
|
1555
|
+
const stored = this.readStoredSession();
|
|
1556
|
+
if (stored && now - stored.lastActivityAt < SESSION_RESUME_THRESHOLD_MS) {
|
|
1557
|
+
this.session = {
|
|
1558
|
+
sessionId: stored.id,
|
|
1559
|
+
startedAt: stored.startedAt,
|
|
1560
|
+
lastActivityAt: now,
|
|
1561
|
+
hiddenAt: null,
|
|
1562
|
+
endedSent: false,
|
|
1563
|
+
acquisition: stored.acquisition
|
|
1564
|
+
};
|
|
1565
|
+
this.persistSession();
|
|
1566
|
+
} else {
|
|
1567
|
+
this.session = this.startNewSession();
|
|
1568
|
+
this.persistSession();
|
|
1569
|
+
this.emitSessionStart();
|
|
1570
|
+
}
|
|
1533
1571
|
const onVisChange = () => {
|
|
1534
1572
|
if (!this.session) return;
|
|
1535
1573
|
const doc2 = globalThis.document;
|
|
1536
1574
|
if (doc2.visibilityState === "hidden") {
|
|
1537
1575
|
this.session.hiddenAt = Date.now();
|
|
1576
|
+
this.persistSession();
|
|
1538
1577
|
} else if (doc2.visibilityState === "visible") {
|
|
1539
|
-
const
|
|
1540
|
-
if (
|
|
1578
|
+
const idleFor = Date.now() - this.session.lastActivityAt;
|
|
1579
|
+
if (idleFor >= SESSION_RESUME_THRESHOLD_MS) {
|
|
1541
1580
|
this.emitSessionEnd();
|
|
1542
1581
|
this.pageviewId = null;
|
|
1543
1582
|
this.session = this.startNewSession();
|
|
1583
|
+
this.persistSession();
|
|
1544
1584
|
this.emitSessionStart();
|
|
1545
1585
|
} else {
|
|
1546
1586
|
this.session.hiddenAt = null;
|
|
1547
1587
|
}
|
|
1548
1588
|
}
|
|
1549
1589
|
};
|
|
1550
|
-
const onPageHide = () => this.
|
|
1590
|
+
const onPageHide = () => this.persistSession();
|
|
1551
1591
|
const w = globalThis.window;
|
|
1552
1592
|
const doc = globalThis.document;
|
|
1553
1593
|
doc.addEventListener("visibilitychange", onVisChange);
|
|
@@ -1560,14 +1600,63 @@ var AutoTracker = class {
|
|
|
1560
1600
|
});
|
|
1561
1601
|
}
|
|
1562
1602
|
startNewSession() {
|
|
1603
|
+
const now = Date.now();
|
|
1563
1604
|
return {
|
|
1564
1605
|
sessionId: mintSessionId(),
|
|
1565
|
-
startedAt:
|
|
1606
|
+
startedAt: now,
|
|
1607
|
+
lastActivityAt: now,
|
|
1566
1608
|
hiddenAt: null,
|
|
1567
1609
|
endedSent: false,
|
|
1568
1610
|
acquisition: captureAcquisition()
|
|
1569
1611
|
};
|
|
1570
1612
|
}
|
|
1613
|
+
/**
|
|
1614
|
+
* Read the persisted session continuity record. Returns null on no
|
|
1615
|
+
* storage, no record, malformed JSON, or a record missing its required
|
|
1616
|
+
* fields — every failure degrades to "no session to resume", which is
|
|
1617
|
+
* the safe (start-fresh) path.
|
|
1618
|
+
*/
|
|
1619
|
+
readStoredSession() {
|
|
1620
|
+
if (!this.storage) return null;
|
|
1621
|
+
try {
|
|
1622
|
+
const raw = this.storage.getItem(this.sessionKey);
|
|
1623
|
+
if (!raw) return null;
|
|
1624
|
+
const p = JSON.parse(raw);
|
|
1625
|
+
if (!p || typeof p.id !== "string" || typeof p.startedAt !== "number" || typeof p.lastActivityAt !== "number") {
|
|
1626
|
+
return null;
|
|
1627
|
+
}
|
|
1628
|
+
return {
|
|
1629
|
+
id: p.id,
|
|
1630
|
+
startedAt: p.startedAt,
|
|
1631
|
+
lastActivityAt: p.lastActivityAt,
|
|
1632
|
+
acquisition: p.acquisition && typeof p.acquisition === "object" ? p.acquisition : EMPTY_ACQUISITION
|
|
1633
|
+
};
|
|
1634
|
+
} catch {
|
|
1635
|
+
return null;
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
/** Flush the current session to storage. No-op without storage/session. */
|
|
1639
|
+
persistSession() {
|
|
1640
|
+
if (!this.storage || !this.session) return;
|
|
1641
|
+
this.lastPersistAt = Date.now();
|
|
1642
|
+
try {
|
|
1643
|
+
const rec = {
|
|
1644
|
+
id: this.session.sessionId,
|
|
1645
|
+
startedAt: this.session.startedAt,
|
|
1646
|
+
lastActivityAt: this.session.lastActivityAt,
|
|
1647
|
+
acquisition: this.session.acquisition
|
|
1648
|
+
};
|
|
1649
|
+
this.storage.setItem(this.sessionKey, JSON.stringify(rec));
|
|
1650
|
+
} catch {
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
clearStoredSession() {
|
|
1654
|
+
if (!this.storage) return;
|
|
1655
|
+
try {
|
|
1656
|
+
this.storage.removeItem(this.sessionKey);
|
|
1657
|
+
} catch {
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1571
1660
|
emitSessionStart() {
|
|
1572
1661
|
if (!this.session) return;
|
|
1573
1662
|
this.track("session.started", { sessionId: this.session.sessionId });
|
|
@@ -1739,6 +1828,78 @@ function isInsidePasswordField(el) {
|
|
|
1739
1828
|
if (el.closest('input[type="password"]')) return true;
|
|
1740
1829
|
return false;
|
|
1741
1830
|
}
|
|
1831
|
+
var INLINE_LABEL_TAGS = /* @__PURE__ */ new Set([
|
|
1832
|
+
"span",
|
|
1833
|
+
"b",
|
|
1834
|
+
"strong",
|
|
1835
|
+
"em",
|
|
1836
|
+
"i",
|
|
1837
|
+
"small",
|
|
1838
|
+
"mark",
|
|
1839
|
+
"u",
|
|
1840
|
+
"label",
|
|
1841
|
+
"abbr",
|
|
1842
|
+
"time",
|
|
1843
|
+
"bdi",
|
|
1844
|
+
"cite",
|
|
1845
|
+
"code",
|
|
1846
|
+
"kbd",
|
|
1847
|
+
"q",
|
|
1848
|
+
"sub",
|
|
1849
|
+
"sup"
|
|
1850
|
+
]);
|
|
1851
|
+
var NON_LABEL_SUBTREES = /* @__PURE__ */ new Set(["svg", "style", "script", "noscript"]);
|
|
1852
|
+
var CONTAINER_MARKERS = "a[href], button, input, select, textarea, [role='button'], [role='link'], h1, h2, h3, h4, h5, h6";
|
|
1853
|
+
var HEADING_SELECTOR = "h1, h2, h3, h4, h5, h6";
|
|
1854
|
+
function collapseWs(s) {
|
|
1855
|
+
return s.replace(/\s+/g, " ").trim();
|
|
1856
|
+
}
|
|
1857
|
+
function boundaryText(el) {
|
|
1858
|
+
let out = "";
|
|
1859
|
+
const walk = (node) => {
|
|
1860
|
+
const kids = node.childNodes;
|
|
1861
|
+
for (let i = 0; i < kids.length; i++) {
|
|
1862
|
+
const child = kids[i];
|
|
1863
|
+
if (!child) continue;
|
|
1864
|
+
if (child.nodeType === 3) {
|
|
1865
|
+
out += child.textContent || "";
|
|
1866
|
+
} else if (child.nodeType === 1) {
|
|
1867
|
+
if (NON_LABEL_SUBTREES.has(child.tagName.toLowerCase())) continue;
|
|
1868
|
+
out += " ";
|
|
1869
|
+
walk(child);
|
|
1870
|
+
out += " ";
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
};
|
|
1874
|
+
walk(el);
|
|
1875
|
+
return collapseWs(out);
|
|
1876
|
+
}
|
|
1877
|
+
function directLabel(el) {
|
|
1878
|
+
let out = "";
|
|
1879
|
+
const kids = el.childNodes;
|
|
1880
|
+
for (let i = 0; i < kids.length; i++) {
|
|
1881
|
+
const child = kids[i];
|
|
1882
|
+
if (!child) continue;
|
|
1883
|
+
if (child.nodeType === 3) {
|
|
1884
|
+
out += " " + (child.textContent || "");
|
|
1885
|
+
} else if (child.nodeType === 1 && INLINE_LABEL_TAGS.has(child.tagName.toLowerCase())) {
|
|
1886
|
+
out += " " + boundaryText(child);
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
return collapseWs(out);
|
|
1890
|
+
}
|
|
1891
|
+
function visibleLabel(el) {
|
|
1892
|
+
const isContainer = el.querySelector(CONTAINER_MARKERS) !== null;
|
|
1893
|
+
if (!isContainer) return boundaryText(el);
|
|
1894
|
+
const direct = directLabel(el);
|
|
1895
|
+
if (direct) return direct;
|
|
1896
|
+
const heading = el.querySelector(HEADING_SELECTOR);
|
|
1897
|
+
if (heading) {
|
|
1898
|
+
const h = boundaryText(heading);
|
|
1899
|
+
if (h) return h;
|
|
1900
|
+
}
|
|
1901
|
+
return "";
|
|
1902
|
+
}
|
|
1742
1903
|
function extractText(el) {
|
|
1743
1904
|
const clean = (s) => s.replace(/\s+/g, " ").trim();
|
|
1744
1905
|
const explicit = el.getAttribute("data-cd-track") || el.getAttribute("data-track") || el.getAttribute("data-testid");
|
|
@@ -1765,7 +1926,7 @@ function extractText(el) {
|
|
|
1765
1926
|
const t = clean(el.value);
|
|
1766
1927
|
if (t) return t;
|
|
1767
1928
|
}
|
|
1768
|
-
const text =
|
|
1929
|
+
const text = visibleLabel(el);
|
|
1769
1930
|
if (text) return text;
|
|
1770
1931
|
const title = el.getAttribute("title");
|
|
1771
1932
|
if (title) {
|
|
@@ -3312,19 +3473,58 @@ var DEFAULT_ERROR_CAPTURE = {
|
|
|
3312
3473
|
"ResizeObserver loop completed with undelivered notifications",
|
|
3313
3474
|
"Non-Error promise rejection captured"
|
|
3314
3475
|
// NOTE: We deliberately do NOT drop cross-origin "Script error."
|
|
3315
|
-
// events here.
|
|
3316
|
-
//
|
|
3317
|
-
//
|
|
3318
|
-
// `
|
|
3319
|
-
//
|
|
3320
|
-
//
|
|
3321
|
-
//
|
|
3322
|
-
//
|
|
3323
|
-
// init config.
|
|
3476
|
+
// events here. The actionability principle (see denyUrls
|
|
3477
|
+
// comment below) draws the line at "developer cannot act":
|
|
3478
|
+
// cross-origin CORS opacity HAS a real, code-shaped fix (add
|
|
3479
|
+
// `crossorigin="anonymous"` to the script tag + CORS headers
|
|
3480
|
+
// on the script's origin). The dashboard surfaces these with a
|
|
3481
|
+
// `cross_origin` tag pointing at that fix. Apps that genuinely
|
|
3482
|
+
// want them muted can re-add "Script error" to ignoreErrors
|
|
3483
|
+
// via init config.
|
|
3324
3484
|
],
|
|
3325
3485
|
allowUrls: [],
|
|
3326
3486
|
denyUrls: [
|
|
3327
|
-
//
|
|
3487
|
+
// The actionability principle
|
|
3488
|
+
// ---------------------------
|
|
3489
|
+
// Crossdeck's default philosophy is "classify, don't silently
|
|
3490
|
+
// drop" — surfacing errors the developer can fix is more useful
|
|
3491
|
+
// than hiding them. That's right for cross-origin "Script
|
|
3492
|
+
// error." (real, code-shaped CORS fix) and for plain
|
|
3493
|
+
// application bugs (obviously real).
|
|
3494
|
+
//
|
|
3495
|
+
// It's NOT right for events the developer cannot act on.
|
|
3496
|
+
// - A user's installed browser extension throwing inside
|
|
3497
|
+
// its own `chrome-extension://` URL: the developer can't
|
|
3498
|
+
// ship a fix for someone else's extension.
|
|
3499
|
+
// - An ad blocker preventing `googletagmanager.com` from
|
|
3500
|
+
// loading: the developer can't unblock the user's blocker.
|
|
3501
|
+
//
|
|
3502
|
+
// Capturing these creates a noise tab that's always non-empty
|
|
3503
|
+
// and never actionable, which trains the dev to ignore the
|
|
3504
|
+
// noise tab — and then the actionable noise (CORS opacity,
|
|
3505
|
+
// etc.) gets ignored along with it. Same lesson as Crossdeck's
|
|
3506
|
+
// "0-2 notifications a week" discipline: a signal that fires
|
|
3507
|
+
// constantly with nothing actionable behind it stops being a
|
|
3508
|
+
// signal.
|
|
3509
|
+
//
|
|
3510
|
+
// So we drop at the source for the unactionable category, and
|
|
3511
|
+
// keep capturing for the actionable category. Same principle
|
|
3512
|
+
// applied to two structurally different inputs — not a new
|
|
3513
|
+
// philosophy.
|
|
3514
|
+
//
|
|
3515
|
+
// The bootstrap list below is the minimum that ships in code.
|
|
3516
|
+
// The full, versioned list arrives at boot via /v1/config —
|
|
3517
|
+
// see `backend/src/lib/error-noise-deny-list.ts`. The SDK
|
|
3518
|
+
// applies the union of (bootstrap + remote), so a freshly-
|
|
3519
|
+
// installed SDK protects users immediately, and remote updates
|
|
3520
|
+
// (new ad-network domains, new pixel hosts) reach every
|
|
3521
|
+
// install without an SDK release.
|
|
3522
|
+
//
|
|
3523
|
+
// The backend Layer-2 classifier
|
|
3524
|
+
// (`backend/src/api/lib/noise-classifier.ts`) is the safety
|
|
3525
|
+
// net for events that slip past these patterns — older SDK
|
|
3526
|
+
// versions in the wild that haven't fetched the remote list
|
|
3527
|
+
// yet, or brand-new patterns the list hasn't named.
|
|
3328
3528
|
/^chrome-extension:\/\//,
|
|
3329
3529
|
/^moz-extension:\/\//,
|
|
3330
3530
|
/^safari-extension:\/\//,
|
|
@@ -4117,7 +4317,12 @@ var CrossdeckClient = class {
|
|
|
4117
4317
|
if (autoTrack.sessions || autoTrack.pageViews) {
|
|
4118
4318
|
const tracker = new AutoTracker(
|
|
4119
4319
|
autoTrack,
|
|
4120
|
-
(name, properties) => this.track(name, properties)
|
|
4320
|
+
(name, properties) => this.track(name, properties),
|
|
4321
|
+
// Persist session continuity on the SAME adapter as identity, so
|
|
4322
|
+
// a visit survives full-page navigations (multi-page sites
|
|
4323
|
+
// re-install the SDK on every page) and honours the same consent
|
|
4324
|
+
// posture — MemoryStorage when identity persistence is off.
|
|
4325
|
+
{ storage: effectiveStorage, storageKey: opts.storagePrefix + "session" }
|
|
4121
4326
|
);
|
|
4122
4327
|
this.state.autoTracker = tracker;
|
|
4123
4328
|
tracker.install();
|
|
@@ -4582,6 +4787,12 @@ var CrossdeckClient = class {
|
|
|
4582
4787
|
* true even before the session's first network round-trip. Returns
|
|
4583
4788
|
* false only for a genuinely new install that has never completed a
|
|
4584
4789
|
* getEntitlements(), or for an entitlement past its own validUntil.
|
|
4790
|
+
*
|
|
4791
|
+
* Throws `not_initialized` (CrossdeckError, type
|
|
4792
|
+
* `configuration_error`) if called before `Crossdeck.init()`. The
|
|
4793
|
+
* `useEntitlement` React hook and the Vue composable both swallow
|
|
4794
|
+
* this and return `false`; bare callers must guard for it (or call
|
|
4795
|
+
* after `init()` resolves).
|
|
4585
4796
|
*/
|
|
4586
4797
|
isEntitled(key) {
|
|
4587
4798
|
const s = this.requireStarted();
|
|
@@ -4597,8 +4808,21 @@ var CrossdeckClient = class {
|
|
|
4597
4808
|
*
|
|
4598
4809
|
* The listener is invoked AFTER the cache mutates — once after a
|
|
4599
4810
|
* successful `getEntitlements()` warms it, again after `syncPurchases()`
|
|
4600
|
-
* delivers fresh entitlements,
|
|
4601
|
-
*
|
|
4811
|
+
* delivers fresh entitlements, once on `reset()` to fire the empty-
|
|
4812
|
+
* cache state for logout flows, AND once on `identify()` after the
|
|
4813
|
+
* per-user cache slot rotates and re-hydrates from device storage.
|
|
4814
|
+
*
|
|
4815
|
+
* IMPORTANT — the `identify()` fire is a TRAP if you treat it as
|
|
4816
|
+
* authoritative network state. `identify()` does NOT fetch entitlements;
|
|
4817
|
+
* it switches the per-user cache slot and rehydrates from device
|
|
4818
|
+
* storage (which is empty for a brand-new install, and last-known-good
|
|
4819
|
+
* — possibly stale — for a returning user). A listener that gates a
|
|
4820
|
+
* paywall on the first fire after an identity switch will read
|
|
4821
|
+
* `false` for a paying customer on a fresh device and let them past
|
|
4822
|
+
* the gate as free. The network-truth fire is the one that follows
|
|
4823
|
+
* the next `getEntitlements()` resolution. Either call
|
|
4824
|
+
* `getEntitlements()` explicitly after `identify()`, or have your
|
|
4825
|
+
* gating code tolerate the empty-then-populated transition.
|
|
4602
4826
|
*
|
|
4603
4827
|
* It is NOT invoked synchronously on subscribe. Callers that need
|
|
4604
4828
|
* the current state should read it via `isEntitled()` / `listEntitlements()`
|
|
@@ -4707,6 +4931,7 @@ var CrossdeckClient = class {
|
|
|
4707
4931
|
);
|
|
4708
4932
|
}
|
|
4709
4933
|
}
|
|
4934
|
+
s.autoTracker?.markActivity();
|
|
4710
4935
|
const enriched = { ...s.deviceInfo };
|
|
4711
4936
|
const sessionId = s.autoTracker?.currentSessionId;
|
|
4712
4937
|
if (sessionId) enriched.sessionId = sessionId;
|
|
@@ -4961,6 +5186,25 @@ var CrossdeckClient = class {
|
|
|
4961
5186
|
events: s.events.getStats()
|
|
4962
5187
|
};
|
|
4963
5188
|
}
|
|
5189
|
+
/**
|
|
5190
|
+
* The stable reference to hand Stripe at checkout so the resulting
|
|
5191
|
+
* purchase attributes to THIS person. Stamp it on the Checkout Session
|
|
5192
|
+
* as `metadata.crossdeck_ref` (see docs/connect-stripe) — the platform
|
|
5193
|
+
* webhook reads it back, validates it, and attaches the subscription to
|
|
5194
|
+
* the right customer instead of stranding it on an anonymous record.
|
|
5195
|
+
*
|
|
5196
|
+
* Returns the strongest identifier the SDK currently holds, in the same
|
|
5197
|
+
* precedence the server resolves by:
|
|
5198
|
+
* crossdeckCustomerId (cdcust_…) > developerUserId > anonymousId
|
|
5199
|
+
* anonymousId is always present, so this always returns a usable,
|
|
5200
|
+
* non-empty reference — even before identify(). Identify the user as
|
|
5201
|
+
* early as you can, though, so the reference is their stable identity
|
|
5202
|
+
* and not a per-device anon id.
|
|
5203
|
+
*/
|
|
5204
|
+
getCheckoutReference() {
|
|
5205
|
+
const s = this.requireStarted();
|
|
5206
|
+
return s.identity.crossdeckCustomerId ?? s.developerUserId ?? s.identity.anonymousId;
|
|
5207
|
+
}
|
|
4964
5208
|
// ---------- private helpers ----------
|
|
4965
5209
|
requireStarted() {
|
|
4966
5210
|
if (!this.state) {
|
|
@@ -5282,8 +5526,8 @@ function getErrorCode(code) {
|
|
|
5282
5526
|
}
|
|
5283
5527
|
|
|
5284
5528
|
// src/_contracts-bundled.ts
|
|
5285
|
-
var BUNDLED_IN = "@cross-deck/web@1.
|
|
5286
|
-
var SDK_VERSION2 = "1.
|
|
5529
|
+
var BUNDLED_IN = "@cross-deck/web@1.6.1";
|
|
5530
|
+
var SDK_VERSION2 = "1.6.1";
|
|
5287
5531
|
var BUNDLED_CONTRACTS = Object.freeze([
|
|
5288
5532
|
{
|
|
5289
5533
|
"id": "contract-failed-payload-schema-lock",
|
|
@@ -5405,7 +5649,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
5405
5649
|
"legal/security/index.html#diagnostic",
|
|
5406
5650
|
"legal/sdk-data/index.html#b-diagnostic"
|
|
5407
5651
|
],
|
|
5408
|
-
"bundledIn": "@cross-deck/web@1.
|
|
5652
|
+
"bundledIn": "@cross-deck/web@1.6.1"
|
|
5409
5653
|
},
|
|
5410
5654
|
{
|
|
5411
5655
|
"id": "error-envelope-shape",
|
|
@@ -5444,7 +5688,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
5444
5688
|
],
|
|
5445
5689
|
"registeredAt": "2026-05-26",
|
|
5446
5690
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 8 (codifies existing contract)",
|
|
5447
|
-
"bundledIn": "@cross-deck/web@1.
|
|
5691
|
+
"bundledIn": "@cross-deck/web@1.6.1"
|
|
5448
5692
|
},
|
|
5449
5693
|
{
|
|
5450
5694
|
"id": "flush-interval-parity",
|
|
@@ -5489,7 +5733,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
5489
5733
|
],
|
|
5490
5734
|
"registeredAt": "2026-05-26",
|
|
5491
5735
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.3",
|
|
5492
|
-
"bundledIn": "@cross-deck/web@1.
|
|
5736
|
+
"bundledIn": "@cross-deck/web@1.6.1"
|
|
5493
5737
|
},
|
|
5494
5738
|
{
|
|
5495
5739
|
"id": "idempotency-key-deterministic",
|
|
@@ -5594,7 +5838,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
5594
5838
|
],
|
|
5595
5839
|
"registeredAt": "2026-05-26",
|
|
5596
5840
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 2.2.a + 2.2.b + 2.2.c",
|
|
5597
|
-
"bundledIn": "@cross-deck/web@1.
|
|
5841
|
+
"bundledIn": "@cross-deck/web@1.6.1"
|
|
5598
5842
|
},
|
|
5599
5843
|
{
|
|
5600
5844
|
"id": "init-reentry-drains-prior-queue",
|
|
@@ -5621,7 +5865,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
5621
5865
|
],
|
|
5622
5866
|
"registeredAt": "2026-05-26",
|
|
5623
5867
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 5.5",
|
|
5624
|
-
"bundledIn": "@cross-deck/web@1.
|
|
5868
|
+
"bundledIn": "@cross-deck/web@1.6.1"
|
|
5625
5869
|
},
|
|
5626
5870
|
{
|
|
5627
5871
|
"id": "per-user-cache-isolation",
|
|
@@ -5700,7 +5944,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
5700
5944
|
],
|
|
5701
5945
|
"registeredAt": "2026-05-26",
|
|
5702
5946
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 1.3 (web/RN) + dogfood-gap fix (swift + android)",
|
|
5703
|
-
"bundledIn": "@cross-deck/web@1.
|
|
5947
|
+
"bundledIn": "@cross-deck/web@1.6.1"
|
|
5704
5948
|
},
|
|
5705
5949
|
{
|
|
5706
5950
|
"id": "sdk-error-codes-catalogue",
|
|
@@ -5740,7 +5984,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
5740
5984
|
],
|
|
5741
5985
|
"registeredAt": "2026-05-26",
|
|
5742
5986
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 6.2",
|
|
5743
|
-
"bundledIn": "@cross-deck/web@1.
|
|
5987
|
+
"bundledIn": "@cross-deck/web@1.6.1"
|
|
5744
5988
|
},
|
|
5745
5989
|
{
|
|
5746
5990
|
"id": "sync-purchases-funnel-parity",
|
|
@@ -5773,7 +6017,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
|
|
|
5773
6017
|
],
|
|
5774
6018
|
"registeredAt": "2026-05-26",
|
|
5775
6019
|
"firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.5",
|
|
5776
|
-
"bundledIn": "@cross-deck/web@1.
|
|
6020
|
+
"bundledIn": "@cross-deck/web@1.6.1"
|
|
5777
6021
|
}
|
|
5778
6022
|
]);
|
|
5779
6023
|
|