@cross-deck/web 1.11.2 → 1.13.0
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 +22 -0
- package/README.md +49 -1
- 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 +294 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +25 -4
- package/dist/index.d.ts +25 -4
- package/dist/index.mjs +291 -15
- package/dist/index.mjs.map +1 -1
- package/dist/react.cjs +262 -5
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.mts +40 -2
- package/dist/react.d.ts +40 -2
- package/dist/react.mjs +260 -5
- package/dist/react.mjs.map +1 -1
- package/dist/trust-C0RcpR5I.d.mts +91 -0
- package/dist/trust-C0RcpR5I.d.ts +91 -0
- package/dist/vue.cjs +244 -5
- package/dist/vue.cjs.map +1 -1
- package/dist/vue.d.mts +25 -1
- package/dist/vue.d.ts +25 -1
- package/dist/vue.mjs +242 -4
- package/dist/vue.mjs.map +1 -1
- package/package.json +1 -1
package/dist/react.cjs
CHANGED
|
@@ -21,8 +21,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var react_exports = {};
|
|
22
22
|
__export(react_exports, {
|
|
23
23
|
CrossdeckProvider: () => CrossdeckProvider,
|
|
24
|
+
CrossdeckTrust: () => CrossdeckTrust,
|
|
24
25
|
useEntitlement: () => useEntitlement,
|
|
25
|
-
useEntitlements: () => useEntitlements
|
|
26
|
+
useEntitlements: () => useEntitlements,
|
|
27
|
+
useTrustToken: () => useTrustToken
|
|
26
28
|
});
|
|
27
29
|
module.exports = __toCommonJS(react_exports);
|
|
28
30
|
var import_react = require("react");
|
|
@@ -98,7 +100,7 @@ function typeMapForStatus(status) {
|
|
|
98
100
|
}
|
|
99
101
|
|
|
100
102
|
// src/_version.ts
|
|
101
|
-
var SDK_VERSION = "1.
|
|
103
|
+
var SDK_VERSION = "1.13.0";
|
|
102
104
|
var SDK_NAME = "@cross-deck/web";
|
|
103
105
|
|
|
104
106
|
// src/http.ts
|
|
@@ -355,6 +357,125 @@ function randomChars(count) {
|
|
|
355
357
|
return out.join("");
|
|
356
358
|
}
|
|
357
359
|
|
|
360
|
+
// src/trust.ts
|
|
361
|
+
var TRUST_PANEL_ORIGIN = "https://trust.cross-deck.com";
|
|
362
|
+
var MAX_WIDTH_PX = 360;
|
|
363
|
+
var INITIAL_HEIGHT_PX = 132;
|
|
364
|
+
var MIN_HEIGHT_PX = 96;
|
|
365
|
+
var MAX_HEIGHT_PX = 220;
|
|
366
|
+
var DEFAULT_TIMEOUT_MS2 = 15e3;
|
|
367
|
+
function mountTrustPanel(opts) {
|
|
368
|
+
const origin = normalizeOrigin(opts.origin) || TRUST_PANEL_ORIGIN;
|
|
369
|
+
const timeoutMs = clampInt(opts.timeoutMs, DEFAULT_TIMEOUT_MS2, 1e3, 12e4);
|
|
370
|
+
let settled = false;
|
|
371
|
+
let onMessage = null;
|
|
372
|
+
let timer = null;
|
|
373
|
+
let frame = null;
|
|
374
|
+
let resolveReady;
|
|
375
|
+
const ready = new Promise((res) => {
|
|
376
|
+
resolveReady = res;
|
|
377
|
+
});
|
|
378
|
+
function settle(result, reason) {
|
|
379
|
+
if (settled) return;
|
|
380
|
+
settled = true;
|
|
381
|
+
if (timer) safe(() => clearTimeout(timer));
|
|
382
|
+
if (onMessage) safe(() => window.removeEventListener("message", onMessage));
|
|
383
|
+
if (result) {
|
|
384
|
+
safe(() => opts.onToken?.(result));
|
|
385
|
+
resolveReady(result);
|
|
386
|
+
} else {
|
|
387
|
+
const why = reason || "unavailable";
|
|
388
|
+
safe(() => opts.onUnavailable?.(why));
|
|
389
|
+
resolveReady({ token: null, expiresAt: null, reason: why });
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
try {
|
|
393
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
394
|
+
queueMicrotask(() => settle(null, "no_dom"));
|
|
395
|
+
return makeHandle();
|
|
396
|
+
}
|
|
397
|
+
const host = resolveTarget(opts.target);
|
|
398
|
+
frame = createFrame(origin, opts.publicKey);
|
|
399
|
+
if (!host) {
|
|
400
|
+
queueMicrotask(() => settle(null, "no_mount_target"));
|
|
401
|
+
return makeHandle();
|
|
402
|
+
}
|
|
403
|
+
onMessage = (ev) => {
|
|
404
|
+
if (ev.origin !== origin) return;
|
|
405
|
+
if (!ev.data || !frame || ev.source !== frame.contentWindow) return;
|
|
406
|
+
const d = ev.data;
|
|
407
|
+
if (d.type === "cd-trust-size" && typeof d.height === "number") {
|
|
408
|
+
safe(() => {
|
|
409
|
+
if (frame) frame.style.height = clampInt(d.height, INITIAL_HEIGHT_PX, MIN_HEIGHT_PX, MAX_HEIGHT_PX) + "px";
|
|
410
|
+
});
|
|
411
|
+
} else if (d.type === "cd-trust-token" && typeof d.token === "string") {
|
|
412
|
+
settle({ token: d.token, expiresAt: typeof d.expMs === "number" ? d.expMs : null });
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
window.addEventListener("message", onMessage);
|
|
416
|
+
host.appendChild(frame);
|
|
417
|
+
timer = setTimeout(() => settle(null, "timeout"), timeoutMs);
|
|
418
|
+
} catch (err) {
|
|
419
|
+
queueMicrotask(() => settle(null, "mount_error:" + safeMessage(err)));
|
|
420
|
+
}
|
|
421
|
+
return makeHandle();
|
|
422
|
+
function makeHandle() {
|
|
423
|
+
return {
|
|
424
|
+
frame,
|
|
425
|
+
ready,
|
|
426
|
+
destroy() {
|
|
427
|
+
if (onMessage) safe(() => window.removeEventListener("message", onMessage));
|
|
428
|
+
if (timer) safe(() => clearTimeout(timer));
|
|
429
|
+
safe(() => frame?.parentNode?.removeChild(frame));
|
|
430
|
+
settle(null, "destroyed");
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
function createFrame(origin, publicKey) {
|
|
436
|
+
const frame = document.createElement("iframe");
|
|
437
|
+
frame.title = "Protected by Crossdeck Trust";
|
|
438
|
+
frame.setAttribute("aria-label", "Protected by Crossdeck Trust");
|
|
439
|
+
frame.setAttribute("scrolling", "no");
|
|
440
|
+
frame.setAttribute("allowtransparency", "true");
|
|
441
|
+
frame.style.cssText = "border:0;display:block;width:100%;max-width:" + MAX_WIDTH_PX + "px;height:" + INITIAL_HEIGHT_PX + "px;margin:0;background:transparent;color-scheme:light;";
|
|
442
|
+
frame.src = origin + "/panel?k=" + encodeURIComponent(publicKey || "");
|
|
443
|
+
return frame;
|
|
444
|
+
}
|
|
445
|
+
function resolveTarget(target) {
|
|
446
|
+
try {
|
|
447
|
+
if (typeof target === "string") return document.querySelector(target);
|
|
448
|
+
return target || null;
|
|
449
|
+
} catch {
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
function normalizeOrigin(o) {
|
|
454
|
+
if (!o || typeof o !== "string") return null;
|
|
455
|
+
try {
|
|
456
|
+
return new URL(o).origin;
|
|
457
|
+
} catch {
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
function clampInt(v, fallback, min, max) {
|
|
462
|
+
const n = typeof v === "number" && isFinite(v) ? v : fallback;
|
|
463
|
+
return Math.min(Math.max(n, min), max);
|
|
464
|
+
}
|
|
465
|
+
function safe(fn) {
|
|
466
|
+
try {
|
|
467
|
+
fn();
|
|
468
|
+
} catch {
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
function safeMessage(err) {
|
|
472
|
+
try {
|
|
473
|
+
return err instanceof Error ? err.message : String(err);
|
|
474
|
+
} catch {
|
|
475
|
+
return "unknown";
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
358
479
|
// src/hash.ts
|
|
359
480
|
var K = new Uint32Array([
|
|
360
481
|
1116352408,
|
|
@@ -1588,6 +1709,7 @@ var AutoTracker = class {
|
|
|
1588
1709
|
this.pageviewId = null;
|
|
1589
1710
|
this.storage = opts?.storage ?? null;
|
|
1590
1711
|
this.sessionKey = opts?.storageKey ?? SESSION_STORAGE_KEY;
|
|
1712
|
+
this.cookieStorage = opts?.cookieStorage ?? null;
|
|
1591
1713
|
}
|
|
1592
1714
|
install() {
|
|
1593
1715
|
if (!isBrowserSafe()) return;
|
|
@@ -1739,8 +1861,73 @@ var AutoTracker = class {
|
|
|
1739
1861
|
lastActivityAt: now,
|
|
1740
1862
|
hiddenAt: null,
|
|
1741
1863
|
endedSent: false,
|
|
1742
|
-
acquisition:
|
|
1864
|
+
acquisition: this.resolveAcquisition()
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
/**
|
|
1868
|
+
* Session acquisition, with FIRST-TOUCH survival across sessions.
|
|
1869
|
+
*
|
|
1870
|
+
* WHY: `captureAcquisition()` reads the CURRENT page. That is correct for a
|
|
1871
|
+
* fresh arrival, but a session boundary (30-min idle, or a return visit next
|
|
1872
|
+
* week) starts a new session — and a visitor returning directly has no utm_*
|
|
1873
|
+
* and no click id, so the acquisition that actually WON them was silently
|
|
1874
|
+
* replaced with empty. Someone who arrives from LinkedIn on Monday, returns
|
|
1875
|
+
* direct on Tuesday and signs up on Wednesday converted with no source at all.
|
|
1876
|
+
*
|
|
1877
|
+
* That is exactly the question the product exists to answer — "where did this
|
|
1878
|
+
* PAYING CUSTOMER come from" — so origin has to outlive the session that
|
|
1879
|
+
* captured it. We persist the first touch that carried a real signal and,
|
|
1880
|
+
* whenever a later session arrives with nothing, fall back to it.
|
|
1881
|
+
*
|
|
1882
|
+
* Deliberately NOT last-touch-wins: a real new campaign click overwrites
|
|
1883
|
+
* nothing, it simply records a newer touch for that session while the stored
|
|
1884
|
+
* first touch stays put. Write-once by design.
|
|
1885
|
+
*/
|
|
1886
|
+
resolveAcquisition() {
|
|
1887
|
+
const current = captureAcquisition();
|
|
1888
|
+
if (!this.storage && !this.cookieStorage) return current;
|
|
1889
|
+
const key = `${this.sessionKey}_origin_first`;
|
|
1890
|
+
const readFirstTouch = () => {
|
|
1891
|
+
try {
|
|
1892
|
+
const v = this.storage?.getItem(key);
|
|
1893
|
+
if (v) return v;
|
|
1894
|
+
} catch {
|
|
1895
|
+
}
|
|
1896
|
+
try {
|
|
1897
|
+
return this.cookieStorage?.getItem(key) ?? null;
|
|
1898
|
+
} catch {
|
|
1899
|
+
return null;
|
|
1900
|
+
}
|
|
1901
|
+
};
|
|
1902
|
+
const writeFirstTouch = (v) => {
|
|
1903
|
+
try {
|
|
1904
|
+
this.storage?.setItem(key, v);
|
|
1905
|
+
} catch {
|
|
1906
|
+
}
|
|
1907
|
+
try {
|
|
1908
|
+
this.cookieStorage?.setItem(key, v);
|
|
1909
|
+
} catch {
|
|
1910
|
+
}
|
|
1743
1911
|
};
|
|
1912
|
+
const hasSignal = (a) => Boolean(a.utm_source || a.utm_medium || a.utm_campaign || a.utm_content || a.utm_term || a.gclid || a.fbclid || a.msclkid || a.ttclid || a.li_fat_id || a.twclid);
|
|
1913
|
+
try {
|
|
1914
|
+
if (hasSignal(current)) {
|
|
1915
|
+
if (readFirstTouch() === null) writeFirstTouch(JSON.stringify(current));
|
|
1916
|
+
return current;
|
|
1917
|
+
}
|
|
1918
|
+
const raw = readFirstTouch();
|
|
1919
|
+
if (raw) {
|
|
1920
|
+
const stored = JSON.parse(raw);
|
|
1921
|
+
return {
|
|
1922
|
+
...EMPTY_ACQUISITION,
|
|
1923
|
+
...stored,
|
|
1924
|
+
// Referrer always describes THIS visit, never the remembered one.
|
|
1925
|
+
referrer: current.referrer
|
|
1926
|
+
};
|
|
1927
|
+
}
|
|
1928
|
+
} catch {
|
|
1929
|
+
}
|
|
1930
|
+
return current;
|
|
1744
1931
|
}
|
|
1745
1932
|
/**
|
|
1746
1933
|
* Read the persisted session continuity record. Returns null on no
|
|
@@ -4788,7 +4975,15 @@ var CrossdeckClient = class {
|
|
|
4788
4975
|
// a visit survives full-page navigations (multi-page sites
|
|
4789
4976
|
// re-install the SDK on every page) and honours the same consent
|
|
4790
4977
|
// posture — MemoryStorage when identity persistence is off.
|
|
4791
|
-
|
|
4978
|
+
// cookieStorage is the SAME registrable-domain cookie identity uses.
|
|
4979
|
+
// First-touch origin rides it so the campaign that won a visitor on the
|
|
4980
|
+
// marketing site is still known when they land on the app subdomain —
|
|
4981
|
+
// localStorage alone is per-origin and would lose it at exactly that hop.
|
|
4982
|
+
{
|
|
4983
|
+
storage: effectiveStorage,
|
|
4984
|
+
storageKey: opts.storagePrefix + "session",
|
|
4985
|
+
cookieStorage: cookieStore ?? void 0
|
|
4986
|
+
}
|
|
4792
4987
|
);
|
|
4793
4988
|
this.state.autoTracker = tracker;
|
|
4794
4989
|
tracker.install();
|
|
@@ -5743,6 +5938,30 @@ var CrossdeckClient = class {
|
|
|
5743
5938
|
getAnonymousId() {
|
|
5744
5939
|
return this.state ? this.state.identity.anonymousId : null;
|
|
5745
5940
|
}
|
|
5941
|
+
/**
|
|
5942
|
+
* **Crossdeck Trust** — human-proof at your signup, native to the SDK.
|
|
5943
|
+
*
|
|
5944
|
+
* `Crossdeck.trust.panel({ target, onToken })` renders the branded, un-restylable
|
|
5945
|
+
* Trust panel (the same cross-origin iframe on every install) and mints a
|
|
5946
|
+
* single-use attestation. Hand the token to your server and verify it at the gate
|
|
5947
|
+
* (`crossdeck.trust.gate(...)` in @cross-deck/node). The publishable key is taken
|
|
5948
|
+
* from your `init()` — you don't pass it again.
|
|
5949
|
+
*
|
|
5950
|
+
* Fail-open by contract: if the panel can't mint (adblocker, offline, our outage),
|
|
5951
|
+
* `ready` resolves with `{ token: null }` and your signup proceeds — the server
|
|
5952
|
+
* scores the missing token. It never throws and never blocks your form.
|
|
5953
|
+
*
|
|
5954
|
+
* @example
|
|
5955
|
+
* const { ready } = Crossdeck.trust.panel({ target: "#cd-trust" });
|
|
5956
|
+
* const result = await ready; // { token, expiresAt } | { token: null }
|
|
5957
|
+
* await createUser({ email, cdTrustToken: result.token });
|
|
5958
|
+
*/
|
|
5959
|
+
get trust() {
|
|
5960
|
+
const publicKey = this.state ? this.state.options.publicKey : "";
|
|
5961
|
+
return {
|
|
5962
|
+
panel: (input) => mountTrustPanel({ ...input, publicKey })
|
|
5963
|
+
};
|
|
5964
|
+
}
|
|
5746
5965
|
// ---------- private helpers ----------
|
|
5747
5966
|
requireStarted() {
|
|
5748
5967
|
if (!this.state) {
|
|
@@ -5949,10 +6168,48 @@ function safeListKeys() {
|
|
|
5949
6168
|
return [];
|
|
5950
6169
|
}
|
|
5951
6170
|
}
|
|
6171
|
+
function CrossdeckTrust(props) {
|
|
6172
|
+
const { onToken, onUnavailable, className, style, id } = props;
|
|
6173
|
+
const hostRef = (0, import_react.useRef)(null);
|
|
6174
|
+
const onTokenRef = (0, import_react.useRef)(onToken);
|
|
6175
|
+
onTokenRef.current = onToken;
|
|
6176
|
+
const onUnavailRef = (0, import_react.useRef)(onUnavailable);
|
|
6177
|
+
onUnavailRef.current = onUnavailable;
|
|
6178
|
+
(0, import_react.useEffect)(() => {
|
|
6179
|
+
if (!hostRef.current) return;
|
|
6180
|
+
const handle = Crossdeck.trust.panel({
|
|
6181
|
+
target: hostRef.current,
|
|
6182
|
+
onToken: (t) => onTokenRef.current?.(t),
|
|
6183
|
+
onUnavailable: (r) => onUnavailRef.current?.(r)
|
|
6184
|
+
});
|
|
6185
|
+
return () => handle.destroy();
|
|
6186
|
+
}, []);
|
|
6187
|
+
return (0, import_react.createElement)("div", { ref: hostRef, className, style, id });
|
|
6188
|
+
}
|
|
6189
|
+
function useTrustToken() {
|
|
6190
|
+
const ref = (0, import_react.useRef)(null);
|
|
6191
|
+
const [token, setToken] = (0, import_react.useState)(null);
|
|
6192
|
+
const [status, setStatus] = (0, import_react.useState)("pending");
|
|
6193
|
+
(0, import_react.useEffect)(() => {
|
|
6194
|
+
if (!ref.current) return;
|
|
6195
|
+
const handle = Crossdeck.trust.panel({
|
|
6196
|
+
target: ref.current,
|
|
6197
|
+
onToken: (t) => {
|
|
6198
|
+
setToken(t.token);
|
|
6199
|
+
setStatus("ready");
|
|
6200
|
+
},
|
|
6201
|
+
onUnavailable: () => setStatus("unavailable")
|
|
6202
|
+
});
|
|
6203
|
+
return () => handle.destroy();
|
|
6204
|
+
}, []);
|
|
6205
|
+
return { ref, token, status };
|
|
6206
|
+
}
|
|
5952
6207
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5953
6208
|
0 && (module.exports = {
|
|
5954
6209
|
CrossdeckProvider,
|
|
6210
|
+
CrossdeckTrust,
|
|
5955
6211
|
useEntitlement,
|
|
5956
|
-
useEntitlements
|
|
6212
|
+
useEntitlements,
|
|
6213
|
+
useTrustToken
|
|
5957
6214
|
});
|
|
5958
6215
|
//# sourceMappingURL=react.cjs.map
|