1window.ApiRateLimiter = (function() { 2 'use strict'; 3 4 var CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour local storage TTL 5 var BUFFER_DELAY_MS = 500; // 500ms delay between consecutive requests per domain 6 7 var queues = {}; 8 var lastRequestTime = {}; 9 10 function getCache(key) { 11 try { 12 var data = localStorage.getItem(key); 13 if (!data) return null; 14 var parsed = JSON.parse(data); 15 if (parsed && parsed.timestamp && (Date.now() - parsed.timestamp < CACHE_TTL_MS)) { 16 return parsed.value; 17 } 18 } catch(e) {} 19 return null; 20 } 21 22 function setCache(key, value) { 23 try { 24 if (!value) return; 25 localStorage.setItem(key, JSON.stringify({ 26 timestamp: Date.now(), 27 value: value 28 })); 29 } catch(e) {} 30 } 31 32 function clearCache(key) { 33 try { 34 localStorage.removeItem(key); 35 } catch(e) {} 36 } 37 38 function getDomain(url) { 39 try { 40 if (url && url.indexOf('/api/proxy/') !== -1) { 41 var parts = url.split('/api/proxy/')[1].split('?')[0].split('/'); 42 return 'proxy-' + (parts[0] || 'local'); 43 } 44 if (url && url.indexOf('://') !== -1) { 45 return url.split('/')[2]; 46 } 47 } catch(e) {} 48 return 'local'; 49 } 50 51 52 function resetDomain(url) { 53 var domain = getDomain(url); 54 if (queues[domain]) { 55 queues[domain].failed = false; 56 queues[domain].probeDone = false; 57 } 58 } 59 60 function processQueue(domain) { 61 var q = queues[domain]; 62 if (!q || q.processing || q.items.length === 0) return; 63 64 if (q.failed) { 65 // Single probe request failed previously for this domain; fail remaining items immediately 66 while (q.items.length > 0) { 67 var item = q.items.shift(); 68 if (item.onError) item.onError(new Error("API Probe Failed or Rate Limited")); 69 } 70 return; 71 } 72 73 q.processing = true; 74 var now = Date.now(); 75 var last = lastRequestTime[domain] || 0; 76 var waitTime = Math.max(0, BUFFER_DELAY_MS - (now - last)); 77 78 setTimeout(function() { 79 if (q.items.length === 0) { 80 q.processing = false; 81 return; 82 } 83 84 var current = q.items.shift(); 85 lastRequestTime[domain] = Date.now(); 86 87 current.task() 88 .then(function(result) { 89 q.probeDone = true; 90 q.processing = false; 91 if (current.onSuccess) current.onSuccess(result); 92 processQueue(domain); 93 }) 94 .catch(function(err) { 95 if (!q.probeDone) { 96 // First single probe request failed. Stop flooding this endpoint! 97 q.failed = true; 98 } 99 q.processing = false; 100 if (current.onError) current.onError(err); 101 processQueue(domain); 102 }); 103 }, waitTime); 104 } 105 106 function enqueue(url, taskFn, onSuccess, onError) { 107 if (url && typeof url === 'string' && url.startsWith('data:')) { 108 if (typeof onError === 'function') { 109 onError(new Error("Cannot fetch data: URIs via CORS")); 110 } 111 return; 112 } 113 var domain = getDomain(url); 114 if (!queues[domain]) { 115 queues[domain] = { items: [], processing: false, probeDone: false, failed: false }; 116 } 117 queues[domain].items.push({ 118 task: taskFn, 119 onSuccess: onSuccess, 120 onError: onError 121 }); 122 processQueue(domain); 123 } 124 125 function getApiBaseUrl() { 126 try { 127 var host = window.location.host; 128 var hostname = window.location.hostname; 129 var protocol = window.location.protocol; 130 131 var mainHost = host; 132 if (hostname.endsWith('.sol.vin') && hostname !== 'sol.vin' && hostname !== 'www.sol.vin' && hostname !== 'dev.sol.vin') { 133 mainHost = host.replace(/^[^.]+\./, ''); 134 } else if (hostname.endsWith('.lvh.me') && hostname !== 'lvh.me') { 135 mainHost = host.replace(/^[^.]+\./, ''); 136 } else if (hostname.endsWith('.localhost') && hostname !== 'localhost') { 137 mainHost = host.replace(/^[^.]+\./, ''); 138 } 139 140 return protocol + '//' + mainHost; 141 } catch(e) {} 142 return ''; 143 } 144 145 return { 146 CACHE_TTL_MS: CACHE_TTL_MS, 147 getCache: getCache, 148 setCache: setCache, 149 clearCache: clearCache, 150 resetDomain: resetDomain, 151 enqueue: enqueue, 152 getApiBaseUrl: getApiBaseUrl 153 }; 154})();
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.