@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/analytics-tracker.d.ts +4 -0
- package/dist/index.esm.js +147 -11
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +147 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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.
|
|
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 ? '
|
|
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
|
-
|
|
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);
|