@zaplier/sdk 1.7.5 โ†’ 1.7.7

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/index.cjs CHANGED
@@ -19570,78 +19570,19 @@ class AutoTracker {
19570
19570
  console.log("[๐Ÿ‘† AutoTracker] Click delegation handler added");
19571
19571
  }
19572
19572
  }
19573
- // CRITICAL FIX: Scroll delegation with enhanced detection
19573
+ // MODERN APPROACH: IntersectionObserver + Scroll Fallback (2025 Best Practice)
19574
19574
  if (this.config.trackScrolls) {
19575
- const scrollHandler = this.createEnhancedScrollHandler();
19576
- this.delegationHandlers.set('scroll', scrollHandler);
19577
- // MAJOR FIX: Use window instead of document for scroll events
19578
- // Document doesn't reliably receive scroll events in all browsers
19579
- const scrollTarget = window;
19580
- const scrollOptions = {
19581
- passive: false, // Changed from passive: true to ensure handler executes
19582
- capture: false
19583
- };
19584
- scrollTarget.addEventListener('scroll', scrollHandler, scrollOptions);
19585
- // Also add to document as backup (some scenarios need both)
19586
- document.addEventListener('scroll', scrollHandler, scrollOptions);
19587
19575
  if (this.config.debug) {
19588
- console.log("[๐Ÿ“œ AutoTracker] โœ… Scroll delegation handler added to BOTH window AND document");
19589
- console.log("[๐Ÿ“œ AutoTracker] ๐Ÿ“‹ Event listener details:", {
19590
- primaryTarget: 'window',
19591
- backupTarget: 'document',
19592
- eventType: 'scroll',
19593
- handlerType: typeof scrollHandler,
19594
- options: scrollOptions,
19595
- handlerStored: this.delegationHandlers.has('scroll')
19596
- });
19597
- // Enhanced test function with real scroll simulation
19598
- const testHandler = () => {
19599
- console.log("[๐Ÿ“œ AutoTracker] ๐Ÿงช MANUAL SCROLL TEST - Handler should trigger");
19600
- scrollHandler(new Event('scroll'));
19601
- };
19602
- const testRealScroll = () => {
19603
- console.log("[๐Ÿ“œ AutoTracker] ๐Ÿงช TESTING REAL SCROLL EVENT");
19604
- window.scrollBy(0, 1); // Trigger real scroll
19605
- setTimeout(() => {
19606
- window.scrollBy(0, -1); // Scroll back
19607
- }, 100);
19608
- };
19609
- // Expose test functions globally for manual testing
19610
- if (typeof window !== 'undefined') {
19611
- window.testScrollHandler = testHandler;
19612
- window.testRealScroll = testRealScroll;
19613
- console.log("[๐Ÿ“œ AutoTracker] ๐Ÿงช Manual tests available:");
19614
- console.log("[๐Ÿ“œ AutoTracker] ๐Ÿงช - window.testScrollHandler() (direct handler test)");
19615
- console.log("[๐Ÿ“œ AutoTracker] ๐Ÿงช - window.testRealScroll() (real scroll test)");
19616
- }
19617
- // Add debug listener to verify scroll events are firing
19618
- const debugScrollListener = () => {
19619
- console.log("[๐Ÿ“œ AutoTracker] ๐Ÿ” DEBUG: Real scroll event detected!");
19620
- };
19621
- window.addEventListener('scroll', debugScrollListener, { once: true });
19622
- console.log("[๐Ÿ“œ AutoTracker] ๐Ÿ” Debug scroll listener added (will fire once on next scroll)");
19623
- // Immediate test of element detection
19624
- setTimeout(() => {
19625
- const scrollElements = this.getCachedScrollElements();
19626
- console.log(`[๐Ÿ“œ AutoTracker] ๐Ÿ“ Initial scroll elements check: ${scrollElements.length} found`);
19627
- if (scrollElements.length > 0) {
19628
- Array.from(scrollElements).forEach((element, index) => {
19629
- console.log(`[๐Ÿ“œ AutoTracker] Element ${index + 1}:`, {
19630
- id: element.id || 'no-id',
19631
- trackEvent: element.getAttribute('data-track-scroll'),
19632
- threshold: element.getAttribute('data-scroll-threshold') || '0.5',
19633
- tagName: element.tagName,
19634
- rect: element.getBoundingClientRect(),
19635
- isVisible: element.getBoundingClientRect().height > 0
19636
- });
19637
- });
19638
- }
19639
- // Test auto-scroll detection
19640
- setTimeout(() => {
19641
- console.log("[๐Ÿ“œ AutoTracker] ๐Ÿงช Auto-testing scroll detection in 1 second...");
19642
- testRealScroll();
19643
- }, 1000);
19644
- }, 100);
19576
+ console.log("[๐Ÿ”„ AutoTracker] Setting up modern scroll tracking with IntersectionObserver");
19577
+ }
19578
+ // PRIMARY: Modern IntersectionObserver approach (recommended 2025)
19579
+ this.setupIntersectionObserverScrollTracking();
19580
+ // FALLBACK: Traditional scroll listener for edge cases
19581
+ this.setupFallbackScrollListener();
19582
+ if (this.config.debug) {
19583
+ console.log("[๐Ÿ“œ AutoTracker] โœ… Modern scroll tracking setup complete:");
19584
+ console.log("[๐Ÿ“œ AutoTracker] ๐Ÿ“‹ Primary: IntersectionObserver (modern, performant)");
19585
+ console.log("[๐Ÿ“œ AutoTracker] ๐Ÿ“‹ Fallback: Traditional scroll listener (compatibility)");
19645
19586
  }
19646
19587
  }
