CoolFace
Apppublic

Leon4gr45/builder

sourceHugging Facemitupdated 2d agoView on Hugging Face
0likes
tracking-script.ts317 linesDownload Raw Back to analytics
1/**2 * Analytics Tracking Script Generator3 *4 * Generates a comprehensive, privacy-focused tracking script for built-in analytics.5 *6 * Privacy Features:7 * - No cookies8 * - IP anonymization (country-level only)9 * - Session ID from anonymized fingerprint10 * - No cross-site tracking11 *12 * Tracked Metrics:13 * - Pageview tracking14 * - Click heatmaps (with scroll position)15 * - Scroll depth (25%, 50%, 75%, 100%)16 * - Time on page17 * - Referrer sources18 * - Device type19 *20 * Performance:21 * - Efficient event batching (30s or 50 events)22 * - Single HTTP request per batch23 * - Forced flush on page exit/close24 * - 80-90% reduction in network requests25 *26 * Security:27 * - Origin/Referer validation (browser-enforced)28 * - Rate limiting (100/min pageviews, 500/min interactions)29 * - Bot detection30 * - No tokens required (same-origin hosting advantage)31 */32 33export interface TrackingScriptOptions {34  deploymentId: string;35  apiEndpoint?: string; // Default: /api/analytics/track36  interactionEndpoint?: string; // Default: /api/analytics/interaction37  features?: {38    basicTracking?: boolean;       // Pageviews, referrers39    heatmaps?: boolean;             // Click/scroll heatmaps40    sessionRecording?: boolean;     // Journey tracking (reserved for future)41    performanceMetrics?: boolean;   // Core Web Vitals (reserved for future)42    engagementTracking?: boolean;   // Time on page, scroll depth43    customEvents?: boolean;         // Custom event tracking (reserved for future)44  };45}46 47/**48 * Generate the inline analytics tracking script49 */50export function generateTrackingScript(options: TrackingScriptOptions): string {51  const {52    deploymentId,53    apiEndpoint = '/api/analytics/track',54    interactionEndpoint = '/api/analytics/interaction',55    features = {56      basicTracking: true,57      heatmaps: false,58      sessionRecording: false,59      performanceMetrics: false,60      engagementTracking: false,61      customEvents: false,62    },63  } = options;64 65  return `66<!-- OSW Studio Analytics -->67<script>68(function() {69  'use strict';70 71  // Configuration72  var config = {73    deploymentId: '${deploymentId}',74    apiEndpoint: '${apiEndpoint}',75    interactionEndpoint: '${interactionEndpoint}',76    features: ${JSON.stringify(features)}77  };78 79  // State80  var pageLoadTime = Date.now();81  var scrollMilestones = { 25: false, 50: false, 75: false, 100: false };82  var eventQueue = [];83  var lastFlush = Date.now();84 85  // Generate anonymous session ID from browser fingerprint (no cookies)86  function generateSessionId() {87    var canvas = document.createElement('canvas');88    var ctx = canvas.getContext('2d');89    ctx.textBaseline = 'top';90    ctx.font = '14px Arial';91    ctx.fillText('osw', 0, 0);92    var canvasData = canvas.toDataURL();93 94    var fingerprint = [95      navigator.userAgent,96      navigator.language,97      screen.colorDepth,98      screen.width + 'x' + screen.height,99      new Date().getTimezoneOffset(),100      canvasData.slice(0, 100)101    ].join('|');102 103    var hash = 0;104    for (var i = 0; i < fingerprint.length; i++) {105      var char = fingerprint.charCodeAt(i);106      hash = ((hash << 5) - hash) + char;107      hash = hash & hash;108    }109    return Math.abs(hash).toString(36);110  }111 112  // Detect device type113  function getDeviceType() {114    var width = window.innerWidth;115    if (width < 768) return 'mobile';116    if (width < 1024) return 'tablet';117    return 'desktop';118  }119 120  // Send analytics data121  // Security: Origin/Referer validation on server (browser-enforced, cannot be spoofed cross-domain)122  function sendData(endpoint, data) {123    fetch(endpoint, {124      method: 'POST',125      headers: { 'Content-Type': 'application/json' },126      body: JSON.stringify(data),127      keepalive: true128    }).catch(function() {});129  }130 131  // Track pageview132  function trackPageview() {133    if (!config.features.basicTracking) return;134 135    var data = {136      deploymentId: config.deploymentId,137      pagePath: window.location.pathname,138      referrer: document.referrer || '',139      userAgent: navigator.userAgent,140      deviceType: getDeviceType()141    };142 143    sendData(config.apiEndpoint, data);144    pageLoadTime = Date.now();145    scrollMilestones = { 25: false, 50: false, 75: false, 100: false };146  }147 148  // Track click (for heatmaps)149  function trackClick(event) {150    if (!config.features.heatmaps) return;151 152    var target = event.target;153    var selector = target.tagName;154    if (target.id) selector += '#' + target.id;155    if (target.className) selector += '.' + target.className.split(' ').join('.');156 157    eventQueue.push({158      type: 'click',159      data: {160        deploymentId: config.deploymentId,161        pagePath: window.location.pathname,162        interactionType: 'click',163        elementSelector: selector,164        coordinates: {165          x: event.clientX,166          y: event.clientY,167          scrollY: window.scrollY || window.pageYOffset || 0,168          viewportWidth: window.innerWidth,169          viewportHeight: window.innerHeight,170          documentHeight: Math.max(171            document.body.scrollHeight,172            document.body.offsetHeight,173            document.documentElement.clientHeight,174            document.documentElement.scrollHeight,175            document.documentElement.offsetHeight176          )177        },178        timeOnPage: Date.now() - pageLoadTime179      }180    });181 182    flushEvents();183  }184 185  // Track scroll depth186  function trackScroll() {187    if (!config.features.engagementTracking && !config.features.heatmaps) return;188 189    var scrollHeight = document.documentElement.scrollHeight - window.innerHeight;190    var scrolled = window.scrollY;191    var percent = scrollHeight > 0 ? Math.round((scrolled / scrollHeight) * 100) : 100;192 193    // Track milestones194    [25, 50, 75, 100].forEach(function(milestone) {195      if (percent >= milestone && !scrollMilestones[milestone]) {196        scrollMilestones[milestone] = true;197 198        eventQueue.push({199          type: 'scroll',200          data: {201            deploymentId: config.deploymentId,202            pagePath: window.location.pathname,203            interactionType: 'scroll',204            scrollDepth: milestone,205            timeOnPage: Date.now() - pageLoadTime206          }207        });208      }209    });210 211    flushEvents();212  }213 214  // Track page exit (send time on page)215  function trackExit() {216    if (!config.features.engagementTracking) return;217 218    var timeOnPage = Date.now() - pageLoadTime;219    if (timeOnPage < 1000) return; // Ignore very short visits220 221    sendData(config.interactionEndpoint, {222      deploymentId: config.deploymentId,223      pagePath: window.location.pathname,224      interactionType: 'exit',225      timeOnPage: timeOnPage226    });227  }228 229  // Flush event queue (batching)230  function flushEvents(force) {231    var now = Date.now();232    if (eventQueue.length === 0) return;233 234    // Increased thresholds: 30s or 50 events (more efficient batching)235    if (!force && now - lastFlush < 30000 && eventQueue.length < 50) return;236 237    var batch = eventQueue.splice(0, eventQueue.length);238 239    // Send as single batched request instead of individual requests240    var batchData = {241      batch: true,242      interactions: batch.map(function(event) { return event.data; })243    };244 245    sendData(config.interactionEndpoint, batchData);246    lastFlush = now;247  }248 249  // Initialize tracking250  function init() {251    // Track initial pageview (only once)252    var pageviewTracked = false;253    function trackInitialPageview() {254      if (pageviewTracked) return;255      pageviewTracked = true;256      trackPageview();257    }258 259    if (document.readyState === 'complete') {260      trackInitialPageview();261    } else {262      window.addEventListener('load', trackInitialPageview, { once: true });263    }264 265    // Track SPA navigation266    var pushState = history.pushState;267    history.pushState = function() {268      pushState.apply(history, arguments);269      trackPageview();270    };271    window.addEventListener('popstate', trackPageview);272 273    // Track clicks for heatmaps274    if (config.features.heatmaps) {275      document.addEventListener('click', trackClick, true);276    }277 278    // Track scroll279    if (config.features.engagementTracking || config.features.heatmaps) {280      var scrollTimer;281      window.addEventListener('scroll', function() {282        clearTimeout(scrollTimer);283        scrollTimer = setTimeout(trackScroll, 100);284      });285    }286 287    // Track page exit (with deduplication)288    var exitTracked = false;289    function handlePageExit() {290      flushEvents(true); // Force flush pending events291      if (config.features.engagementTracking && !exitTracked) {292        exitTracked = true;293        trackExit();294      }295    }296 297    // Both beforeunload and visibilitychange may fire - deduplicate298    window.addEventListener('beforeunload', handlePageExit);299    document.addEventListener('visibilitychange', function() {300      if (document.visibilityState === 'hidden') {301        handlePageExit();302      } else if (document.visibilityState === 'visible') {303        // Reset exit tracking when page becomes visible again304        exitTracked = false;305      }306    });307 308    // Flush events periodically (increased to 30s for efficiency)309    setInterval(function() { flushEvents(false); }, 30000);310  }311 312  init();313})();314</script>315`.trim();316}317