@checkflow/sdk 1.0.4 → 1.0.6

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.js CHANGED
@@ -612,14 +612,40 @@ async function captureScreenshot(options = {}) {
612
612
  }
613
613
  // Mask elements if specified
614
614
  const maskedElements = [];
615
- if (options.maskElements) {
616
- options.maskElements.forEach((selector) => {
617
- document.querySelectorAll(selector).forEach((el) => {
618
- maskedElements.push({ element: el, innerHTML: el.innerHTML });
619
- el.innerHTML = '••••••••';
620
- });
615
+ // Default sensitive selectors to mask
616
+ const defaultMaskSelectors = [
617
+ 'input[type="password"]',
618
+ 'input[type="email"]',
619
+ 'input[type="tel"]',
620
+ 'input[name*="password"]',
621
+ 'input[name*="email"]',
622
+ 'input[name*="phone"]',
623
+ 'input[placeholder*="password"]',
624
+ 'input[placeholder*="email"]',
625
+ 'textarea[name*="message"]',
626
+ '[data-sensitive]'
627
+ ];
628
+ const maskSelectors = [...defaultMaskSelectors, ...(options.maskElements || [])];
629
+ maskSelectors.forEach((selector) => {
630
+ document.querySelectorAll(selector).forEach((el) => {
631
+ maskedElements.push({ element: el, innerHTML: el.innerHTML });
632
+ // Mask based on element type
633
+ if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {
634
+ const input = el;
635
+ if (input.value) {
636
+ input.setAttribute('data-original-value', input.value);
637
+ input.value = '*'.repeat(Math.min(input.value.length, 8));
638
+ }
639
+ }
640
+ else {
641
+ // Mask text content
642
+ const text = el.textContent || '';
643
+ if (text.trim()) {
644
+ el.innerHTML = '*'.repeat(Math.min(text.length, 10));
645
+ }
646
+ }
621
647
  });
622
- }
648
+ });
623
649
  // Wait for delay if specified
624
650
  if (options.delay) {
625
651
  await new Promise((resolve) => setTimeout(resolve, options.delay));
@@ -639,7 +665,17 @@ async function captureScreenshot(options = {}) {
639
665
  });
640
666
  // Restore masked elements
641
667
  maskedElements.forEach(({ element, innerHTML }) => {
642
- element.innerHTML = innerHTML;
668
+ if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
669
+ const input = element;
670
+ const originalValue = input.getAttribute('data-original-value');
671
+ if (originalValue) {
672
+ input.value = originalValue;
673
+ input.removeAttribute('data-original-value');
674
+ }
675
+ }
676
+ else {
677
+ element.innerHTML = innerHTML;
678
+ }
643
679
  });
644
680
  // Convert to base64
645
681
  const quality = (options.quality || 80) / 100;
@@ -727,6 +763,14 @@ class ContextCapture {
727
763
  }
728
764
  }
729
765
 
