UNPKG

clickchutney-analytics

Version:

Privacy-first web analytics - bite-sized insights for your website 🌶️

166 lines (163 loc) 5.02 kB
'use strict'; let endpoint = ''; let trackingId = ''; let debug = false; let beforeSend; let queue = []; const getDefaultEndpoint = () => { if (typeof window !== 'undefined') { const hostname = window.location.hostname; if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname.includes('localhost')) { return 'http://localhost:8787/api/collect'; } } return 'https://clickchutney-analytics.contact-sushilpandey.workers.dev/api/collect'; }; const generateId = () => { return Date.now().toString(36) + Math.random().toString(36).slice(2); }; const getSessionId = () => { if (typeof window === 'undefined') return generateId(); let sessionId = sessionStorage.getItem('cc_session_id'); if (!sessionId) { sessionId = generateId(); sessionStorage.setItem('cc_session_id', sessionId); } return sessionId; }; const getUserId = () => { if (typeof window === 'undefined') return generateId(); let userId = localStorage.getItem('cc_user_id'); if (!userId) { userId = generateId(); localStorage.setItem('cc_user_id', userId); } return userId; }; const log = (message, data) => { if (debug) { console.log(`[ClickChutney] ${message}`, data || ''); } }; const sendEvents = async (events) => { try { if (!trackingId) { log('No tracking ID provided, events will not be sent'); return; } const payload = { trackingId, events: events.map(event => beforeSend ? beforeSend(event) : event).filter(Boolean) }; if (payload.events.length === 0) return; await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(payload), keepalive: true, }); log('Events sent', payload); } catch (error) { log('Failed to send events', { error, events }); } }; const track = (name, properties = {}) => { if (typeof window === 'undefined') return; const event = { type: name, data: { ...properties, url: window.location.href, referrer: document.referrer || undefined, title: document.title, sessionId: getSessionId(), userId: getUserId(), userAgent: navigator.userAgent, timestamp: new Date().toISOString(), screen: { width: window.screen.width, height: window.screen.height, }, viewport: { width: window.innerWidth, height: window.innerHeight, } } }; queue.push(event); log('Event queued', event); // For page views or if queue is full, send immediately if (name === 'pageview' || queue.length >= 5) { flushQueue(); } else { // Batch other events - send after 3 seconds setTimeout(flushQueue, 3000); } }; const flushQueue = async () => { if (queue.length === 0) return; const eventsToSend = [...queue]; queue = []; await sendEvents(eventsToSend); }; const page = (path) => { const url = path || (typeof window !== 'undefined' ? window.location.pathname : ''); track('pageview', { path: url }); }; let hasSetupHistoryTracking = false; const setupAutoTracking = () => { if (typeof window === 'undefined' || hasSetupHistoryTracking) return; hasSetupHistoryTracking = true; const trackPageView = () => { setTimeout(() => page(), 0); }; const originalPushState = history.pushState; const originalReplaceState = history.replaceState; history.pushState = function (...args) { originalPushState.apply(history, args); trackPageView(); }; history.replaceState = function (...args) { originalReplaceState.apply(history, args); trackPageView(); }; window.addEventListener('popstate', trackPageView); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', trackPageView); } else { trackPageView(); } }; const inject = (options = {}) => { trackingId = options.trackingId || ''; endpoint = options.endpoint || getDefaultEndpoint(); debug = options.debug || false; beforeSend = options.beforeSend; if (!trackingId) { console.warn('[ClickChutney] No tracking ID provided. Analytics will not be sent.'); } log('ClickChutney Analytics initialized', { trackingId: trackingId ? '***' : 'missing', endpoint }); setupAutoTracking(); }; // Export flush function for manual use const flush = () => { return flushQueue(); }; if (typeof window !== 'undefined') { window.ccAnalytics = { inject, track, page }; } exports.flush = flush; exports.inject = inject; exports.page = page; exports.track = track;