1/** 2 * OI Analytics Tracker v2.0 3 * Comprehensive visitor tracking for omarismailo.com 4 * Add to your site: <script src="tracker.js" defer></script> 5 */ 6(function(){ 7 'use strict'; 8 const OIT = { 9 sid: function(){ 10 let s = sessionStorage.getItem('oi_sid'); 11 if(!s){ s = 's_'+Date.now()+'_'+Math.random().toString(36).substr(2,9); sessionStorage.setItem('oi_sid',s); } 12 return s; 13 }, 14 vid: function(){ 15 let v = localStorage.getItem('oi_vid'); 16 if(!v){ v = 'v_'+Date.now()+'_'+Math.random().toString(36).substr(2,9); localStorage.setItem('oi_vid',v); } 17 return v; 18 }, 19 isNew: function(){ return !localStorage.getItem('oi_returning'); }, 20 parseUA: function(){ 21 const u = navigator.userAgent; 22 let b='Unknown', os='Unknown', d='Desktop', bv=''; 23 if(/CriOS/i.test(u)) b='Chrome iOS'; 24 else if(/FxiOS/i.test(u)) b='Firefox iOS'; 25 else if(/EdgA/i.test(u)) b='Edge Android'; 26 else if(/Edg\//i.test(u)){ b='Edge'; bv=(u.match(/Edg\/([\d.]+)/)||[])[1]||''; } 27 else if(/OPR|Opera/i.test(u)) b='Opera'; 28 else if(/SamsungBrowser/i.test(u)) b='Samsung'; 29 else if(/UCBrowser/i.test(u)) b='UC Browser'; 30 else if(/Chrome/i.test(u)){ b='Chrome'; bv=(u.match(/Chrome\/([\d.]+)/)||[])[1]||''; } 31 else if(/Safari/i.test(u)&&!/Chrome/i.test(u)){ b='Safari'; bv=(u.match(/Version\/([\d.]+)/)||[])[1]||''; } 32 else if(/Firefox/i.test(u)){ b='Firefox'; bv=(u.match(/Firefox\/([\d.]+)/)||[])[1]||''; } 33 else if(/MSIE|Trident/i.test(u)) b='IE'; 34 if(/Windows NT 10/i.test(u)) os='Windows 10/11'; 35 else if(/Windows/i.test(u)) os='Windows'; 36 else if(/Mac OS X/i.test(u)&&!/iPhone|iPad/i.test(u)){ os='macOS'; } 37 else if(/Android ([\d.]+)/i.test(u)){ os='Android '+(u.match(/Android ([\d.]+)/)||[])[1]; } 38 else if(/iPhone OS ([\d_]+)/i.test(u)){ os='iOS '+(u.match(/iPhone OS ([\d_]+)/)||[])[1].replace(/_/g,'.'); } 39 else if(/iPad/i.test(u)) os='iPadOS'; 40 else if(/Linux/i.test(u)) os='Linux'; 41 else if(/CrOS/i.test(u)) os='ChromeOS'; 42 if(/Mobile|Android.*Mobile|iPhone/i.test(u)) d='Mobile'; 43 else if(/iPad|Android(?!.*Mobile)|Tablet/i.test(u)) d='Tablet'; 44 return { browser:b, browserVersion:bv, os:os, device:d }; 45 }, 46 getSource: function(){ 47 const r = document.referrer; 48 const u = new URL(window.location.href); 49 const utm_s = u.searchParams.get('utm_source'); 50 const utm_m = u.searchParams.get('utm_medium'); 51 const utm_c = u.searchParams.get('utm_campaign'); 52 const utm_t = u.searchParams.get('utm_term'); 53 const utm_ct = u.searchParams.get('utm_content'); 54 let source='Direct', medium='none', campaign='', keyword=''; 55 if(utm_s){ source=utm_s; medium=utm_m||''; campaign=utm_c||''; keyword=utm_t||''; } 56 else if(r){ 57 try{ 58 const rh = new URL(r).hostname.toLowerCase(); 59 if(/google\./i.test(rh)){source='Google';medium='organic';} 60 else if(/bing\./i.test(rh)){source='Bing';medium='organic';} 61 else if(/yahoo\./i.test(rh)){source='Yahoo';medium='organic';} 62 else if(/duckduckgo/i.test(rh)){source='DuckDuckGo';medium='organic';} 63 else if(/yandex/i.test(rh)){source='Yandex';medium='organic';} 64 else if(/baidu/i.test(rh)){source='Baidu';medium='organic';} 65 else if(/facebook\.com|fb\.com/i.test(rh)){source='Facebook';medium='social';} 66 else if(/instagram/i.test(rh)){source='Instagram';medium='social';} 67 else if(/twitter\.com|x\.com|t\.co/i.test(rh)){source='Twitter/X';medium='social';} 68 else if(/linkedin/i.test(rh)){source='LinkedIn';medium='social';} 69 else if(/whatsapp|wa\.me/i.test(rh)){source='WhatsApp';medium='social';} 70 else if(/tiktok/i.test(rh)){source='TikTok';medium='social';} 71 else if(/youtube/i.test(rh)){source='YouTube';medium='social';} 72 else if(/snapchat/i.test(rh)){source='Snapchat';medium='social';} 73 else if(/reddit/i.test(rh)){source='Reddit';medium='social';} 74 else if(/pinterest/i.test(rh)){source='Pinterest';medium='social';} 75 else if(/telegram/i.test(rh)){source='Telegram';medium='social';} 76 else{source=rh;medium='referral';} 77 }catch(e){source='Unknown';medium='referral';} 78 } 79 if(u.searchParams.get('fbclid')){source='Facebook';medium='paid_social';} 80 if(u.searchParams.get('gclid')){source='Google Ads';medium='cpc';} 81 if(u.searchParams.get('msclkid')){source='Bing Ads';medium='cpc';} 82 return {source,medium,campaign,keyword,utm_content:utm_ct||'',referrer:r||'Direct'}; 83 }, 84 getPerf: function(){ 85 try{ 86 const p=performance.getEntriesByType('navigation')[0]; 87 return { 88 dns:Math.round(p.domainLookupEnd-p.domainLookupStart), 89 tcp:Math.round(p.connectEnd-p.connectStart), 90 ttfb:Math.round(p.responseStart-p.requestStart), 91 domReady:Math.round(p.domContentLoadedEventEnd-p.startTime), 92 fullLoad:Math.round(p.loadEventEnd-p.startTime), 93 type:p.type 94 }; 95 }catch(e){return {dns:0,tcp:0,ttfb:0,domReady:0,fullLoad:0,type:'navigate'};} 96 }, 97 track: function(){ 98 const ua = this.parseUA();
99 const src = this.getSource(); 100 const perf = this.getPerf(); 101 const visitor = { 102 id: this.sid(), vid: this.vid(), isNew: this.isNew(), 103 timestamp: new Date().toISOString(), ts: Date.now(), 104 page: window.location.pathname + window.location.search, 105 title: document.title, hash: window.location.hash, 106 browser: ua.browser, browserVersion: ua.browserVersion, 107 os: ua.os, device: ua.device, 108 screen: screen.width+'x'+screen.height, 109 viewport: window.innerWidth+'x'+window.innerHeight, 110 pixelRatio: window.devicePixelRatio||1, 111 colorDepth: screen.colorDepth||24, 112 language: navigator.language||'unknown', 113 languages: (navigator.languages||[]).join(','), 114 timezone: Intl.DateTimeFormat().resolvedOptions().timeZone||'unknown', 115 cookiesEnabled: navigator.cookieEnabled, 116 doNotTrack: navigator.doNotTrack==='1', 117 online: navigator.onLine, 118 connection: (navigator.connection||{}).effectiveType||'unknown', 119 downlink: (navigator.connection||{}).downlink||0, 120 cores: navigator.hardwareConcurrency||0, 121 memory: navigator.deviceMemory||0, 122 touchPoints: navigator.maxTouchPoints||0, 123 source: src.source, medium: src.medium, campaign: src.campaign, 124 keyword: src.keyword, utm_content: src.utm_content, referrer: src.referrer, 125 perf: perf, scrollMax: 0, clicks: 0, duration: 0, 126 country:'', countryCode:'', city:'', region:'', isp:'', ip:'', 127 lat:0, lon:0, org:'', zip:'', exitPage:'' 128 }; 129 // Geo lookup 130 fetch('https://ip-api.com/json/?fields=66846719') 131 .then(r=>r.json()).then(d=>{ 132 if(d.status==='success'){ 133 visitor.country=d.country; visitor.countryCode=d.countryCode; 134 visitor.city=d.city; visitor.region=d.regionName; 135 visitor.isp=d.isp; visitor.ip=d.query; 136 visitor.lat=d.lat; visitor.lon=d.lon; 137 visitor.org=d.org; visitor.zip=d.zip; 138 } 139 OIT.save(visitor); 140 }).catch(()=>OIT.save(visitor)); 141 // Scroll depth 142 let maxS=0; 143 window.addEventListener('scroll',function(){ 144 const h=document.documentElement.scrollHeight-window.innerHeight; 145 const p=h>0?Math.round((window.scrollY/h)*100):0; 146 if(p>maxS){maxS=p;visitor.scrollMax=p;} 147 },{passive:true}); 148 // Clicks 149 document.addEventListener('click',function(e){ 150 visitor.clicks++; 151 const t=e.target.closest('a,button'); 152 if(t){ 153 const ce={ts:Date.now(),tag:t.tagName,text:(t.textContent||'').trim().substring(0,50),href:t.href||'',section:t.closest('section')?.id||''}; 154 let clicks=[];try{clicks=JSON.parse(localStorage.getItem('oi_clicks')||'[]')}catch(x){} 155 clicks.push(ce);if(clicks.length>1000)clicks=clicks.slice(-1000); 156 try{localStorage.setItem('oi_clicks',JSON.stringify(clicks))}catch(x){} 157 } 158 }); 159 // Page views (multi-page tracking) 160 let pvs=[];try{pvs=JSON.parse(localStorage.getItem('oi_pageviews')||'[]')}catch(x){} 161 pvs.push({page:visitor.page,ts:visitor.ts,title:visitor.title}); 162 if(pvs.length>2000) pvs=pvs.slice(-2000); 163 try{localStorage.setItem('oi_pageviews',JSON.stringify(pvs))}catch(x){} 164 // Duration + save on unload 165 const saveOnExit = () => { 166 visitor.duration=Math.round((Date.now()-visitor.ts)/1000); 167 visitor.exitPage=window.location.pathname; 168 OIT.save(visitor); 169 }; 170 window.addEventListener('beforeunload', saveOnExit); 171 document.addEventListener('visibilitychange', function(){ 172 if(document.hidden) saveOnExit(); 173 }); 174 // Auto-save every 10 seconds 175 setInterval(()=>{ 176 visitor.duration=Math.round((Date.now()-visitor.ts)/1000); 177 OIT.save(visitor); 178 },10000); 179 // Mark returning 180 setTimeout(()=>localStorage.setItem('oi_returning','1'),3000); 181 }, 182 save: function(v){ 183 let visits=[]; 184 try{visits=JSON.parse(localStorage.getItem('oi_analytics')||'[]')}catch(e){visits=[];} 185 const idx = visits.findIndex(x=>x.id===v.id); 186 if(idx>-1) visits[idx]=v; else visits.push(v); 187 if(visits.length>5000) visits=visits.slice(-5000); 188 try{localStorage.setItem('oi_analytics',JSON.stringify(visits))}catch(e){ 189 visits=visits.slice(-2500); 190 try{localStorage.setItem('oi_analytics',JSON.stringify(visits))}catch(x){} 191 } 192 } 193 }; 194 if(document.readyState==='loading') document.addEventListener('DOMContentLoaded',()=>OIT.track()); 195 else OIT.track(); 196})();
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.