766
+ var contextCapture = /*#__PURE__*/Object.freeze({
767
+ __proto__: null,
768
+ ContextCapture: ContextCapture,
769
+ capturePageContext: capturePageContext,
770
+ capturePerformance: capturePerformance,
771
+ captureScreenshot: captureScreenshot
772
+ });
773
+
730
774
  /**
731
775
  * Error Capture Module
732
776
  * Automatic error capture and reporting
@@ -2902,7 +2946,7 @@ class SessionRecording {
2902
2946
  const isSensitive = this.isSensitiveElement(target);
2903
2947
  this.addEvent('input', {
2904
2948
  selector: this.getElementSelector(target),
2905
- value: isSensitive ? '••••••••' : target.value?.substring(0, 100),
2949
+ value: isSensitive ? '********' : target.value?.substring(0, 100),
2906
2950
  type: target.type || 'text',
2907
2951
  });
2908
2952
  };
@@ -3485,6 +3529,9 @@ class AnalyticsTracker {
3485
3529
  this.scrollEvents = 0;
3486
3530
  this.lastScrollTime = 0;
3487
3531
  this.isFirstPageView = true;
3532
+ // Session recording upload timer (every 10 minutes)
3533
+ this.recordingUploadTimer = null;
3534
+ this.RECORDING_UPLOAD_INTERVAL = 10 * 60 * 1000; // 10 minutes
3488
3535
  // ==================== Private Methods - Event Handlers ====================
3489
3536
  this.handleClick = (event) => {
3490
3537
  const target = event.target;
@@ -3566,6 +3613,7 @@ class AnalyticsTracker {
3566
3613
  this.queueInteraction(interaction);
3567
3614
  };
3568
3615
  this.handlePageUnload = () => {
3616
+ console.log('🎬 [AnalyticsTracker] Page unload detected - saving session recording and interactions');
3569
3617
  // Send any pending interactions synchronously
3570
3618
  if (this.interactionBuffer.length > 0) {
3571
3619
  // Use sendBeacon for reliable delivery
@@ -3575,11 +3623,51 @@ class AnalyticsTracker {
3575
3623
  navigator.sendBeacon(url, data);
3576
3624
  }
3577
3625
  }
3626
+ // Save session recording synchronously before page unloads
3627
+ try {
3628
+ const checkflowInstance = window.checkflow;
3629
+ if (checkflowInstance?.sessionRecording) {
3630
+ const recordingData = checkflowInstance.sessionRecording.getRecordingData();
3631
+ if (recordingData && recordingData.events.length > 0) {
3632
+ console.log('🎬 [AnalyticsTracker] Sending session recording via sendBeacon', {
3633
+ eventCount: recordingData.events.length,
3634
+ duration: recordingData.duration
3635
+ });
3636
+ // Send recording data via sendBeacon
3637
+ const recordingPayload = JSON.stringify({
3638
+ events: recordingData.events,
3639
+ durationMs: recordingData.duration * 1000
3640
+ });
3641
+ const recordingUrl = `${this.apiClient.getBaseUrl()}/api/v1/analytics/sessions/${this.sessionId}/recording`;
3642
+ if (navigator.sendBeacon) {
3643
+ const success = navigator.sendBeacon(recordingUrl, recordingPayload);
3644
+ console.log('🎬 [AnalyticsTracker] sendBeacon recording result:', success);
3645
+ }
3646
+ }
3647
+ else {
3648
+ console.warn('🎬 [AnalyticsTracker] No recording events to save on unload');
3649
+ }
3650
+ }
3651
+ else {
3652
+ console.warn('🎬 [AnalyticsTracker] No session recording instance found on unload');
3653
+ }
3654
+ }
3655
+ catch (error) {
3656
+ console.error('🎬 [AnalyticsTracker] Failed to save recording on unload:', error);
3657
+ }
3578
3658
  };
3579
3659
  this.handleVisibilityChange = () => {
3580
3660
  if (document.hidden) {
3581
- // Page became hidden - flush interactions
3661
+ console.log('🎬 [AnalyticsTracker] Tab became hidden - saving data immediately');
3662
+ // Page became hidden - flush interactions AND save session recording
3582
3663
  this.flushInteractions();
3664
+ // Save session recording immediately when tab becomes hidden
3665
+ this.saveSessionRecording().catch(error => {
3666
+ console.warn('🎬 [AnalyticsTracker] Failed to save recording on visibility change:', error);
3667
+ });
3668
+ }
3669
+ else {
3670
+ console.log('🎬 [AnalyticsTracker] Tab became visible again');
3583
3671
  }
3584
3672
  };
3585
3673
  this.apiClient = apiClient;
@@ -3614,6 +3702,8 @@ class AnalyticsTracker {
3614
3702
  this.setupEventListeners();
3615
3703
  // Track initial page view
3616
3704
  this.trackPageView();
3705
+ // Start periodic session recording upload timer (every 10 minutes)
3706
+ this.setupPeriodicRecordingUpload();
3617
3707
  this.isActive = true;
3618
3708
  this.log('Analytics tracking started', { sessionId: this.sessionId });
3619
3709
  }
@@ -3639,6 +3729,10 @@ class AnalyticsTracker {
3639
3729
  clearTimeout(this.batchTimer);
3640
3730
  this.batchTimer = null;
3641
3731
  }
3732
+ if (this.recordingUploadTimer) {
3733
+ clearInterval(this.recordingUploadTimer);
3734
+ this.recordingUploadTimer = null;
3735
+ }
3642
3736
  // Stop performance observer
3643
3737
  if (this.performanceObserver) {
3644
3738
  this.performanceObserver.disconnect();
@@ -3701,6 +3795,24 @@ class AnalyticsTracker {
3701
3795
  }
3702
3796
  }
3703
3797
  // ==================== Private Methods - Session Management ====================
3798
+ setupPeriodicRecordingUpload() {
3799
+ console.log('🎬 [AnalyticsTracker] Setting up periodic recording upload (every 10 minutes)');
3800
+ this.recordingUploadTimer = window.setInterval(async () => {
3801
+ try {
3802
+ console.log('🎬 [AnalyticsTracker] Periodic upload triggered');
3803
+ await this.saveSessionRecording();
3804
+ // Clear events from recording to avoid duplicate uploads
3805
+ const checkflowInstance = window.checkflow;
3806
+ if (checkflowInstance?.sessionRecording) {
3807
+ checkflowInstance.sessionRecording.clearEvents();
3808
+ console.log('🎬 [AnalyticsTracker] Cleared recording events after upload');
3809
+ }
3810
+ }
3811
+ catch (error) {
3812
+ console.error('🎬 [AnalyticsTracker] Periodic recording upload failed:', error);
3813
+ }
3814
+ }, this.RECORDING_UPLOAD_INTERVAL);
3815
+ }
3704
3816
  async createSession() {
3705
3817
  const sessionData = {
3706
3818
  sessionId: this.sessionId,
@@ -3831,7 +3943,7 @@ class AnalyticsTracker {
3831
3943
  this.log('Performance tracking setup failed:', error);
3832
3944
  }
3833
3945
  }
3834
- trackPageView() {
3946
+ async trackPageView() {
3835
3947
  const loadTime = this.isFirstPageView ? this.getPageLoadTime() : undefined;
3836
3948
  const domReadyTime = this.isFirstPageView ? this.getDOMReadyTime() : undefined;
3837
3949
  const interaction = {
@@ -3845,6 +3957,30 @@ class AnalyticsTracker {
3845
3957
  this.isFirstPageView = false;
3846
3958
  // Reset scroll tracking for new page
3847
3959
  this.scrollEvents = 0;
3960
+ // Capture page screenshot for heatmap background (async, don't wait)
3961
+ this.capturePageScreenshotForHeatmap().catch(error => {
3962
+ console.warn('🖼️ [AnalyticsTracker] Page screenshot capture failed:', error);
3963
+ });
3964
+ }
3965
+ async capturePageScreenshotForHeatmap() {
3966
+ try {
3967
+ console.log('🖼️ [AnalyticsTracker] Capturing page screenshot for heatmap...');
3968
+ // Dynamic import of html2canvas to avoid bundle bloat
3969
+ const { captureScreenshot } = await Promise.resolve().then(function () { return contextCapture; });
3970
+ const screenshot = await captureScreenshot({
3971
+ quality: 60, // Lower quality for heatmap backgrounds
3972
+ fullPage: false, // Capture viewport only for consistency
3973
+ });
3974
+ if (screenshot) {
3975
+ // Upload screenshot to backend for heatmap association
3976
+ await this.apiClient.uploadScreenshotOnly(screenshot, window.location.href);
3977
+ console.log('🖼️ [AnalyticsTracker] Page screenshot uploaded for heatmap');
3978
+ }
3979
+ }
3980
+ catch (error) {
3981
+ // Don't throw - screenshot capture is optional for heatmaps
3982
+ console.warn('🖼️ [AnalyticsTracker] Could not capture page screenshot:', error);
3983
+ }
3848
3984
  }
3849
3985
  trackPageLoadPerformance(timing) {
3850
3986
  const loadTime = Math.round(timing.loadEventEnd - timing.loadEventStart);