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