1/** 2 * Global Client IP Detector 3 * Detects real client IPv4 address (not server IP or IPv6) 4 * 5 * Usage: Include this script in your HTML file: 6 * <script src="https://api.theeztools.com/api/js/client-ip-detector.js"></script> 7 * 8 * The script automatically detects IP and makes it available globally 9 * Only our tools API requests (fetch, XHR, jQuery AJAX) get X-Client-IPv4 â 10 * never third-party hosts (e.g. Google Translate), to avoid CORS preflight failures. 11 */ 12 13(function () { 14 'use strict'; 15 16 // Laravel API host for /api/client-ip/detect only. Never use location.origin here: 17 // on third-party sites (e.g. freedapachecker.io) that would POST to the wrong host â 404. 18 // Optional override: <script>window.TOOLS_API_BASE_URL='https://api.theeztools.com';</script> before this file. 19 const toolsApiBase = (function () { 20 if (typeof window !== 'undefined' && window.TOOLS_API_BASE_URL) { 21 return String(window.TOOLS_API_BASE_URL).replace(/\/+$/, ''); 22 } 23 return 'https://api.theeztools.com'; 24 })(); 25 26 function normalizeRequestUrl(url) { 27 if (typeof url === 'string') return url; 28 if (url && typeof url.url === 'string') return url.url; 29 return ''; 30 } 31 32 function getAllowedApiHosts() { 33 const hosts = new Set(['api.theeztools.com', 'tools.xrespond.com']); 34 try { 35 hosts.add(new URL(toolsApiBase).hostname); 36 } catch (e) { /* ignore */ } 37 if (typeof window !== 'undefined' && window.location) { 38 const h = window.location.hostname; 39 if (h === 'localhost' || h === '127.0.0.1') { 40 hosts.add(h); 41 } 42 } 43 return hosts; 44 } 45 46 /** Free IP lookup endpoints â never wait/attach header (needed for detection). */ 47 function isIpDetectionSkipUrl(url) { 48 const u = normalizeRequestUrl(url); 49 if (!u) return false; 50 if (u.includes('/api/client-ip/detect')) return true; 51 return u.includes('ipify.org') || u.includes('ifconfig.me') || u.includes('icanhazip.com') || 52 u.includes('ident.me') || u.includes('ipecho.net') || u.includes('checkip.amazonaws.com') || 53 u.includes('ipinfo.io') || u.includes('seeip.org') || u.includes('wtfismyip.com') || 54 u.includes('dnsomatic.com') || u.includes('ip.42.pl') || u.includes('l2.io') || 55 u.includes('myip.com') || u.includes('ipapi.co') || u.includes('ip-api.com') || 56 u.includes('ip.sb') || u.includes('anysrc.net'); 57 } 58 59 /** 60 * Only attach X-Client-IPv4 to our tools API host. 61 * Custom headers on cross-origin third parties (Translate, etc.) trigger CORS preflight. 62 */ 63 function shouldAttachClientIpHeader(url) { 64 const u = normalizeRequestUrl(url); 65 if (!u || isIpDetectionSkipUrl(u)) return false; 66 try { 67 const base = (typeof window !== 'undefined' && window.location && window.location.href) 68 ? window.location.href 69 : toolsApiBase; 70 const resolved = new URL(u, base); 71 return getAllowedApiHosts().has(resolved.hostname); 72 } catch (e) { 73 return false; 74 } 75 } 76 77 /** Current browser page â used as site_url / Detail URL for any embed domain. */ 78 function getToolPageUrl() { 79 try { 80 return String((window.location && window.location.href) || '').trim(); 81 } catch (e) { 82 return ''; 83 } 84 } 85 86 function attachToolPageUrlToHeadersObject(headers) { 87 const pageUrl = getToolPageUrl(); 88 if (!pageUrl || !headers || typeof headers !== 'object') return headers; 89 const has = Object.keys(headers).some(function (key) { 90 return String(key).toLowerCase() === 'x-tool-page-url'; 91 }); 92 if (!has) { 93 headers['X-Tool-Page-Url'] = pageUrl; 94 } 95 return headers; 96 } 97 98 // Free IP APIs list - 15+ different services 99 // Each API will be checked one by one with 5-second timeout 100 const freeIpApis = [ 101 // JSON APIs 102 { url: 'https://api.ipify.org?format=json', type: 'json', field: 'ip', name: 'ipify-json' }, 103 { url: 'https://api64.ipify.org?format=json', type: 'json', field: 'ip', name: 'ipify64-json' }, 104 { url: 'https://api.myip.com', type: 'json', field: 'ip', name: 'myip-json' }, 105 { url: 'https://ipapi.co/json', type: 'json', field: 'ip', name: 'ipapi-json' }, 106 { url: 'https://ip-api.com/json', type: 'json', field: 'query', name: 'ip-api-json' }, 107 108 // Plain text APIs (IPv4 only) - Fastest and most reliable 109 { url: 'https://api.ipify.org', type: 'text', name: 'ipify-text' }, 110 { url: 'https://ifconfig.me/ip', type: 'text', name: 'ifconfig-me' }, 111 { url: 'https://icanhazip.com', type: 'text', name: 'icanhazip' },
112 { url: 'https://ident.me', type: 'text', name: 'ident-me' }, 113 { url: 'https://ipecho.net/plain', type: 'text', name: 'ipecho' }, 114 { url: 'https://checkip.amazonaws.com', type: 'text', name: 'amazonaws' }, 115 { url: 'https://ipinfo.io/ip', type: 'text', name: 'ipinfo' }, 116 { url: 'https://ip.seeip.org', type: 'text', name: 'seeip' }, 117 { url: 'https://wtfismyip.com/text', type: 'text', name: 'wtfismyip' }, 118 { url: 'https://myip.dnsomatic.com', type: 'text', name: 'dnsomatic' }, 119 { url: 'https://ip.42.pl/raw', type: 'text', name: 'ip42' }, 120 { url: 'https://l2.io/ip', type: 'text', name: 'l2io' }, 121 { url: 'https://ipv4.icanhazip.com', type: 'text', name: 'icanhazip-v4' }, 122 { url: 'https://ipv4.ident.me', type: 'text', name: 'ident-v4' }, 123 { url: 'https://v4.ident.me', type: 'text', name: 'ident-v4-alt' }, 124 { url: 'https://4.ident.me', type: 'text', name: 'ident-4' }, 125 { url: 'https://api.ip.sb/ip', type: 'text', name: 'ip-sb' }, 126 { url: 'https://ipv4.wtfismyip.com/text', type: 'text', name: 'wtfismyip-v4' }, 127 { url: 'https://ip.anysrc.net/plain', type: 'text', name: 'anysrc' }, 128 ]; 129 130 // IP Detection state 131 let detectedClientIp = null; 132 let detectedIpv4 = null; 133 let ipDetectionInProgress = false; 134 let ipDetectionPromise = null; 135 136 /** 137 * Intercept jQuery AJAX globally (if jQuery is available) 138 * Also handles jQuery loading after this script 139 * Defined early so it can be called from anywhere 140 */ 141 function interceptJQueryAjax() { 142 if (typeof jQuery !== 'undefined' && jQuery.ajax) { 143 // Check if already intercepted 144 if (jQuery.ajax._clientIpIntercepted) { 145 return; 146 } 147 148 // Get the current jQuery.ajax (might already be intercepted by global-rate-limit-handler.js) 149 const originalAjax = jQuery.ajax; 150 151 // Check if it's already been intercepted by us 152 if (jQuery.ajax._clientIpIntercepted) { 153 return; // Already intercepted 154 } 155 156 jQuery.ajax = async function (options) { 157 const url = options.url || ''; 158 159 // Skip IP-detection URLs and any non-tools-API host (avoids CORS preflight) 160 if (!shouldAttachClientIpHeader(url)) { 161 return originalAjax.call(this, options); 162 } 163 164 // Wait for IP detection before sending request 165 const ip = await waitForIpDetection(10000); 166 167 if (!ip) { 168 console.warn('[Client IP Detector] â ï¸ IP not detected yet, jQuery AJAX request may proceed without X-Client-IPv4 header'); 169 } 170 171 // Ensure headers object exists 172 if (!options.headers) { 173 options.headers = {}; 174 } 175 176 // Add client-detected IPv4 (this will override any IP from global-rate-limit-handler.js) 177 if (ip) { 178 options.headers['X-Client-IPv4'] = ip; 179 } 180 attachToolPageUrlToHeadersObject(options.headers); 181 182 // Also add to beforeSend hook as backup (runs after headers are set) 183 const originalBeforeSend = options.beforeSend; 184 options.beforeSend = function (xhr, settings) { 185 // Get latest IP (might have been detected after options were set) 186 const latestIp = detectedIpv4 || window.clientDetectedIpv4 || window.detectedClientIpv4 || ip || null; 187 if (latestIp) { 188 try { 189 xhr.setRequestHeader('X-Client-IPv4', latestIp); 190 } catch (e) { 191 // Header might already be set, ignore error 192 } 193 } 194 const pageUrl = getToolPageUrl(); 195 if (pageUrl) { 196 try { 197 xhr.setRequestHeader('X-Tool-Page-Url', pageUrl); 198 } catch (e) { 199 // Header might already be set, ignore error 200 } 201 } 202 // Call original beforeSend (might be from global-rate-limit-handler.js or user co
202de) 203 if (originalBeforeSend) { 204 return originalBeforeSend.call(this, xhr, settings); 205 } 206 }; 207 208 // Call original ajax (which might be global-rate-limit-handler.js's interception) 209 return originalAjax.call(this, options); 210 }; 211 212 // Mark as intercepted 213 jQuery.ajax._clientIpIntercepted = true; 214 } 215 } 216 217 /** 218 * Get IPv4 from free APIs (browser-side - sees user's real IP) 219 * Only returns IPv4, ignores IPv6 220 * Each API has 5-second timeout, checks one by one until IP is found 221 */ 222 async function getIpv4FromFreeApis() { 223 const API_TIMEOUT = 5000; // 5 seconds per API 224 225 console.log(`[Client IP Detector] Checking ${freeIpApis.length} free IP APIs (5s timeout each)...`); 226 227 // Check each API one by one with timeout 228 for (let i = 0; i < freeIpApis.length; i++) { 229 const api = freeIpApis[i]; 230 const apiName = api.name || `API-${i + 1}`; 231 232 try { 233 console.log(`[Client IP Detector] Trying ${apiName} (${i + 1}/${freeIpApis.length})...`); 234 235 // Create timeout promise 236 const timeoutPromise = new Promise((_, reject) => { 237 setTimeout(() => reject(new Error('Timeout')), API_TIMEOUT); 238 }); 239 240 // Create fetch promise 241 const fetchPromise = fetch(api.url, { 242 method: 'GET', 243 headers: { 244 'Accept': api.type === 'json' ? 'application/json' : 'text/plain', 245 }, 246 }); 247 248 // Race between fetch and timeout 249 const response = await Promise.race([fetchPromise, timeoutPromise]); 250 251 if (response && response.ok) { 252 let ip = null; 253 254 if (api.type === 'json') { 255 const data = await response.json(); 256 ip = data[api.field] || null; 257 } else { 258 ip = (await response.text()).trim(); 259 } 260 261 // Validate it's a valid IPv4 (NOT IPv6) 262 if (ip && /^(\d{1,3}\.){3}\d{1,3}$/.test(ip)) { 263 // Double check it's not IPv6 mapped IPv4 264 if (!ip.includes(':') && filterIPv4(ip)) { 265 console.log(`[Client IP Detector] â IP found from ${apiName}: ${ip}`); 266 return ip; // Found valid IPv4, stop immediately 267 } 268 } 269 } 270 } catch (error) { 271 // Timeout or other error - try next API 272 if (error.message === 'Timeout') { 273 console.log(`[Client IP Detector] â±ï¸ ${apiName} timed out after 5s, trying next...`); 274 } else { 275 console.log(`[Client IP Detector] â ${apiName} failed: ${error.message}, trying next...`); 276 } 277 continue; // Try next API 278 } 279 } 280 281 console.warn(`[Client IP Detector] â ï¸ No IPv4 found from any of ${freeIpApis.length} APIs`); 282 return null; // No IPv4 found from any API 283 } 284 285 /** 286 * Filter to ensure it's IPv4 only 287 */ 288 function filterIPv4(ip) { 289 const parts = ip.split('.'); 290 if (parts.length !== 4) return false; 291 292 for (const part of parts) { 293 const num = parseInt(part, 10); 294 if (isNaN(num) || num < 0 || num > 255) { 295 return false; 296 } 297 } 298 return true; 299 } 300 301 /** 302 * Detect user's real IPv4 address 303 * ONLY uses free APIs (client-side) - NO backend fallback 304 * Will retry once if first attempt fails 305 */ 306 async function detectClientIp() { 307 // Prevent multiple simultaneous detections 308 if (ipDetectionInProgress && ipDetectionPromise) { 309 return ipDetectionPromise; 310 } 311 312 // Return cached IP if already detected 313 if (detectedIpv4) { 314 return detectedIpv4; 315 } 316 317 ipDetectionInProgress = true; 318 ipDetectionPromise = (async () => { 319 try { 320 // Step 1: Try free APIs first (most reliable for client IP) 321 console.log('[Client IP Detector] Detecting IPv4 from free APIs...'); 322 const apiIpv4 = await getIpv4FromFreeApis(); 323 324 if (apiIpv4) { 325 detectedIpv4 = apiIpv4; 326 detectedClientIp = apiIpv4; 327 328 // Make IP available globally (multiple ways for compatibility) 329 window.clientDetectedIp = detectedClientIp; 330 window.clientDetectedIpv4 = detectedIpv4; 331 window.detectedClientIpv4 = detectedIpv4; // Additional global variable 332 333 console.log('[Client IP Detector] â IPv4 detected from free APIs:', detectedIpv4); 334 console.log('[Client IP Detector] â All future API requests will include X-Client-IPv4 header'); 335 336 // Re-intercept jQuery in case it loaded after initial interception 337 if (typeof window !== 'undefined') { 338 setTimeout(() => { 339 if (typeof jQuery !== 'undefined' && jQuery.ajax) { 340 const originalAjax = jQuery.ajax; 341 if (!jQuery.ajax._clientIpIntercepted) { 342 interceptJQueryAjax(); 343 } 344 } 345 }, 100); 346 } 347
348 // Automatically send to service after successful detection 349 sendIpToService().catch(err => { 350 console.warn('[Client IP Detector] Failed to auto-send to service:', err); 351 }); 352 353 return detectedIpv4; 354 } 355 356 // No fallback - only use client-side free APIs 357 // If free APIs fail, keep retrying (don't use backend) 358 console.warn('[Client IP Detector] â ï¸ Free APIs failed, retrying...'); 359 360 // Retry once more with a short delay 361 await new Promise(resolve => setTimeout(resolve, 500)); 362 const retryIpv4 = await getIpv4FromFreeApis(); 363 364 if (retryIpv4) { 365 detectedIpv4 = retryIpv4; 366 detectedClientIp = retryIpv4; 367 368 // Make IP available globally (multiple ways for compatibility) 369 window.clientDetectedIp = detectedClientIp; 370 window.clientDetectedIpv4 = detectedIpv4; 371 window.detectedClientIpv4 = detectedIpv4; 372 373 console.log('[Client IP Detector] â IPv4 detected on retry:', detectedIpv4); 374 375 // Re-intercept jQuery in case it loaded after initial interception 376 if (typeof window !== 'undefined') { 377 setTimeout(() => { 378 if (typeof jQuery !== 'undefined' && jQuery.ajax) { 379 if (!jQuery.ajax._clientIpIntercepted) { 380 interceptJQueryAjax(); 381 } 382 } 383 }, 100); 384 } 385 386 // Automatically send to service after successful detection 387 sendIpToService().catch(err => { 388 console.warn('[Client IP Detector] Failed to auto-send to service:', err); 389 }); 390 391 return detectedIpv4; 392 } 393 394 console.error('[Client IP Detector] â Failed to detect IPv4 from free APIs - no fallback, will retry on next request'); 395 return null; 396 } catch (error) { 397 console.error('[Client IP Detector] â Error detecting IP:', error); 398 return null; 399 } finally { 400 ipDetectionInProgress = false; 401 } 402 })(); 403 404 return ipDetectionPromise; 405 } 406 407 /** 408 * Get detected IP (synchronous getter) 409 * Returns cached IPv4 if available, otherwise returns null 410 */ 411 function getDetectedIp() { 412 return detectedIpv4; 413 } 414 415 /** 416 * Send detected IP to backend service 417 * This is called automatically after detection 418 * Properly sends IP to service with correct headers 419 */ 420 async function sendIpToService() { 421 if (!detectedIpv4) { 422 console.warn('[Client IP Detector] No IP detected yet, cannot send to service'); 423 return null; 424 } 425 426 try { 427 console.log('[Client IP Detector] Sending IP to service:', detectedIpv4); 428 429 const response = await fetch(`${toolsApiBase}/api/client-ip/detect`, { 430 method: 'POST', 431 headers: { 432 'Content-Type': 'application/json', 433 'X-Client-IPv4': detectedIpv4, // Send IP in header 434 }, 435 body: JSON.stringify({ 436 client_ip: detectedIpv4, 437 detected_at: new Date().toISOString() 438 }) 439 }); 440 441 if (!response.ok) { 442 throw new Error(`HTTP error! status: ${response.status}`); 443 } 444 445 const data = await response.json(); 446 console.log('[Client IP Detector] â IP successfully sent to service:', data); 447 return data; 448 } catch (error) { 449 console.error('[Client IP Detector] â Failed to send IP to service:', error); 450 return null; 451 } 452 } 453 454 // Auto-detect IP immediately (don't wait for DOMContentLoaded) 455 // This ensures IP is detected before any API requests are made 456 if (typeof window !== 'undefined') { 457 // Start detection immediately 458 detectClientIp().then(() => { 459 sendIpToService(); 460 console.log('[Client IP Detector] â IP detected and ready:', detectedIpv4); 461 }).catch(err => { 462 console.warn('[Client IP Detector] Auto-detection failed:', err); 463 }); 464 } 465 466 // Expose functions globally 467 window.clientIpDetector = { 468 detect: detectClientIp, 469 getIp: getDetectedIp, 470 getIpv4: () => detectedIpv4, 471 isDetected: () => detectedIpv4 !== null, 472 sendToService: sendIpToService 473 }; 474 475 /** 476 * Wait for IP detection (with timeout) 477 * Returns detected IP or null if timeout 478 */ 479 async function waitForIpDetection(timeout = 10000) { 480 if (detectedIpv4) { 481 return detectedIpv4; 482 } 483 484 const startTime = Date.now(); 485 while (!detectedIpv4 && (Date.now() - startTime) < timeout) { 486 await new Promise(resolve => setTimeout(resolve, 100)); 487 } 488 489 return detectedIpv4; 490 } 491 492 /** 493 * Intercept Fetch API: add X-Client-IPv4 only for our tools API host 494 * WAITS for IP detection before those requests (no fallback) 495 */ 496 const originalFetch = window.fetch; 497 window.fetch = async function (...args) { 498 const url = args[0]; 499 500 // Skip IP-detection URLs and third-party hosts (Google Translate, etc.) 501 if (!shouldAttachClientIpHeader(url)) { 502 return originalFetch.apply(this, args); 503 } 504 505 // Wait for IP detection before sending request 506 const ip = await waitForIpDetection(10000); 507 508 if (!ip) { 509 console.warn('[Client IP Detector] â ï¸ IP not detected yet, request may proceed without X-Cl
509ient-IPv4 header'); 510 } 511 512 // Add client-detected IPv4 to headers 513 const options = args[1] || {}; 514 let headers = options.headers; 515 516 // Handle different header types 517 if (headers instanceof Headers) { 518 // Headers object - add to it 519 if (ip) { 520 headers.set('X-Client-IPv4', ip); 521 } 522 const pageUrl = getToolPageUrl(); 523 if (pageUrl && !headers.has('X-Tool-Page-Url')) { 524 headers.set('X-Tool-Page-Url', pageUrl); 525 } 526 } else if (typeof headers === 'object' && headers !== null && !Array.isArray(headers)) { 527 // Plain object - add property 528 headers = { ...headers }; 529 if (ip) { 530 headers['X-Client-IPv4'] = ip; 531 } 532 attachToolPageUrlToHeadersObject(headers); 533 } else { 534 // No headers or other type - create new object 535 headers = ip ? { 'X-Client-IPv4': ip } : {}; 536 attachToolPageUrlToHeadersObject(headers); 537 } 538 539 // Update options with new headers 540 const newOptions = { 541 ...options, 542 headers: headers 543 }; 544 545 return originalFetch.apply(this, [args[0], newOptions]); 546 }; 547 548 /** 549 * Intercept XMLHttpRequest to add X-Client-IPv4 header 550 */ 551 const originalXHROpen = XMLHttpRequest.prototype.open; 552 const originalXHRSend = XMLHttpRequest.prototype.send; 553 554 XMLHttpRequest.prototype.open = function (method, url, ...args) { 555 this._url = url; 556 // Store detected IPv4 for this request 557 this._clientIpv4 = detectedIpv4; 558 return originalXHROpen.apply(this, [method, url, ...args]); 559 }; 560 561 XMLHttpRequest.prototype.send = async function (...args) { 562 const url = this._url || ''; 563 564 // Skip IP-detection URLs and third-party hosts (Google Translate, etc.) 565 if (!shouldAttachClientIpHeader(url)) { 566 return originalXHRSend.apply(this, args); 567 } 568 569 // Wait for IP detection before sending request 570 const ip = await waitForIpDetection(10000); 571 572 if (!ip) { 573 console.warn('[Client IP Detector] â ï¸ IP not detected yet, XHR request may proceed without X-Client-IPv4 header'); 574 } 575 576 // Add client-detected IPv4 header to XHR request 577 if (ip) { 578 try { 579 this.setRequestHeader('X-Client-IPv4', ip); 580 } catch (e) { 581 // Header might already be set, ignore error 582 } 583 } 584 const pageUrl = getToolPageUrl(); 585 if (pageUrl) { 586 try { 587 this.setRequestHeader('X-Tool-Page-Url', pageUrl); 588 } catch (e) { 589 // Header might already be set, ignore error 590 } 591 } 592 593 return originalXHRSend.apply(this, args); 594 }; 595 596 // Intercept immediately if jQuery is already loaded 597 interceptJQueryAjax(); 598 599 // Also intercept when jQuery loads (if it loads after this script) 600 if (typeof window !== 'undefined') { 601 // Try to intercept when jQuery is available 602 const checkJQuery = setInterval(() => { 603 if (typeof jQuery !== 'undefined' && jQuery.ajax) { 604 interceptJQueryAjax(); 605 clearInterval(checkJQuery); 606 } 607 }, 100); 608 609 // Stop checking after 10 seconds 610 setTimeout(() => clearInterval(checkJQuery), 10000); 611 } 612 613 console.log('â Client IP Detector loaded - detecting real client IPv4...'); 614 console.log('â Tools API requests will include X-Client-IPv4 + X-Tool-Page-Url (third-party hosts skipped)'); 615})(); 616
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.