UNPKG

@randyd45/web-behavior-tracker

Version:

A framework-agnostic package for tracking user behavior on web forms

322 lines 13.6 kB
export class BrowserMetadataCollector { /** * Collects comprehensive browser metadata using modern APIs */ static getBrowserMetadata() { const now = new Date(); // Modern platform detection using User-Agent Client Hints API const platform = this.getPlatformInfo(); const browserInfo = this.getBrowserInfo(); const isMobile = this.detectMobile(); return { userAgent: navigator.userAgent, platform: platform.name, platformVersion: platform.version, browserName: browserInfo.name, browserVersion: browserInfo.version, isMobile: isMobile, language: navigator.language, languages: [...(navigator.languages || [])], cookieEnabled: navigator.cookieEnabled, onLine: navigator.onLine, hardwareConcurrency: navigator.hardwareConcurrency || 0, maxTouchPoints: navigator.maxTouchPoints || 0, screenWidth: screen.width, screenHeight: screen.height, screenAvailWidth: screen.availWidth, screenAvailHeight: screen.availHeight, screenColorDepth: screen.colorDepth, screenPixelDepth: screen.pixelDepth, viewportWidth: window.innerWidth, viewportHeight: window.innerHeight, devicePixelRatio: window.devicePixelRatio, pageTitle: document.title, pageUrl: window.location.href, referrer: document.referrer, domain: document.domain, characterSet: document.characterSet, readyState: document.readyState, timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, timezoneOffset: now.getTimezoneOffset(), timestamp: now.getTime() }; } /** * Gets platform information using modern APIs with fallbacks */ static getPlatformInfo() { // Try modern User-Agent Client Hints API first if ('userAgentData' in navigator) { const uaData = navigator.userAgentData; if (uaData && uaData.platform) { return { name: uaData.platform, version: uaData.platformVersion }; } } // Fallback to deprecated navigator.platform (with privacy considerations) const platform = navigator.platform || 'unknown'; // Parse platform from user agent as additional fallback const userAgent = navigator.userAgent.toLowerCase(); if (userAgent.includes('windows')) return { name: 'Windows' }; if (userAgent.includes('mac')) return { name: 'macOS' }; if (userAgent.includes('linux')) return { name: 'Linux' }; if (userAgent.includes('android')) return { name: 'Android' }; if (userAgent.includes('ios') || userAgent.includes('iphone') || userAgent.includes('ipad')) return { name: 'iOS' }; return { name: platform }; } /** * Gets browser information using modern APIs with fallbacks */ static getBrowserInfo() { // Try modern User-Agent Client Hints API first if ('userAgentData' in navigator) { const uaData = navigator.userAgentData; if (uaData && uaData.brands) { const browser = uaData.brands.find((brand) => brand.brand && !brand.brand.includes('Not')); if (browser) { return { name: browser.brand, version: browser.version }; } } } // Fallback to user agent parsing const userAgent = navigator.userAgent; // Chrome if (userAgent.includes('Chrome') && !userAgent.includes('Edg')) { const match = userAgent.match(/Chrome\/(\d+\.\d+)/); return { name: 'Chrome', version: match ? match[1] : undefined }; } // Firefox if (userAgent.includes('Firefox')) { const match = userAgent.match(/Firefox\/(\d+\.\d+)/); return { name: 'Firefox', version: match ? match[1] : undefined }; } // Safari if (userAgent.includes('Safari') && !userAgent.includes('Chrome')) { const match = userAgent.match(/Version\/(\d+\.\d+)/); return { name: 'Safari', version: match ? match[1] : undefined }; } // Edge if (userAgent.includes('Edg')) { const match = userAgent.match(/Edg\/(\d+\.\d+)/); return { name: 'Edge', version: match ? match[1] : undefined }; } return { name: 'Unknown' }; } /** * Detects if the device is mobile using multiple indicators */ static detectMobile() { // Check for touch capability and screen size const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0; const isSmallScreen = window.innerWidth <= 768 || window.innerHeight <= 768; // Check user agent for mobile indicators const userAgent = navigator.userAgent.toLowerCase(); const mobileIndicators = [ 'mobile', 'android', 'iphone', 'ipad', 'ipod', 'blackberry', 'windows phone', 'opera mini', 'iemobile' ]; const hasMobileUA = mobileIndicators.some(indicator => userAgent.includes(indicator)); // Check for mobile-specific APIs const hasMobileAPIs = 'orientation' in screen || 'vibrate' in navigator; return hasMobileUA || (hasTouch && isSmallScreen) || hasMobileAPIs; } /** * Gets high-entropy metadata using User-Agent Client Hints API */ static async getHighEntropyMetadata() { const metadata = {}; try { // Try to get high-entropy values from User-Agent Client Hints API if ('userAgentData' in navigator && 'getHighEntropyValues' in navigator.userAgentData) { const uaData = navigator.userAgentData; const highEntropyValues = await uaData.getHighEntropyValues([ 'platform', 'platformVersion', 'architecture', 'model', 'uaFullVersion', 'fullVersionList' ]); metadata.highEntropyUA = highEntropyValues; } } catch (error) { console.warn('High-entropy values not available:', error); } return metadata; } /** * Gets additional metadata including performance, network, and device information */ static getAdditionalMetadata() { const metadata = {}; try { // Performance timing if (performance.timing) { metadata.navigationTiming = { navigationStart: performance.timing.navigationStart, loadEventEnd: performance.timing.loadEventEnd, domContentLoadedEventEnd: performance.timing.domContentLoadedEventEnd, loadComplete: performance.timing.loadEventEnd - performance.timing.navigationStart }; } // Memory usage (Chrome only) if ('memory' in performance) { const memory = performance.memory; metadata.memory = { usedJSHeapSize: memory.usedJSHeapSize, totalJSHeapSize: memory.totalJSHeapSize, jsHeapSizeLimit: memory.jsHeapSizeLimit }; } // Connection information (if available) if ('connection' in navigator) { const connection = navigator.connection; metadata.connection = { effectiveType: connection.effectiveType, downlink: connection.downlink, rtt: connection.rtt, saveData: connection.saveData }; } // Battery information (if available and permission granted) if ('getBattery' in navigator) { navigator.getBattery().then((battery) => { metadata.battery = { charging: battery.charging, chargingTime: battery.chargingTime, dischargingTime: battery.dischargingTime, level: battery.level }; }).catch(() => { // Battery API not available or permission denied }); } // Media devices (if available) if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) { navigator.mediaDevices.enumerateDevices().then((devices) => { metadata.mediaDevices = devices.map(device => ({ kind: device.kind, label: device.label, deviceId: device.deviceId })); }).catch(() => { // Media devices API not available }); } // WebGL information const canvas = document.createElement('canvas'); const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); if (gl) { const debugInfo = gl.getExtension('WEBGL_debug_renderer_info'); if (debugInfo) { metadata.webgl = { vendor: gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL), renderer: gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) }; } } // Canvas fingerprinting const canvas2d = document.createElement('canvas'); const ctx = canvas2d.getContext('2d'); if (ctx) { ctx.textBaseline = 'top'; ctx.font = '14px Arial'; ctx.fillText('Browser fingerprint', 2, 2); metadata.canvasFingerprint = canvas2d.toDataURL(); } // Timezone and locale information metadata.locale = { timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, locale: Intl.DateTimeFormat().resolvedOptions().locale, numberFormat: Intl.NumberFormat().resolvedOptions().locale, dateTimeFormat: Intl.DateTimeFormat().resolvedOptions().locale }; // Screen orientation if (screen.orientation) { metadata.screenOrientation = { angle: screen.orientation.angle, type: screen.orientation.type }; } // Page visibility metadata.pageVisibility = { hidden: document.hidden, visibilityState: document.visibilityState }; } catch (error) { console.warn('Error collecting additional metadata:', error); } return metadata; } /** * Gets a simple browser fingerprint for identification purposes */ static getBrowserFingerprint() { var _a; const metadata = this.getBrowserMetadata(); const additional = this.getAdditionalMetadata(); // Create a simple fingerprint from key characteristics const fingerprintData = { platform: metadata.platform, browser: metadata.browserName, language: metadata.language, screen: `${metadata.screenWidth}x${metadata.screenHeight}`, timezone: metadata.timezone, hardwareConcurrency: metadata.hardwareConcurrency, maxTouchPoints: metadata.maxTouchPoints, devicePixelRatio: metadata.devicePixelRatio, webgl: ((_a = additional.webgl) === null || _a === void 0 ? void 0 : _a.renderer) || 'unknown' }; // Create a simple hash-like string return btoa(JSON.stringify(fingerprintData)).substring(0, 32); } /** * Checks if the browser supports modern features */ static getBrowserCapabilities() { return { userAgentData: 'userAgentData' in navigator, highEntropyValues: 'userAgentData' in navigator && 'getHighEntropyValues' in navigator.userAgentData, webGL: !!document.createElement('canvas').getContext('webgl'), webGL2: !!document.createElement('canvas').getContext('webgl2'), touchEvents: 'ontouchstart' in window, vibration: 'vibrate' in navigator, geolocation: 'geolocation' in navigator, mediaDevices: 'mediaDevices' in navigator, battery: 'getBattery' in navigator, connection: 'connection' in navigator, memory: 'memory' in performance, serviceWorker: 'serviceWorker' in navigator, pushManager: 'PushManager' in window, notifications: 'Notification' in window, clipboard: 'clipboard' in navigator, share: 'share' in navigator, webShare: 'canShare' in navigator }; } } //# sourceMappingURL=BrowserMetadataCollector.js.map