@zaplier/sdk 1.6.9 → 1.7.0

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.d.ts CHANGED
@@ -932,9 +932,25 @@ declare class ZaplierSDK implements ZaplierSDK$1 {
932
932
  trackHovers?: boolean;
933
933
  trackForms?: boolean;
934
934
  }) => void;
935
+ /**
936
+ * Refresh tracking for dynamic content (React/SPA route changes)
937
+ * Modern alternative to rescan() - follows GA4/industry best practices
938
+ */
939
+ refreshTracking: () => void;
940
+ /**
941
+ * Legacy rescan method - use refreshTracking() instead
942
+ * @deprecated Use refreshTracking() for better performance
943
+ */
944
+ rescan: () => void;
945
+ /**
946
+ * Get diagnostic information for debugging
947
+ */
948
+ getDiagnostics: () => object | null;
935
949
  getStats: () => {
936
950
  observedElements: number;
937
951
  config: AutoTrackerConfig;
952
+ isModern: boolean;
953
+ diagnostics?: object;
938
954
  } | null;
939
955
  isEnabled: () => boolean;
940
956
  };
@@ -1119,6 +1135,8 @@ declare const Zaplier: {
1119
1135
  getStats: () => {
1120
1136
  observedElements: number;
1121
1137
  config: AutoTrackerConfig;
1138
+ isModern: boolean;
1139
+ diagnostics?: object;
1122
1140
  } | null;
1123
1141
  isEnabled: () => boolean;
1124
1142
  };