19647
19588
  // Setup debounced mutation observer for dynamic content
@@ -19775,7 +19716,209 @@ class AutoTracker {
19775
19716
  return null;
19776
19717
  }
19777
19718
  /**
19778
- * Create enhanced scroll handler with better performance
19719
+ * Modern IntersectionObserver-based scroll tracking (2025 Best Practice)
19720
+ * More performant and reliable than traditional scroll listeners
19721
+ */
19722
+ setupIntersectionObserverScrollTracking() {
19723
+ if (typeof window === 'undefined' || !('IntersectionObserver' in window)) {
19724
+ if (this.config.debug) {
19725
+ console.log("[โš ๏ธ AutoTracker] IntersectionObserver not available, using fallback only");
19726
+ }
19727
+ return;
19728
+ }
19729
+ // Get all scroll elements
19730
+ const scrollElements = document.querySelectorAll('[data-track-scroll]');
19731
+ if (scrollElements.length === 0) {
19732
+ if (this.config.debug) {
19733
+ console.log("[๐Ÿ“ AutoTracker] No scroll elements found for IntersectionObserver");
19734
+ }
19735
+ return;
19736
+ }
19737
+ // Create observer with multiple thresholds for precise tracking
19738
+ const observerOptions = {
19739
+ root: null, // Use viewport as root
19740
+ rootMargin: '0px',
19741
+ threshold: [0, 0.1, 0.25, 0.5, 0.75, 0.9, 1.0] // Multiple thresholds for accuracy
19742
+ };
19743
+ const observer = new IntersectionObserver((entries) => {
19744
+ entries.forEach((entry) => {
19745
+ const element = entry.target;
19746
+ const eventName = element.getAttribute('data-track-scroll');
19747
+ const customThreshold = parseFloat(element.getAttribute('data-scroll-threshold') || '0.5');
19748
+ const hasTriggered = element.getAttribute('data-scroll-triggered') === 'true';
19749
+ if (!eventName || hasTriggered)
19750
+ return;
19751
+ const visibilityRatio = entry.intersectionRatio;
19752
+ if (this.config.debug) {
19753
+ console.log(`[๐Ÿ” IntersectionObserver] Element "${eventName}":`, {
19754
+ elementId: element.id || 'no-id',
19755
+ visibilityRatio: Math.round(visibilityRatio * 1000) / 1000,
19756
+ threshold: customThreshold,
19757
+ isIntersecting: entry.isIntersecting,
19758
+ shouldTrigger: visibilityRatio >= customThreshold
19759
+ });
19760
+ }
19761
+ // Trigger event when visibility threshold is met
19762
+ if (visibilityRatio >= customThreshold) {
19763
+ element.setAttribute('data-scroll-triggered', 'true');
19764
+ const metadata = this.extractMetadata(element);
19765
+ this.trackEvent(eventName, {
19766
+ type: 'scroll',
19767
+ method: 'IntersectionObserver',
19768
+ element: element.tagName.toLowerCase(),
19769
+ threshold: customThreshold,
19770
+ visibilityRatio: Math.round(visibilityRatio * 1000) / 1000,
19771
+ enhanced: true,
19772
+ modern: true,
19773
+ ...metadata,
19774
+ });
19775
+ if (this.config.debug) {
19776
+ console.log(`[๐ŸŽฏ IntersectionObserver] Event "${eventName}" triggered! (${Math.round(visibilityRatio * 100)}% visible)`);
19777
+ }
19778
+ // Unobserve after triggering (one-time trigger)
19779
+ observer.unobserve(element);
19780
+ }
19781
+ });
19782
+ }, observerOptions);
19783
+ // Observe all scroll elements
19784
+ scrollElements.forEach((element) => {
19785
+ observer.observe(element);
19786
+ if (this.config.debug) {
19787
+ console.log(`[๐Ÿ‘๏ธ IntersectionObserver] Now observing: ${element.getAttribute('data-track-scroll')} (${element.id || 'no-id'})`);
19788
+ }
19789
+ });
19790
+ // Store observer for cleanup
19791
+ this.intersectionObserver = observer;
19792
+ if (this.config.debug) {
19793
+ console.log(`[โœ… IntersectionObserver] Setup complete - observing ${scrollElements.length} elements`);
19794
+ }
19795
+ }
19796
+ /**
19797
+ * Fallback scroll listener for compatibility (React/Next.js issues)
19798
+ */
19799
+ setupFallbackScrollListener() {
19800
+ if (typeof window === 'undefined')
19801
+ return;
19802
+ const fallbackHandler = this.createThrottledScrollHandler();
19803
+ // Try multiple attachment strategies for React/Next.js compatibility
19804
+ const attachStrategies = [
19805
+ () => window.addEventListener('scroll', fallbackHandler, { passive: true }),
19806
+ () => document.addEventListener('scroll', fallbackHandler, { passive: true }),
19807
+ () => window.addEventListener('scroll', fallbackHandler, { passive: true, capture: true }),
19808
+ () => {
19809
+ // Delayed attachment for React hydration
19810
+ setTimeout(() => {
19811
+ window.addEventListener('scroll', fallbackHandler, { passive: true });
19812
+ }, 100);
19813
+ },
19814
+ () => {
19815
+ // Document ready state check
19816
+ if (document.readyState === 'complete') {
19817
+ window.addEventListener('scroll', fallbackHandler, { passive: true });
19818
+ }
19819
+ else {
19820
+ window.addEventListener('load', () => {
19821
+ window.addEventListener('scroll', fallbackHandler, { passive: true });
19822
+ });
19823
+ }
19824
+ }
19825
+ ];
19826
+ // Try each strategy
19827
+ attachStrategies.forEach((strategy, index) => {
19828
+ try {
19829
+ strategy();
19830
+ if (this.config.debug) {
19831
+ console.log(`[๐Ÿ“œ AutoTracker] Fallback scroll strategy ${index + 1} attached`);
19832
+ }
19833
+ }
19834
+ catch (error) {
19835
+ if (this.config.debug) {
19836
+ console.log(`[โš ๏ธ AutoTracker] Fallback strategy ${index + 1} failed:`, error);
19837
+ }
19838
+ }
19839
+ });
19840
+ this.delegationHandlers.set('scroll', fallbackHandler);
19841
+ // Test scroll detection after setup
19842
+ if (this.config.debug) {
19843
+ setTimeout(() => {
19844
+ console.log("[๐Ÿงช AutoTracker] Testing fallback scroll in 2 seconds...");
19845
+ // Add one-time debug listener
19846
+ const testListener = () => {
19847
+ console.log("[โœ… AutoTracker] Fallback scroll listener working!");
19848
+ };
19849
+ window.addEventListener('scroll', testListener, { once: true, passive: true });
19850
+ // Trigger test scroll
19851
+ const originalScrollY = window.scrollY;
19852
+ window.scrollBy(0, 1);
19853
+ setTimeout(() => window.scrollTo(0, originalScrollY), 50);
19854
+ }, 2000);
19855
+ }
19856
+ }
19857
+ /**
19858
+ * Create throttled scroll handler (optimized for performance)
19859
+ */
19860
+ createThrottledScrollHandler() {
19861
+ let lastKnownScrollPosition = 0;
19862
+ let ticking = false;
19863
+ return () => {
19864
+ lastKnownScrollPosition = window.scrollY;
19865
+ if (!ticking) {
19866
+ // Use setTimeout throttling (MDN recommended 2025)
19867
+ setTimeout(() => {
19868
+ if (this.config.debug) {
19869
+ console.log(`[๐Ÿ“œ AutoTracker] Fallback scroll handler triggered (scrollY: ${lastKnownScrollPosition})`);
19870
+ }
19871
+ this.processFallbackScroll(lastKnownScrollPosition);
19872
+ ticking = false;
19873
+ }, 20); // 20ms throttle (MDN recommendation)
19874
+ ticking = true;
19875
+ }
19876
+ };
19877
+ }
19878
+ /**
19879
+ * Process scroll for fallback handler (when IntersectionObserver fails)
19880
+ */
19881
+ processFallbackScroll(scrollPosition) {
19882
+ // Only process if IntersectionObserver didn't handle it
19883
+ const scrollElements = document.querySelectorAll('[data-track-scroll]:not([data-scroll-triggered="true"])');
19884
+ if (scrollElements.length === 0)
19885
+ return;
19886
+ scrollElements.forEach((element) => {
19887
+ const eventName = element.getAttribute('data-track-scroll');
19888
+ const threshold = parseFloat(element.getAttribute('data-scroll-threshold') || '0.5');
19889
+ if (!eventName)
19890
+ return;
19891
+ const rect = element.getBoundingClientRect();
19892
+ const windowHeight = window.innerHeight;
19893
+ const elementHeight = rect.height;
19894
+ if (elementHeight <= 0)
19895
+ return;
19896
+ const visibleTop = Math.max(rect.top, 0);
19897
+ const visibleBottom = Math.min(rect.bottom, windowHeight);
19898
+ const visibleHeight = Math.max(0, visibleBottom - visibleTop);
19899
+ const visibilityRatio = visibleHeight / elementHeight;
19900
+ if (visibilityRatio >= threshold) {
19901
+ element.setAttribute('data-scroll-triggered', 'true');
19902
+ const metadata = this.extractMetadata(element);
19903
+ this.trackEvent(eventName, {
19904
+ type: 'scroll',
19905
+ method: 'fallback_scroll',
19906
+ element: element.tagName.toLowerCase(),
19907
+ threshold,
19908
+ visibilityRatio: Math.round(visibilityRatio * 1000) / 1000,
19909
+ scrollPosition,
19910
+ fallback: true,
19911
+ ...metadata,
19912
+ });
19913
+ if (this.config.debug) {
19914
+ console.log(`[๐ŸŽฏ Fallback] Event "${eventName}" triggered! (${Math.round(visibilityRatio * 100)}% visible)`);
19915
+ }
19916
+ }
19917
+ });
19918
+ }
19919
+ /**
19920
+ * LEGACY: Create enhanced scroll handler with better performance
19921
+ * @deprecated Use IntersectionObserver approach instead
19779
19922
  */
