@datalyr/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.
- package/dist/attribution.d.ts.map +1 -1
- package/dist/container.d.ts +22 -0
- package/dist/container.d.ts.map +1 -1
- package/dist/datalyr.cjs.js +396 -53
- package/dist/datalyr.cjs.js.map +1 -1
- package/dist/datalyr.esm.js +396 -53
- package/dist/datalyr.esm.js.map +1 -1
- package/dist/datalyr.esm.min.js +1 -1
- package/dist/datalyr.esm.min.js.map +1 -1
- package/dist/datalyr.js +396 -53
- package/dist/datalyr.js.map +1 -1
- package/dist/datalyr.min.js +1 -1
- package/dist/datalyr.min.js.map +1 -1
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +14 -0
- package/dist/utils.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/datalyr.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @datalyr/web v1.6.
|
|
2
|
+
* @datalyr/web v1.6.4
|
|
3
3
|
* Datalyr Web SDK - Modern attribution tracking for web applications
|
|
4
4
|
* (c) 2026 Datalyr Inc.
|
|
5
5
|
* Released under the MIT License
|
|
@@ -585,6 +585,37 @@ var Datalyr = (function (exports) {
|
|
|
585
585
|
/**
|
|
586
586
|
* Utility Functions
|
|
587
587
|
*/
|
|
588
|
+
/**
|
|
589
|
+
* SHA-256 hex digest aligned with the worker's CAPI hashing (cloudflare/
|
|
590
|
+
* postback/core/utils.js `sha256()`). Always lowercases + trims the input
|
|
591
|
+
* before hashing — Meta's matching requires byte-for-byte identical hashes
|
|
592
|
+
* on the Pixel side and the CAPI server side, and the worker uniformly
|
|
593
|
+
* normalizes everything it hashes (em, ph, fn, ln, external_id, etc).
|
|
594
|
+
*
|
|
595
|
+
* Pixel-side hashes that don't match CAPI hashes silently fail to dedupe,
|
|
596
|
+
* which is why this helper does the normalization for callers — easy to
|
|
597
|
+
* forget, hard to debug after release. Returns null when Web Crypto isn't
|
|
598
|
+
* available (very old browsers, non-secure contexts) so callers can
|
|
599
|
+
* gracefully skip advanced matching rather than throwing.
|
|
600
|
+
*/
|
|
601
|
+
function sha256Hex(input) {
|
|
602
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
603
|
+
if (typeof crypto === 'undefined' || !crypto.subtle)
|
|
604
|
+
return null;
|
|
605
|
+
if (!input)
|
|
606
|
+
return null;
|
|
607
|
+
try {
|
|
608
|
+
const bytes = new TextEncoder().encode(input.toLowerCase().trim());
|
|
609
|
+
const digest = yield crypto.subtle.digest('SHA-256', bytes);
|
|
610
|
+
return Array.from(new Uint8Array(digest))
|
|
611
|
+
.map((b) => b.toString(16).padStart(2, '0'))
|
|
612
|
+
.join('');
|
|
613
|
+
}
|
|
614
|
+
catch (_a) {
|
|
615
|
+
return null;
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
}
|
|
588
619
|
/**
|
|
589
620
|
* Generate UUID v4
|
|
590
621
|
*/
|
|
@@ -1496,6 +1527,7 @@ var Datalyr = (function (exports) {
|
|
|
1496
1527
|
* Capture advertising platform cookies
|
|
1497
1528
|
*/
|
|
1498
1529
|
captureAdCookies() {
|
|
1530
|
+
var _a, _b;
|
|
1499
1531
|
const adCookies = {};
|
|
1500
1532
|
// Facebook/Meta cookies
|
|
1501
1533
|
adCookies._fbp = cookies.get('_fbp');
|
|
@@ -1520,13 +1552,29 @@ var Datalyr = (function (exports) {
|
|
|
1520
1552
|
// Optionally set the cookie for future use
|
|
1521
1553
|
cookies.set('_fbp', adCookies._fbp, 90);
|
|
1522
1554
|
}
|
|
1555
|
+
// Persist the click time the FIRST time we see fbclid/gclid in URL so we can
|
|
1556
|
+
// rebuild fbc (and stamp real click time on server-side events) later even
|
|
1557
|
+
// when _fbc/_gclid cookies get evicted. Once-only: never overwrite.
|
|
1558
|
+
const fbclid = this.getCurrentFbclid();
|
|
1559
|
+
if (fbclid && !cookies.get('_dl_fbclid_at')) {
|
|
1560
|
+
cookies.set('_dl_fbclid_at', String(Date.now()), 90);
|
|
1561
|
+
}
|
|
1562
|
+
const gclid = this.hasClickId('gclid') ? ((_b = (_a = this.queryParamsCache) === null || _a === void 0 ? void 0 : _a.gclid) !== null && _b !== void 0 ? _b : null) : null;
|
|
1563
|
+
if (gclid && !cookies.get('_dl_gclid_at')) {
|
|
1564
|
+
cookies.set('_dl_gclid_at', String(Date.now()), 90);
|
|
1565
|
+
}
|
|
1523
1566
|
// Generate _fbc if we have fbclid but no _fbc.
|
|
1524
1567
|
// Meta's fbc format is `fb.{subdomainIndex}.{creationTime}.{fbclid}` where
|
|
1525
1568
|
// creationTime is UNIX time in MILLISECONDS (matches the _fbp generation above
|
|
1526
1569
|
// and the real _fbc cookie the Meta Pixel writes). Do NOT use seconds here.
|
|
1527
|
-
const fbclid = this.getCurrentFbclid();
|
|
1528
1570
|
if (fbclid && !adCookies._fbc) {
|
|
1529
|
-
|
|
1571
|
+
// Prefer the persisted click time when valid; fall back to now if the
|
|
1572
|
+
// cookie is missing or corrupted (e.g. user edited it to garbage).
|
|
1573
|
+
// Without this guard, Number("abc") = NaN and Meta would reject
|
|
1574
|
+
// `fb.1.NaN.{fbclid}`.
|
|
1575
|
+
const stored = cookies.get('_dl_fbclid_at');
|
|
1576
|
+
const parsed = stored ? Number(stored) : NaN;
|
|
1577
|
+
const timestamp = Number.isFinite(parsed) && parsed > 0 ? parsed : Date.now();
|
|
1530
1578
|
adCookies._fbc = `fb.1.${timestamp}.${fbclid}`;
|
|
1531
1579
|
// Optionally set the cookie for future use
|
|
1532
1580
|
cookies.set('_fbc', adCookies._fbc, 90);
|
|
@@ -2314,6 +2362,7 @@ var Datalyr = (function (exports) {
|
|
|
2314
2362
|
// Container scripts use the same endpoint as tracking (ingest)
|
|
2315
2363
|
this.endpoint = options.endpoint || 'https://ingest.datalyr.com';
|
|
2316
2364
|
this.debug = options.debug || false;
|
|
2365
|
+
this.getIdentity = options.getIdentity;
|
|
2317
2366
|
// Load session scripts from storage
|
|
2318
2367
|
const sessionScripts = storage.get('dl_session_scripts', []);
|
|
2319
2368
|
this.sessionLoadedScripts = new Set(sessionScripts);
|
|
@@ -2357,9 +2406,11 @@ var Datalyr = (function (exports) {
|
|
|
2357
2406
|
// Store scripts and pixels
|
|
2358
2407
|
this.scripts = data.scripts || [];
|
|
2359
2408
|
this.pixels = data.pixels || null;
|
|
2360
|
-
// Initialize pixels if configured
|
|
2409
|
+
// Initialize pixels if configured. Awaited so advanced-matching hashes
|
|
2410
|
+
// are resolved before the first dl.track() flushes through trackToPixels
|
|
2411
|
+
// (otherwise fbq('init') would lag fbq('track') in fast-path tracks).
|
|
2361
2412
|
if (this.pixels) {
|
|
2362
|
-
this.initializePixels();
|
|
2413
|
+
yield this.initializePixels();
|
|
2363
2414
|
}
|
|
2364
2415
|
// Load scripts based on trigger
|
|
2365
2416
|
this.loadScriptsByTrigger('page_load');
|
|
@@ -2651,57 +2702,92 @@ var Datalyr = (function (exports) {
|
|
|
2651
2702
|
* Initialize third-party pixels (Meta, Google, TikTok)
|
|
2652
2703
|
*/
|
|
2653
2704
|
initializePixels() {
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
this.
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
this.
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
this.
|
|
2668
|
-
|
|
2705
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2706
|
+
var _a, _b, _c;
|
|
2707
|
+
if (!this.pixels)
|
|
2708
|
+
return;
|
|
2709
|
+
// Initialize Meta Pixel
|
|
2710
|
+
if (((_a = this.pixels.meta) === null || _a === void 0 ? void 0 : _a.enabled) && this.pixels.meta.pixel_id) {
|
|
2711
|
+
yield this.initializeMetaPixel(this.pixels.meta);
|
|
2712
|
+
}
|
|
2713
|
+
// Initialize Google Tag
|
|
2714
|
+
if (((_b = this.pixels.google) === null || _b === void 0 ? void 0 : _b.enabled) && this.pixels.google.tag_id) {
|
|
2715
|
+
this.initializeGoogleTag(this.pixels.google);
|
|
2716
|
+
}
|
|
2717
|
+
// Initialize TikTok Pixel
|
|
2718
|
+
if (((_c = this.pixels.tiktok) === null || _c === void 0 ? void 0 : _c.enabled) && this.pixels.tiktok.pixel_id) {
|
|
2719
|
+
this.initializeTikTokPixel(this.pixels.tiktok);
|
|
2720
|
+
}
|
|
2721
|
+
});
|
|
2669
2722
|
}
|
|
2670
2723
|
/**
|
|
2671
2724
|
* Initialize Meta (Facebook) Pixel
|
|
2725
|
+
*
|
|
2726
|
+
* Async because we resolve SHA-256 hashes for advanced matching (Meta's
|
|
2727
|
+
* `external_id` / `em`) before calling fbq('init'). Aligning the hashes the
|
|
2728
|
+
* browser Pixel sends with what CAPI sends (meta.js shovels user_id /
|
|
2729
|
+
* visitor_id / anonymous_id into external_id[], and `em` is sha256 of the
|
|
2730
|
+
* lowercased email) is the dedup-quality lift the CAPI side can't fix alone.
|
|
2672
2731
|
*/
|
|
2673
2732
|
initializeMetaPixel(config) {
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2733
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2734
|
+
var _a;
|
|
2735
|
+
try {
|
|
2736
|
+
// Load Meta Pixel script
|
|
2737
|
+
(function (f, b, e, v, n, t, s) {
|
|
2738
|
+
if (f.fbq)
|
|
2739
|
+
return;
|
|
2740
|
+
n = f.fbq = function () {
|
|
2741
|
+
n.callMethod ? n.callMethod.apply(n, arguments) : n.queue.push(arguments);
|
|
2742
|
+
};
|
|
2743
|
+
if (!f._fbq)
|
|
2744
|
+
f._fbq = n;
|
|
2745
|
+
n.push = n;
|
|
2746
|
+
n.loaded = !0;
|
|
2747
|
+
n.version = '2.0';
|
|
2748
|
+
n.queue = [];
|
|
2749
|
+
t = b.createElement(e);
|
|
2750
|
+
t.async = !0;
|
|
2751
|
+
t.src = v;
|
|
2752
|
+
s = b.getElementsByTagName(e)[0];
|
|
2753
|
+
s.parentNode.insertBefore(t, s);
|
|
2754
|
+
})(window, document, 'script', 'https://connect.facebook.net/en_US/fbevents.js');
|
|
2755
|
+
// Build advanced-matching object. Skipped silently if Web Crypto isn't
|
|
2756
|
+
// available — Pixel still initializes, just without advanced matching.
|
|
2757
|
+
// Anonymous_id is stable across the session and always present, so we
|
|
2758
|
+
// never need to re-init on identify(): CAPI carries both anonymous_id
|
|
2759
|
+
// AND user_id in its external_id[] array, so either side matching one
|
|
2760
|
+
// hash slot is enough to dedupe.
|
|
2761
|
+
const advancedMatching = {};
|
|
2762
|
+
const identity = (_a = this.getIdentity) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
2763
|
+
if (identity === null || identity === void 0 ? void 0 : identity.externalId) {
|
|
2764
|
+
const hash = yield sha256Hex(String(identity.externalId));
|
|
2765
|
+
if (hash)
|
|
2766
|
+
advancedMatching.external_id = hash;
|
|
2767
|
+
}
|
|
2768
|
+
if (identity === null || identity === void 0 ? void 0 : identity.email) {
|
|
2769
|
+
const hash = yield sha256Hex(String(identity.email).toLowerCase().trim());
|
|
2770
|
+
if (hash)
|
|
2771
|
+
advancedMatching.em = hash;
|
|
2772
|
+
}
|
|
2773
|
+
// Initialize pixel only — do NOT fire PageView here. The SDK's own pageview
|
|
2774
|
+
// tracking (track('pageview')) routes through trackToPixels and fires a single
|
|
2775
|
+
// mapped PageView with a shared eventID. Firing it again here produced TWO
|
|
2776
|
+
// PageViews per load (one un-deduped). Note: if the host app disables pageview
|
|
2777
|
+
// tracking entirely, no PageView is sent — which is the correct outcome.
|
|
2778
|
+
if (Object.keys(advancedMatching).length > 0) {
|
|
2779
|
+
window.fbq('init', config.pixel_id, advancedMatching);
|
|
2780
|
+
this.log('Meta Pixel initialized with advanced matching:', config.pixel_id, Object.keys(advancedMatching));
|
|
2781
|
+
}
|
|
2782
|
+
else {
|
|
2783
|
+
window.fbq('init', config.pixel_id);
|
|
2784
|
+
this.log('Meta Pixel initialized (no advanced matching):', config.pixel_id);
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
catch (error) {
|
|
2788
|
+
this.log('Error initializing Meta Pixel:', error);
|
|
2789
|
+
}
|
|
2790
|
+
});
|
|
2705
2791
|
}
|
|
2706
2792
|
/**
|
|
2707
2793
|
* Initialize Google Tag
|
|
@@ -3445,6 +3531,14 @@ var Datalyr = (function (exports) {
|
|
|
3445
3531
|
}
|
|
3446
3532
|
// Set default config values
|
|
3447
3533
|
this.config = Object.assign({ endpoint: 'https://ingest.datalyr.com', debug: false, batchSize: 10, flushInterval: 5000, flushAt: 10, criticalEvents: undefined, highPriorityEvents: undefined, sessionTimeout: 60 * 60 * 1000, trackSessions: true, attributionWindow: 90 * 24 * 60 * 60 * 1000, trackedParams: [], respectDoNotTrack: false, respectGlobalPrivacyControl: true, privacyMode: 'standard', cookieDomain: 'auto', cookieExpires: 365, secureCookie: 'auto', sameSite: 'Lax', cookiePrefix: '__dl_', enablePerformanceTracking: true, enableFingerprinting: true, maxRetries: 5, retryDelay: 1000, maxOfflineQueueSize: 100, trackSPA: true, trackPageViews: true, fallbackEndpoints: [], plugins: [] }, config);
|
|
3534
|
+
// platform:'shopify' implies cart-attribute sync: stamp visitor_id + Meta
|
|
3535
|
+
// click signals into note_attributes so server-side order webhooks can
|
|
3536
|
+
// attribute guest checkouts. Lets the Shopify theme app-embed enable it via
|
|
3537
|
+
// data-platform="shopify" alone. `=== undefined` so an explicit
|
|
3538
|
+
// shopifyCartAttributes:false still wins.
|
|
3539
|
+
if (this.config.platform === 'shopify' && this.config.shopifyCartAttributes === undefined) {
|
|
3540
|
+
this.config.shopifyCartAttributes = true;
|
|
3541
|
+
}
|
|
3448
3542
|
// Initialize cookie storage with config
|
|
3449
3543
|
this.cookies = new CookieStorage({
|
|
3450
3544
|
domain: this.config.cookieDomain,
|
|
@@ -3456,6 +3550,12 @@ var Datalyr = (function (exports) {
|
|
|
3456
3550
|
storage.migrateFromLegacyPrefix();
|
|
3457
3551
|
// Check opt-out AFTER cookies configured (Issue #14)
|
|
3458
3552
|
this.optedOut = this.cookies.get('__dl_opt_out') === 'true';
|
|
3553
|
+
// CC funnel page entry: restore the _dl_* URL bridge BEFORE IdentityManager
|
|
3554
|
+
// initializes so the storefront's visitor_id is the one used here (instead
|
|
3555
|
+
// of auto-generating a fresh one on this domain).
|
|
3556
|
+
if (this.config.platform === 'checkoutchamp') {
|
|
3557
|
+
this.restoreFromURL();
|
|
3558
|
+
}
|
|
3459
3559
|
// Initialize modules
|
|
3460
3560
|
this.identity = new IdentityManager();
|
|
3461
3561
|
this.session = new SessionManager(this.config.sessionTimeout);
|
|
@@ -3521,13 +3621,35 @@ var Datalyr = (function (exports) {
|
|
|
3521
3621
|
this.container = new ContainerManager({
|
|
3522
3622
|
workspaceId: this.config.workspaceId,
|
|
3523
3623
|
endpoint: this.config.endpoint,
|
|
3524
|
-
debug: this.config.debug
|
|
3624
|
+
debug: this.config.debug,
|
|
3625
|
+
// Lazy: invoked at the moment a third-party pixel inits, AFTER the
|
|
3626
|
+
// /container-scripts roundtrip resolves — so a pre-init identify()
|
|
3627
|
+
// already updated this.identity / this.userProperties. distinctId
|
|
3628
|
+
// mirrors what CAPI puts in external_id[] (user_id || anonymous_id);
|
|
3629
|
+
// email lets the Pixel match on em with the same hash CAPI sends.
|
|
3630
|
+
getIdentity: () => {
|
|
3631
|
+
var _a, _b;
|
|
3632
|
+
return ({
|
|
3633
|
+
externalId: (_a = this.identity) === null || _a === void 0 ? void 0 : _a.getDistinctId(),
|
|
3634
|
+
email: (_b = this.userProperties) === null || _b === void 0 ? void 0 : _b.email,
|
|
3635
|
+
});
|
|
3636
|
+
}
|
|
3525
3637
|
});
|
|
3526
3638
|
// Initialize container asynchronously
|
|
3527
3639
|
yield this.container.init().catch(error => {
|
|
3528
3640
|
this.log('Container initialization failed:', error);
|
|
3529
3641
|
});
|
|
3530
3642
|
}
|
|
3643
|
+
// autoIdentify default for the CC bridge:
|
|
3644
|
+
// - platform === 'checkoutchamp' (CC funnel pages) OR
|
|
3645
|
+
// - checkoutChampDomains set (Shopify storefronts feeding a CC funnel)
|
|
3646
|
+
// turn autoIdentify on unless the caller explicitly set it to false.
|
|
3647
|
+
// The 95% claim relies on email capture on BOTH ends of the bridge.
|
|
3648
|
+
const ccBridgeActive = this.config.platform === 'checkoutchamp'
|
|
3649
|
+
|| (Array.isArray(this.config.checkoutChampDomains) && this.config.checkoutChampDomains.length > 0);
|
|
3650
|
+
if (ccBridgeActive && this.config.autoIdentify === undefined) {
|
|
3651
|
+
this.config.autoIdentify = true;
|
|
3652
|
+
}
|
|
3531
3653
|
// Initialize auto-identify if explicitly enabled (opt-in)
|
|
3532
3654
|
if (this.config.autoIdentify === true) {
|
|
3533
3655
|
this.autoIdentify = new AutoIdentifyManager({
|
|
@@ -3560,6 +3682,12 @@ var Datalyr = (function (exports) {
|
|
|
3560
3682
|
this.log('Shopify cart attribute sync failed:', error);
|
|
3561
3683
|
});
|
|
3562
3684
|
}
|
|
3685
|
+
// Storefront → CC bridge: stamp _dl_* params on outbound links to the
|
|
3686
|
+
// configured CC domains so visitor_id + click signals cross the domain
|
|
3687
|
+
// boundary. Inert unless checkoutChampDomains is set.
|
|
3688
|
+
if (Array.isArray(this.config.checkoutChampDomains) && this.config.checkoutChampDomains.length > 0) {
|
|
3689
|
+
this.syncOutboundLinkParams(this.config.checkoutChampDomains);
|
|
3690
|
+
}
|
|
3563
3691
|
// Track initial page view if enabled (AFTER encryption ready)
|
|
3564
3692
|
if (this.config.trackPageViews) {
|
|
3565
3693
|
this.page();
|
|
@@ -4030,6 +4158,7 @@ var Datalyr = (function (exports) {
|
|
|
4030
4158
|
const visitorId = this.identity.getAnonymousId();
|
|
4031
4159
|
const fbc = this.cookies.get("_fbc") || attribution._fbc;
|
|
4032
4160
|
const fbp = this.cookies.get("_fbp") || attribution._fbp;
|
|
4161
|
+
const fbclidAt = this.cookies.get("_dl_fbclid_at");
|
|
4033
4162
|
if (visitorId)
|
|
4034
4163
|
attributes._datalyr_visitor_id = visitorId;
|
|
4035
4164
|
if (fbc)
|
|
@@ -4038,6 +4167,8 @@ var Datalyr = (function (exports) {
|
|
|
4038
4167
|
attributes._datalyr_fbp = String(fbp);
|
|
4039
4168
|
if (fbclid)
|
|
4040
4169
|
attributes._datalyr_fbclid = String(fbclid);
|
|
4170
|
+
if (fbclidAt)
|
|
4171
|
+
attributes._datalyr_fbclid_at = String(fbclidAt);
|
|
4041
4172
|
if (Object.keys(attributes).length === 0)
|
|
4042
4173
|
return;
|
|
4043
4174
|
try {
|
|
@@ -4055,6 +4186,218 @@ var Datalyr = (function (exports) {
|
|
|
4055
4186
|
}
|
|
4056
4187
|
});
|
|
4057
4188
|
}
|
|
4189
|
+
/**
|
|
4190
|
+
* Restore _dl_* URL bridge params on a Checkout Champ funnel page.
|
|
4191
|
+
* Runs BEFORE IdentityManager so the storefront's visitor_id wins over a
|
|
4192
|
+
* freshly auto-generated one. Also restores _fbc / _fbp cookies and the
|
|
4193
|
+
* fbclid click time so server-side rebuilds carry the real click moment.
|
|
4194
|
+
*
|
|
4195
|
+
* Strategy: stamp matching cookies (without clobbering pre-existing values),
|
|
4196
|
+
* then rewrite the URL so `_dl_fbclid` becomes `fbclid` — that way the rest
|
|
4197
|
+
* of the SDK's attribution layer (which reads `params.fbclid`) works
|
|
4198
|
+
* unchanged, and any merchant analytics also see the canonical click ID.
|
|
4199
|
+
*/
|
|
4200
|
+
restoreFromURL() {
|
|
4201
|
+
var _a;
|
|
4202
|
+
if (typeof window === "undefined" || typeof document === "undefined")
|
|
4203
|
+
return;
|
|
4204
|
+
try {
|
|
4205
|
+
const params = new URLSearchParams(window.location.search);
|
|
4206
|
+
const get = (k) => params.get(k);
|
|
4207
|
+
const vid = get("_dl_vid");
|
|
4208
|
+
const fbc = get("_dl_fbc");
|
|
4209
|
+
const fbp = get("_dl_fbp");
|
|
4210
|
+
const fbclid = get("_dl_fbclid");
|
|
4211
|
+
const fbclidAt = get("_dl_fbclid_at");
|
|
4212
|
+
const gclid = get("_dl_gclid");
|
|
4213
|
+
const gclidAt = get("_dl_gclid_at");
|
|
4214
|
+
// visitor_id: bridge wins. The whole point of `_dl_vid` is to unify the
|
|
4215
|
+
// storefront's session with the CC funnel session. A pre-existing local
|
|
4216
|
+
// cookie from a prior direct CC visit would silently sink the integration
|
|
4217
|
+
// (CC events stay on the local id; storefront events use the bridged id;
|
|
4218
|
+
// the two never link). Overwrite — orphaned local events are fine.
|
|
4219
|
+
if (vid)
|
|
4220
|
+
this.cookies.set("__dl_visitor_id", vid, 365);
|
|
4221
|
+
// Meta cookies + click-time cookies: existing wins. _fbc / _fbp may have
|
|
4222
|
+
// been written by Meta Pixel on the CC funnel page itself (more recent
|
|
4223
|
+
// than the bridged value); _dl_fbclid_at should record first-touch click
|
|
4224
|
+
// time per device, not get reset by a bridge from a new campaign.
|
|
4225
|
+
const setIfMissing = (name, value) => {
|
|
4226
|
+
if (!value)
|
|
4227
|
+
return;
|
|
4228
|
+
if (this.cookies.get(name))
|
|
4229
|
+
return;
|
|
4230
|
+
this.cookies.set(name, value, 365);
|
|
4231
|
+
};
|
|
4232
|
+
setIfMissing("_fbc", fbc);
|
|
4233
|
+
setIfMissing("_fbp", fbp);
|
|
4234
|
+
setIfMissing("_dl_fbclid_at", fbclidAt);
|
|
4235
|
+
setIfMissing("_dl_gclid_at", gclidAt);
|
|
4236
|
+
// Rewrite URL: _dl_fbclid → fbclid (etc.) so captureAttribution() picks
|
|
4237
|
+
// them up via its existing `params.fbclid` path. Strip the _dl_* params
|
|
4238
|
+
// either way so they don't leak into downstream analytics URLs.
|
|
4239
|
+
let rewrote = false;
|
|
4240
|
+
const mappings = [
|
|
4241
|
+
["fbclid", fbclid],
|
|
4242
|
+
["gclid", gclid]
|
|
4243
|
+
];
|
|
4244
|
+
for (const [canonical, value] of mappings) {
|
|
4245
|
+
if (value && !params.get(canonical)) {
|
|
4246
|
+
params.set(canonical, value);
|
|
4247
|
+
rewrote = true;
|
|
4248
|
+
}
|
|
4249
|
+
}
|
|
4250
|
+
for (const k of ["_dl_vid", "_dl_fbc", "_dl_fbp", "_dl_fbclid", "_dl_fbclid_at", "_dl_gclid", "_dl_gclid_at"]) {
|
|
4251
|
+
if (params.has(k)) {
|
|
4252
|
+
params.delete(k);
|
|
4253
|
+
rewrote = true;
|
|
4254
|
+
}
|
|
4255
|
+
}
|
|
4256
|
+
if (rewrote && typeof ((_a = window.history) === null || _a === void 0 ? void 0 : _a.replaceState) === "function") {
|
|
4257
|
+
const newSearch = params.toString();
|
|
4258
|
+
const newUrl = window.location.pathname +
|
|
4259
|
+
(newSearch ? "?" + newSearch : "") +
|
|
4260
|
+
window.location.hash;
|
|
4261
|
+
window.history.replaceState(window.history.state, "", newUrl);
|
|
4262
|
+
}
|
|
4263
|
+
this.log("Checkout Champ bridge restored:", {
|
|
4264
|
+
had_vid: !!vid,
|
|
4265
|
+
had_fbc: !!fbc,
|
|
4266
|
+
had_fbp: !!fbp,
|
|
4267
|
+
had_fbclid: !!fbclid,
|
|
4268
|
+
had_gclid: !!gclid
|
|
4269
|
+
});
|
|
4270
|
+
}
|
|
4271
|
+
catch (error) {
|
|
4272
|
+
// Don't let bridge restoration block init — fall through to normal SDK
|
|
4273
|
+
// behavior (a fresh visitor_id, no restored click signals).
|
|
4274
|
+
this.log("restoreFromURL failed:", error);
|
|
4275
|
+
}
|
|
4276
|
+
}
|
|
4277
|
+
/**
|
|
4278
|
+
* Storefront → Checkout Champ link stamping. Finds every `<a href>` whose host
|
|
4279
|
+
* matches the configured CC domain list and appends `?_dl_vid=…&_dl_fbc=…&
|
|
4280
|
+
* _dl_fbp=…&_dl_fbclid=…&_dl_fbclid_at=…&_dl_gclid=…` so the user's
|
|
4281
|
+
* visitor_id + Meta click signals cross the domain. MutationObserver watches
|
|
4282
|
+
* for dynamically-injected links. On click, force-flush the event queue so
|
|
4283
|
+
* any pending track() events land before the browser navigates away.
|
|
4284
|
+
*/
|
|
4285
|
+
syncOutboundLinkParams(domains) {
|
|
4286
|
+
if (typeof window === "undefined" || typeof document === "undefined")
|
|
4287
|
+
return;
|
|
4288
|
+
const lowerDomains = domains.map((d) => d.toLowerCase());
|
|
4289
|
+
const matchesCcDomain = (href) => {
|
|
4290
|
+
try {
|
|
4291
|
+
const host = new URL(href, window.location.href).hostname.toLowerCase();
|
|
4292
|
+
return lowerDomains.some((d) => host === d || host.endsWith("." + d));
|
|
4293
|
+
}
|
|
4294
|
+
catch (_a) {
|
|
4295
|
+
return false;
|
|
4296
|
+
}
|
|
4297
|
+
};
|
|
4298
|
+
const buildBridgeParams = () => {
|
|
4299
|
+
const attribution = this.attribution.getAttributionData();
|
|
4300
|
+
const out = {};
|
|
4301
|
+
const vid = this.identity.getAnonymousId();
|
|
4302
|
+
const fbc = this.cookies.get("_fbc") || attribution._fbc;
|
|
4303
|
+
const fbp = this.cookies.get("_fbp") || attribution._fbp;
|
|
4304
|
+
const fbclid = attribution.clickIdType === "fbclid" ? attribution.clickId : null;
|
|
4305
|
+
const fbclidAt = this.cookies.get("_dl_fbclid_at");
|
|
4306
|
+
const gclid = attribution.clickIdType === "gclid" ? attribution.clickId : null;
|
|
4307
|
+
const gclidAt = this.cookies.get("_dl_gclid_at");
|
|
4308
|
+
if (vid)
|
|
4309
|
+
out._dl_vid = vid;
|
|
4310
|
+
if (fbc)
|
|
4311
|
+
out._dl_fbc = String(fbc);
|
|
4312
|
+
if (fbp)
|
|
4313
|
+
out._dl_fbp = String(fbp);
|
|
4314
|
+
if (fbclid)
|
|
4315
|
+
out._dl_fbclid = String(fbclid);
|
|
4316
|
+
if (fbclidAt)
|
|
4317
|
+
out._dl_fbclid_at = String(fbclidAt);
|
|
4318
|
+
if (gclid)
|
|
4319
|
+
out._dl_gclid = String(gclid);
|
|
4320
|
+
if (gclidAt)
|
|
4321
|
+
out._dl_gclid_at = String(gclidAt);
|
|
4322
|
+
return out;
|
|
4323
|
+
};
|
|
4324
|
+
const stampLink = (anchor) => {
|
|
4325
|
+
if (!anchor.href || !matchesCcDomain(anchor.href))
|
|
4326
|
+
return;
|
|
4327
|
+
try {
|
|
4328
|
+
const u = new URL(anchor.href, window.location.href);
|
|
4329
|
+
const bridge = buildBridgeParams();
|
|
4330
|
+
let mutated = false;
|
|
4331
|
+
for (const [k, v] of Object.entries(bridge)) {
|
|
4332
|
+
if (!u.searchParams.get(k)) {
|
|
4333
|
+
u.searchParams.set(k, v);
|
|
4334
|
+
mutated = true;
|
|
4335
|
+
}
|
|
4336
|
+
}
|
|
4337
|
+
if (mutated)
|
|
4338
|
+
anchor.href = u.toString();
|
|
4339
|
+
}
|
|
4340
|
+
catch (_a) {
|
|
4341
|
+
// Ignore malformed URLs — don't break the page.
|
|
4342
|
+
}
|
|
4343
|
+
};
|
|
4344
|
+
const stampAll = () => {
|
|
4345
|
+
document.querySelectorAll("a[href]").forEach((el) => stampLink(el));
|
|
4346
|
+
};
|
|
4347
|
+
const onClick = (e) => {
|
|
4348
|
+
var _a, _b;
|
|
4349
|
+
const target = (_a = e.target) === null || _a === void 0 ? void 0 : _a.closest("a[href]");
|
|
4350
|
+
if (!target)
|
|
4351
|
+
return;
|
|
4352
|
+
const anchor = target;
|
|
4353
|
+
if (!matchesCcDomain(anchor.href))
|
|
4354
|
+
return;
|
|
4355
|
+
stampLink(anchor); // re-stamp in case attribution changed since DOMReady
|
|
4356
|
+
try {
|
|
4357
|
+
(_b = this.queue) === null || _b === void 0 ? void 0 : _b.flush();
|
|
4358
|
+
}
|
|
4359
|
+
catch (_c) {
|
|
4360
|
+
// Best-effort — never block navigation.
|
|
4361
|
+
}
|
|
4362
|
+
};
|
|
4363
|
+
stampAll();
|
|
4364
|
+
document.addEventListener("click", onClick, true);
|
|
4365
|
+
try {
|
|
4366
|
+
// Debounce re-stamping. A busy SPA (infinite scroll, animations,
|
|
4367
|
+
// React/Vue updates) can fire thousands of mutations per second; a naive
|
|
4368
|
+
// re-stamp on every notification would burn CPU pointlessly when
|
|
4369
|
+
// outbound link sets only change occasionally. 150ms is small enough to
|
|
4370
|
+
// catch links before the user can click them, large enough to coalesce
|
|
4371
|
+
// bursts.
|
|
4372
|
+
let restampTimer = null;
|
|
4373
|
+
const scheduleRestamp = () => {
|
|
4374
|
+
if (restampTimer != null)
|
|
4375
|
+
return;
|
|
4376
|
+
restampTimer = setTimeout(() => {
|
|
4377
|
+
restampTimer = null;
|
|
4378
|
+
stampAll();
|
|
4379
|
+
}, 150);
|
|
4380
|
+
};
|
|
4381
|
+
const observer = new MutationObserver(scheduleRestamp);
|
|
4382
|
+
observer.observe(document.documentElement, { childList: true, subtree: true });
|
|
4383
|
+
// Observer + click listener live for the session; pagehide cleans up on
|
|
4384
|
+
// full-page unload. SPA route changes are fine — we WANT stamping to keep
|
|
4385
|
+
// working across virtual navigations.
|
|
4386
|
+
window.addEventListener("pagehide", () => {
|
|
4387
|
+
try {
|
|
4388
|
+
observer.disconnect();
|
|
4389
|
+
}
|
|
4390
|
+
catch ( /* idempotent */_a) { /* idempotent */ }
|
|
4391
|
+
if (restampTimer != null)
|
|
4392
|
+
clearTimeout(restampTimer);
|
|
4393
|
+
document.removeEventListener("click", onClick, true);
|
|
4394
|
+
}, { once: true });
|
|
4395
|
+
}
|
|
4396
|
+
catch (error) {
|
|
4397
|
+
this.log("MutationObserver setup failed (CC link sync):", error);
|
|
4398
|
+
}
|
|
4399
|
+
this.log("Checkout Champ outbound-link sync active for:", lowerDomains);
|
|
4400
|
+
}
|
|
4058
4401
|
/**
|
|
4059
4402
|
* Create event payload
|
|
4060
4403
|
*/
|
|
@@ -4115,7 +4458,7 @@ var Datalyr = (function (exports) {
|
|
|
4115
4458
|
resolution_method: 'browser_sdk',
|
|
4116
4459
|
resolution_confidence: 1.0,
|
|
4117
4460
|
// SDK metadata (keep in sync with package.json version)
|
|
4118
|
-
sdk_version: '1.6.
|
|
4461
|
+
sdk_version: '1.6.4',
|
|
4119
4462
|
sdk_name: 'datalyr-web-sdk'
|
|
4120
4463
|
};
|
|
4121
4464
|
return payload;
|