package/dist/index.esm.js CHANGED
@@ -19262,6 +19262,8 @@ class AutoTracker {
19262
19262
  constructor(sdkInstance, config = {}) {
19263
19263
  this.observedElements = new Set();
19264
19264
  this.scrollThrottle = 0;
19265
+ this.isInitialized = false;
19266
+ this.delegationHandlers = new Map();
19265
19267
  this.handleScroll = () => {
19266
19268
  if (!this.config.trackScrolls)
19267
19269
  return;
@@ -19366,30 +19368,29 @@ class AutoTracker {
19366
19368
  trackScrolls: this.config.trackScrolls,
19367
19369
  trackViews: this.config.trackViews,
19368
19370
  trackHovers: this.config.trackHovers,
19369
- trackForms: this.config.trackForms
19371
+ trackForms: this.config.trackForms,
19372
+ domReady: document.readyState
19370
19373
  });
19371
- // Log elementos existentes encontrados
19372
- setTimeout(() => {
19373
- const clickElements = document.querySelectorAll('[data-track-click]');
19374
- const scrollElements = document.querySelectorAll('[data-track-scroll]');
19375
- const viewElements = document.querySelectorAll('[data-track-view]');
19376
- const hoverElements = document.querySelectorAll('[data-track-hover]');
19377
- const formElements = document.querySelectorAll('[data-track-form]');
19378
- console.log("[Zaplier AutoTracker] Elementos encontrados:", {
19379
- clickElements: clickElements.length,
19380
- scrollElements: scrollElements.length,
19381
- viewElements: viewElements.length,
19382
- hoverElements: hoverElements.length,
19383
- formElements: formElements.length
19384
- });
19385
- if (scrollElements.length > 0) {
19386
- console.log("[Zaplier AutoTracker] Elementos de scroll:", Array.from(scrollElements).map(el => ({
19387
- tagName: el.tagName,
19388
- trackEvent: el.getAttribute('data-track-scroll'),
19389
- threshold: el.getAttribute('data-scroll-threshold') || '0.5'
19390
- })));
19374
+ }
19375
+ // Aguardar DOM ready se necessário
19376
+ if (document.readyState === 'loading') {
19377
+ if (this.config.debug) {
19378
+ console.log("[Zaplier AutoTracker] DOM still loading, waiting for DOMContentLoaded...");
19379
+ }
19380
+ document.addEventListener('DOMContentLoaded', () => {
19381
+ if (this.config.debug) {
19382
+ console.log("[Zaplier AutoTracker] DOMContentLoaded fired, initializing tracking...");
19391
19383
  }
19392
- }, 100);
19384
+ this.initializeTracking();
19385
+ }, { once: true });
19386
+ }
19387
+ else {
19388
+ this.initializeTracking();
19389
+ }
19390
+ }
19391
+ initializeTracking() {
19392
+ if (this.config.debug) {
19393
+ console.log("[Zaplier AutoTracker] Initializing tracking...");
19393
19394
  }
19394
19395
  // Observer para novos elementos no DOM
19395
19396
  this.observeDOM();
@@ -19399,42 +19400,269 @@ class AutoTracker {
19399
19400
  if (this.config.trackViews) {
19400
19401
  this.setupViewTracking();
19401
19402
  }
19402
- // Setup global event listeners
19403
+ // Setup event delegation (modern approach)
19404
+ this.setupEventDelegation();
19405
+ // Debug: Log elementos encontrados após inicialização
19406
+ if (this.config.debug) {
19407
+ this.logFoundElements();
19408
+ // Também agendar uma verificação após delay para elementos React
19409
+ setTimeout(() => {
19410
+ console.log("[Zaplier AutoTracker] Re-checking elements after 500ms...");
19411
+ this.logFoundElements();
19412
+ }, 500);
19413
+ setTimeout(() => {
19414
+ console.log("[Zaplier AutoTracker] Re-checking elements after 2s...");
19415
+ this.logFoundElements();
19416
+ }, 2000);
19417
+ }
19418
+ }
19419
+ logFoundElements() {
19420
+ const clickElements = document.querySelectorAll('[data-track-click]');
19421
+ const scrollElements = document.querySelectorAll('[data-track-scroll]');
19422
+ const viewElements = document.querySelectorAll('[data-track-view]');
19423
+ const hoverElements = document.querySelectorAll('[data-track-hover]');
19424
+ const formElements = document.querySelectorAll('[data-track-form]');
19425
+ console.log("[Zaplier AutoTracker] Elementos encontrados:", {
19426
+ clickElements: clickElements.length,
19427
+ scrollElements: scrollElements.length,
19428
+ viewElements: viewElements.length,
19429
+ hoverElements: hoverElements.length,
19430
+ formElements: formElements.length,
19431
+ totalElements: clickElements.length + scrollElements.length + viewElements.length + hoverElements.length + formElements.length
19432
+ });
19433
+ if (scrollElements.length > 0) {
19434
+ console.log("[Zaplier AutoTracker] Elementos de scroll:", Array.from(scrollElements).map(el => ({
19435
+ tagName: el.tagName,
19436
+ trackEvent: el.getAttribute('data-track-scroll'),
19437
+ threshold: el.getAttribute('data-scroll-threshold') || '0.5',
19438
+ id: el.id || 'no-id',
19439
+ classes: el.className || 'no-classes'
19440
+ })));
19441
+ }
19442
+ }
19443
+ /**
19444
+ * Setup Event Delegation (Industry Best Practice)
19445
+ * Uses single document-level listeners instead of individual element listeners
19446
+ */
19447
+ setupEventDelegation() {
19448
+ if (this.config.debug) {
19449
+ console.log("[AutoTracker] Setting up event delegation...");
19450
+ }
19451
+ // Click delegation
19403
19452
  if (this.config.trackClicks) {
19404
- document.addEventListener("click", this.handleClick, { passive: true });
19453
+ const clickHandler = this.createDelegatedClickHandler();
19454
+ this.delegationHandlers.set('click', clickHandler);
19455
+ document.addEventListener('click', clickHandler, { passive: true });
19405
19456
  }
19457
+ // Scroll delegation (using global handler with element detection)
19406
19458
  if (this.config.trackScrolls) {
19407
- document.addEventListener("scroll", this.handleScroll, { passive: true });
19459
+ const scrollHandler = this.createDelegatedScrollHandler();
19460
+ this.delegationHandlers.set('scroll', scrollHandler);
19461
+ document.addEventListener('scroll', scrollHandler, { passive: true });
19408
19462
  }
19463
+ // Setup debounced mutation observer for dynamic content
19464
+ this.setupIntelligentMutationObserver();
19465
+ this.isInitialized = true;
19409
19466
  }
19410
19467
  /**
19411
- * Parar auto tracking
19468
+ * Create delegated click handler (no individual listeners needed)
19412
19469
  */
19413
- stop() {
19414
- document.removeEventListener("click", this.handleClick);
19415
- document.removeEventListener("scroll", this.handleScroll);
19416
- if (this.intersectionObserver) {
19417
- this.intersectionObserver.disconnect();
19470
+ createDelegatedClickHandler() {
19471
+ return (event) => {
19472
+ const target = event.target;
19473
+ if (!target || !this.config.trackClicks)
19474
+ return;
19475
+ // Check if target or any parent has data-track-click
19476
+ const trackingElement = target.closest('[data-track-click]');
19477
+ if (!trackingElement)
19478
+ return;
19479
+ const eventName = trackingElement.getAttribute('data-track-click');
19480
+ if (!eventName)
19481
+ return;
19482
+ const metadata = this.extractMetadata(trackingElement);
19483
+ this.trackEvent(eventName, {
19484
+ type: 'click',
19485
+ element: trackingElement.tagName.toLowerCase(),
19486
+ ...metadata,
19487
+ });
19488
+ if (this.config.debug) {
19489
+ console.log(`[AutoTracker] Click tracked via delegation: ${eventName}`, {
19490
+ element: trackingElement.tagName.toLowerCase(),
19491
+ ...metadata
19492
+ });
19493
+ }
19494
+ };
19495
+ }
19496
+ /**
19497
+ * Create delegated scroll handler (efficient global approach)
19498
+ */
19499
+ createDelegatedScrollHandler() {
19500
+ return () => {
19501
+ if (!this.config.trackScrolls)
19502
+ return;
19503
+ // Throttle scroll events for performance
19504
+ const now = Date.now();
19505
+ if (now - this.scrollThrottle < 100)
19506
+ return;
19507
+ this.scrollThrottle = now;
19508
+ // Find all scroll tracking elements and check visibility
19509
+ const scrollElements = document.querySelectorAll('[data-track-scroll]');
19510
+ if (this.config.debug && scrollElements.length > 0) {
19511
+ console.log(`[AutoTracker] Checking ${scrollElements.length} scroll elements via delegation`);
19512
+ }
19513
+ scrollElements.forEach((element) => {
19514
+ this.processScrollElement(element);
19515
+ });
19516
+ };
19517
+ }
19518
+ /**
19519
+ * Process individual scroll element (extracted for reusability)
19520
+ */
19521
+ processScrollElement(element) {
19522
+ const hasTriggered = element.getAttribute('data-scroll-triggered') === 'true';
19523
+ if (hasTriggered)
19524
+ return;
19525
+ const threshold = parseFloat(element.getAttribute('data-scroll-threshold') || '0.5');
19526
+ const rect = element.getBoundingClientRect();
19527
+ const elementHeight = rect.height;
19528
+ const visibleHeight = Math.min(rect.bottom, window.innerHeight) - Math.max(rect.top, 0);
19529
+ const visibilityRatio = Math.max(0, visibleHeight) / elementHeight;
19530
+ if (this.config.debug) {
19531
+ console.log(`[AutoTracker] Scroll element check via delegation:`, {
19532
+ elementId: element.id || element.className,
19533
+ visibilityRatio: Math.round(visibilityRatio * 100) / 100,
19534
+ threshold,
19535
+ triggered: hasTriggered
19536
+ });
19537
+ }
19538
+ if (visibilityRatio >= threshold) {
19539
+ element.setAttribute('data-scroll-triggered', 'true');
19540
+ const eventName = element.getAttribute('data-track-scroll');
19541
+ if (!eventName)
19542
+ return;
19543
+ const metadata = this.extractMetadata(element);
19544
+ this.trackEvent(eventName, {
19545
+ type: 'scroll',
19546
+ element: element.tagName.toLowerCase(),
19547
+ threshold,
19548
+ scrollDepth: window.scrollY,
19549
+ visibilityRatio: Math.round(visibilityRatio * 100) / 100,
19550
+ ...metadata,
19551
+ });
19552
+ if (this.config.debug) {
19553
+ console.log(`[AutoTracker] Scroll tracked via delegation: ${eventName}`, {
19554
+ threshold,
19555
+ visibilityRatio,
19556
+ scrollDepth: window.scrollY,
19557
+ ...metadata
19558
+ });
19559
+ }
19418
19560
  }
19419
- this.observedElements.clear();
19420
19561
  }
19421
19562
  /**
19422
- * Observar mudanças no DOM para novos elementos
19563
+ * Setup intelligent mutation observer (debounced for performance)
19423
19564
  */
19424
- observeDOM() {
19425
- const observer = new MutationObserver((mutations) => {
19426
- mutations.forEach((mutation) => {
19427
- mutation.addedNodes.forEach((node) => {
19565
+ setupIntelligentMutationObserver() {
19566
+ if (this.mutationObserver)
19567
+ return; // Already setup
19568
+ const debouncedCallback = this.debounce((mutations) => {
19569
+ let hasRelevantChanges = false;
19570
+ mutations.forEach(mutation => {
19571
+ mutation.addedNodes.forEach(node => {
19428
19572
  if (node.nodeType === Node.ELEMENT_NODE) {
19429
- this.processElement(node);
19573
+ const element = node;
19574
+ if (this.hasTrackingAttributes(element) || this.hasTrackingDescendants(element)) {
19575
+ hasRelevantChanges = true;
19576
+ if (this.config.debug) {
19577
+ console.log('[AutoTracker] New trackable element detected via mutation observer:', element);
19578
+ }
19579
+ }
19430
19580
  }
19431
19581
  });
19432
19582
  });
19433
- });
19434
- observer.observe(document.body, {
19583
+ if (hasRelevantChanges) {
19584
+ if (this.config.debug) {
19585
+ console.log('[AutoTracker] Processing new elements from mutations...');
19586
+ }
19587
+ // Process elements for view tracking (only thing that needs setup)
19588
+ this.processExistingElements();
19589
+ }
19590
+ }, 150);
19591
+ this.mutationObserver = new MutationObserver(debouncedCallback);
19592
+ this.mutationObserver.observe(document.body, {
19435
19593
  childList: true,
19436
19594
  subtree: true,
19437
19595
  });
19596
+ if (this.config.debug) {
19597
+ console.log('[AutoTracker] Intelligent mutation observer setup complete');
19598
+ }
19599
+ }
19600
+ /**
19601
+ * Check if element has tracking attributes
19602
+ */
19603
+ hasTrackingAttributes(element) {
19604
+ return element.hasAttribute && (element.hasAttribute('data-track-click') ||
19605
+ element.hasAttribute('data-track-scroll') ||
19606
+ element.hasAttribute('data-track-view') ||
19607
+ element.hasAttribute('data-track-hover') ||
19608
+ element.hasAttribute('data-track-form'));
19609
+ }
19610
+ /**
19611
+ * Check if element has tracking descendants
19612
+ */
19613
+ hasTrackingDescendants(element) {
19614
+ if (!element.querySelector)
19615
+ return false;
19616
+ return !!(element.querySelector('[data-track-click]') ||
19617
+ element.querySelector('[data-track-scroll]') ||
19618
+ element.querySelector('[data-track-view]') ||
19619
+ element.querySelector('[data-track-hover]') ||
19620
+ element.querySelector('[data-track-form]'));
19621
+ }
19622
+ /**
19623
+ * Utility: Debounce function for performance optimization
19624
+ */
19625
+ debounce(func, wait) {
19626
+ let timeout;
19627
+ return (...args) => {
19628
+ clearTimeout(timeout);
19629
+ timeout = setTimeout(() => func.apply(this, args), wait);
19630
+ };
19631
+ }
19632
+ /**
19633
+ * Parar auto tracking (with proper cleanup)
19634
+ */
19635
+ stop() {
19636
+ if (this.config.debug) {
19637
+ console.log('[AutoTracker] Stopping auto tracking and cleaning up...');
19638
+ }
19639
+ // Remove delegation handlers
19640
+ this.delegationHandlers.forEach((handler, eventType) => {
19641
+ document.removeEventListener(eventType, handler);
19642
+ });
19643
+ this.delegationHandlers.clear();
19644
+ // Disconnect observers
19645
+ if (this.intersectionObserver) {
19646
+ this.intersectionObserver.disconnect();
19647
+ this.intersectionObserver = undefined;
19648
+ }
19649
+ if (this.mutationObserver) {
19650
+ this.mutationObserver.disconnect();
19651
+ this.mutationObserver = undefined;
19652
+ }
19653
+ // Clear caches
19654
+ this.observedElements.clear();
19655
+ this.isInitialized = false;
19656
+ }
19657
+ /**
19658
+ * Legacy method - now handled by intelligent mutation observer
19659
+ * @deprecated Use setupIntelligentMutationObserver instead
19660
+ */
19661
+ observeDOM() {
19662
+ // This is now handled by setupIntelligentMutationObserver in setupEventDelegation
19663
+ if (this.config.debug) {
19664
+ console.log('[AutoTracker] observeDOM() is deprecated - using intelligent mutation observer');
19665
+ }
19438
19666
  }
19439
19667
  /**
19440
19668
  * Processar elementos já existentes no DOM
@@ -19622,15 +19850,35 @@ class AutoTracker {
19622
19850
  * Enviar evento para o SDK
19623
19851
  */
19624
19852
  trackEvent(eventName, metadata) {
19625
- if (!this.sdkInstance)
19853
+ if (this.config.debug) {
19854
+ console.log("[AutoTracker] trackEvent called:", { eventName, metadata });
19855
+ }
19856
+ if (!this.sdkInstance) {
19857
+ if (this.config.debug) {
19858
+ console.error("[AutoTracker] SDK instance is null!");
19859
+ }
19860
+ return;
19861
+ }
19862
+ if (typeof this.sdkInstance.trackCustomEvent !== 'function') {
19863
+ if (this.config.debug) {
19864
+ console.error("[AutoTracker] trackCustomEvent is not a function:", typeof this.sdkInstance.trackCustomEvent);
19865
+ }
19626
19866
  return;
19867
+ }
19627
19868
  try {
19628
- this.sdkInstance.trackCustomEvent(eventName, {
19869
+ const eventData = {
19629
19870
  autoTracked: true,
19630
19871
  timestamp: Date.now(),
19631
19872
  url: window.location.href,
19632
19873
  ...metadata,
19633
- });
19874
+ };
19875
+ if (this.config.debug) {
19876
+ console.log("[AutoTracker] Calling trackCustomEvent:", { eventName, eventData });
19877
+ }
19878
+ const result = this.sdkInstance.trackCustomEvent(eventName, eventData);
19879
+ if (this.config.debug) {
19880
+ console.log("[AutoTracker] trackCustomEvent result:", result);
19881
+ }
19634
19882
  }
19635
19883
  catch (error) {
19636
19884
  if (this.config.debug) {
@@ -19638,6 +19886,55 @@ class AutoTracker {
19638
19886
  }
19639
19887
  }
19640
19888
  }
19889
+ /**
19890
+ * Modern API: Refresh tracking for dynamic content (React/SPA support)
19891
+ * Industry best practice for SPA route changes
19892
+ */
19893
+ refreshTracking() {
19894
+ if (!this.isInitialized) {
19895
+ if (this.config.debug) {
19896
+ console.log('[AutoTracker] Not initialized, cannot refresh tracking');
19897
+ }
19898
+ return;
19899
+ }
19900
+ if (this.config.debug) {
19901
+ console.log('[AutoTracker] Refreshing tracking for dynamic content...');
19902
+ }
19903
+ // Re-process elements for view tracking (only thing that needs setup)
19904
+ this.processExistingElements();
19905
+ // Log current state
19906
+ if (this.config.debug) {
19907
+ this.logFoundElements();
19908
+ }
19909
+ }
19910
+ /**
19911
+ * Legacy method - use refreshTracking() instead
19912
+ * @deprecated Use refreshTracking() for better performance
19913
+ */
19914
+ rescan() {
19915
+ this.refreshTracking();
19916
+ }
19917
+ /**
19918
+ * Get diagnostic information for debugging
19919
+ */
19920
+ getDiagnostics() {
19921
+ return {
19922
+ isInitialized: this.isInitialized,
19923
+ hasClickHandler: this.delegationHandlers.has('click'),
19924
+ hasScrollHandler: this.delegationHandlers.has('scroll'),
19925
+ observedElements: this.observedElements.size,
19926
+ hasMutationObserver: !!this.mutationObserver,
19927
+ hasIntersectionObserver: !!this.intersectionObserver,
19928
+ config: this.config,
19929
+ elementsFound: {
19930
+ click: document.querySelectorAll('[data-track-click]').length,
19931
+ scroll: document.querySelectorAll('[data-track-scroll]').length,
19932
+ view: document.querySelectorAll('[data-track-view]').length,
19933
+ hover: document.querySelectorAll('[data-track-hover]').length,
19934
+ form: document.querySelectorAll('[data-track-form]').length
19935
+ }
19936
+ };
19937
+ }
19641
19938
  /**
19642
19939
  * Configurar tracking
19643
19940
  */
@@ -19645,12 +19942,14 @@ class AutoTracker {
19645
19942
  this.config = { ...this.config, ...config };
19646
19943
  }
19647
19944
  /**
19648
- * Obter estatísticas
19945
+ * Enhanced stats with diagnostic information
19649
19946
  */
19650
19947
  getStats() {
19651
19948
  return {
19652
19949
  observedElements: this.observedElements.size,
19653
19950
  config: this.config,
19951
+ isModern: this.isInitialized, // Indicates if using new delegation system
19952
+ diagnostics: this.config.debug ? this.getDiagnostics() : undefined
19654
19953
  };
19655
19954
  }
19656
19955
  }
@@ -20199,6 +20498,36 @@ class ZaplierSDK {
20199
20498
  console.log("[Zaplier] Auto tracking configured:", config);
20200
20499
  }
20201
20500
  },
20501
+ /**
20502
+ * Refresh tracking for dynamic content (React/SPA route changes)
20503
+ * Modern alternative to rescan() - follows GA4/industry best practices
20504
+ */
20505
+ refreshTracking: () => {
20506
+ if (this.autoTracker) {
20507
+ this.autoTracker.refreshTracking();
20508
+ }
20509
+ if (this.config.debug) {
20510
+ console.log("[Zaplier] Auto tracking refreshed");
20511
+ }
20512
+ },
20513
+ /**
20514
+ * Legacy rescan method - use refreshTracking() instead
20515
+ * @deprecated Use refreshTracking() for better performance
20516
+ */
20517
+ rescan: () => {
20518
+ if (this.autoTracker) {
20519
+ this.autoTracker.rescan();
20520
+ }
20521
+ if (this.config.debug) {
20522
+ console.log("[Zaplier] Auto tracking rescanned (deprecated - use refreshTracking)");
20523
+ }
20524
+ },
20525
+ /**
20526
+ * Get diagnostic information for debugging
20527
+ */
20528
+ getDiagnostics: () => {
20529
+ return this.autoTracker ? this.autoTracker.getDiagnostics() : null;
20530
+ },
20202
20531
  getStats: () => {
20203
20532
  return this.autoTracker ? this.autoTracker.getStats() : null;
20204
20533
  },