@zaplier/sdk 1.6.9 → 1.7.2

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
@@ -19266,6 +19266,16 @@ class AutoTracker {
19266
19266
  constructor(sdkInstance, config = {}) {
19267
19267
  this.observedElements = new Set();
19268
19268
  this.scrollThrottle = 0;
19269
+ this.isInitialized = false;
19270
+ this.delegationHandlers = new Map();
19271
+ this.initializationAttempts = 0;
19272
+ this.maxInitializationAttempts = 3;
19273
+ this.diagnostics = {};
19274
+ /**
19275
+ * Cache scroll elements for better performance
19276
+ */
19277
+ this.cachedScrollElements = null;
19278
+ this.lastScrollElementsCheck = 0;
19269
19279
  this.handleScroll = () => {
19270
19280
  if (!this.config.trackScrolls)
19271
19281
  return;
@@ -19363,37 +19373,211 @@ class AutoTracker {
19363
19373
  start() {
19364
19374
  if (!this.config.enabled)
19365
19375
  return;
19376
+ this.initializationAttempts++;
19377
+ // Enhanced diagnostics
19378
+ this.runDiagnostics();
19366
19379
  if (this.config.debug) {
19367
- console.log("[Zaplier AutoTracker] Iniciando auto tracking", {
19368
- enabled: this.config.enabled,
19369
- trackClicks: this.config.trackClicks,
19370
- trackScrolls: this.config.trackScrolls,
19371
- trackViews: this.config.trackViews,
19372
- trackHovers: this.config.trackHovers,
19373
- trackForms: this.config.trackForms
19380
+ console.log("[🔥 AutoTracker ENHANCED] Starting auto tracking", this.diagnostics);
19381
+ }
19382
+ // Multiple initialization strategies
19383
+ this.tryInitialization();
19384
+ }
19385
+ /**
19386
+ * Run comprehensive diagnostics
19387
+ */
19388
+ runDiagnostics() {
19389
+ this.diagnostics = {
19390
+ timestamp: new Date().toISOString(),
19391
+ attempt: this.initializationAttempts,
19392
+ environment: {
19393
+ domReady: document.readyState,
19394
+ reactDetected: this.detectReact(),
19395
+ reactVersion: this.getReactVersion(),
19396
+ userAgent: navigator.userAgent,
19397
+ isMobile: /Mobile|Android|iPhone|iPad/.test(navigator.userAgent),
19398
+ isSafari: /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent),
19399
+ protocol: window.location.protocol,
19400
+ isLocalhost: window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'
19401
+ },
19402
+ dom: {
19403
+ documentElement: !!document.documentElement,
19404
+ body: !!document.body,
19405
+ querySelector: typeof document.querySelector === 'function',
19406
+ addEventListener: typeof document.addEventListener === 'function',
19407
+ elementsFound: this.getElementCounts(),
19408
+ hasReactRoot: this.findReactRoot()
19409
+ },
19410
+ tracking: {
19411
+ sdkInstance: !!this.sdkInstance,
19412
+ trackCustomEvent: typeof this.sdkInstance?.trackCustomEvent === 'function',
19413
+ config: this.config,
19414
+ isInitialized: this.isInitialized,
19415
+ activeHandlers: Array.from(this.delegationHandlers.keys())
19416
+ }
19417
+ };
19418
+ }
19419
+ /**
19420
+ * Detect React presence
19421
+ */
19422
+ detectReact() {
19423
+ return !!(window.__REACT_DEVTOOLS_GLOBAL_HOOK__ ||
19424
+ window.React ||
19425
+ document.querySelector('[data-reactroot]') ||
19426
+ document.querySelector('#root') ||
19427
+ document.querySelector('#__next'));
19428
+ }
19429
+ /**
19430
+ * Get React version if available
19431
+ */
19432
+ getReactVersion() {
19433
+ try {
19434
+ const reactInstance = window.React;
19435
+ return reactInstance?.version || null;
19436
+ }
19437
+ catch {
19438
+ return null;
19439
+ }
19440
+ }
19441
+ /**
19442
+ * Find React root element
19443
+ */
19444
+ findReactRoot() {
19445
+ const reactRoot = document.querySelector('[data-reactroot]') ||
19446
+ document.querySelector('#root') ||
19447
+ document.querySelector('#__next') ||
19448
+ document.querySelector('div[data-react-helmet]');
19449
+ return reactRoot ? reactRoot.tagName + (reactRoot.id ? '#' + reactRoot.id : '') : null;
19450
+ }
19451
+ /**
19452
+ * Get element counts for all tracking types
19453
+ */
19454
+ getElementCounts() {
19455
+ return {
19456
+ click: document.querySelectorAll('[data-track-click]').length,
19457
+ scroll: document.querySelectorAll('[data-track-scroll]').length,
19458
+ view: document.querySelectorAll('[data-track-view]').length,
19459
+ hover: document.querySelectorAll('[data-track-hover]').length,
19460
+ form: document.querySelectorAll('[data-track-form]').length
19461
+ };
19462
+ }
19463
+ /**
19464
+ * Try multiple initialization strategies
19465
+ */
19466
+ tryInitialization() {
19467
+ const strategies = [
19468
+ () => this.immediateInitialization(),
19469
+ () => this.delayedInitialization(100),
19470
+ () => this.delayedInitialization(500),
19471
+ () => this.delayedInitialization(1000),
19472
+ () => this.reactReadyInitialization()
19473
+ ];
19474
+ // Try immediate initialization first
19475
+ if (document.readyState === 'complete') {
19476
+ this.attemptStrategy(0, strategies);
19477
+ }
19478
+ else if (document.readyState === 'interactive') {
19479
+ // DOM ready but resources still loading
19480
+ this.attemptStrategy(1, strategies);
19481
+ }
19482
+ else {
19483
+ // DOM still loading
19484
+ document.addEventListener('DOMContentLoaded', () => {
19485
+ this.attemptStrategy(0, strategies);
19486
+ }, { once: true });
19487
+ }
19488
+ }
19489
+ /**
19490
+ * Attempt initialization strategy with fallback
19491
+ */
19492
+ attemptStrategy(index, strategies) {
19493
+ if (index >= strategies.length || this.isInitialized)
19494
+ return;
19495
+ if (this.config.debug) {
19496
+ console.log(`[🔄 AutoTracker] Trying initialization strategy ${index + 1}/${strategies.length}`);
19497
+ }
19498
+ try {
19499
+ const strategy = strategies[index];
19500
+ if (strategy && typeof strategy === 'function') {
19501
+ strategy();
19502
+ // Verify initialization after a short delay
19503
+ setTimeout(() => {
19504
+ if (!this.verifyInitialization()) {
19505
+ if (this.config.debug) {
19506
+ console.log(`[⚠️ AutoTracker] Strategy ${index + 1} failed verification, trying next...`);
19507
+ }
19508
+ this.attemptStrategy(index + 1, strategies);
19509
+ }
19510
+ else {
19511
+ if (this.config.debug) {
19512
+ console.log(`[✅ AutoTracker] Strategy ${index + 1} successful!`);
19513
+ }
19514
+ }
19515
+ }, 50);
19516
+ }
19517
+ else {
19518
+ // Strategy is undefined or not a function, try next
19519
+ this.attemptStrategy(index + 1, strategies);
19520
+ }
19521
+ }
19522
+ catch (error) {
19523
+ if (this.config.debug) {
19524
+ console.error(`[❌ AutoTracker] Strategy ${index + 1} error:`, error);
19525
+ }
19526
+ this.attemptStrategy(index + 1, strategies);
19527
+ }
19528
+ }
19529
+ /**
19530
+ * Immediate initialization
19531
+ */
19532
+ immediateInitialization() {
19533
+ this.initializeTracking();
19534
+ }
19535
+ /**
19536
+ * Delayed initialization
19537
+ */
19538
+ delayedInitialization(delay) {
19539
+ setTimeout(() => {
19540
+ this.initializeTracking();
19541
+ }, delay);
19542
+ }
19543
+ /**
19544
+ * React-specific initialization timing
19545
+ */
19546
+ reactReadyInitialization() {
19547
+ // Wait for React to finish rendering
19548
+ if (typeof window.requestAnimationFrame === 'function') {
19549
+ window.requestAnimationFrame(() => {
19550
+ setTimeout(() => {
19551
+ this.initializeTracking();
19552
+ }, 0);
19374
19553
  });
19375
- // Log elementos existentes encontrados
19554
+ }
19555
+ else {
19376
19556
  setTimeout(() => {
19377
- const clickElements = document.querySelectorAll('[data-track-click]');
19378
- const scrollElements = document.querySelectorAll('[data-track-scroll]');
19379
- const viewElements = document.querySelectorAll('[data-track-view]');
19380
- const hoverElements = document.querySelectorAll('[data-track-hover]');
19381
- const formElements = document.querySelectorAll('[data-track-form]');
19382
- console.log("[Zaplier AutoTracker] Elementos encontrados:", {
19383
- clickElements: clickElements.length,
19384
- scrollElements: scrollElements.length,
19385
- viewElements: viewElements.length,
19386
- hoverElements: hoverElements.length,
19387
- formElements: formElements.length
19388
- });
19389
- if (scrollElements.length > 0) {
19390
- console.log("[Zaplier AutoTracker] Elementos de scroll:", Array.from(scrollElements).map(el => ({
19391
- tagName: el.tagName,
19392
- trackEvent: el.getAttribute('data-track-scroll'),
19393
- threshold: el.getAttribute('data-scroll-threshold') || '0.5'
19394
- })));
19395
- }
19396
- }, 100);
19557
+ this.initializeTracking();
19558
+ }, 16); // ~60fps fallback
19559
+ }
19560
+ }
19561
+ /**
19562
+ * Verify initialization was successful
19563
+ */
19564
+ verifyInitialization() {
19565
+ const elementsFound = this.getElementCounts();
19566
+ const totalElements = Object.values(elementsFound).reduce((sum, count) => sum + count, 0);
19567
+ const hasActiveHandlers = this.delegationHandlers.size > 0;
19568
+ if (this.config.debug) {
19569
+ console.log('[🔍 AutoTracker] Verification:', {
19570
+ isInitialized: this.isInitialized,
19571
+ elementsFound: totalElements,
19572
+ hasActiveHandlers,
19573
+ handlersActive: Array.from(this.delegationHandlers.keys())
19574
+ });
19575
+ }
19576
+ return this.isInitialized && (totalElements > 0 || hasActiveHandlers);
19577
+ }
19578
+ initializeTracking() {
19579
+ if (this.config.debug) {
19580
+ console.log("[Zaplier AutoTracker] Initializing tracking...");
19397
19581
  }
19398
19582
  // Observer para novos elementos no DOM
19399
19583
  this.observeDOM();
@@ -19403,42 +19587,422 @@ class AutoTracker {
19403
19587
  if (this.config.trackViews) {
19404
19588
  this.setupViewTracking();
19405
19589
  }
19406
- // Setup global event listeners
19590
+ // Setup event delegation (modern approach)
19591
+ this.setupEventDelegation();
19592
+ // Debug: Log elementos encontrados após inicialização
19593
+ if (this.config.debug) {
19594
+ this.logFoundElements();
19595
+ // Também agendar uma verificação após delay para elementos React
19596
+ setTimeout(() => {
19597
+ console.log("[Zaplier AutoTracker] Re-checking elements after 500ms...");
19598
+ this.logFoundElements();
19599
+ }, 500);
19600
+ setTimeout(() => {
19601
+ console.log("[Zaplier AutoTracker] Re-checking elements after 2s...");
19602
+ this.logFoundElements();
19603
+ }, 2000);
19604
+ }
19605
+ }
19606
+ logFoundElements() {
19607
+ const clickElements = document.querySelectorAll('[data-track-click]');
19608
+ const scrollElements = document.querySelectorAll('[data-track-scroll]');
19609
+ const viewElements = document.querySelectorAll('[data-track-view]');
19610
+ const hoverElements = document.querySelectorAll('[data-track-hover]');
19611
+ const formElements = document.querySelectorAll('[data-track-form]');
19612
+ console.log("[Zaplier AutoTracker] Elementos encontrados:", {
19613
+ clickElements: clickElements.length,
19614
+ scrollElements: scrollElements.length,
19615
+ viewElements: viewElements.length,
19616
+ hoverElements: hoverElements.length,
19617
+ formElements: formElements.length,
19618
+ totalElements: clickElements.length + scrollElements.length + viewElements.length + hoverElements.length + formElements.length
19619
+ });
19620
+ if (scrollElements.length > 0) {
19621
+ console.log("[Zaplier AutoTracker] Elementos de scroll:", Array.from(scrollElements).map(el => ({
19622
+ tagName: el.tagName,
19623
+ trackEvent: el.getAttribute('data-track-scroll'),
19624
+ threshold: el.getAttribute('data-scroll-threshold') || '0.5',
19625
+ id: el.id || 'no-id',
19626
+ classes: el.className || 'no-classes'
19627
+ })));
19628
+ }
19629
+ }
19630
+ /**
19631
+ * Setup Event Delegation (Industry Best Practice)
19632
+ * Uses single document-level listeners instead of individual element listeners
19633
+ */
19634
+ setupEventDelegation() {
19635
+ if (this.config.debug) {
19636
+ console.log("[🚀 AutoTracker] Setting up enhanced event delegation...");
19637
+ }
19638
+ // Determine optimal delegation target based on React setup
19639
+ const delegationTarget = this.getOptimalDelegationTarget();
19640
+ if (this.config.debug) {
19641
+ console.log("[🎯 AutoTracker] Using delegation target:", delegationTarget);
19642
+ }
19643
+ // Click delegation with React compatibility
19407
19644
  if (this.config.trackClicks) {
19408
- document.addEventListener("click", this.handleClick, { passive: true });
19645
+ const clickHandler = this.createReactCompatibleClickHandler();
19646
+ this.delegationHandlers.set('click', clickHandler);
19647
+ delegationTarget.addEventListener('click', clickHandler, this.getEventOptions('click'));
19409
19648
  }
19649
+ // Scroll delegation with enhanced detection
19410
19650
  if (this.config.trackScrolls) {
19411
- document.addEventListener("scroll", this.handleScroll, { passive: true });
19651
+ const scrollHandler = this.createEnhancedScrollHandler();
19652
+ this.delegationHandlers.set('scroll', scrollHandler);
19653
+ document.addEventListener('scroll', scrollHandler, this.getEventOptions('scroll'));
19654
+ }
19655
+ // Setup debounced mutation observer for dynamic content
19656
+ this.setupIntelligentMutationObserver();
19657
+ this.isInitialized = true;
19658
+ if (this.config.debug) {
19659
+ console.log("[✅ AutoTracker] Event delegation setup complete");
19412
19660
  }
19413
19661
  }
19414
19662
  /**
19415
- * Parar auto tracking
19663
+ * Get optimal delegation target based on React setup
19416
19664
  */
19417
- stop() {
19418
- document.removeEventListener("click", this.handleClick);
19419
- document.removeEventListener("scroll", this.handleScroll);
19420
- if (this.intersectionObserver) {
19421
- this.intersectionObserver.disconnect();
19665
+ getOptimalDelegationTarget() {
19666
+ // For React 17+, prefer root container over document
19667
+ const reactRoot = document.querySelector('#root') ||
19668
+ document.querySelector('#__next') ||
19669
+ document.querySelector('[data-reactroot]');
19670
+ if (reactRoot && this.diagnostics.environment?.reactDetected) {
19671
+ if (this.config.debug) {
19672
+ console.log("[⚛️ AutoTracker] Using React root for delegation:", reactRoot);
19673
+ }
19674
+ return reactRoot;
19422
19675
  }
19423
- this.observedElements.clear();
19676
+ return document;
19424
19677
  }
19425
19678
  /**
19426
- * Observar mudanças no DOM para novos elementos
19679
+ * Get optimized event options for different browsers
19427
19680
  */
19428
- observeDOM() {
19429
- const observer = new MutationObserver((mutations) => {
19430
- mutations.forEach((mutation) => {
19431
- mutation.addedNodes.forEach((node) => {
19681
+ getEventOptions(eventType) {
19682
+ this.diagnostics.environment?.isMobile;
19683
+ const isSafari = this.diagnostics.environment?.isSafari;
19684
+ let options = {};
19685
+ switch (eventType) {
19686
+ case 'click':
19687
+ // Safari iOS requires non-passive click events for proper delegation
19688
+ options = {
19689
+ passive: !isSafari,
19690
+ capture: false
19691
+ };
19692
+ break;
19693
+ case 'scroll':
19694
+ options = {
19695
+ passive: true,
19696
+ capture: false
19697
+ };
19698
+ break;
19699
+ default:
19700
+ options = { passive: true };
19701
+ }
19702
+ if (this.config.debug) {
19703
+ console.log(`[⚙️ AutoTracker] Event options for ${eventType}:`, options);
19704
+ }
19705
+ return options;
19706
+ }
19707
+ /**
19708
+ * Create React-compatible click handler
19709
+ */
19710
+ createReactCompatibleClickHandler() {
19711
+ return (event) => {
19712
+ try {
19713
+ if (!this.config.trackClicks)
19714
+ return;
19715
+ // Enhanced target detection for nested React components
19716
+ const target = event.target;
19717
+ if (!target)
19718
+ return;
19719
+ // Multiple strategies to find tracking element
19720
+ let trackingElement = null;
19721
+ // Strategy 1: Direct closest (most common)
19722
+ trackingElement = target.closest('[data-track-click]');
19723
+ // Strategy 2: Check if target itself has the attribute (React edge case)
19724
+ if (!trackingElement && target.hasAttribute && target.hasAttribute('data-track-click')) {
19725
+ trackingElement = target;
19726
+ }
19727
+ // Strategy 3: Walk up DOM tree manually (React portal handling)
19728
+ if (!trackingElement) {
19729
+ trackingElement = this.findTrackingElementManually(target, 'data-track-click');
19730
+ }
19731
+ if (!trackingElement) {
19732
+ if (this.config.debug) {
19733
+ console.log('[🔍 AutoTracker] No tracking element found for click:', {
19734
+ target: target.tagName,
19735
+ hasClosest: typeof target.closest === 'function',
19736
+ hasAttribute: typeof target.hasAttribute === 'function'
19737
+ });
19738
+ }
19739
+ return;
19740
+ }
19741
+ const eventName = trackingElement.getAttribute('data-track-click');
19742
+ if (!eventName)
19743
+ return;
19744
+ const metadata = this.extractMetadata(trackingElement);
19745
+ this.trackEvent(eventName, {
19746
+ type: 'click',
19747
+ element: trackingElement.tagName.toLowerCase(),
19748
+ reactCompatible: true,
19749
+ ...metadata,
19750
+ });
19751
+ if (this.config.debug) {
19752
+ console.log(`[🎯 AutoTracker] Click tracked (React-compatible): ${eventName}`, {
19753
+ element: trackingElement.tagName.toLowerCase(),
19754
+ strategy: 'enhanced_delegation',
19755
+ ...metadata
19756
+ });
19757
+ }
19758
+ }
19759
+ catch (error) {
19760
+ if (this.config.debug) {
19761
+ console.error('[❌ AutoTracker] Click handler error:', error);
19762
+ }
19763
+ }
19764
+ };
19765
+ }
19766
+ /**
19767
+ * Manually walk up DOM tree to find tracking element
19768
+ */
19769
+ findTrackingElementManually(element, attribute, maxDepth = 10) {
19770
+ let current = element;
19771
+ let depth = 0;
19772
+ while (current && current !== document.documentElement && depth < maxDepth) {
19773
+ if (current.hasAttribute && current.hasAttribute(attribute)) {
19774
+ return current;
19775
+ }
19776
+ current = current.parentElement;
19777
+ depth++;
19778
+ }
19779
+ return null;
19780
+ }
19781
+ /**
19782
+ * Create enhanced scroll handler with better performance
19783
+ */
19784
+ createEnhancedScrollHandler() {
19785
+ return () => {
19786
+ try {
19787
+ if (!this.config.trackScrolls)
19788
+ return;
19789
+ // Adaptive throttling based on scroll frequency
19790
+ const now = Date.now();
19791
+ const timeDelta = now - this.scrollThrottle;
19792
+ const throttleDelay = this.diagnostics.environment?.isMobile ? 150 : 100;
19793
+ if (timeDelta < throttleDelay)
19794
+ return;
19795
+ this.scrollThrottle = now;
19796
+ // Enhanced element finding with caching
19797
+ const scrollElements = this.getCachedScrollElements();
19798
+ if (this.config.debug && scrollElements.length > 0) {
19799
+ console.log(`[📜 AutoTracker] Processing ${scrollElements.length} scroll elements`, {
19800
+ throttleDelay,
19801
+ timeDelta,
19802
+ scrollY: window.scrollY
19803
+ });
19804
+ }
19805
+ // Process elements with enhanced visibility detection
19806
+ scrollElements.forEach((element) => {
19807
+ this.processScrollElementEnhanced(element);
19808
+ });
19809
+ }
19810
+ catch (error) {
19811
+ if (this.config.debug) {
19812
+ console.error('[❌ AutoTracker] Scroll handler error:', error);
19813
+ }
19814
+ }
19815
+ };
19816
+ }
19817
+ getCachedScrollElements() {
19818
+ const now = Date.now();
19819
+ const cacheExpiry = 5000; // 5 seconds
19820
+ if (!this.cachedScrollElements || (now - this.lastScrollElementsCheck) > cacheExpiry) {
19821
+ this.cachedScrollElements = document.querySelectorAll('[data-track-scroll]');
19822
+ this.lastScrollElementsCheck = now;
19823
+ if (this.config.debug) {
19824
+ console.log(`[🔄 AutoTracker] Refreshed scroll elements cache: ${this.cachedScrollElements.length} elements`);
19825
+ }
19826
+ }
19827
+ return this.cachedScrollElements;
19828
+ }
19829
+ /**
19830
+ * Process scroll element with enhanced detection
19831
+ */
19832
+ processScrollElementEnhanced(element) {
19833
+ try {
19834
+ const hasTriggered = element.getAttribute('data-scroll-triggered') === 'true';
19835
+ if (hasTriggered)
19836
+ return;
19837
+ const threshold = parseFloat(element.getAttribute('data-scroll-threshold') || '0.5');
19838
+ // Enhanced visibility calculation
19839
+ const rect = element.getBoundingClientRect();
19840
+ const elementHeight = rect.height;
19841
+ const windowHeight = window.innerHeight;
19842
+ // Handle edge cases
19843
+ if (elementHeight <= 0)
19844
+ return;
19845
+ const visibleTop = Math.max(rect.top, 0);
19846
+ const visibleBottom = Math.min(rect.bottom, windowHeight);
19847
+ const visibleHeight = Math.max(0, visibleBottom - visibleTop);
19848
+ const visibilityRatio = visibleHeight / elementHeight;
19849
+ if (this.config.debug) {
19850
+ console.log(`[📊 AutoTracker] Enhanced scroll check:`, {
19851
+ elementId: element.id || element.className || 'no-id',
19852
+ rect: {
19853
+ top: Math.round(rect.top),
19854
+ bottom: Math.round(rect.bottom),
19855
+ height: Math.round(elementHeight)
19856
+ },
19857
+ visibility: {
19858
+ ratio: Math.round(visibilityRatio * 1000) / 1000,
19859
+ threshold,
19860
+ triggered: hasTriggered
19861
+ },
19862
+ scroll: window.scrollY
19863
+ });
19864
+ }
19865
+ if (visibilityRatio >= threshold) {
19866
+ element.setAttribute('data-scroll-triggered', 'true');
19867
+ const eventName = element.getAttribute('data-track-scroll');
19868
+ if (!eventName)
19869
+ return;
19870
+ const metadata = this.extractMetadata(element);
19871
+ this.trackEvent(eventName, {
19872
+ type: 'scroll',
19873
+ element: element.tagName.toLowerCase(),
19874
+ threshold,
19875
+ scrollDepth: window.scrollY,
19876
+ visibilityRatio: Math.round(visibilityRatio * 1000) / 1000,
19877
+ enhanced: true,
19878
+ ...metadata,
19879
+ });
19880
+ if (this.config.debug) {
19881
+ console.log(`[🎯 AutoTracker] Scroll tracked (enhanced): ${eventName}`, {
19882
+ threshold,
19883
+ visibilityRatio: Math.round(visibilityRatio * 1000) / 1000,
19884
+ scrollDepth: window.scrollY,
19885
+ ...metadata
19886
+ });
19887
+ }
19888
+ }
19889
+ }
19890
+ catch (error) {
19891
+ if (this.config.debug) {
19892
+ console.error('[❌ AutoTracker] Scroll element processing error:', error);
19893
+ }
19894
+ }
19895
+ }
19896
+ /**
19897
+ * Legacy method for compatibility
19898
+ */
19899
+ processScrollElement(element) {
19900
+ this.processScrollElementEnhanced(element);
19901
+ }
19902
+ /**
19903
+ * Setup intelligent mutation observer (debounced for performance)
19904
+ */
19905
+ setupIntelligentMutationObserver() {
19906
+ if (this.mutationObserver)
19907
+ return; // Already setup
19908
+ const debouncedCallback = this.debounce((mutations) => {
19909
+ let hasRelevantChanges = false;
19910
+ mutations.forEach(mutation => {
19911
+ mutation.addedNodes.forEach(node => {
19432
19912
  if (node.nodeType === Node.ELEMENT_NODE) {
19433
- this.processElement(node);
19913
+ const element = node;
19914
+ if (this.hasTrackingAttributes(element) || this.hasTrackingDescendants(element)) {
19915
+ hasRelevantChanges = true;
19916
+ if (this.config.debug) {
19917
+ console.log('[AutoTracker] New trackable element detected via mutation observer:', element);
19918
+ }
19919
+ }
19434
19920
  }
19435
19921
  });
19436
19922
  });
19437
- });
19438
- observer.observe(document.body, {
19923
+ if (hasRelevantChanges) {
19924
+ if (this.config.debug) {
19925
+ console.log('[AutoTracker] Processing new elements from mutations...');
19926
+ }
19927
+ // Process elements for view tracking (only thing that needs setup)
19928
+ this.processExistingElements();
19929
+ }
19930
+ }, 150);
19931
+ this.mutationObserver = new MutationObserver(debouncedCallback);
19932
+ this.mutationObserver.observe(document.body, {
19439
19933
  childList: true,
19440
19934
  subtree: true,
19441
19935
  });
19936
+ if (this.config.debug) {
19937
+ console.log('[AutoTracker] Intelligent mutation observer setup complete');
19938
+ }
19939
+ }
19940
+ /**
19941
+ * Check if element has tracking attributes
19942
+ */
19943
+ hasTrackingAttributes(element) {
19944
+ return element.hasAttribute && (element.hasAttribute('data-track-click') ||
19945
+ element.hasAttribute('data-track-scroll') ||
19946
+ element.hasAttribute('data-track-view') ||
19947
+ element.hasAttribute('data-track-hover') ||
19948
+ element.hasAttribute('data-track-form'));
19949
+ }
19950
+ /**
19951
+ * Check if element has tracking descendants
19952
+ */
19953
+ hasTrackingDescendants(element) {
19954
+ if (!element.querySelector)
19955
+ return false;
19956
+ return !!(element.querySelector('[data-track-click]') ||
19957
+ element.querySelector('[data-track-scroll]') ||
19958
+ element.querySelector('[data-track-view]') ||
19959
+ element.querySelector('[data-track-hover]') ||
19960
+ element.querySelector('[data-track-form]'));
19961
+ }
19962
+ /**
19963
+ * Utility: Debounce function for performance optimization
19964
+ */
19965
+ debounce(func, wait) {
19966
+ let timeout;
19967
+ return (...args) => {
19968
+ clearTimeout(timeout);
19969
+ timeout = setTimeout(() => func.apply(this, args), wait);
19970
+ };
19971
+ }
19972
+ /**
19973
+ * Parar auto tracking (with proper cleanup)
19974
+ */
19975
+ stop() {
19976
+ if (this.config.debug) {
19977
+ console.log('[AutoTracker] Stopping auto tracking and cleaning up...');
19978
+ }
19979
+ // Remove delegation handlers
19980
+ this.delegationHandlers.forEach((handler, eventType) => {
19981
+ document.removeEventListener(eventType, handler);
19982
+ });
19983
+ this.delegationHandlers.clear();
19984
+ // Disconnect observers
19985
+ if (this.intersectionObserver) {
19986
+ this.intersectionObserver.disconnect();
19987
+ this.intersectionObserver = undefined;
19988
+ }
19989
+ if (this.mutationObserver) {
19990
+ this.mutationObserver.disconnect();
19991
+ this.mutationObserver = undefined;
19992
+ }
19993
+ // Clear caches
19994
+ this.observedElements.clear();
19995
+ this.isInitialized = false;
19996
+ }
19997
+ /**
19998
+ * Legacy method - now handled by intelligent mutation observer
19999
+ * @deprecated Use setupIntelligentMutationObserver instead
20000
+ */
20001
+ observeDOM() {
20002
+ // This is now handled by setupIntelligentMutationObserver in setupEventDelegation
20003
+ if (this.config.debug) {
20004
+ console.log('[AutoTracker] observeDOM() is deprecated - using intelligent mutation observer');
20005
+ }
19442
20006
  }
19443
20007
  /**
19444
20008
  * Processar elementos já existentes no DOM
@@ -19625,23 +20189,194 @@ class AutoTracker {
19625
20189
  /**
19626
20190
  * Enviar evento para o SDK
19627
20191
  */
20192
+ /**
20193
+ * Enhanced event tracking with comprehensive debugging
20194
+ */
19628
20195
  trackEvent(eventName, metadata) {
19629
- if (!this.sdkInstance)
20196
+ const trackingStart = performance.now();
20197
+ if (this.config.debug) {
20198
+ console.log("[🚀 AutoTracker] Enhanced trackEvent called:", {
20199
+ eventName,
20200
+ metadata,
20201
+ timestamp: new Date().toISOString()
20202
+ });
20203
+ }
20204
+ // Comprehensive validation
20205
+ const validation = this.validateTrackingCall(eventName, metadata);
20206
+ if (!validation.isValid) {
20207
+ if (this.config.debug) {
20208
+ console.error("[❌ AutoTracker] Validation failed:", validation.errors);
20209
+ }
19630
20210
  return;
20211
+ }
19631
20212
  try {
19632
- this.sdkInstance.trackCustomEvent(eventName, {
20213
+ const eventData = {
20214
+ // Core tracking data
19633
20215
  autoTracked: true,
20216
+ enhanced: true,
19634
20217
  timestamp: Date.now(),
19635
20218
  url: window.location.href,
20219
+ userAgent: navigator.userAgent,
20220
+ // Performance data
20221
+ trackingLatency: Math.round((performance.now() - trackingStart) * 100) / 100,
20222
+ // Environment data
20223
+ isReactApp: this.diagnostics.environment?.reactDetected,
20224
+ isMobile: this.diagnostics.environment?.isMobile,
20225
+ // Event metadata
19636
20226
  ...metadata,
19637
- });
20227
+ };
20228
+ if (this.config.debug) {
20229
+ console.log("[📡 AutoTracker] Sending enhanced event:", { eventName, eventData });
20230
+ }
20231
+ const result = this.sdkInstance.trackCustomEvent(eventName, eventData);
20232
+ if (this.config.debug) {
20233
+ const trackingEnd = performance.now();
20234
+ console.log("[✅ AutoTracker] Event sent successfully:", {
20235
+ eventName,
20236
+ result,
20237
+ totalLatency: Math.round((trackingEnd - trackingStart) * 100) / 100 + 'ms'
20238
+ });
20239
+ }
19638
20240
  }
19639
20241
  catch (error) {
19640
20242
  if (this.config.debug) {
19641
- console.error("[AutoTracker] Error sending event:", error);
20243
+ console.error("[💥 AutoTracker] Critical tracking error:", {
20244
+ eventName,
20245
+ error: error instanceof Error ? error.message : error,
20246
+ stack: error instanceof Error ? error.stack : undefined,
20247
+ metadata
20248
+ });
19642
20249
  }
19643
20250
  }
19644
20251
  }
20252
+ /**
20253
+ * Validate tracking call before sending
20254
+ */
20255
+ validateTrackingCall(eventName, metadata) {
20256
+ const errors = [];
20257
+ if (!eventName || typeof eventName !== 'string') {
20258
+ errors.push('Event name is required and must be a string');
20259
+ }
20260
+ if (!this.sdkInstance) {
20261
+ errors.push('SDK instance is null or undefined');
20262
+ }
20263
+ if (this.sdkInstance && typeof this.sdkInstance.trackCustomEvent !== 'function') {
20264
+ errors.push(`trackCustomEvent is not a function: ${typeof this.sdkInstance.trackCustomEvent}`);
20265
+ }
20266
+ if (!window || typeof window !== 'object') {
20267
+ errors.push('Window object is not available');
20268
+ }
20269
+ return {
20270
+ isValid: errors.length === 0,
20271
+ errors
20272
+ };
20273
+ }
20274
+ /**
20275
+ * Modern API: Refresh tracking for dynamic content (React/SPA support)
20276
+ * Industry best practice for SPA route changes
20277
+ */
20278
+ refreshTracking() {
20279
+ if (!this.isInitialized) {
20280
+ if (this.config.debug) {
20281
+ console.log('[AutoTracker] Not initialized, cannot refresh tracking');
20282
+ }
20283
+ return;
20284
+ }
20285
+ if (this.config.debug) {
20286
+ console.log('[AutoTracker] Refreshing tracking for dynamic content...');
20287
+ }
20288
+ // Re-process elements for view tracking (only thing that needs setup)
20289
+ this.processExistingElements();
20290
+ // Log current state
20291
+ if (this.config.debug) {
20292
+ this.logFoundElements();
20293
+ }
20294
+ }
20295
+ /**
20296
+ * Legacy method - use refreshTracking() instead
20297
+ * @deprecated Use refreshTracking() for better performance
20298
+ */
20299
+ rescan() {
20300
+ this.refreshTracking();
20301
+ }
20302
+ /**
20303
+ * Get comprehensive diagnostic information for debugging
20304
+ */
20305
+ getDiagnostics() {
20306
+ return {
20307
+ // Initialization status
20308
+ initialization: {
20309
+ isInitialized: this.isInitialized,
20310
+ attempts: this.initializationAttempts,
20311
+ maxAttempts: this.maxInitializationAttempts,
20312
+ timestamp: new Date().toISOString()
20313
+ },
20314
+ // Event handlers status
20315
+ eventHandlers: {
20316
+ hasClickHandler: this.delegationHandlers.has('click'),
20317
+ hasScrollHandler: this.delegationHandlers.has('scroll'),
20318
+ activeHandlers: Array.from(this.delegationHandlers.keys()),
20319
+ delegationTarget: this.getOptimalDelegationTarget() === document ? 'document' : 'react-root'
20320
+ },
20321
+ // Observers status
20322
+ observers: {
20323
+ observedElements: this.observedElements.size,
20324
+ hasMutationObserver: !!this.mutationObserver,
20325
+ hasIntersectionObserver: !!this.intersectionObserver,
20326
+ scrollElementsCache: !!this.cachedScrollElements,
20327
+ cacheExpiry: new Date(this.lastScrollElementsCheck + 5000).toISOString()
20328
+ },
20329
+ // Configuration
20330
+ config: this.config,
20331
+ // Current environment diagnostics
20332
+ environment: this.diagnostics.environment || {},
20333
+ // DOM state
20334
+ dom: this.diagnostics.dom || {},
20335
+ // SDK state
20336
+ tracking: this.diagnostics.tracking || {},
20337
+ // Real-time element detection
20338
+ elementsFound: this.getElementCounts(),
20339
+ // Performance metrics
20340
+ performance: {
20341
+ lastScrollThrottle: this.scrollThrottle,
20342
+ cacheLastUpdate: this.lastScrollElementsCheck
20343
+ },
20344
+ // Validation status
20345
+ validation: this.validateTrackingCall('test', {}),
20346
+ // Debug recommendations
20347
+ recommendations: this.generateRecommendations()
20348
+ };
20349
+ }
20350
+ /**
20351
+ * Generate diagnostic recommendations
20352
+ */
20353
+ generateRecommendations() {
20354
+ const recommendations = [];
20355
+ const elementsFound = this.getElementCounts();
20356
+ const totalElements = Object.values(elementsFound).reduce((sum, count) => sum + count, 0);
20357
+ if (!this.isInitialized) {
20358
+ recommendations.push('AutoTracker is not initialized. Check console for errors.');
20359
+ }
20360
+ if (totalElements === 0) {
20361
+ recommendations.push('No trackable elements found. Ensure data-track-* attributes are present in the DOM.');
20362
+ }
20363
+ if (!this.sdkInstance) {
20364
+ recommendations.push('SDK instance is missing. Ensure the SDK is properly initialized.');
20365
+ }
20366
+ if (this.sdkInstance && typeof this.sdkInstance.trackCustomEvent !== 'function') {
20367
+ recommendations.push('trackCustomEvent method is not available on SDK instance.');
20368
+ }
20369
+ if (!this.diagnostics.environment?.reactDetected && window.location.hostname.includes('localhost')) {
20370
+ recommendations.push('React not detected. If using React, ensure proper setup and try refreshTracking() after route changes.');
20371
+ }
20372
+ if (this.diagnostics.environment?.isMobile && !this.delegationHandlers.has('scroll')) {
20373
+ recommendations.push('Mobile device detected but scroll tracking not active. Check scroll tracking configuration.');
20374
+ }
20375
+ if (this.initializationAttempts > 1) {
20376
+ recommendations.push(`AutoTracker required ${this.initializationAttempts} initialization attempts. Consider adding delay before enabling.`);
20377
+ }
20378
+ return recommendations;
20379
+ }
19645
20380
  /**
19646
20381
  * Configurar tracking
19647
20382
  */
@@ -19649,12 +20384,14 @@ class AutoTracker {
19649
20384
  this.config = { ...this.config, ...config };
19650
20385
  }
19651
20386
  /**
19652
- * Obter estatísticas
20387
+ * Enhanced stats with diagnostic information
19653
20388
  */
19654
20389
  getStats() {
19655
20390
  return {
19656
20391
  observedElements: this.observedElements.size,
19657
20392
  config: this.config,
20393
+ isModern: this.isInitialized, // Indicates if using new delegation system
20394
+ diagnostics: this.config.debug ? this.getDiagnostics() : undefined
19658
20395
  };
19659
20396
  }
19660
20397
  }
@@ -20203,6 +20940,36 @@ class ZaplierSDK {
20203
20940
  console.log("[Zaplier] Auto tracking configured:", config);
20204
20941
  }
20205
20942
  },
20943
+ /**
20944
+ * Refresh tracking for dynamic content (React/SPA route changes)
20945
+ * Modern alternative to rescan() - follows GA4/industry best practices
20946
+ */
20947
+ refreshTracking: () => {
20948
+ if (this.autoTracker) {
20949
+ this.autoTracker.refreshTracking();
20950
+ }
20951
+ if (this.config.debug) {
20952
+ console.log("[Zaplier] Auto tracking refreshed");
20953
+ }
20954
+ },
20955
+ /**
20956
+ * Legacy rescan method - use refreshTracking() instead
20957
+ * @deprecated Use refreshTracking() for better performance
20958
+ */
20959
+ rescan: () => {
20960
+ if (this.autoTracker) {
20961
+ this.autoTracker.rescan();
20962
+ }
20963
+ if (this.config.debug) {
20964
+ console.log("[Zaplier] Auto tracking rescanned (deprecated - use refreshTracking)");
20965
+ }
20966
+ },
20967
+ /**
20968
+ * Get diagnostic information for debugging
20969
+ */
20970
+ getDiagnostics: () => {
20971
+ return this.autoTracker ? this.autoTracker.getDiagnostics() : null;
20972
+ },
20206
20973
  getStats: () => {
20207
20974
  return this.autoTracker ? this.autoTracker.getStats() : null;
20208
20975
  },