19780
19923
  createEnhancedScrollHandler() {
19781
19924
  return () => {
@@ -20460,6 +20603,33 @@ class AutoTracker {
20460
20603
  source: 'manual_diagnostic',
20461
20604
  timestamp: Date.now()
20462
20605
  });
20606
+ },
20607
+ testIntersectionObserver: () => {
20608
+ console.log(`[๐Ÿงช AutoTracker] Testing IntersectionObserver setup`);
20609
+ const scrollElements = document.querySelectorAll('[data-track-scroll]');
20610
+ console.log(`Found ${scrollElements.length} scroll elements for IntersectionObserver`);
20611
+ if (this.intersectionObserver && scrollElements.length > 0) {
20612
+ const firstElement = scrollElements[0];
20613
+ if (firstElement) {
20614
+ console.log('Testing first element:', firstElement.getAttribute('data-track-scroll'));
20615
+ // Temporarily remove triggered state for testing
20616
+ firstElement.removeAttribute('data-scroll-triggered');
20617
+ // Re-observe for testing
20618
+ this.intersectionObserver.observe(firstElement);
20619
+ console.log('Re-observing element for test...');
20620
+ }
20621
+ }
20622
+ },
20623
+ testScrollFallback: () => {
20624
+ console.log(`[๐Ÿงช AutoTracker] Testing scroll fallback mechanism`);
20625
+ const handler = this.delegationHandlers.get('scroll');
20626
+ if (handler) {
20627
+ console.log('Triggering fallback scroll handler...');
20628
+ handler(new Event('scroll'));
20629
+ }
20630
+ else {
20631
+ console.log('No scroll handler found in delegation handlers');
20632
+ }
20463
20633
  }
