1(function () { 2 'use strict'; 3 4 if (typeof window.laOutbound !== 'object' || !window.laOutbound.url) return; 5 6 // Respect Do-Not-Track â keeps us consent-free. 7 if (navigator.doNotTrack === '1' || window.doNotTrack === '1') return; 8 9 var endpoint = window.laOutbound.url; 10 var siteHost = (window.laOutbound.siteHost || location.hostname || '').toLowerCase(); 11 var patterns = Array.isArray(window.laOutbound.affiliatePatterns) ? window.laOutbound.affiliatePatterns : []; 12 var dlExts = Array.isArray(window.laOutbound.downloadExtensions) ? window.laOutbound.downloadExtensions : []; 13 14 // Lowercase, strip leading dots, build a Set for O(1) lookup. 15 var dlSet = (function () { 16 var s = Object.create(null); 17 for (var i = 0; i < dlExts.length; i++) { 18 var e = String(dlExts[i] || '').toLowerCase().replace(/^\./, ''); 19 if (e) s[e] = true; 20 } 21 return s; 22 })(); 23 24 // Glob -> RegExp. `*` matches any char except `/`, `**` matches any char. 25 // Anchored. Same semantics as the server-side va_path_matches_rules(). 26 function globToRegex(pattern) { 27 var rx = '^'; 28 var parts = pattern.split(/(\*\*|\*)/); 29 for (var i = 0; i < parts.length; i++) { 30 var seg = parts[i]; 31 if (seg === '**') rx += '.*'; 32 else if (seg === '*') rx += '[^/]*'; 33 else rx += seg.replace(/[.+?^${}()|[\]\\]/g, '\\$&'); 34 } 35 rx += '$'; 36 return new RegExp(rx, 'i'); 37 } 38 var compiledPatterns = patterns.map(globToRegex); 39 40 function matchesAffiliate(pathname) { 41 if (!compiledPatterns.length) return false; 42 for (var i = 0; i < compiledPatterns.length; i++) { 43 if (compiledPatterns[i].test(pathname)) return true; 44 } 45 return false; 46 } 47 48 function pathExtension(pathname) { 49 // Strip query / fragment defensively (URL.pathname already does this, 50 // but be conservative since some callers pass raw hrefs). 51 var p = pathname.split('?')[0].split('#')[0]; 52 var slash = p.lastIndexOf('/'); 53 var name = slash >= 0 ? p.slice(slash + 1) : p; 54 var dot = name.lastIndexOf('.'); 55 if (dot < 0 || dot === name.length - 1) return ''; 56 return name.slice(dot + 1).toLowerCase(); 57 } 58 59 function isDownload(pathname) { 60 var ext = pathExtension(pathname); 61 return ext !== '' && dlSet[ext] === true; 62 } 63 64 function fire(url, type) { 65 try { 66 var body = new URLSearchParams(); 67 body.set('action', 'va_outbound_click'); 68 body.set('url', url); 69 if (type) body.set('type', type); 70 fetch(endpoint, { 71 method: 'POST', 72 credentials: 'omit', 73 headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, 74 body: body.toString(), 75 keepalive: true, 76 }); 77 } catch (e) { /* ignore */ } 78 } 79 80 function onClick(e) { 81 // Only plain left clicks. Middle-click / cmd-click still navigate but 82 // we can't reliably catch them without blocking navigation. 83 if (e.defaultPrevented) return; 84 if (e.button !== 0) return; 85 if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return; 86 87 var a = e.target.closest ? e.target.closest('a[href]') : null; 88 if (!a) return; 89 var href = a.getAttribute('href'); 90 if (!href) return; 91 if (href.startsWith('#') || href.startsWith('javascript:') || href.startsWith('mailto:') || href.startsWith('tel:')) return; 92 93 var url; 94 try { url = new URL(a.href, location.href); } catch (err) { return; } 95 if (url.protocol !== 'http:' && url.protocol !== 'https:') return; 96 97 var host = url.hostname.toLowerCase(); 98 var pathname = url.pathname || '/'; 99 var isExternal = host !== '' && host !== siteHost; 100 var isAffiliate = !isExternal && matchesAffiliate(pathname); 101 var isDl = isDownload(pathname); 102 103 // Three categories, two writes: 104 // - external link â type=link 105 // - external download â type=download 106 // - same-host affiliate (e.g. /go/foo) â type=link 107 // - same-host download (e.g. /wp-content/uploads/foo.pdf) â type=download 108 // Anything else (regular internal navigation) is dropped. 109 if (!isExternal && !isAffiliate && !isDl) return; 110 111 fire(url.href, isDl ? 'download' : 'link'); 112 } 113 114 function start() { 115 // Capture phase so we run before most in-page handlers. We never 116 // preventDefault â navigation proceeds normally. 117 document.addEventListener('click', onClick, true); 118 } 119 120 if (document.readyState === 'loading') { 121 document.addEventListener('DOMContentLoaded', start); 122 } else { 123 start(); 124 } 125})();
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.