@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/vue.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Ref } from 'vue';
|
|
2
|
+
import { e as TrustTokenStatus } from './trust-C0RcpR5I.mjs';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* @cross-deck/web/vue — Vue 3 composables for the Crossdeck SDK.
|
|
@@ -33,5 +34,28 @@ declare function useEntitlement(key: string): Ref<boolean>;
|
|
|
33
34
|
* mutation. Useful for rendering a "you have unlocked: ..." block.
|
|
34
35
|
*/
|
|
35
36
|
declare function useEntitlements(): Ref<readonly string[]>;
|
|
37
|
+
/**
|
|
38
|
+
* Crossdeck Trust — the human-proof panel as a Vue composable.
|
|
39
|
+
*
|
|
40
|
+
* Bind `el` to the element the panel mounts into; read `token` / `status`
|
|
41
|
+
* reactively. Sits on `Crossdeck.trust.panel(...)`, takes the publishable key
|
|
42
|
+
* from `init()`, and is fail-open (can't mint → status "unavailable", signup
|
|
43
|
+
* still proceeds, server scores the absent token). Never throws.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* <script setup>
|
|
47
|
+
* import { useTrustToken } from "@cross-deck/web/vue";
|
|
48
|
+
* const { el, token, status } = useTrustToken();
|
|
49
|
+
* </script>
|
|
50
|
+
* <template><div ref="el" /></template>
|
|
51
|
+
*/
|
|
52
|
+
declare function useTrustToken(): {
|
|
53
|
+
/** Template ref — bind to the mount element: `<div ref="el" />`. */
|
|
54
|
+
el: Ref<HTMLElement | null>;
|
|
55
|
+
/** The minted token, or null until it mints (or if the panel failed open). */
|
|
56
|
+
token: Ref<string | null>;
|
|
57
|
+
/** Lifecycle: pending → ready (minted) or unavailable (failed open). */
|
|
58
|
+
status: Ref<TrustTokenStatus>;
|
|
59
|
+
};
|
|
36
60
|
|
|
37
|
-
export { useEntitlement, useEntitlements };
|
|
61
|
+
export { TrustTokenStatus, useEntitlement, useEntitlements, useTrustToken };
|
package/dist/vue.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Ref } from 'vue';
|
|
2
|
+
import { e as TrustTokenStatus } from './trust-C0RcpR5I.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* @cross-deck/web/vue — Vue 3 composables for the Crossdeck SDK.
|
|
@@ -33,5 +34,28 @@ declare function useEntitlement(key: string): Ref<boolean>;
|
|
|
33
34
|
* mutation. Useful for rendering a "you have unlocked: ..." block.
|
|
34
35
|
*/
|
|
35
36
|
declare function useEntitlements(): Ref<readonly string[]>;
|
|
37
|
+
/**
|
|
38
|
+
* Crossdeck Trust — the human-proof panel as a Vue composable.
|
|
39
|
+
*
|
|
40
|
+
* Bind `el` to the element the panel mounts into; read `token` / `status`
|
|
41
|
+
* reactively. Sits on `Crossdeck.trust.panel(...)`, takes the publishable key
|
|
42
|
+
* from `init()`, and is fail-open (can't mint → status "unavailable", signup
|
|
43
|
+
* still proceeds, server scores the absent token). Never throws.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* <script setup>
|
|
47
|
+
* import { useTrustToken } from "@cross-deck/web/vue";
|
|
48
|
+
* const { el, token, status } = useTrustToken();
|
|
49
|
+
* </script>
|
|
50
|
+
* <template><div ref="el" /></template>
|
|
51
|
+
*/
|
|
52
|
+
declare function useTrustToken(): {
|
|
53
|
+
/** Template ref — bind to the mount element: `<div ref="el" />`. */
|
|
54
|
+
el: Ref<HTMLElement | null>;
|
|
55
|
+
/** The minted token, or null until it mints (or if the panel failed open). */
|
|
56
|
+
token: Ref<string | null>;
|
|
57
|
+
/** Lifecycle: pending → ready (minted) or unavailable (failed open). */
|
|
58
|
+
status: Ref<TrustTokenStatus>;
|
|
59
|
+
};
|
|
36
60
|
|
|
37
|
-
export { useEntitlement, useEntitlements };
|
|
61
|
+
export { TrustTokenStatus, useEntitlement, useEntitlements, useTrustToken };
|
package/dist/vue.mjs
CHANGED
|
@@ -72,7 +72,7 @@ function typeMapForStatus(status) {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
// src/_version.ts
|
|
75
|
-
var SDK_VERSION = "1.
|
|
75
|
+
var SDK_VERSION = "1.13.0";
|
|
76
76
|
var SDK_NAME = "@cross-deck/web";
|
|
77
77
|
|
|
78
78
|
// src/http.ts
|
|
@@ -329,6 +329,125 @@ function randomChars(count) {
|
|
|
329
329
|
return out.join("");
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
+
// src/trust.ts
|
|
333
|
+
var TRUST_PANEL_ORIGIN = "https://trust.cross-deck.com";
|
|
334
|
+
var MAX_WIDTH_PX = 360;
|
|
335
|
+
var INITIAL_HEIGHT_PX = 132;
|
|
336
|
+
var MIN_HEIGHT_PX = 96;
|
|
337
|
+
var MAX_HEIGHT_PX = 220;
|
|
338
|
+
var DEFAULT_TIMEOUT_MS2 = 15e3;
|
|
339
|
+
function mountTrustPanel(opts) {
|
|
340
|
+
const origin = normalizeOrigin(opts.origin) || TRUST_PANEL_ORIGIN;
|
|
341
|
+
const timeoutMs = clampInt(opts.timeoutMs, DEFAULT_TIMEOUT_MS2, 1e3, 12e4);
|
|
342
|
+
let settled = false;
|
|
343
|
+
let onMessage = null;
|
|
344
|
+
let timer = null;
|
|
345
|
+
let frame = null;
|
|
346
|
+
let resolveReady;
|
|
347
|
+
const ready = new Promise((res) => {
|
|
348
|
+
resolveReady = res;
|
|
349
|
+
});
|
|
350
|
+
function settle(result, reason) {
|
|
351
|
+
if (settled) return;
|
|
352
|
+
settled = true;
|
|
353
|
+
if (timer) safe(() => clearTimeout(timer));
|
|
354
|
+
if (onMessage) safe(() => window.removeEventListener("message", onMessage));
|
|
355
|
+
if (result) {
|
|
356
|
+
safe(() => opts.onToken?.(result));
|
|
357
|
+
resolveReady(result);
|
|
358
|
+
} else {
|
|
359
|
+
const why = reason || "unavailable";
|
|
360
|
+
safe(() => opts.onUnavailable?.(why));
|
|
361
|
+
resolveReady({ token: null, expiresAt: null, reason: why });
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
try {
|
|
365
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
366
|
+
queueMicrotask(() => settle(null, "no_dom"));
|
|
367
|
+
return makeHandle();
|
|
368
|
+
}
|
|
369
|
+
const host = resolveTarget(opts.target);
|
|
370
|
+
frame = createFrame(origin, opts.publicKey);
|
|
371
|
+
if (!host) {
|
|
372
|
+
queueMicrotask(() => settle(null, "no_mount_target"));
|
|
373
|
+
return makeHandle();
|
|
374
|
+
}
|
|
375
|
+
onMessage = (ev) => {
|
|
376
|
+
if (ev.origin !== origin) return;
|
|
377
|
+
if (!ev.data || !frame || ev.source !== frame.contentWindow) return;
|
|
378
|
+
const d = ev.data;
|
|
379
|
+
if (d.type === "cd-trust-size" && typeof d.height === "number") {
|
|
380
|
+
safe(() => {
|
|
381
|
+
if (frame) frame.style.height = clampInt(d.height, INITIAL_HEIGHT_PX, MIN_HEIGHT_PX, MAX_HEIGHT_PX) + "px";
|
|
382
|
+
});
|
|
383
|
+
} else if (d.type === "cd-trust-token" && typeof d.token === "string") {
|
|
384
|
+
settle({ token: d.token, expiresAt: typeof d.expMs === "number" ? d.expMs : null });
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
window.addEventListener("message", onMessage);
|
|
388
|
+
host.appendChild(frame);
|
|
389
|
+
timer = setTimeout(() => settle(null, "timeout"), timeoutMs);
|
|
390
|
+
} catch (err) {
|
|
391
|
+
queueMicrotask(() => settle(null, "mount_error:" + safeMessage(err)));
|
|
392
|
+
}
|
|
393
|
+
return makeHandle();
|
|
394
|
+
function makeHandle() {
|
|
395
|
+
return {
|
|
396
|
+
frame,
|
|
397
|
+
ready,
|
|
398
|
+
destroy() {
|
|
399
|
+
if (onMessage) safe(() => window.removeEventListener("message", onMessage));
|
|
400
|
+
if (timer) safe(() => clearTimeout(timer));
|
|
401
|
+
safe(() => frame?.parentNode?.removeChild(frame));
|
|
402
|
+
settle(null, "destroyed");
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
function createFrame(origin, publicKey) {
|
|
408
|
+
const frame = document.createElement("iframe");
|
|
409
|
+
frame.title = "Protected by Crossdeck Trust";
|
|
410
|
+
frame.setAttribute("aria-label", "Protected by Crossdeck Trust");
|
|
411
|
+
frame.setAttribute("scrolling", "no");
|
|
412
|
+
frame.setAttribute("allowtransparency", "true");
|
|
413
|
+
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;";
|
|
414
|
+
frame.src = origin + "/panel?k=" + encodeURIComponent(publicKey || "");
|
|
415
|
+
return frame;
|
|
416
|
+
}
|
|
417
|
+
function resolveTarget(target) {
|
|
418
|
+
try {
|
|
419
|
+
if (typeof target === "string") return document.querySelector(target);
|
|
420
|
+
return target || null;
|
|
421
|
+
} catch {
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
function normalizeOrigin(o) {
|
|
426
|
+
if (!o || typeof o !== "string") return null;
|
|
427
|
+
try {
|
|
428
|
+
return new URL(o).origin;
|
|
429
|
+
} catch {
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function clampInt(v, fallback, min, max) {
|
|
434
|
+
const n = typeof v === "number" && isFinite(v) ? v : fallback;
|
|
435
|
+
return Math.min(Math.max(n, min), max);
|
|
436
|
+
}
|
|
437
|
+
function safe(fn) {
|
|
438
|
+
try {
|
|
439
|
+
fn();
|
|
440
|
+
} catch {
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
function safeMessage(err) {
|
|
444
|
+
try {
|
|
445
|
+
return err instanceof Error ? err.message : String(err);
|
|
446
|
+
} catch {
|
|
447
|
+
return "unknown";
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
332
451
|
// src/hash.ts
|
|
333
452
|
var K = new Uint32Array([
|
|
334
453
|
1116352408,
|
|
@@ -1562,6 +1681,7 @@ var AutoTracker = class {
|
|
|
1562
1681
|
this.pageviewId = null;
|
|
1563
1682
|
this.storage = opts?.storage ?? null;
|
|
1564
1683
|
this.sessionKey = opts?.storageKey ?? SESSION_STORAGE_KEY;
|
|
1684
|
+
this.cookieStorage = opts?.cookieStorage ?? null;
|
|
1565
1685
|
}
|
|
1566
1686
|
install() {
|
|
1567
1687
|
if (!isBrowserSafe()) return;
|
|
@@ -1713,9 +1833,74 @@ var AutoTracker = class {
|
|
|
1713
1833
|
lastActivityAt: now,
|
|
1714
1834
|
hiddenAt: null,
|
|
1715
1835
|
endedSent: false,
|
|
1716
|
-
acquisition:
|
|
1836
|
+
acquisition: this.resolveAcquisition()
|
|
1717
1837
|
};
|
|
1718
1838
|
}
|
|
1839
|
+
/**
|
|
1840
|
+
* Session acquisition, with FIRST-TOUCH survival across sessions.
|
|
1841
|
+
*
|
|
1842
|
+
* WHY: `captureAcquisition()` reads the CURRENT page. That is correct for a
|
|
1843
|
+
* fresh arrival, but a session boundary (30-min idle, or a return visit next
|
|
1844
|
+
* week) starts a new session — and a visitor returning directly has no utm_*
|
|
1845
|
+
* and no click id, so the acquisition that actually WON them was silently
|
|
1846
|
+
* replaced with empty. Someone who arrives from LinkedIn on Monday, returns
|
|
1847
|
+
* direct on Tuesday and signs up on Wednesday converted with no source at all.
|
|
1848
|
+
*
|
|
1849
|
+
* That is exactly the question the product exists to answer — "where did this
|
|
1850
|
+
* PAYING CUSTOMER come from" — so origin has to outlive the session that
|
|
1851
|
+
* captured it. We persist the first touch that carried a real signal and,
|
|
1852
|
+
* whenever a later session arrives with nothing, fall back to it.
|
|
1853
|
+
*
|
|
1854
|
+
* Deliberately NOT last-touch-wins: a real new campaign click overwrites
|
|
1855
|
+
* nothing, it simply records a newer touch for that session while the stored
|
|
1856
|
+
* first touch stays put. Write-once by design.
|
|
1857
|
+
*/
|
|
1858
|
+
resolveAcquisition() {
|
|
1859
|
+
const current = captureAcquisition();
|
|
1860
|
+
if (!this.storage && !this.cookieStorage) return current;
|
|
1861
|
+
const key = `${this.sessionKey}_origin_first`;
|
|
1862
|
+
const readFirstTouch = () => {
|
|
1863
|
+
try {
|
|
1864
|
+
const v = this.storage?.getItem(key);
|
|
1865
|
+
if (v) return v;
|
|
1866
|
+
} catch {
|
|
1867
|
+
}
|
|
1868
|
+
try {
|
|
1869
|
+
return this.cookieStorage?.getItem(key) ?? null;
|
|
1870
|
+
} catch {
|
|
1871
|
+
return null;
|
|
1872
|
+
}
|
|
1873
|
+
};
|
|
1874
|
+
const writeFirstTouch = (v) => {
|
|
1875
|
+
try {
|
|
1876
|
+
this.storage?.setItem(key, v);
|
|
1877
|
+
} catch {
|
|
1878
|
+
}
|
|
1879
|
+
try {
|
|
1880
|
+
this.cookieStorage?.setItem(key, v);
|
|
1881
|
+
} catch {
|
|
1882
|
+
}
|
|
1883
|
+
};
|
|
1884
|
+
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);
|
|
1885
|
+
try {
|
|
1886
|
+
if (hasSignal(current)) {
|
|
1887
|
+
if (readFirstTouch() === null) writeFirstTouch(JSON.stringify(current));
|
|
1888
|
+
return current;
|
|
1889
|
+
}
|
|
1890
|
+
const raw = readFirstTouch();
|
|
1891
|
+
if (raw) {
|
|
1892
|
+
const stored = JSON.parse(raw);
|
|
1893
|
+
return {
|
|
1894
|
+
...EMPTY_ACQUISITION,
|
|
1895
|
+
...stored,
|
|
1896
|
+
// Referrer always describes THIS visit, never the remembered one.
|
|
1897
|
+
referrer: current.referrer
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1900
|
+
} catch {
|
|
1901
|
+
}
|
|
1902
|
+
return current;
|
|
1903
|
+
}
|
|
1719
1904
|
/**
|
|
1720
1905
|
* Read the persisted session continuity record. Returns null on no
|
|
1721
1906
|
* storage, no record, malformed JSON, or a record missing its required
|
|
@@ -4762,7 +4947,15 @@ var CrossdeckClient = class {
|
|
|
4762
4947
|
// a visit survives full-page navigations (multi-page sites
|
|
4763
4948
|
// re-install the SDK on every page) and honours the same consent
|
|
4764
4949
|
// posture — MemoryStorage when identity persistence is off.
|
|
4765
|
-
|
|
4950
|
+
// cookieStorage is the SAME registrable-domain cookie identity uses.
|
|
4951
|
+
// First-touch origin rides it so the campaign that won a visitor on the
|
|
4952
|
+
// marketing site is still known when they land on the app subdomain —
|
|
4953
|
+
// localStorage alone is per-origin and would lose it at exactly that hop.
|
|
4954
|
+
{
|
|
4955
|
+
storage: effectiveStorage,
|
|
4956
|
+
storageKey: opts.storagePrefix + "session",
|
|
4957
|
+
cookieStorage: cookieStore ?? void 0
|
|
4958
|
+
}
|
|
4766
4959
|
);
|
|
4767
4960
|
this.state.autoTracker = tracker;
|
|
4768
4961
|
tracker.install();
|
|
@@ -5717,6 +5910,30 @@ var CrossdeckClient = class {
|
|
|
5717
5910
|
getAnonymousId() {
|
|
5718
5911
|
return this.state ? this.state.identity.anonymousId : null;
|
|
5719
5912
|
}
|
|
5913
|
+
/**
|
|
5914
|
+
* **Crossdeck Trust** — human-proof at your signup, native to the SDK.
|
|
5915
|
+
*
|
|
5916
|
+
* `Crossdeck.trust.panel({ target, onToken })` renders the branded, un-restylable
|
|
5917
|
+
* Trust panel (the same cross-origin iframe on every install) and mints a
|
|
5918
|
+
* single-use attestation. Hand the token to your server and verify it at the gate
|
|
5919
|
+
* (`crossdeck.trust.gate(...)` in @cross-deck/node). The publishable key is taken
|
|
5920
|
+
* from your `init()` — you don't pass it again.
|
|
5921
|
+
*
|
|
5922
|
+
* Fail-open by contract: if the panel can't mint (adblocker, offline, our outage),
|
|
5923
|
+
* `ready` resolves with `{ token: null }` and your signup proceeds — the server
|
|
5924
|
+
* scores the missing token. It never throws and never blocks your form.
|
|
5925
|
+
*
|
|
5926
|
+
* @example
|
|
5927
|
+
* const { ready } = Crossdeck.trust.panel({ target: "#cd-trust" });
|
|
5928
|
+
* const result = await ready; // { token, expiresAt } | { token: null }
|
|
5929
|
+
* await createUser({ email, cdTrustToken: result.token });
|
|
5930
|
+
*/
|
|
5931
|
+
get trust() {
|
|
5932
|
+
const publicKey = this.state ? this.state.options.publicKey : "";
|
|
5933
|
+
return {
|
|
5934
|
+
panel: (input) => mountTrustPanel({ ...input, publicKey })
|
|
5935
|
+
};
|
|
5936
|
+
}
|
|
5720
5937
|
// ---------- private helpers ----------
|
|
5721
5938
|
requireStarted() {
|
|
5722
5939
|
if (!this.state) {
|
|
@@ -5876,6 +6093,26 @@ function useEntitlements() {
|
|
|
5876
6093
|
});
|
|
5877
6094
|
return r;
|
|
5878
6095
|
}
|
|
6096
|
+
function useTrustToken() {
|
|
6097
|
+
const el = ref(null);
|
|
6098
|
+
const token = ref(null);
|
|
6099
|
+
const status = ref("pending");
|
|
6100
|
+
onMounted(() => {
|
|
6101
|
+
if (!el.value) return;
|
|
6102
|
+
const handle = Crossdeck.trust.panel({
|
|
6103
|
+
target: el.value,
|
|
6104
|
+
onToken: (t) => {
|
|
6105
|
+
token.value = t.token;
|
|
6106
|
+
status.value = "ready";
|
|
6107
|
+
},
|
|
6108
|
+
onUnavailable: () => {
|
|
6109
|
+
status.value = "unavailable";
|
|
6110
|
+
}
|
|
6111
|
+
});
|
|
6112
|
+
onScopeDispose(() => handle.destroy());
|
|
6113
|
+
});
|
|
6114
|
+
return { el, token, status };
|
|
6115
|
+
}
|
|
5879
6116
|
function safeIsEntitled(key) {
|
|
5880
6117
|
try {
|
|
5881
6118
|
return Crossdeck.isEntitled(key);
|
|
@@ -5892,6 +6129,7 @@ function safeListKeys() {
|
|
|
5892
6129
|
}
|
|
5893
6130
|
export {
|
|
5894
6131
|
useEntitlement,
|
|
5895
|
-
useEntitlements
|
|
6132
|
+
useEntitlements,
|
|
6133
|
+
useTrustToken
|
|
5896
6134
|
};
|
|
5897
6135
|
//# sourceMappingURL=vue.mjs.map
|