20464
20634
  };
20465
20635
  // Expose tests globally for easy access
@@ -20514,7 +20684,9 @@ class AutoTracker {
20514
20684
  usage: {
20515
20685
  triggerScrollHandler: 'window.autoTrackerTests.triggerScrollHandler()',
20516
20686
  forceScrollEvent: 'window.autoTrackerTests.forceScrollEvent("[data-track-scroll]")',
20517
- testTrackEvent: 'window.autoTrackerTests.testTrackEvent("my_event")'
20687
+ testTrackEvent: 'window.autoTrackerTests.testTrackEvent("my_event")',
20688
+ testIntersectionObserver: 'window.autoTrackerTests.testIntersectionObserver()',
20689
+ testScrollFallback: 'window.autoTrackerTests.testScrollFallback()'
20518
20690
  },
20519
20691
  description: 'Use these functions in the browser console to manually test the AutoTracker'
20520
20692
  }
@@ -21513,6 +21685,70 @@ class ZaplierSDK {
21513
21685
  window.location.hostname.startsWith("10.") ||
21514
21686
  window.location.hostname.includes("local"));
21515
21687
  }
21688
+ /**
21689
+ * Extract UTM parameters from current URL
21690
+ */
21691
+ extractUTMParameters() {
21692
+ const utmParams = {};
21693
+ if (typeof window === "undefined" || !window.location) {
21694
+ return utmParams;
21695
+ }
21696
+ try {
21697
+ const urlParams = new URLSearchParams(window.location.search);
21698
+ // Extract UTM parameters (camelCase for consistency with backend)
21699
+ const utmSource = urlParams.get('utm_source');
21700
+ const utmMedium = urlParams.get('utm_medium');
21701
+ const utmCampaign = urlParams.get('utm_campaign');
21702
+ const utmContent = urlParams.get('utm_content');
21703
+ const utmTerm = urlParams.get('utm_term');
21704
+ // Extract click IDs
21705
+ const fbclid = urlParams.get('fbclid');
21706
+ const gclid = urlParams.get('gclid');
21707
+ const ttclid = urlParams.get('ttclid');
21708
+ // Extract ad platform parameters
21709
+ const creativeId = urlParams.get('creative_id');
21710
+ const adId = urlParams.get('ad_id');
21711
+ const adsetId = urlParams.get('adset_id');
21712
+ const campaignId = urlParams.get('campaign_id');
21713
+ const platform = urlParams.get('platform');
21714
+ // Add to result object (only non-null values)
21715
+ if (utmSource)
21716
+ utmParams.utmSource = utmSource;
21717
+ if (utmMedium)
21718
+ utmParams.utmMedium = utmMedium;
21719
+ if (utmCampaign)
21720
+ utmParams.utmCampaign = utmCampaign;
21721
+ if (utmContent)
21722
+ utmParams.utmContent = utmContent;
21723
+ if (utmTerm)
21724
+ utmParams.utmTerm = utmTerm;
21725
+ if (fbclid)
21726
+ utmParams.fbclid = fbclid;
21727
+ if (gclid)
21728
+ utmParams.gclid = gclid;
21729
+ if (ttclid)
21730
+ utmParams.ttclid = ttclid;
21731
+ if (creativeId)
21732
+ utmParams.creativeId = creativeId;
21733
+ if (adId)
21734
+ utmParams.adId = adId;
21735
+ if (adsetId)
21736
+ utmParams.adsetId = adsetId;
21737
+ if (campaignId)
21738
+ utmParams.campaignId = campaignId;
21739
+ if (platform)
21740
+ utmParams.platform = platform;
21741
+ if (this.config.debug && Object.keys(utmParams).length > 0) {
21742
+ console.log("[Zaplier] UTM parameters extracted from URL:", utmParams);
21743
+ }
21744
+ }
21745
+ catch (error) {
21746
+ if (this.config.debug) {
21747
+ console.warn("[Zaplier] Failed to extract UTM parameters:", error);
21748
+ }
21749
+ }
21750
+ return utmParams;
21751
+ }
21516
21752
  /**
21517
21753
  * Send event to backend
21518
21754
  */
@@ -21554,7 +21790,9 @@ class ZaplierSDK {
21554
21790
  }
21555
21791
  : undefined,
21556
21792
  fingerprintComponents: this.fingerprint?.components || {},
21557
- ...eventData,
21793
+ // CRITICAL: Auto-extract UTM parameters from URL, but allow eventData to override
21794
+ ...this.extractUTMParameters(), // Auto-extracted UTM parameters (background)
21795
+ ...eventData, // User-provided event data (can override UTM parameters)
21558
21796
  timestamp: new Date().toISOString(),
21559
21797
  url: typeof window !== "undefined" && window.location
21560
21798
  ? window.location.href