@datalyr/web 1.6.3 → 1.6.5
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/datalyr.cjs.js +363 -4
- package/dist/datalyr.cjs.js.map +1 -1
- package/dist/datalyr.esm.js +363 -4
- 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 +363 -4
- 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 +55 -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/package.json +1 -1
package/dist/datalyr.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @datalyr/web v1.6.
|
|
2
|
+
* @datalyr/web v1.6.5
|
|
3
3
|
* Datalyr Web SDK - Modern attribution tracking for web applications
|
|
4
4
|
* (c) 2026 Datalyr Inc.
|
|
5
5
|
* Released under the MIT License
|
|
@@ -1524,6 +1524,7 @@ class AttributionManager {
|
|
|
1524
1524
|
* Capture advertising platform cookies
|
|
1525
1525
|
*/
|
|
1526
1526
|
captureAdCookies() {
|
|
1527
|
+
var _a, _b;
|
|
1527
1528
|
const adCookies = {};
|
|
1528
1529
|
// Facebook/Meta cookies
|
|
1529
1530
|
adCookies._fbp = cookies.get('_fbp');
|
|
@@ -1548,13 +1549,29 @@ class AttributionManager {
|
|
|
1548
1549
|
// Optionally set the cookie for future use
|
|
1549
1550
|
cookies.set('_fbp', adCookies._fbp, 90);
|
|
1550
1551
|
}
|
|
1552
|
+
// Persist the click time the FIRST time we see fbclid/gclid in URL so we can
|
|
1553
|
+
// rebuild fbc (and stamp real click time on server-side events) later even
|
|
1554
|
+
// when _fbc/_gclid cookies get evicted. Once-only: never overwrite.
|
|
1555
|
+
const fbclid = this.getCurrentFbclid();
|
|
1556
|
+
if (fbclid && !cookies.get('_dl_fbclid_at')) {
|
|
1557
|
+
cookies.set('_dl_fbclid_at', String(Date.now()), 90);
|
|
1558
|
+
}
|
|
1559
|
+
const gclid = this.hasClickId('gclid') ? ((_b = (_a = this.queryParamsCache) === null || _a === void 0 ? void 0 : _a.gclid) !== null && _b !== void 0 ? _b : null) : null;
|
|
1560
|
+
if (gclid && !cookies.get('_dl_gclid_at')) {
|
|
1561
|
+
cookies.set('_dl_gclid_at', String(Date.now()), 90);
|
|
1562
|
+
}
|
|
1551
1563
|
// Generate _fbc if we have fbclid but no _fbc.
|
|
1552
1564
|
// Meta's fbc format is `fb.{subdomainIndex}.{creationTime}.{fbclid}` where
|
|
1553
1565
|
// creationTime is UNIX time in MILLISECONDS (matches the _fbp generation above
|
|
1554
1566
|
// and the real _fbc cookie the Meta Pixel writes). Do NOT use seconds here.
|
|
1555
|
-
const fbclid = this.getCurrentFbclid();
|
|
1556
1567
|
if (fbclid && !adCookies._fbc) {
|
|
1557
|
-
|
|
1568
|
+
// Prefer the persisted click time when valid; fall back to now if the
|
|
1569
|
+
// cookie is missing or corrupted (e.g. user edited it to garbage).
|
|
1570
|
+
// Without this guard, Number("abc") = NaN and Meta would reject
|
|
1571
|
+
// `fb.1.NaN.{fbclid}`.
|
|
1572
|
+
const stored = cookies.get('_dl_fbclid_at');
|
|
1573
|
+
const parsed = stored ? Number(stored) : NaN;
|
|
1574
|
+
const timestamp = Number.isFinite(parsed) && parsed > 0 ? parsed : Date.now();
|
|
1558
1575
|
adCookies._fbc = `fb.1.${timestamp}.${fbclid}`;
|
|
1559
1576
|
// Optionally set the cookie for future use
|
|
1560
1577
|
cookies.set('_fbc', adCookies._fbc, 90);
|
|
@@ -3511,6 +3528,14 @@ class Datalyr {
|
|
|
3511
3528
|
}
|
|
3512
3529
|
// Set default config values
|
|
3513
3530
|
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);
|
|
3531
|
+
// platform:'shopify' implies cart-attribute sync: stamp visitor_id + Meta
|
|
3532
|
+
// click signals into note_attributes so server-side order webhooks can
|
|
3533
|
+
// attribute guest checkouts. Lets the Shopify theme app-embed enable it via
|
|
3534
|
+
// data-platform="shopify" alone. `=== undefined` so an explicit
|
|
3535
|
+
// shopifyCartAttributes:false still wins.
|
|
3536
|
+
if (this.config.platform === 'shopify' && this.config.shopifyCartAttributes === undefined) {
|
|
3537
|
+
this.config.shopifyCartAttributes = true;
|
|
3538
|
+
}
|
|
3514
3539
|
// Initialize cookie storage with config
|
|
3515
3540
|
this.cookies = new CookieStorage({
|
|
3516
3541
|
domain: this.config.cookieDomain,
|
|
@@ -3522,6 +3547,12 @@ class Datalyr {
|
|
|
3522
3547
|
storage.migrateFromLegacyPrefix();
|
|
3523
3548
|
// Check opt-out AFTER cookies configured (Issue #14)
|
|
3524
3549
|
this.optedOut = this.cookies.get('__dl_opt_out') === 'true';
|
|
3550
|
+
// CC funnel page entry: restore the _dl_* URL bridge BEFORE IdentityManager
|
|
3551
|
+
// initializes so the storefront's visitor_id is the one used here (instead
|
|
3552
|
+
// of auto-generating a fresh one on this domain).
|
|
3553
|
+
if (this.config.platform === 'checkoutchamp') {
|
|
3554
|
+
this.restoreFromURL();
|
|
3555
|
+
}
|
|
3525
3556
|
// Initialize modules
|
|
3526
3557
|
this.identity = new IdentityManager();
|
|
3527
3558
|
this.session = new SessionManager(this.config.sessionTimeout);
|
|
@@ -3605,6 +3636,26 @@ class Datalyr {
|
|
|
3605
3636
|
yield this.container.init().catch(error => {
|
|
3606
3637
|
this.log('Container initialization failed:', error);
|
|
3607
3638
|
});
|
|
3639
|
+
// Checkout Champ thank-you/upsell page: co-fire the browser Meta Pixel
|
|
3640
|
+
// Purchase with the SAME deterministic event_id the CC webhook stamps
|
|
3641
|
+
// server-side, so Meta dedupes the browser event against the server-side
|
|
3642
|
+
// CAPI event (dedup = event_name + event_id). Pixel-only — the CC Export
|
|
3643
|
+
// Profile webhook owns the server event + CAPI postback. No-op anywhere
|
|
3644
|
+
// except a post-purchase page (keyed on CC's sessionStorage orderData).
|
|
3645
|
+
// Must run AFTER container.init() so fbq is loaded + init'd.
|
|
3646
|
+
if (this.config.platform === 'checkoutchamp') {
|
|
3647
|
+
this.fireCheckoutChampPurchasePixel();
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
// autoIdentify default for the CC bridge:
|
|
3651
|
+
// - platform === 'checkoutchamp' (CC funnel pages) OR
|
|
3652
|
+
// - checkoutChampDomains set (Shopify storefronts feeding a CC funnel)
|
|
3653
|
+
// turn autoIdentify on unless the caller explicitly set it to false.
|
|
3654
|
+
// The 95% claim relies on email capture on BOTH ends of the bridge.
|
|
3655
|
+
const ccBridgeActive = this.config.platform === 'checkoutchamp'
|
|
3656
|
+
|| (Array.isArray(this.config.checkoutChampDomains) && this.config.checkoutChampDomains.length > 0);
|
|
3657
|
+
if (ccBridgeActive && this.config.autoIdentify === undefined) {
|
|
3658
|
+
this.config.autoIdentify = true;
|
|
3608
3659
|
}
|
|
3609
3660
|
// Initialize auto-identify if explicitly enabled (opt-in)
|
|
3610
3661
|
if (this.config.autoIdentify === true) {
|
|
@@ -3638,6 +3689,12 @@ class Datalyr {
|
|
|
3638
3689
|
this.log('Shopify cart attribute sync failed:', error);
|
|
3639
3690
|
});
|
|
3640
3691
|
}
|
|
3692
|
+
// Storefront → CC bridge: stamp _dl_* params on outbound links to the
|
|
3693
|
+
// configured CC domains so visitor_id + click signals cross the domain
|
|
3694
|
+
// boundary. Inert unless checkoutChampDomains is set.
|
|
3695
|
+
if (Array.isArray(this.config.checkoutChampDomains) && this.config.checkoutChampDomains.length > 0) {
|
|
3696
|
+
this.syncOutboundLinkParams(this.config.checkoutChampDomains);
|
|
3697
|
+
}
|
|
3641
3698
|
// Track initial page view if enabled (AFTER encryption ready)
|
|
3642
3699
|
if (this.config.trackPageViews) {
|
|
3643
3700
|
this.page();
|
|
@@ -4108,6 +4165,7 @@ class Datalyr {
|
|
|
4108
4165
|
const visitorId = this.identity.getAnonymousId();
|
|
4109
4166
|
const fbc = this.cookies.get("_fbc") || attribution._fbc;
|
|
4110
4167
|
const fbp = this.cookies.get("_fbp") || attribution._fbp;
|
|
4168
|
+
const fbclidAt = this.cookies.get("_dl_fbclid_at");
|
|
4111
4169
|
if (visitorId)
|
|
4112
4170
|
attributes._datalyr_visitor_id = visitorId;
|
|
4113
4171
|
if (fbc)
|
|
@@ -4116,6 +4174,8 @@ class Datalyr {
|
|
|
4116
4174
|
attributes._datalyr_fbp = String(fbp);
|
|
4117
4175
|
if (fbclid)
|
|
4118
4176
|
attributes._datalyr_fbclid = String(fbclid);
|
|
4177
|
+
if (fbclidAt)
|
|
4178
|
+
attributes._datalyr_fbclid_at = String(fbclidAt);
|
|
4119
4179
|
if (Object.keys(attributes).length === 0)
|
|
4120
4180
|
return;
|
|
4121
4181
|
try {
|
|
@@ -4133,6 +4193,305 @@ class Datalyr {
|
|
|
4133
4193
|
}
|
|
4134
4194
|
});
|
|
4135
4195
|
}
|
|
4196
|
+
/**
|
|
4197
|
+
* Restore _dl_* URL bridge params on a Checkout Champ funnel page.
|
|
4198
|
+
* Runs BEFORE IdentityManager so the storefront's visitor_id wins over a
|
|
4199
|
+
* freshly auto-generated one. Also restores _fbc / _fbp cookies and the
|
|
4200
|
+
* fbclid click time so server-side rebuilds carry the real click moment.
|
|
4201
|
+
*
|
|
4202
|
+
* Strategy: stamp matching cookies (without clobbering pre-existing values),
|
|
4203
|
+
* then rewrite the URL so `_dl_fbclid` becomes `fbclid` — that way the rest
|
|
4204
|
+
* of the SDK's attribution layer (which reads `params.fbclid`) works
|
|
4205
|
+
* unchanged, and any merchant analytics also see the canonical click ID.
|
|
4206
|
+
*/
|
|
4207
|
+
restoreFromURL() {
|
|
4208
|
+
var _a;
|
|
4209
|
+
if (typeof window === "undefined" || typeof document === "undefined")
|
|
4210
|
+
return;
|
|
4211
|
+
try {
|
|
4212
|
+
const params = new URLSearchParams(window.location.search);
|
|
4213
|
+
const get = (k) => params.get(k);
|
|
4214
|
+
const vid = get("_dl_vid");
|
|
4215
|
+
const fbc = get("_dl_fbc");
|
|
4216
|
+
const fbp = get("_dl_fbp");
|
|
4217
|
+
const fbclid = get("_dl_fbclid");
|
|
4218
|
+
const fbclidAt = get("_dl_fbclid_at");
|
|
4219
|
+
const gclid = get("_dl_gclid");
|
|
4220
|
+
const gclidAt = get("_dl_gclid_at");
|
|
4221
|
+
// visitor_id: bridge wins. The whole point of `_dl_vid` is to unify the
|
|
4222
|
+
// storefront's session with the CC funnel session. A pre-existing local
|
|
4223
|
+
// cookie from a prior direct CC visit would silently sink the integration
|
|
4224
|
+
// (CC events stay on the local id; storefront events use the bridged id;
|
|
4225
|
+
// the two never link). Overwrite — orphaned local events are fine.
|
|
4226
|
+
if (vid)
|
|
4227
|
+
this.cookies.set("__dl_visitor_id", vid, 365);
|
|
4228
|
+
// Meta cookies + click-time cookies: existing wins. _fbc / _fbp may have
|
|
4229
|
+
// been written by Meta Pixel on the CC funnel page itself (more recent
|
|
4230
|
+
// than the bridged value); _dl_fbclid_at should record first-touch click
|
|
4231
|
+
// time per device, not get reset by a bridge from a new campaign.
|
|
4232
|
+
const setIfMissing = (name, value) => {
|
|
4233
|
+
if (!value)
|
|
4234
|
+
return;
|
|
4235
|
+
if (this.cookies.get(name))
|
|
4236
|
+
return;
|
|
4237
|
+
this.cookies.set(name, value, 365);
|
|
4238
|
+
};
|
|
4239
|
+
setIfMissing("_fbc", fbc);
|
|
4240
|
+
setIfMissing("_fbp", fbp);
|
|
4241
|
+
setIfMissing("_dl_fbclid_at", fbclidAt);
|
|
4242
|
+
setIfMissing("_dl_gclid_at", gclidAt);
|
|
4243
|
+
// Rewrite URL: _dl_fbclid → fbclid (etc.) so captureAttribution() picks
|
|
4244
|
+
// them up via its existing `params.fbclid` path. Strip the _dl_* params
|
|
4245
|
+
// either way so they don't leak into downstream analytics URLs.
|
|
4246
|
+
let rewrote = false;
|
|
4247
|
+
const mappings = [
|
|
4248
|
+
["fbclid", fbclid],
|
|
4249
|
+
["gclid", gclid]
|
|
4250
|
+
];
|
|
4251
|
+
for (const [canonical, value] of mappings) {
|
|
4252
|
+
if (value && !params.get(canonical)) {
|
|
4253
|
+
params.set(canonical, value);
|
|
4254
|
+
rewrote = true;
|
|
4255
|
+
}
|
|
4256
|
+
}
|
|
4257
|
+
for (const k of ["_dl_vid", "_dl_fbc", "_dl_fbp", "_dl_fbclid", "_dl_fbclid_at", "_dl_gclid", "_dl_gclid_at"]) {
|
|
4258
|
+
if (params.has(k)) {
|
|
4259
|
+
params.delete(k);
|
|
4260
|
+
rewrote = true;
|
|
4261
|
+
}
|
|
4262
|
+
}
|
|
4263
|
+
if (rewrote && typeof ((_a = window.history) === null || _a === void 0 ? void 0 : _a.replaceState) === "function") {
|
|
4264
|
+
const newSearch = params.toString();
|
|
4265
|
+
const newUrl = window.location.pathname +
|
|
4266
|
+
(newSearch ? "?" + newSearch : "") +
|
|
4267
|
+
window.location.hash;
|
|
4268
|
+
window.history.replaceState(window.history.state, "", newUrl);
|
|
4269
|
+
}
|
|
4270
|
+
this.log("Checkout Champ bridge restored:", {
|
|
4271
|
+
had_vid: !!vid,
|
|
4272
|
+
had_fbc: !!fbc,
|
|
4273
|
+
had_fbp: !!fbp,
|
|
4274
|
+
had_fbclid: !!fbclid,
|
|
4275
|
+
had_gclid: !!gclid
|
|
4276
|
+
});
|
|
4277
|
+
}
|
|
4278
|
+
catch (error) {
|
|
4279
|
+
// Don't let bridge restoration block init — fall through to normal SDK
|
|
4280
|
+
// behavior (a fresh visitor_id, no restored click signals).
|
|
4281
|
+
this.log("restoreFromURL failed:", error);
|
|
4282
|
+
}
|
|
4283
|
+
}
|
|
4284
|
+
/**
|
|
4285
|
+
* Checkout Champ Meta Pixel ⇄ CAPI deduplication.
|
|
4286
|
+
*
|
|
4287
|
+
* On a CC thank-you / upsell page, fire the BROWSER Meta Pixel Purchase with the
|
|
4288
|
+
* exact same event_id the CC webhook stamps server-side, so Meta collapses the
|
|
4289
|
+
* browser Pixel event and the server-side CAPI event into one (dedup key =
|
|
4290
|
+
* event_name + event_id). This gives the EMQ lift of a matched browser+server
|
|
4291
|
+
* event without double-counting conversions in Ads Manager.
|
|
4292
|
+
*
|
|
4293
|
+
* PIXEL-ONLY by design. We do NOT enqueue a server event here: the CC Export
|
|
4294
|
+
* Profile webhook (webhooks/platforms/checkoutchamp.js) already ingests the
|
|
4295
|
+
* purchase and fires CAPI. Calling track() here would create a second server
|
|
4296
|
+
* event (source='web') AND double-fire CAPI — defeating the whole point.
|
|
4297
|
+
*
|
|
4298
|
+
* The event_id MUST stay byte-identical to the server formula:
|
|
4299
|
+
* webhooks/platforms/checkoutchamp.js:250
|
|
4300
|
+
* generateEventId('checkoutchamp', `${event_type}_${order_id}`)
|
|
4301
|
+
* webhooks/core/ingest.js:122 → `${platform}_${webhookEventId}`
|
|
4302
|
+
* ⇒ `checkoutchamp_purchase_<order_id>`
|
|
4303
|
+
* where <order_id> is the value CC posts to the webhook via its [orderId] macro.
|
|
4304
|
+
* We read the browser-side counterpart from CC's own client-side order object:
|
|
4305
|
+
* JSON.parse(sessionStorage.getItem('orderData')).orderId
|
|
4306
|
+
* (CC docs: referenced in custom scripts as `orderDataTmp.orderId`).
|
|
4307
|
+
*
|
|
4308
|
+
* ASSUMPTION TO VERIFY ON A REAL CC TEST ORDER: that sessionStorage
|
|
4309
|
+
* orderData.orderId === the [orderId] CC sends to the postback. If a merchant's
|
|
4310
|
+
* CC plan exposes a different id client-side, the two event_ids won't match and
|
|
4311
|
+
* Meta will show duplicates — caught by the test order in the setup checklist.
|
|
4312
|
+
*
|
|
4313
|
+
* v1 scope: the primary order only. Per-upsell Pixel dedup (each upsell is its
|
|
4314
|
+
* own order_id + parent_order_id server-side) needs the upsell sessionStorage
|
|
4315
|
+
* shape confirmed on a live funnel first — tracked as a follow-up.
|
|
4316
|
+
*/
|
|
4317
|
+
fireCheckoutChampPurchasePixel() {
|
|
4318
|
+
var _a;
|
|
4319
|
+
if (typeof window === "undefined")
|
|
4320
|
+
return;
|
|
4321
|
+
try {
|
|
4322
|
+
// Needs the container (where the Meta Pixel lives). trackToPixels itself
|
|
4323
|
+
// no-ops unless a Meta pixel is enabled + fbq is present, so an
|
|
4324
|
+
// unconfigured workspace silently does nothing.
|
|
4325
|
+
if (!this.container)
|
|
4326
|
+
return;
|
|
4327
|
+
const raw = (_a = window.sessionStorage) === null || _a === void 0 ? void 0 : _a.getItem("orderData");
|
|
4328
|
+
if (!raw)
|
|
4329
|
+
return; // not a post-purchase page — nothing to dedupe
|
|
4330
|
+
const order = JSON.parse(raw) || {};
|
|
4331
|
+
const orderId = order.orderId;
|
|
4332
|
+
if (!orderId)
|
|
4333
|
+
return;
|
|
4334
|
+
// orderData persists across upsell page loads — fire the primary Purchase
|
|
4335
|
+
// Pixel once per order. (Meta also dedupes by event_id over 48h, so this is
|
|
4336
|
+
// belt-and-suspenders against a per-page re-fire.)
|
|
4337
|
+
const guardKey = `__dl_cc_purchase_${orderId}`;
|
|
4338
|
+
if (window.sessionStorage.getItem(guardKey))
|
|
4339
|
+
return;
|
|
4340
|
+
window.sessionStorage.setItem(guardKey, "1");
|
|
4341
|
+
// KEEP IN SYNC with the server formula above.
|
|
4342
|
+
const eventId = `checkoutchamp_purchase_${orderId}`;
|
|
4343
|
+
// value/currency are for EMQ quality only — Meta dedup is event_id+name, so
|
|
4344
|
+
// a mismatch here never breaks dedup. Best-effort parse of CC's fields.
|
|
4345
|
+
const value = Number(order.totalAmount);
|
|
4346
|
+
const properties = {
|
|
4347
|
+
order_id: String(orderId),
|
|
4348
|
+
content_type: "product",
|
|
4349
|
+
};
|
|
4350
|
+
if (Number.isFinite(value))
|
|
4351
|
+
properties.value = value;
|
|
4352
|
+
const currency = order.currencyCode || order.currency;
|
|
4353
|
+
if (currency)
|
|
4354
|
+
properties.currency = String(currency);
|
|
4355
|
+
if (order.productId)
|
|
4356
|
+
properties.content_ids = [String(order.productId)];
|
|
4357
|
+
// Pixel-only co-fire. 'purchase' maps to Meta 'Purchase' in trackToPixels,
|
|
4358
|
+
// matching the server-side rule's platform_event_name.
|
|
4359
|
+
this.container.trackToPixels("purchase", properties, eventId);
|
|
4360
|
+
this.log("Checkout Champ Purchase Pixel co-fired (CAPI dedup):", {
|
|
4361
|
+
eventId,
|
|
4362
|
+
value: properties.value,
|
|
4363
|
+
currency: properties.currency,
|
|
4364
|
+
});
|
|
4365
|
+
}
|
|
4366
|
+
catch (error) {
|
|
4367
|
+
// Never let dedup co-fire break the page or init.
|
|
4368
|
+
this.log("fireCheckoutChampPurchasePixel failed:", error);
|
|
4369
|
+
}
|
|
4370
|
+
}
|
|
4371
|
+
/**
|
|
4372
|
+
* Storefront → Checkout Champ link stamping. Finds every `<a href>` whose host
|
|
4373
|
+
* matches the configured CC domain list and appends `?_dl_vid=…&_dl_fbc=…&
|
|
4374
|
+
* _dl_fbp=…&_dl_fbclid=…&_dl_fbclid_at=…&_dl_gclid=…` so the user's
|
|
4375
|
+
* visitor_id + Meta click signals cross the domain. MutationObserver watches
|
|
4376
|
+
* for dynamically-injected links. On click, force-flush the event queue so
|
|
4377
|
+
* any pending track() events land before the browser navigates away.
|
|
4378
|
+
*/
|
|
4379
|
+
syncOutboundLinkParams(domains) {
|
|
4380
|
+
if (typeof window === "undefined" || typeof document === "undefined")
|
|
4381
|
+
return;
|
|
4382
|
+
const lowerDomains = domains.map((d) => d.toLowerCase());
|
|
4383
|
+
const matchesCcDomain = (href) => {
|
|
4384
|
+
try {
|
|
4385
|
+
const host = new URL(href, window.location.href).hostname.toLowerCase();
|
|
4386
|
+
return lowerDomains.some((d) => host === d || host.endsWith("." + d));
|
|
4387
|
+
}
|
|
4388
|
+
catch (_a) {
|
|
4389
|
+
return false;
|
|
4390
|
+
}
|
|
4391
|
+
};
|
|
4392
|
+
const buildBridgeParams = () => {
|
|
4393
|
+
const attribution = this.attribution.getAttributionData();
|
|
4394
|
+
const out = {};
|
|
4395
|
+
const vid = this.identity.getAnonymousId();
|
|
4396
|
+
const fbc = this.cookies.get("_fbc") || attribution._fbc;
|
|
4397
|
+
const fbp = this.cookies.get("_fbp") || attribution._fbp;
|
|
4398
|
+
const fbclid = attribution.clickIdType === "fbclid" ? attribution.clickId : null;
|
|
4399
|
+
const fbclidAt = this.cookies.get("_dl_fbclid_at");
|
|
4400
|
+
const gclid = attribution.clickIdType === "gclid" ? attribution.clickId : null;
|
|
4401
|
+
const gclidAt = this.cookies.get("_dl_gclid_at");
|
|
4402
|
+
if (vid)
|
|
4403
|
+
out._dl_vid = vid;
|
|
4404
|
+
if (fbc)
|
|
4405
|
+
out._dl_fbc = String(fbc);
|
|
4406
|
+
if (fbp)
|
|
4407
|
+
out._dl_fbp = String(fbp);
|
|
4408
|
+
if (fbclid)
|
|
4409
|
+
out._dl_fbclid = String(fbclid);
|
|
4410
|
+
if (fbclidAt)
|
|
4411
|
+
out._dl_fbclid_at = String(fbclidAt);
|
|
4412
|
+
if (gclid)
|
|
4413
|
+
out._dl_gclid = String(gclid);
|
|
4414
|
+
if (gclidAt)
|
|
4415
|
+
out._dl_gclid_at = String(gclidAt);
|
|
4416
|
+
return out;
|
|
4417
|
+
};
|
|
4418
|
+
const stampLink = (anchor) => {
|
|
4419
|
+
if (!anchor.href || !matchesCcDomain(anchor.href))
|
|
4420
|
+
return;
|
|
4421
|
+
try {
|
|
4422
|
+
const u = new URL(anchor.href, window.location.href);
|
|
4423
|
+
const bridge = buildBridgeParams();
|
|
4424
|
+
let mutated = false;
|
|
4425
|
+
for (const [k, v] of Object.entries(bridge)) {
|
|
4426
|
+
if (!u.searchParams.get(k)) {
|
|
4427
|
+
u.searchParams.set(k, v);
|
|
4428
|
+
mutated = true;
|
|
4429
|
+
}
|
|
4430
|
+
}
|
|
4431
|
+
if (mutated)
|
|
4432
|
+
anchor.href = u.toString();
|
|
4433
|
+
}
|
|
4434
|
+
catch (_a) {
|
|
4435
|
+
// Ignore malformed URLs — don't break the page.
|
|
4436
|
+
}
|
|
4437
|
+
};
|
|
4438
|
+
const stampAll = () => {
|
|
4439
|
+
document.querySelectorAll("a[href]").forEach((el) => stampLink(el));
|
|
4440
|
+
};
|
|
4441
|
+
const onClick = (e) => {
|
|
4442
|
+
var _a, _b;
|
|
4443
|
+
const target = (_a = e.target) === null || _a === void 0 ? void 0 : _a.closest("a[href]");
|
|
4444
|
+
if (!target)
|
|
4445
|
+
return;
|
|
4446
|
+
const anchor = target;
|
|
4447
|
+
if (!matchesCcDomain(anchor.href))
|
|
4448
|
+
return;
|
|
4449
|
+
stampLink(anchor); // re-stamp in case attribution changed since DOMReady
|
|
4450
|
+
try {
|
|
4451
|
+
(_b = this.queue) === null || _b === void 0 ? void 0 : _b.flush();
|
|
4452
|
+
}
|
|
4453
|
+
catch (_c) {
|
|
4454
|
+
// Best-effort — never block navigation.
|
|
4455
|
+
}
|
|
4456
|
+
};
|
|
4457
|
+
stampAll();
|
|
4458
|
+
document.addEventListener("click", onClick, true);
|
|
4459
|
+
try {
|
|
4460
|
+
// Debounce re-stamping. A busy SPA (infinite scroll, animations,
|
|
4461
|
+
// React/Vue updates) can fire thousands of mutations per second; a naive
|
|
4462
|
+
// re-stamp on every notification would burn CPU pointlessly when
|
|
4463
|
+
// outbound link sets only change occasionally. 150ms is small enough to
|
|
4464
|
+
// catch links before the user can click them, large enough to coalesce
|
|
4465
|
+
// bursts.
|
|
4466
|
+
let restampTimer = null;
|
|
4467
|
+
const scheduleRestamp = () => {
|
|
4468
|
+
if (restampTimer != null)
|
|
4469
|
+
return;
|
|
4470
|
+
restampTimer = setTimeout(() => {
|
|
4471
|
+
restampTimer = null;
|
|
4472
|
+
stampAll();
|
|
4473
|
+
}, 150);
|
|
4474
|
+
};
|
|
4475
|
+
const observer = new MutationObserver(scheduleRestamp);
|
|
4476
|
+
observer.observe(document.documentElement, { childList: true, subtree: true });
|
|
4477
|
+
// Observer + click listener live for the session; pagehide cleans up on
|
|
4478
|
+
// full-page unload. SPA route changes are fine — we WANT stamping to keep
|
|
4479
|
+
// working across virtual navigations.
|
|
4480
|
+
window.addEventListener("pagehide", () => {
|
|
4481
|
+
try {
|
|
4482
|
+
observer.disconnect();
|
|
4483
|
+
}
|
|
4484
|
+
catch ( /* idempotent */_a) { /* idempotent */ }
|
|
4485
|
+
if (restampTimer != null)
|
|
4486
|
+
clearTimeout(restampTimer);
|
|
4487
|
+
document.removeEventListener("click", onClick, true);
|
|
4488
|
+
}, { once: true });
|
|
4489
|
+
}
|
|
4490
|
+
catch (error) {
|
|
4491
|
+
this.log("MutationObserver setup failed (CC link sync):", error);
|
|
4492
|
+
}
|
|
4493
|
+
this.log("Checkout Champ outbound-link sync active for:", lowerDomains);
|
|
4494
|
+
}
|
|
4136
4495
|
/**
|
|
4137
4496
|
* Create event payload
|
|
4138
4497
|
*/
|
|
@@ -4193,7 +4552,7 @@ class Datalyr {
|
|
|
4193
4552
|
resolution_method: 'browser_sdk',
|
|
4194
4553
|
resolution_confidence: 1.0,
|
|
4195
4554
|
// SDK metadata (keep in sync with package.json version)
|
|
4196
|
-
sdk_version: '1.6.
|
|
4555
|
+
sdk_version: '1.6.5',
|
|
4197
4556
|
sdk_name: 'datalyr-web-sdk'
|
|
4198
4557
|
};
|
|
4199
4558
|
return payload;
|