clickchutney-analytics
Version:
Privacy-first web analytics - bite-sized insights for your website 🌶️
166 lines (163 loc) • 5.02 kB
JavaScript
;
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;