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