1 2(function () { 3 'use strict'; 4 5 const SCRIPT = document.currentScript; 6 const DOMAIN = SCRIPT?.getAttribute('data-domain') || window.location.hostname; 7 const ENDPOINT = '/api/track'; 8 const SESSION_TIMEOUT = 30 * 60 * 1000; 9 10 if (localStorage.getItem('agc_optout') === '1') return; 11 12 function getSessionId() { 13 const stored = sessionStorage.getItem('agc_session'); 14 if (stored) { 15 const data = JSON.parse(stored); 16 if (Date.now() - data.lastActivity < SESSION_TIMEOUT) { 17 data.lastActivity = Date.now(); 18 sessionStorage.setItem('agc_session', JSON.stringify(data)); 19 return data.id; 20 } 21 } 22 const newSession = { 23 id: Math.random().toString(36).substring(2) + Date.now().toString(36), 24 lastActivity: Date.now(), 25 pageViews: 0, 26 }; 27 sessionStorage.setItem('agc_session', JSON.stringify(newSession)); 28 return newSession.id; 29 } 30 31 function parseUserAgent() { 32 const ua = navigator.userAgent; 33 let browser = 'Unknown'; 34 if (ua.includes('Firefox/')) browser = 'Firefox'; 35 else if (ua.includes('Edg/')) browser = 'Edge'; 36 else if (ua.includes('Chrome/')) browser = 'Chrome'; 37 else if (ua.includes('Safari/') && !ua.includes('Chrome')) browser = 'Safari'; 38 else if (ua.includes('Opera') || ua.includes('OPR/')) browser = 'Opera'; 39 40 let os = 'Unknown'; 41 if (ua.includes('Windows')) os = 'Windows'; 42 else if (ua.includes('Mac OS')) os = 'macOS'; 43 else if (ua.includes('Linux')) os = 'Linux'; 44 else if (ua.includes('Android')) os = 'Android'; 45 else if (ua.includes('iOS') || ua.includes('iPhone') || ua.includes('iPad')) os = 'iOS'; 46 47 let device = 'Desktop'; 48 if (/Mobile|Android|iPhone/i.test(ua)) device = 'Mobile'; 49 else if (/iPad|Tablet/i.test(ua)) device = 'Tablet'; 50 51 return { browser, os, device }; 52 } 53 54 function getReferrer() { 55 const ref = document.referrer; 56 if (!ref) return { url: '', type: 'direct' }; 57 try { 58 const refUrl = new URL(ref); 59 if (refUrl.hostname === window.location.hostname) return { url: ref, type: 'internal' }; 60 const searchEngines = ['google', 'bing', 'yahoo', 'duckduckgo', 'baidu', 'yandex']; 61 for (const se of searchEngines) { 62 if (refUrl.hostname.includes(se)) return { url: ref, type: 'search', engine: se }; 63 } 64 const social = ['facebook', 'twitter', 'instagram', 'linkedin', 'pinterest', 'reddit', 'tiktok']; 65 for (const s of social) { 66 if (refUrl.hostname.includes(s)) return { url: ref, type: 'social', platform: s }; 67 } 68 return { url: ref, type: 'referral' }; 69 } catch (e) { 70 return { url: ref, type: 'referral' }; 71 } 72 } 73 74 async function getCountryFromCloudflare() { 75 try { 76 const response = await fetch('https://1.1.1.1/cdn-cgi/trace'); 77 const text = await response.text(); 78 const match = text.match(/loc=([A-Z]{2})/); 79 return match ? match[1] : 'Unknown'; 80 } catch (e) { 81 return getCountryFromTimezone(); 82 } 83 } 84 85 function getCountryFromTimezone() { 86 try { 87 const tz = Intl.DateTimeFormat().resolvedOptions().timeZone; 88 const tzToCountry = { 89 'Asia/Jakarta': 'ID', 'Asia/Makassar': 'ID', 'Asia/Jayapura': 'ID', 90 'Asia/Singapore': 'SG', 'Asia/Kuala_Lumpur': 'MY', 'Asia/Bangkok': 'TH', 91 'America/New_York': 'US', 'America/Chicago': 'US', 'America/Los_Angeles': 'US', 92 'Europe/London': 'GB', 'Europe/Paris': 'FR', 'Europe/Berlin': 'DE', 93 'Australia/Sydney': 'AU', 94 }; 95 return tzToCountry[tz] || 'Unknown'; 96 } catch (e) { 97 return 'Unknown'; 98 } 99 } 100 101 async function trackPageView() { 102 const session = getSessionId(); 103 const { browser, os, device } = parseUserAgent(); 104 const referrer = getReferrer(); 105 const country = await getCountryFromCloudflare(); 106 107 const sessionData = JSON.parse(sessionStorage.getItem('agc_session')); 108 sessionData.pageViews++; 109 sessionStorage.setItem('agc_session', JSON.stringify(sessionData)); 110 111 const data = { 112 domain: DOMAIN, 113 path: window.location.pathname, 114 title: document.title, 115 referrer: referrer.url, 116 referrerType: referrer.type, 117 browser: browser, 118 os: os, 119 device: device, 120 screenWidth: window.screen.width, 121 screenHeight: window.screen.height, 122 language: navigator.language, 123 sessionId: session, 124 isNewSession: sessionData.pageViews === 1, 125 timestamp: Date.now(), 126 country: country, 127 }; 128 129 if (navigator.sendBeacon) { 130 navigator.sendBeacon(ENDPOINT, JSON.stringify(data)); 131 } else { 132 fetch(ENDPOINT, { 133 method: 'POST', 134 body: JSON.stringify(data), 135 headers: { 'Content-Type': 'application/json' }, 136 keepalive: true, 137 }).catch(() => { }); 138 } 139 } 140 141 if (document.readyState === 'complete') trackPageView(); 142 else window.addEventListener('load', trackPageView); 143 144 let startTime = Date.now(); 145 window.addEventListener('beforeunload', () => { 146 const timeOnPage = Math.round((Date.now() - startTime) / 1000); 147 const data = { 148 domain: DOMAIN, 149 type: 'duration', 150 sessionId: getSessionId(), 151 duration: timeOnPage, 152 path: window.location.pathname, 153 }; 154 if (navigator.sendBeacon) navigator.sendBeacon(ENDPOINT, JSON.stringify(data)); 155 }); 156})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.