OneConversorTemplate/OnlyOneAccessTemplate/wwwroot/js/analytics.js
Ricardo Carneiro b1d75213ab first commit
2025-05-30 23:48:28 -03:00

398 lines
14 KiB
JavaScript

// Advanced analytics and heatmap tracking
class AdvancedAnalytics {
constructor() {
this.sessionId = this.generateSessionId();
this.startTime = Date.now();
this.init();
}
init() {
this.trackSession();
this.setupHeatmapTracking();
this.setupScrollTracking();
this.setupClickTracking();
this.setupTimeOnPage();
this.setupVisibilityTracking();
}
generateSessionId() {
return 'session_' + Math.random().toString(36).substr(2, 9) + '_' + Date.now();
}
// Track user session
trackSession() {
const sessionData = {
sessionId: this.sessionId,
userAgent: navigator.userAgent,
screenResolution: `${screen.width}x${screen.height}`,
windowSize: `${window.innerWidth}x${window.innerHeight}`,
language: navigator.language,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
referrer: document.referrer,
landingPage: window.location.pathname,
timestamp: new Date().toISOString()
};
this.sendAnalytics('session_start', sessionData);
}
// Setup heatmap tracking
setupHeatmapTracking() {
let mousePositions = [];
let lastSampleTime = 0;
const sampleRate = 100; // Sample every 100ms
document.addEventListener('mousemove', (e) => {
const now = Date.now();
if (now - lastSampleTime > sampleRate) {
mousePositions.push({
x: e.pageX,
y: e.pageY,
timestamp: now
});
lastSampleTime = now;
// Send data in batches
if (mousePositions.length >= 50) {
this.sendAnalytics('heatmap_data', {
sessionId: this.sessionId,
positions: mousePositions,
page: window.location.pathname
});
mousePositions = [];
}
}
});
// Send remaining data on page unload
window.addEventListener('beforeunload', () => {
if (mousePositions.length > 0) {
navigator.sendBeacon('/api/analytics/heatmap', JSON.stringify({
sessionId: this.sessionId,
positions: mousePositions,
page: window.location.pathname
}));
}
});
}
// Setup scroll depth tracking
setupScrollTracking() {
let maxScroll = 0;
const scrollMilestones = [25, 50, 75, 90, 100];
const triggered = new Set();
window.addEventListener('scroll', () => {
const scrollPercent = Math.round(
(window.pageYOffset / (document.documentElement.scrollHeight - window.innerHeight)) * 100
);
if (scrollPercent > maxScroll) {
maxScroll = scrollPercent;
}
// Track scroll milestones
scrollMilestones.forEach(milestone => {
if (scrollPercent >= milestone && !triggered.has(milestone)) {
triggered.add(milestone);
this.sendAnalytics('scroll_depth', {
sessionId: this.sessionId,
depth: milestone,
page: window.location.pathname,
timestamp: new Date().toISOString()
});
}
});
});
}
// Setup click tracking
setupClickTracking() {
document.addEventListener('click', (e) => {
const element = e.target;
const tagName = element.tagName.toLowerCase();
// Track important elements
if (['a', 'button', 'input'].includes(tagName) || element.classList.contains('trackable')) {
const clickData = {
sessionId: this.sessionId,
element: {
tagName,
id: element.id,
className: element.className,
text: element.textContent?.substring(0, 100),
href: element.href,
type: element.type
},
position: {
x: e.pageX,
y: e.pageY
},
page: window.location.pathname,
timestamp: new Date().toISOString()
};
this.sendAnalytics('click_tracking', clickData);
}
// Track form field clicks
if (tagName === 'input' || tagName === 'textarea' || tagName === 'select') {
this.sendAnalytics('form_interaction', {
sessionId: this.sessionId,
fieldName: element.name,
fieldType: element.type,
formId: element.closest('form')?.id,
action: 'focus',
page: window.location.pathname,
timestamp: new Date().toISOString()
});
}
});
}
// Setup time on page tracking
setupTimeOnPage() {
let pageStartTime = Date.now();
let isActive = true;
let totalActiveTime = 0;
let lastActiveTime = pageStartTime;
// Track when page becomes active/inactive
document.addEventListener('visibilitychange', () => {
const now = Date.now();
if (document.hidden) {
if (isActive) {
totalActiveTime += now - lastActiveTime;
isActive = false;
}
} else {
isActive = true;
lastActiveTime = now;
}
});
// Send time data periodically
setInterval(() => {
const now = Date.now();
const currentActiveTime = isActive ? totalActiveTime + (now - lastActiveTime) : totalActiveTime;
this.sendAnalytics('time_on_page', {
sessionId: this.sessionId,
activeTime: Math.round(currentActiveTime / 1000), // in seconds
totalTime: Math.round((now - pageStartTime) / 1000),
page: window.location.pathname,
timestamp: new Date().toISOString()
});
}, 30000); // Every 30 seconds
// Send final time on page unload
window.addEventListener('beforeunload', () => {
const now = Date.now();
const finalActiveTime = isActive ? totalActiveTime + (now - lastActiveTime) : totalActiveTime;
navigator.sendBeacon('/api/analytics/time', JSON.stringify({
sessionId: this.sessionId,
activeTime: Math.round(finalActiveTime / 1000),
totalTime: Math.round((now - pageStartTime) / 1000),
page: window.location.pathname,
timestamp: new Date().toISOString()
}));
});
}
// Setup visibility tracking for elements
setupVisibilityTracking() {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const element = entry.target;
this.sendAnalytics('element_view', {
sessionId: this.sessionId,
element: {
id: element.id,
className: element.className,
tagName: element.tagName.toLowerCase()
},
page: window.location.pathname,
timestamp: new Date().toISOString()
});
}
});
}, { threshold: 0.5 });
// Track important sections
document.querySelectorAll('section, .hero-section, .features-section, .conversion-section').forEach(el => {
observer.observe(el);
});
}
// A/B Test tracking
trackABTest(testName, variant) {
this.sendAnalytics('ab_test', {
sessionId: this.sessionId,
testName,
variant,
page: window.location.pathname,
timestamp: new Date().toISOString()
});
}
// Error tracking
trackError(error, context = {}) {
this.sendAnalytics('javascript_error', {
sessionId: this.sessionId,
error: {
message: error.message,
stack: error.stack,
filename: error.filename,
lineno: error.lineno,
colno: error.colno
},
context,
page: window.location.pathname,
userAgent: navigator.userAgent,
timestamp: new Date().toISOString()
});
}
// Send analytics data
async sendAnalytics(event, data) {
try {
const payload = {
event,
data,
url: window.location.href,
timestamp: new Date().toISOString()
};
// Use sendBeacon for important events if available
if (navigator.sendBeacon && ['session_start', 'conversion', 'form_submit'].includes(event)) {
navigator.sendBeacon('/api/analytics/track', JSON.stringify(payload));
} else {
// Fallback to fetch
fetch('/api/analytics/track', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
}).catch(err => console.error('Analytics error:', err));
}
} catch (error) {
console.error('Analytics sending error:', error);
}
}
// Get session statistics
getSessionStats() {
return {
sessionId: this.sessionId,
duration: Math.round((Date.now() - this.startTime) / 1000),
page: window.location.pathname
};
}
}
// Performance monitoring
class PerformanceMonitor {
constructor() {
this.init();
}
init() {
// Monitor page load performance
window.addEventListener('load', () => {
setTimeout(() => {
this.trackPagePerformance();
}, 0);
});
// Monitor Core Web Vitals
this.trackWebVitals();
}
trackPagePerformance() {
if ('performance' in window) {
const navigation = performance.getEntriesByType('navigation')[0];
const paint = performance.getEntriesByType('paint');
const performanceData = {
loadTime: Math.round(navigation.loadEventEnd - navigation.loadEventStart),
domContentLoaded: Math.round(navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart),
firstPaint: paint.find(p => p.name === 'first-paint')?.startTime || 0,
firstContentfulPaint: paint.find(p => p.name === 'first-contentful-paint')?.startTime || 0,
pageSize: navigation.transferSize,
resources: performance.getEntriesByType('resource').length
};
window.analytics?.sendAnalytics('page_performance', performanceData);
}
}
trackWebVitals() {
// Track Largest Contentful Paint (LCP)
if ('PerformanceObserver' in window) {
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
window.analytics?.sendAnalytics('web_vital_lcp', {
value: lastEntry.startTime,
rating: lastEntry.startTime > 2500 ? 'poor' : lastEntry.startTime > 1200 ? 'needs-improvement' : 'good'
});
}).observe({ entryTypes: ['largest-contentful-paint'] });
// Track First Input Delay (FID)
new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach(entry => {
window.analytics?.sendAnalytics('web_vital_fid', {
value: entry.processingStart - entry.startTime,
rating: entry.processingStart - entry.startTime > 100 ? 'poor' :
entry.processingStart - entry.startTime > 25 ? 'needs-improvement' : 'good'
});
});
}).observe({ entryTypes: ['first-input'] });
// Track Cumulative Layout Shift (CLS)
let clsScore = 0;
new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach(entry => {
if (!entry.hadRecentInput) {
clsScore += entry.value;
}
});
window.analytics?.sendAnalytics('web_vital_cls', {
value: clsScore,
rating: clsScore > 0.25 ? 'poor' : clsScore > 0.1 ? 'needs-improvement' : 'good'
});
}).observe({ entryTypes: ['layout-shift'] });
}
}
}
// Initialize analytics
window.addEventListener('DOMContentLoaded', () => {
window.analytics = new AdvancedAnalytics();
window.performanceMonitor = new PerformanceMonitor();
// Global error handling
window.addEventListener('error', (event) => {
window.analytics?.trackError(event.error, {
type: 'javascript_error',
source: event.filename,
line: event.lineno,
column: event.colno
});
});
// Promise rejection handling
window.addEventListener('unhandledrejection', (event) => {
window.analytics?.trackError(new Error(event.reason), {
type: 'unhandled_promise_rejection'
});
});
});