/** * AGC Analytics - Lightweight Tracking Script * Version: 1.0.0 * * Include this script in your AGC sites: * */ (function () { 'use strict'; // Configuration const SCRIPT = document.currentScript; const DOMAIN = SCRIPT?.getAttribute('data-domain') || window.location.hostname; const ENDPOINT = SCRIPT?.src.replace('/tracking.js', '/api/track') || '/api/track'; const SESSION_TIMEOUT = 30 * 60 * 1000; // 30 minutes // Check for opt-out if (localStorage.getItem('agc_optout') === '1') { return; } // Get or create session ID function getSessionId() { const stored = sessionStorage.getItem('agc_session'); if (stored) { const data = JSON.parse(stored); if (Date.now() - data.lastActivity < SESSION_TIMEOUT) { data.lastActivity = Date.now(); sessionStorage.setItem('agc_session', JSON.stringify(data)); return data.id; } } const newSession = { id: Math.random().toString(36).substring(2) + Date.now().toString(36), lastActivity: Date.now(), pageViews: 0, }; sessionStorage.setItem('agc_session', JSON.stringify(newSession)); return newSession.id; } // Parse user agent function parseUserAgent() { const ua = navigator.userAgent; // Browser detection let browser = 'Unknown'; if (ua.includes('Firefox/')) browser = 'Firefox'; else if (ua.includes('Edg/')) browser = 'Edge'; else if (ua.includes('Chrome/')) browser = 'Chrome'; else if (ua.includes('Safari/') && !ua.includes('Chrome')) browser = 'Safari'; else if (ua.includes('Opera') || ua.includes('OPR/')) browser = 'Opera'; // OS detection let os = 'Unknown'; if (ua.includes('Windows')) os = 'Windows'; else if (ua.includes('Mac OS')) os = 'macOS'; else if (ua.includes('Linux')) os = 'Linux'; else if (ua.includes('Android')) os = 'Android'; else if (ua.includes('iOS') || ua.includes('iPhone') || ua.includes('iPad')) os = 'iOS'; // Device detection let device = 'Desktop'; if (/Mobile|Android|iPhone/i.test(ua)) device = 'Mobile'; else if (/iPad|Tablet/i.test(ua)) device = 'Tablet'; return { browser, os, device }; } // Get referrer type function getReferrer() { const ref = document.referrer; if (!ref) return { url: '', type: 'direct' }; try { const refUrl = new URL(ref); if (refUrl.hostname === window.location.hostname) { return { url: ref, type: 'internal' }; } // Check for search engines const searchEngines = ['google', 'bing', 'yahoo', 'duckduckgo', 'baidu', 'yandex']; for (const se of searchEngines) { if (refUrl.hostname.includes(se)) { return { url: ref, type: 'search', engine: se }; } } // Check for social const social = ['facebook', 'twitter', 'instagram', 'linkedin', 'pinterest', 'reddit', 'tiktok']; for (const s of social) { if (refUrl.hostname.includes(s)) { return { url: ref, type: 'social', platform: s }; } } return { url: ref, type: 'referral' }; } catch (e) { return { url: ref, type: 'referral' }; } } // Get country from Cloudflare trace (most accurate) async function getCountryFromCloudflare() { try { const response = await fetch('https://1.1.1.1/cdn-cgi/trace'); const text = await response.text(); // Parse text format: look for "loc=ID" pattern const match = text.match(/loc=([A-Z]{2})/); return match ? match[1] : 'Unknown'; } catch (e) { // Fallback to timezone-based detection return getCountryFromTimezone(); } } // Fallback: Get country from browser timezone function getCountryFromTimezone() { try { const tz = Intl.DateTimeFormat().resolvedOptions().timeZone; const tzToCountry = { 'Asia/Jakarta': 'ID', 'Asia/Makassar': 'ID', 'Asia/Jayapura': 'ID', 'Asia/Singapore': 'SG', 'Asia/Kuala_Lumpur': 'MY', 'Asia/Bangkok': 'TH', 'Asia/Ho_Chi_Minh': 'VN', 'Asia/Manila': 'PH', 'Asia/Tokyo': 'JP', 'Asia/Seoul': 'KR', 'Asia/Shanghai': 'CN', 'Asia/Hong_Kong': 'HK', 'Asia/Kolkata': 'IN', 'Asia/Dubai': 'AE', 'America/New_York': 'US', 'America/Chicago': 'US', 'America/Los_Angeles': 'US', 'America/Toronto': 'CA', 'America/Sao_Paulo': 'BR', 'Europe/London': 'GB', 'Europe/Paris': 'FR', 'Europe/Berlin': 'DE', 'Europe/Moscow': 'RU', 'Australia/Sydney': 'AU', }; return tzToCountry[tz] || 'Unknown'; } catch (e) { return 'Unknown'; } } // Track page view async function trackPageView() { const session = getSessionId(); const { browser, os, device } = parseUserAgent(); const referrer = getReferrer(); const country = await getCountryFromCloudflare(); // Update session page views const sessionData = JSON.parse(sessionStorage.getItem('agc_session')); sessionData.pageViews++; sessionStorage.setItem('agc_session', JSON.stringify(sessionData)); const data = { domain: DOMAIN, path: window.location.pathname, title: document.title, referrer: referrer.url, referrerType: referrer.type, browser: browser, os: os, device: device, screenWidth: window.screen.width, screenHeight: window.screen.height, language: navigator.language, sessionId: session, isNewSession: sessionData.pageViews === 1, timestamp: Date.now(), country: country, }; // Send using sendBeacon for reliability if (navigator.sendBeacon) { navigator.sendBeacon(ENDPOINT, JSON.stringify(data)); } else { // Fallback to fetch fetch(ENDPOINT, { method: 'POST', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json' }, keepalive: true, }).catch(() => { }); } } // Track on page load if (document.readyState === 'complete') { trackPageView(); } else { window.addEventListener('load', trackPageView); } // Track SPA navigation (if applicable) let lastPath = window.location.pathname; const observer = new MutationObserver(() => { if (window.location.pathname !== lastPath) { lastPath = window.location.pathname; trackPageView(); } }); observer.observe(document.body, { childList: true, subtree: true }); // Track time on page (for session duration) let startTime = Date.now(); window.addEventListener('beforeunload', () => { const timeOnPage = Math.round((Date.now() - startTime) / 1000); const data = { domain: DOMAIN, type: 'duration', sessionId: getSessionId(), duration: timeOnPage, path: window.location.pathname, }; if (navigator.sendBeacon) { navigator.sendBeacon(ENDPOINT, JSON.stringify(data)); } }); })();