@datalyr/web 1.5.0 → 1.6.1

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/datalyr.js CHANGED
@@ -1,10 +1,10 @@
1
1
  /**
2
- * @datalyr/web v1.5.0
2
+ * @datalyr/web v1.6.1
3
3
  * Datalyr Web SDK - Modern attribution tracking for web applications
4
4
  * (c) 2026 Datalyr Inc.
5
5
  * Released under the MIT License
6
6
  */
7
- var Datalyr = (function () {
7
+ var Datalyr = (function (exports) {
8
8
  'use strict';
9
9
 
10
10
  /******************************************************************************
@@ -1507,10 +1507,13 @@ var Datalyr = (function () {
1507
1507
  // Optionally set the cookie for future use
1508
1508
  cookies.set('_fbp', adCookies._fbp, 90);
1509
1509
  }
1510
- // Generate _fbc if we have fbclid but no _fbc
1510
+ // Generate _fbc if we have fbclid but no _fbc.
1511
+ // Meta's fbc format is `fb.{subdomainIndex}.{creationTime}.{fbclid}` where
1512
+ // creationTime is UNIX time in MILLISECONDS (matches the _fbp generation above
1513
+ // and the real _fbc cookie the Meta Pixel writes). Do NOT use seconds here.
1511
1514
  const fbclid = this.getCurrentFbclid();
1512
1515
  if (fbclid && !adCookies._fbc) {
1513
- const timestamp = Math.floor(Date.now() / 1000);
1516
+ const timestamp = Date.now();
1514
1517
  adCookies._fbc = `fb.1.${timestamp}.${fbclid}`;
1515
1518
  // Optionally set the cookie for future use
1516
1519
  cookies.set('_fbc', adCookies._fbc, 90);
@@ -2675,9 +2678,12 @@ var Datalyr = (function () {
2675
2678
  s = b.getElementsByTagName(e)[0];
2676
2679
  s.parentNode.insertBefore(t, s);
2677
2680
  })(window, document, 'script', 'https://connect.facebook.net/en_US/fbevents.js');
2678
- // Initialize pixel
2681
+ // Initialize pixel only — do NOT fire PageView here. The SDK's own pageview
2682
+ // tracking (track('pageview')) routes through trackToPixels and fires a single
2683
+ // mapped PageView with a shared eventID. Firing it again here produced TWO
2684
+ // PageViews per load (one un-deduped). Note: if the host app disables pageview
2685
+ // tracking entirely, no PageView is sent — which is the correct outcome.
2679
2686
  window.fbq('init', config.pixel_id);
2680
- window.fbq('track', 'PageView');
2681
2687
  this.log('Meta Pixel initialized:', config.pixel_id);
2682
2688
  }
2683
2689
  catch (error) {
@@ -2759,22 +2765,63 @@ var Datalyr = (function () {
2759
2765
  /**
2760
2766
  * Track event to all initialized pixels
2761
2767
  */
2762
- trackToPixels(eventName, properties = {}) {
2763
- var _a, _b, _c, _e, _f, _g;
2768
+ trackToPixels(eventName, properties = {}, eventId) {
2769
+ var _a, _b, _c, _e, _f, _g, _h, _j;
2770
+ // Datalyr-internal events ($identify, $group, $alias, $auto_identify,
2771
+ // $app_download_click, …) are not conversions — never forward them to ad
2772
+ // pixels. (Previously they fired as noise custom events with the $ stripped.)
2773
+ if (eventName.startsWith('$'))
2774
+ return;
2764
2775
  // Sanitize inputs to prevent XSS
2765
2776
  const sanitizedEventName = this.sanitizeEventName(eventName);
2766
2777
  const sanitizedProperties = this.sanitizeProperties(properties);
2767
2778
  // Track to Meta Pixel
2768
2779
  if (((_b = (_a = this.pixels) === null || _a === void 0 ? void 0 : _a.meta) === null || _b === void 0 ? void 0 : _b.enabled) && window.fbq) {
2769
2780
  try {
2770
- window.fbq('track', sanitizedEventName, sanitizedProperties);
2781
+ // Map our event name to Meta's standard event vocabulary so the browser
2782
+ // Pixel fires e.g. "Purchase", not "purchase". This MUST match the
2783
+ // platform_standard_event the postback worker sends server-side, or Meta
2784
+ // won't dedupe (dedup = event_name + event_id). These defaults mirror the
2785
+ // server-side auto-detect map; custom rule choices may not line up.
2786
+ const metaEventMap = {
2787
+ page_view: 'PageView', pageview: 'PageView',
2788
+ view_content: 'ViewContent', product_viewed: 'ViewContent', view_item: 'ViewContent',
2789
+ add_to_cart: 'AddToCart', product_added: 'AddToCart',
2790
+ add_to_wishlist: 'AddToWishlist',
2791
+ initiate_checkout: 'InitiateCheckout', begin_checkout: 'InitiateCheckout', checkout_started: 'InitiateCheckout',
2792
+ add_payment_info: 'AddPaymentInfo',
2793
+ purchase: 'Purchase', order_completed: 'Purchase', order_paid: 'Purchase',
2794
+ lead: 'Lead',
2795
+ complete_registration: 'CompleteRegistration', sign_up: 'CompleteRegistration', signup: 'CompleteRegistration',
2796
+ search: 'Search',
2797
+ subscribe: 'Subscribe', subscription_created: 'Subscribe',
2798
+ start_trial: 'StartTrial', trial_started: 'StartTrial',
2799
+ contact: 'Contact',
2800
+ schedule: 'Schedule',
2801
+ };
2802
+ // Source of truth: the workspace's Meta conversion-rule map (from
2803
+ // /container-scripts, keyed by the exact trigger event name) — this is what
2804
+ // the server-side CAPI sends, so it guarantees event_name dedup alignment.
2805
+ // Fall back to the static default map, then the sanitized raw name.
2806
+ const ruleEventMap = (_e = (_c = this.pixels) === null || _c === void 0 ? void 0 : _c.meta) === null || _e === void 0 ? void 0 : _e.event_mappings;
2807
+ const metaEvent = (ruleEventMap === null || ruleEventMap === void 0 ? void 0 : ruleEventMap[eventName])
2808
+ || metaEventMap[String(eventName).toLowerCase()]
2809
+ || sanitizedEventName;
2810
+ // Pass the shared eventID so this Pixel event dedupes against the
2811
+ // server-side CAPI event carrying the same event_id.
2812
+ if (eventId) {
2813
+ window.fbq('track', metaEvent, sanitizedProperties, { eventID: eventId });
2814
+ }
2815
+ else {
2816
+ window.fbq('track', metaEvent, sanitizedProperties);
2817
+ }
2771
2818
  }
2772
2819
  catch (error) {
2773
2820
  this.log('Error tracking Meta Pixel event:', error);
2774
2821
  }
2775
2822
  }
2776
2823
  // Track to Google Tag
2777
- if (((_e = (_c = this.pixels) === null || _c === void 0 ? void 0 : _c.google) === null || _e === void 0 ? void 0 : _e.enabled) && window.gtag) {
2824
+ if (((_g = (_f = this.pixels) === null || _f === void 0 ? void 0 : _f.google) === null || _g === void 0 ? void 0 : _g.enabled) && window.gtag) {
2778
2825
  try {
2779
2826
  window.gtag('event', sanitizedEventName, sanitizedProperties);
2780
2827
  }
@@ -2783,7 +2830,7 @@ var Datalyr = (function () {
2783
2830
  }
2784
2831
  }
2785
2832
  // Track to TikTok Pixel
2786
- if (((_g = (_f = this.pixels) === null || _f === void 0 ? void 0 : _f.tiktok) === null || _g === void 0 ? void 0 : _g.enabled) && window.ttq) {
2833
+ if (((_j = (_h = this.pixels) === null || _h === void 0 ? void 0 : _h.tiktok) === null || _j === void 0 ? void 0 : _j.enabled) && window.ttq) {
2787
2834
  try {
2788
2835
  // Map common events to TikTok names
2789
2836
  const tiktokEventMap = {
@@ -3491,6 +3538,15 @@ var Datalyr = (function () {
3491
3538
  this.identify(email, { email });
3492
3539
  });
3493
3540
  }
3541
+ // Stamp attribution signals into the Shopify cart (OPT-IN, default off).
3542
+ // Lets server-side order webhooks recover the browser visitor + Meta click
3543
+ // signals (the postback webhook reads these as note_attributes). Inert unless
3544
+ // enabled; best-effort and never blocks init.
3545
+ if (this.config.shopifyCartAttributes === true) {
3546
+ this.syncShopifyCartAttributes().catch((error) => {
3547
+ this.log('Shopify cart attribute sync failed:', error);
3548
+ });
3549
+ }
3494
3550
  // Track initial page view if enabled (AFTER encryption ready)
3495
3551
  if (this.config.trackPageViews) {
3496
3552
  this.page();
@@ -3532,13 +3588,19 @@ var Datalyr = (function () {
3532
3588
  // PRIVACY: Heavy fingerprinting removed - using minimal fingerprinting only
3533
3589
  // Update session activity
3534
3590
  this.session.updateActivity(eventName);
3591
+ // Generate the event_id ONCE here so the same value is used for both the
3592
+ // ingested event (→ CAPI dedup key in the postback worker) and the browser
3593
+ // Meta Pixel co-fire below. Sharing it is what lets Meta dedupe the Pixel
3594
+ // event against the server-side CAPI event (dedup = event_id + event_name).
3595
+ const eventId = generateUUID();
3535
3596
  // Create event payload
3536
- const payload = this.createEventPayload(eventName, properties);
3597
+ const payload = this.createEventPayload(eventName, properties, eventId);
3537
3598
  // Queue event
3538
3599
  this.queue.enqueue(payload);
3539
- // Track to third-party pixels if container is initialized
3600
+ // Track to third-party pixels if container is initialized.
3601
+ // Pass the shared eventId so the Meta Pixel fires with the same { eventID }.
3540
3602
  if (this.container) {
3541
- this.container.trackToPixels(eventName, properties);
3603
+ this.container.trackToPixels(eventName, properties, eventId);
3542
3604
  }
3543
3605
  // Call plugin handlers
3544
3606
  if (this.config.plugins) {
@@ -3929,10 +3991,61 @@ var Datalyr = (function () {
3929
3991
  getSuperProperties() {
3930
3992
  return Object.assign({}, this.superProperties);
3931
3993
  }
3994
+ /**
3995
+ * Stamp Datalyr attribution signals into the Shopify cart as cart attributes.
3996
+ * Cart attributes become `order.note_attributes` with the same names, which the
3997
+ * server-side order webhook reads to recover the browser visitor_id + Meta click
3998
+ * signals (_fbc/_fbp/fbclid) — enabling accurate Meta CAPI attribution for orders.
3999
+ *
4000
+ * Opt-in (config.shopifyCartAttributes), best-effort, runs once during init.
4001
+ * Merges via Shopify's /cart/update.js (does not clobber other cart attributes).
4002
+ */
4003
+ syncShopifyCartAttributes() {
4004
+ return __awaiter(this, void 0, void 0, function* () {
4005
+ if (typeof window === "undefined" || typeof document === "undefined")
4006
+ return;
4007
+ const isShopify = !!(window.Shopify ||
4008
+ document.querySelector('meta[name="shopify-checkout-api-token"]') ||
4009
+ window.location.hostname.includes(".myshopify.com"));
4010
+ if (!isShopify)
4011
+ return;
4012
+ const attribution = this.attribution.getAttributionData();
4013
+ const fbclid = attribution.clickIdType === "fbclid" ? attribution.clickId : null;
4014
+ // Cookies are freshest (the real Meta Pixel may have just written them);
4015
+ // fall back to whatever the SDK captured in attribution.
4016
+ const attributes = {};
4017
+ const visitorId = this.identity.getAnonymousId();
4018
+ const fbc = this.cookies.get("_fbc") || attribution._fbc;
4019
+ const fbp = this.cookies.get("_fbp") || attribution._fbp;
4020
+ if (visitorId)
4021
+ attributes._datalyr_visitor_id = visitorId;
4022
+ if (fbc)
4023
+ attributes._datalyr_fbc = String(fbc);
4024
+ if (fbp)
4025
+ attributes._datalyr_fbp = String(fbp);
4026
+ if (fbclid)
4027
+ attributes._datalyr_fbclid = String(fbclid);
4028
+ if (Object.keys(attributes).length === 0)
4029
+ return;
4030
+ try {
4031
+ yield fetch("/cart/update.js", {
4032
+ method: "POST",
4033
+ headers: { "Content-Type": "application/json" },
4034
+ credentials: "same-origin",
4035
+ body: JSON.stringify({ attributes }),
4036
+ });
4037
+ this.log("Shopify cart attributes stamped:", Object.keys(attributes));
4038
+ }
4039
+ catch (error) {
4040
+ // Never let cart sync affect tracking — swallow (e.g. no cart yet / CSP).
4041
+ this.log("Shopify /cart/update.js failed:", error);
4042
+ }
4043
+ });
4044
+ }
3932
4045
  /**
3933
4046
  * Create event payload
3934
4047
  */
3935
- createEventPayload(eventName, properties) {
4048
+ createEventPayload(eventName, properties, eventIdArg) {
3936
4049
  // Sanitize and merge properties
3937
4050
  const sanitizedProperties = sanitizeEventData(properties);
3938
4051
  const eventData = deepMerge({}, this.superProperties, sanitizedProperties);
@@ -3962,7 +4075,9 @@ var Datalyr = (function () {
3962
4075
  });
3963
4076
  // Create payload using snake_case only (matches backend API and production script)
3964
4077
  const identityFields = this.identity.getIdentityFields();
3965
- const eventId = generateUUID();
4078
+ // Use the caller-provided event_id (shared with the Meta Pixel co-fire for
4079
+ // dedup); fall back to a fresh UUID for any direct caller that omits it.
4080
+ const eventId = eventIdArg !== null && eventIdArg !== void 0 ? eventIdArg : generateUUID();
3966
4081
  // Ensure we have all required identity fields
3967
4082
  const distinctId = identityFields.distinct_id;
3968
4083
  const anonymousId = identityFields.anonymous_id;
@@ -3986,8 +4101,8 @@ var Datalyr = (function () {
3986
4101
  // Resolution metadata
3987
4102
  resolution_method: 'browser_sdk',
3988
4103
  resolution_confidence: 1.0,
3989
- // SDK metadata
3990
- sdk_version: '1.4.1',
4104
+ // SDK metadata (keep in sync with package.json version)
4105
+ sdk_version: '1.6.0',
3991
4106
  sdk_name: 'datalyr-web-sdk'
3992
4107
  };
3993
4108
  return payload;
@@ -4207,7 +4322,12 @@ var Datalyr = (function () {
4207
4322
  window.datalyr = datalyr;
4208
4323
  }
4209
4324
 
4210
- return datalyr;
4325
+ exports.datalyr = datalyr;
4326
+ exports.default = datalyr;
4327
+
4328
+ Object.defineProperty(exports, '__esModule', { value: true });
4329
+
4330
+ return exports;
4211
4331
 
4212
- })();
4332
+ })({});
4213
4333
  //# sourceMappingURL=datalyr.js.map