Source Code
The entire project, readable in your browser. No hidden JavaScript, no sneaky beacons. If we claimed we don't track you, this is where you check.
|
Pages/
Feeds/
Backend/
Includes/
Stylesheet/
JavaScript/
data/counter.txt and data/guestbook.txt hold visitor-supplied content
(a number and signed messages), so they are not shown here.
|
1 /* ============================================================================ 2 detect.js -- Browser & hardware detection engine 3 ---------------------------------------------------------------------------- 4 Probes the browser for everything it will tell us: engine/version, OS, 5 architecture, cores, memory, screen, feature-support for 60+ Web APIs, 6 permissions, media devices, codecs, image formats, fonts, and more. 7 8 Exposes: window.EYES.detect() -> Promise<object> 9 10 Nothing here is transmitted anywhere. It is all read locally and handed back 11 to the page that asked for it. The heavy fingerprint hashing lives in 12 fingerprint.js; this file is pure feature/attribute enumeration. 13 ========================================================================== */ 14 (function () { 15 "use strict"; 16 17 window.EYES = window.EYES || {}; 18 19 /* ---- tiny helpers ------------------------------------------------------ */ 20 function safe(fn, dflt) { 21 try { var v = fn(); return (v === undefined ? dflt : v); } 22 catch (e) { return dflt; } 23 } 24 function has(obj, prop) { try { return prop in obj; } catch (e) { return false; } } 25 function yn(v) { return v === true ? 'Yes' : (v === false ? 'No' : (v == null ? 'Unknown' : v)); } 26 27 /* ---- User-Agent parsing (lightweight, no libraries) -------------------- */ 28 function parseUA(ua) { 29 ua = ua || ''; 30 var out = { browser: 'Unknown', version: '', engine: 'Unknown', os: 'Unknown', osVersion: '' }; 31 32 // Engine 33 if (/Gecko\/|rv:.*Gecko/.test(ua) && /Firefox/.test(ua)) out.engine = 'Gecko'; 34 else if (/AppleWebKit/.test(ua) && /Edg\//.test(ua)) out.engine = 'Blink'; 35 else if (/AppleWebKit/.test(ua) && /Chrome\//.test(ua)) out.engine = 'Blink'; 36 else if (/AppleWebKit/.test(ua)) out.engine = 'WebKit'; 37 else if (/Trident/.test(ua)) out.engine = 'Trident'; 38 39 // Browser + version (order matters: check derivatives before Chrome/Safari) 40 var m; 41 if ((m = ua.match(/Firefox\/([\d.]+)/))) { out.browser = 'Firefox'; out.version = m[1]; } 42 else if ((m = ua.match(/Edg\/([\d.]+)/))) { out.browser = 'Edge'; out.version = m[1]; } 43 else if ((m = ua.match(/OPR\/([\d.]+)/))) { out.browser = 'Opera'; out.version = m[1]; } 44 else if ((m = ua.match(/Vivaldi\/([\d.]+)/))) { out.browser = 'Vivaldi'; out.version = m[1]; } 45 else if ((m = ua.match(/SamsungBrowser\/([\d.]+)/))) { out.browser = 'Samsung Internet'; out.version = m[1]; } 46 else if ((m = ua.match(/Chrome\/([\d.]+)/))) { out.browser = 'Chrome'; out.version = m[1]; } 47 else if ((m = ua.match(/Version\/([\d.]+).*Safari/))) { out.browser = 'Safari'; out.version = m[1]; } 48 else if (/MSIE|Trident/.test(ua)) { 49 out.browser = 'Internet Explorer'; 50 m = ua.match(/(?:MSIE |rv:)([\d.]+)/); out.version = m ? m[1] : ''; 51 } 52 53 // OS 54 if (/Windows NT 10/.test(ua)) { out.os = 'Windows'; out.osVersion = '10/11'; } 55 else if (/Windows NT 6\.3/.test(ua)) { out.os = 'Windows'; out.osVersion = '8.1'; } 56 else if (/Windows NT 6\.2/.test(ua)) { out.os = 'Windows'; out.osVersion = '8'; } 57 else if (/Windows NT 6\.1/.test(ua)) { out.os = 'Windows'; out.osVersion = '7'; } 58 else if (/Windows NT 5\.1/.test(ua)) { out.os = 'Windows'; out.osVersion = 'XP'; } 59 else if (/Windows/.test(ua)) { out.os = 'Windows'; } 60 else if (/Android ([\d.]+)/.test(ua)){ out.os = 'Android'; out.osVersion = (ua.match(/Android ([\d.]+)/)||[])[1] || ''; } 61 else if (/iPhone|iPad|iPod/.test(ua)){ out.os = 'iOS'; out.osVersion = ((ua.match(/OS ([\d_]+)/)||[])[1] || '').replace(/_/g,'.'); } 62 else if (/Mac OS X ([\d_]+)/.test(ua)){ out.os = 'macOS'; out.osVersion = (ua.match(/Mac OS X ([\d_]+)/)[1] || '').replace(/_/g,'.'); } 63 else if (/CrOS/.test(ua)) { out.os = 'ChromeOS'; } 64 else if (/Linux/.test(ua)) { out.os = 'Linux'; } 65 else if (/X11/.test(ua)) { out.os = 'Unix'; } 66 67 return out; 68 } 69 70 /* ---- async: battery ---------------------------------------------------- */ 71 function getBattery() { 72 return new Promise(function (resolve) { 73 if (!navigator.getBattery) { resolve(null); return; } 74 var done = false; 75 var t = setTimeout(function () { if (!done) { done = true; resolve(null); } }, 1500); 76 navigator.getBattery().then(function (b) { 77 if (done) return; done = true; clearTimeout(t); 78 resolve({ 79 charging: b.charging, 80 level: Math.round(b.level * 100), 81 chargingTime: b.chargingTime, 82 dischargingTime: b.dischargingTime 83 }); 84 }).catch(function () { if (!done) { done = true; clearTimeout(t); resolve(null); } }); 85 }); 86 } 87 88 /* ---- async: permissions ------------------------------------------------ */ 89 function getPermissions() { 90 return new Promise(function (resolve) { 91 var results = {}; 92 if (!navigator.permissions || !navigator.permissions.query) { resolve(results); return; } 93 var names = ['geolocation','notifications','camera','microphone','clipboard-read', 94 'clipboard-write','persistent-storage','midi','background-sync']; 95 var pending = names.length; 96 if (pending === 0) { resolve(results); return; } 97 names.forEach(function (n) { 98 var settled = false; 99 var finish = function (state) { 100 if (settled) return; settled = true; 101 results[n] = state; if (--pending === 0) resolve(results); 102 }; 103 try { 104 navigator.permissions.query({ name: n }).then(function (p) { finish(p.state); }) 105 .catch(function () { finish('unsupported'); }); 106 } catch (e) { finish('unsupported'); } 107 }); 108 // Safety timeout. 109 setTimeout(function () { resolve(results); }, 2000); 110 }); 111 } 112 113 /* ---- async: media devices (counts only, no labels/permission) ---------- */ 114 function getMediaDevices() { 115 return new Promise(function (resolve) { 116 if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) { 117 resolve({ supported: false }); return; 118 } 119 var t = setTimeout(function () { resolve({ supported: true, timeout: true }); }, 1500); 120 navigator.mediaDevices.enumerateDevices().then(function (list) { 121 clearTimeout(t); 122 var counts = { audioinput: 0, audiooutput: 0, videoinput: 0 }; 123 var labelled = 0; 124 list.forEach(function (d) { 125 if (counts[d.kind] !== undefined) counts[d.kind]++; 126 if (d.label) labelled++; 127 }); 128 resolve({ 129 supported: true, 130 cameras: counts.videoinput, 131 microphones: counts.audioinput, 132 speakers: counts.audiooutput, 133 total: list.length, 134 labelsExposed: labelled > 0 135 }); 136 }).catch(function () { clearTimeout(t); resolve({ supported: true, error: true }); }); 137 }); 138 } 139 140 /* ---- async: storage estimate ------------------------------------------- */ 141 function getStorageEstimate() { 142 return new Promise(function (resolve) { 143 if (!navigator.storage || !navigator.storage.estimate) { resolve(null); return; } 144 var t = setTimeout(function () { resolve(null); }, 1500); 145 navigator.storage.estimate().then(function (est) { 146 clearTimeout(t); 147 resolve({ quota: est.quota || null, usage: est.usage || null }); 148 }).catch(function () { clearTimeout(t); resolve(null); }); 149 }); 150 } 151 152 /* ---- codecs & image formats -------------------------------------------- */ 153 function getCodecs() { 154 var v = document.createElement('video'); 155 var a = document.createElement('audio'); 156 function can(el, type) { 157 try { var r = el.canPlayType(type); return r === '' ? 'no' : r; } 158 catch (e) { return 'no'; } 159 } 160 return { 161 h264: can(v, 'video/mp4; codecs="avc1.42E01E"'), 162 hevc: can(v, 'video/mp4; codecs="hev1.1.6.L93.B0"'), 163 vp8: can(v, 'video/webm; codecs="vp8"'), 164 vp9: can(v, 'video/webm; codecs="vp9"'), 165 av1: can(v, 'video/mp4; codecs="av01.0.05M.08"'), 166 ogg: can(v, 'video/ogg; codecs="theora"'), 167 aac: can(a, 'audio/mp4; codecs="mp4a.40.2"'), 168 mp3: can(a, 'audio/mpeg'), 169 opus: can(a, 'audio/webm; codecs="opus"'), 170 flac: can(a, 'audio/flac'), 171 wav: can(a, 'audio/wav; codecs="1"') 172 }; 173 } 174 function getImageFormats() { 175 function supports(mime) { 176 try { 177 var c = document.createElement('canvas'); 178 if (!c.toDataURL) return false; 179 return c.toDataURL(mime).indexOf('data:' + mime) === 0; 180 } catch (e) { return false; } 181 } 182 return { 183 webp: supports('image/webp'), 184 avif: supports('image/avif'), 185 png: supports('image/png'), 186 jpeg: supports('image/jpeg'), 187 gif: true // universally supported for display 188 }; 189 } 190 191 /* ---- font detection (offset-width probe) ------------------------------- */ 192 function detectFonts() { 193 var baseFonts = ['monospace', 'sans-serif', 'serif']; 194 var testString = 'mmmmmmmmmmlli'; 195 var testSize = '72px'; 196 var candidates = [ 197 'Arial','Arial Black','Arial Narrow','Calibri','Cambria','Candara','Comic Sans MS', 198 'Consolas','Courier','Courier New','Georgia','Helvetica','Impact','Lucida Console', 199 'Lucida Sans Unicode','Microsoft Sans Serif','Palatino Linotype','Segoe UI','Tahoma', 200 'Times','Times New Roman','Trebuchet MS','Verdana','Webdings','Wingdings','MS Gothic', 201 'MS PGothic','MS Mincho','SimSun','SimHei','NSimSun','Menlo','Monaco','DejaVu Sans', 202 'DejaVu Serif','DejaVu Sans Mono','Liberation Sans','Ubuntu','Cantarell','Roboto', 203 'Noto Sans','Fira Sans','Droid Sans','San Francisco','Apple Color Emoji','Segoe UI Emoji' 204 ]; 205 206 var body = document.body; 207 var span = document.createElement('span'); 208 span.style.position = 'absolute'; 209 span.style.left = '-9999px'; 210 span.style.top = '-9999px'; 211 span.style.fontSize = testSize; 212 span.style.lineHeight = 'normal'; 213 span.innerHTML = testString; 214 215 var defaults = {}; 216 var el = {}; 217 218 baseFonts.forEach(function (base) { 219 span.style.fontFamily = base; 220 body.appendChild(span); 221 defaults[base] = { w: span.offsetWidth, h: span.offsetHeight }; 222 body.removeChild(span); 223 }); 224 225 function isAvailable(font) { 226 for (var i = 0; i < baseFonts.length; i++) { 227 span.style.fontFamily = "'" + font + "'," + baseFonts[i]; 228 body.appendChild(span); 229 var matched = (span.offsetWidth !== defaults[baseFonts[i]].w || 230 span.offsetHeight !== defaults[baseFonts[i]].h); 231 body.removeChild(span); 232 if (matched) return true; 233 } 234 return false; 235 } 236 237 var found = []; 238 for (var i = 0; i < candidates.length; i++) { 239 if (isAvailable(candidates[i])) found.push(candidates[i]); 240 } 241 return found; 242 } 243 244 /* ---- WebGL vendor/renderer (unmasked) ---------------------------------- */ 245 function getWebGLInfo() { 246 var info = { supported: false, webgl2: false, vendor: null, renderer: null, 247 version: null, shading: null, maxTexture: null, extensions: 0 }; 248 try { 249 var canvas = document.createElement('canvas'); 250 var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); 251 if (!gl) return info; 252 info.supported = true; 253 info.webgl2 = !!(window.WebGL2RenderingContext && canvas.getContext('webgl2')); 254 info.version = gl.getParameter(gl.VERSION); 255 info.shading = gl.getParameter(gl.SHADING_LANGUAGE_VERSION); 256 info.maxTexture = gl.getParameter(gl.MAX_TEXTURE_SIZE); 257 var dbg = gl.getExtension('WEBGL_debug_renderer_info'); 258 if (dbg) { 259 info.vendor = gl.getParameter(dbg.UNMASKED_VENDOR_WEBGL); 260 info.renderer = gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL); 261 } else { 262 info.vendor = gl.getParameter(gl.VENDOR); 263 info.renderer = gl.getParameter(gl.RENDERER); 264 } 265 var ext = gl.getSupportedExtensions(); 266 info.extensions = ext ? ext.length : 0; 267 } catch (e) {} 268 return info; 269 } 270 271 /* ---- WebGPU (async but quick check) ------------------------------------ */ 272 function getWebGPU() { 273 return new Promise(function (resolve) { 274 if (!navigator.gpu) { resolve({ supported: false }); return; } 275 var t = setTimeout(function () { resolve({ supported: true, adapter: null }); }, 1500); 276 try { 277 navigator.gpu.requestAdapter().then(function (adapter) { 278 clearTimeout(t); 279 if (!adapter) { resolve({ supported: true, adapter: null }); return; } 280 var out = { supported: true, adapter: 'available' }; 281 if (adapter.info) { 282 out.vendor = adapter.info.vendor || null; 283 out.architecture = adapter.info.architecture || null; 284 } 285 resolve(out); 286 }).catch(function () { clearTimeout(t); resolve({ supported: true, adapter: null }); }); 287 } catch (e) { clearTimeout(t); resolve({ supported: true }); } 288 }); 289 } 290 291 /* ---- network information ----------------------------------------------- */ 292 function getNetwork() { 293 var c = navigator.connection || navigator.mozConnection || navigator.webkitConnection; 294 if (!c) return { supported: false }; 295 return { 296 supported: true, 297 type: c.type || null, 298 effectiveType: c.effectiveType || null, 299 downlink: c.downlink != null ? c.downlink : null, 300 rtt: c.rtt != null ? c.rtt : null, 301 saveData: c.saveData === true 302 }; 303 } 304 305 /* ---- storage / worker feature flags ------------------------------------ */ 306 function testStorageAPIs() { 307 function localOK() { 308 try { var k='__t'; localStorage.setItem(k,'1'); localStorage.removeItem(k); return true; } 309 catch (e) { return false; } 310 } 311 function sessionOK() { 312 try { var k='__t'; sessionStorage.setItem(k,'1'); sessionStorage.removeItem(k); return true; } 313 catch (e) { return false; } 314 } 315 return { 316 localStorage: localOK(), 317 sessionStorage: sessionOK(), 318 indexedDB: has(window, 'indexedDB') && !!window.indexedDB, 319 cacheAPI: has(window, 'caches'), 320 cookiesEnabled: navigator.cookieEnabled === true, 321 cookieStore: has(window, 'cookieStore') 322 }; 323 } 324 325 /* ==== main ============================================================== */ 326 EYES.detect = function detect() { 327 var nav = navigator; 328 var ua = nav.userAgent || ''; 329 var uaInfo = parseUA(ua); 330 331 var screenInfo = { 332 width: safe(function(){return screen.width;}, null), 333 height: safe(function(){return screen.height;}, null), 334 availWidth: safe(function(){return screen.availWidth;}, null), 335 availHeight: safe(function(){return screen.availHeight;}, null), 336 colorDepth: safe(function(){return screen.colorDepth;}, null), 337 pixelDepth: safe(function(){return screen.pixelDepth;}, null), 338 pixelRatio: safe(function(){return window.devicePixelRatio;}, 1), 339 orientation: safe(function(){return screen.orientation ? screen.orientation.type : null;}, null), 340 viewportW: safe(function(){return window.innerWidth;}, null), 341 viewportH: safe(function(){return window.innerHeight;}, null) 342 }; 343 344 var mm = function (q) { return safe(function(){return window.matchMedia(q).matches;}, null); }; 345 346 // Synchronous block first. 347 var base = { 348 ua: { 349 raw: ua, 350 browser: uaInfo.browser, 351 version: uaInfo.version, 352 engine: uaInfo.engine, 353 os: uaInfo.os, 354 osVersion: uaInfo.osVersion, 355 vendor: nav.vendor || '', 356 product: nav.product || '', 357 appName: nav.appName || '', 358 appVersion: nav.appVersion || '' 359 }, 360 platform: { 361 platform: nav.platform || 'unknown', 362 oscpu: nav.oscpu || null, 363 architecture: safe(function () { 364 // Guess architecture from UA hints. 365 if (/x64|x86_64|Win64|WOW64|amd64/i.test(ua)) return '64-bit (x86_64)'; 366 if (/arm64|aarch64/i.test(ua)) return '64-bit (ARM)'; 367 if (/arm/i.test(ua)) return '32-bit (ARM)'; 368 if (/i686|i386|x86/i.test(ua)) return '32-bit (x86)'; 369 return 'unknown'; 370 }, 'unknown'), 371 cores: nav.hardwareConcurrency || null, 372 memory: nav.deviceMemory || null, 373 maxTouchPoints: nav.maxTouchPoints || 0, 374 languages: nav.languages || [nav.language], 375 language: nav.language || null, 376 pdfViewer: nav.pdfViewerEnabled === true, 377 webdriver: nav.webdriver === true, 378 online: nav.onLine === true, 379 doNotTrack: (nav.doNotTrack || window.doNotTrack || nav.msDoNotTrack || null) 380 }, 381 screen: screenInfo, 382 preferences: { 383 darkMode: mm('(prefers-color-scheme: dark)'), 384 lightMode: mm('(prefers-color-scheme: light)'), 385 reducedMotion: mm('(prefers-reduced-motion: reduce)'), 386 reducedData: mm('(prefers-reduced-data: reduce)'), 387 highContrast: mm('(prefers-contrast: more)'), 388 forcedColors: mm('(forced-colors: active)'), 389 touch: (('ontouchstart' in window) || nav.maxTouchPoints > 0), 390 hover: mm('(hover: hover)'), 391 pointerFine: mm('(pointer: fine)') 392 }, 393 time: { 394 timezone: safe(function(){return Intl.DateTimeFormat().resolvedOptions().timeZone;}, null), 395 offsetMinutes: new Date().getTimezoneOffset(), 396 locale: safe(function(){return Intl.DateTimeFormat().resolvedOptions().locale;}, null), 397 calendar: safe(function(){return Intl.DateTimeFormat().resolvedOptions().calendar;}, null), 398 numberingSystem: safe(function(){return Intl.DateTimeFormat().resolvedOptions().numberingSystem;}, null) 399 }, 400 apis: { 401 cookies: nav.cookieEnabled === true, 402 clipboard: has(nav, 'clipboard'), 403 notification: has(window, 'Notification'), 404 permissions: has(nav, 'permissions'), 405 geolocation: has(nav, 'geolocation'), 406 serviceWorker: has(nav, 'serviceWorker'), 407 sharedWorker: has(window, 'SharedWorker'), 408 webWorker: has(window, 'Worker'), 409 broadcastChannel: has(window, 'BroadcastChannel'), 410 webAssembly: has(window, 'WebAssembly'), 411 webGPU: has(nav, 'gpu'), 412 webRTC: has(window, 'RTCPeerConnection') || has(window, 'webkitRTCPeerConnection'), 413 webSocket: has(window, 'WebSocket'), 414 webVR: has(nav, 'getVRDisplays'), 415 webXR: has(nav, 'xr'), 416 bluetooth: has(nav, 'bluetooth'), 417 usb: has(nav, 'usb'), 418 hid: has(nav, 'hid'), 419 serial: has(nav, 'serial'), 420 nfc: has(window, 'NDEFReader'), 421 gamepad: has(nav, 'getGamepads'), 422 speechRecognition: has(window, 'SpeechRecognition') || has(window, 'webkitSpeechRecognition'), 423 speechSynthesis: has(window, 'speechSynthesis'), 424 mediaDevices: has(nav, 'mediaDevices'), 425 mediaRecorder: has(window, 'MediaRecorder'), 426 mediaSession: has(nav, 'mediaSession'), 427 paymentRequest: has(window, 'PaymentRequest'), 428 credentials: has(nav, 'credentials'), 429 wakeLock: has(nav, 'wakeLock'), 430 share: has(nav, 'share'), 431 contacts: has(nav, 'contacts'), 432 vibration: has(nav, 'vibrate'), 433 battery: has(nav, 'getBattery'), 434 deviceMemory: has(nav, 'deviceMemory'), 435 virtualKeyboard: has(nav, 'virtualKeyboard'), 436 ambientLight: has(window, 'AmbientLightSensor'), 437 accelerometer: has(window, 'Accelerometer'), 438 gyroscope: has(window, 'Gyroscope'), 439 magnetometer: has(window, 'Magnetometer'), 440 idleDetection: has(window, 'IdleDetector'), 441 fileSystemAccess: has(window, 'showOpenFilePicker'), 442 eyeDropper: has(window, 'EyeDropper'), 443 offscreenCanvas: has(window, 'OffscreenCanvas'), 444 resizeObserver: has(window, 'ResizeObserver'), 445 intersectionObserver: has(window, 'IntersectionObserver'), 446 reportingObserver: has(window, 'ReportingObserver'), 447 performanceMemory: !!(window.performance && performance.memory) 448 }, 449 storage: testStorageAPIs(), 450 network: getNetwork(), 451 webgl: getWebGLInfo(), 452 codecs: getCodecs(), 453 imageFormats: getImageFormats(), 454 audioContext: has(window, 'AudioContext') || has(window, 'webkitAudioContext'), 455 canvas: (function(){ try { return !!document.createElement('canvas').getContext('2d'); } catch(e){ return false; } })(), 456 plugins: safe(function () { 457 var out = []; 458 if (nav.plugins) for (var i = 0; i < nav.plugins.length; i++) out.push(nav.plugins[i].name); 459 return out; 460 }, []), 461 mimeTypes: safe(function () { return nav.mimeTypes ? nav.mimeTypes.length : 0; }, 0), 462 jsHeapLimit: safe(function(){ return performance.memory ? performance.memory.jsHeapSizeLimit : null; }, null), 463 speechVoices: safe(function () { 464 try { return window.speechSynthesis ? window.speechSynthesis.getVoices().length : 0; } 465 catch (e) { return 0; } 466 }, 0) 467 }; 468 469 // Fonts are a little expensive; do them synchronously but guarded. 470 base.fonts = safe(detectFonts, []); 471 472 // Now the async probes, in parallel. 473 return Promise.all([ 474 getBattery(), 475 getPermissions(), 476 getMediaDevices(), 477 getStorageEstimate(), 478 getWebGPU() 479 ]).then(function (res) { 480 base.battery = res[0]; 481 base.permissions = res[1]; 482 base.mediaDevices = res[2]; 483 base.storageQuota = res[3]; 484 base.webgpu = res[4]; 485 base._collectedAt = new Date().toISOString(); 486 return base; 487 }); 488 }; 489 490 /* Expose helpers other modules reuse. */ 491 EYES._util = { safe: safe, has: has, yn: yn, parseUA: parseUA }; 492 493 })(); 494 You are reading the real file being executed on the server. What you see is what runs. |