/** * Função utilitária para obter informações do navegador * Sem usar APIs externas */ /** * Obtém o User-Agent do navegador */ export function getUserAgent(): string { if (typeof window === 'undefined' || !window.navigator) { return ''; } return window.navigator.userAgent || ''; } /** * Valida se uma string tem formato de IP válido */ function isValidIPFormat(ip: string): boolean { if (!ip || ip.length < 7) return false; // IP mínimo: "1.1.1.1" = 7 chars // Validar IPv4 const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/; if (ipv4Regex.test(ip)) { const parts = ip.split('.'); return parts.length === 4 && parts.every(part => { const num = parseInt(part, 10); return !isNaN(num) && num >= 0 && num <= 255; }); } // Validar IPv6 básico (formato simplificado) const ipv6Regex = /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$|^::[0-9a-fA-F]{0,4}(:[0-9a-fA-F]{0,4}){0,6}$|^[0-9a-fA-F]{0,4}::[0-9a-fA-F]{0,4}(:[0-9a-fA-F]{0,4}){0,5}$/; if (ipv6Regex.test(ip)) { return true; } return false; } /** * Verifica se um IP é local/privado */ function isLocalIP(ip: string): boolean { // IPs locais/privados return ( ip.startsWith('127.') || ip.startsWith('192.168.') || ip.startsWith('10.') || ip.startsWith('172.16.') || ip.startsWith('172.17.') || ip.startsWith('172.18.') || ip.startsWith('172.19.') || ip.startsWith('172.20.') || ip.startsWith('172.21.') || ip.startsWith('172.22.') || ip.startsWith('172.23.') || ip.startsWith('172.24.') || ip.startsWith('172.25.') || ip.startsWith('172.26.') || ip.startsWith('172.27.') || ip.startsWith('172.28.') || ip.startsWith('172.29.') || ip.startsWith('172.30.') || ip.startsWith('172.31.') || ip.startsWith('169.254.') || // Link-local ip === '::1' || ip.startsWith('fe80:') // IPv6 link-local ); } /** * Tenta obter o IP usando WebRTC * Prioriza IP público, mas retorna IP local se não encontrar * Esta função não usa API externa, mas pode falhar em alguns navegadores * Retorna undefined se não conseguir obter */ export async function getLocalIP(): Promise { return new Promise((resolve) => { // Verificar se está em ambiente browser if (typeof window === 'undefined' || typeof RTCPeerConnection === 'undefined') { resolve(undefined); return; } try { const pc = new RTCPeerConnection({ iceServers: [] }); let resolved = false; let foundIPs: string[] = []; let publicIP: string | undefined = undefined; let localIP: string | undefined = undefined; const timeout = setTimeout(() => { if (!resolved) { resolved = true; pc.close(); // Priorizar IP público, mas retornar local se não houver resolve(publicIP || localIP || undefined); } }, 5000); // Aumentar timeout para 5 segundos pc.onicecandidate = (event) => { if (event.candidate && !resolved) { const candidate = event.candidate.candidate; // Regex mais rigorosa para IPv4 - deve ser um IP completo e válido // Formato: X.X.X.X onde X é 0-255 const ipv4Match = candidate.match(/\b([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\b/); // Regex para IPv6 - mais específica const ipv6Match = candidate.match(/\b([0-9a-fA-F]{1,4}(?::[0-9a-fA-F]{1,4}){2,7}|::[0-9a-fA-F]{1,4}(?::[0-9a-fA-F]{1,4}){0,6}|[0-9a-fA-F]{1,4}::[0-9a-fA-F]{1,4}(?::[0-9a-fA-F]{1,4}){0,5})\b/); let ip: string | undefined = undefined; if (ipv4Match && ipv4Match[1]) { const candidateIP = ipv4Match[1]; // Validar se cada octeto está entre 0-255 const parts = candidateIP.split('.'); if (parts.length === 4 && parts.every(part => { const num = parseInt(part, 10); return !isNaN(num) && num >= 0 && num <= 255; })) { ip = candidateIP; } } else if (ipv6Match && ipv6Match[1]) { // Validar formato básico de IPv6 const candidateIP = ipv6Match[1]; if (candidateIP.includes(':') && candidateIP.length >= 3) { ip = candidateIP; } } // Validar se o IP é válido antes de processar if (ip && isValidIPFormat(ip) && !foundIPs.includes(ip)) { foundIPs.push(ip); // Ignorar localhost if (ip.startsWith('127.') || ip === '::1') { return; } // Separar IPs públicos e locais if (isLocalIP(ip)) { if (!localIP) { localIP = ip; } } else { // IP público encontrado! if (!publicIP) { publicIP = ip; // Se encontrou IP público, podemos resolver mais cedo if (!resolved) { resolved = true; clearTimeout(timeout); pc.close(); resolve(publicIP); } } } } } else if (event.candidate === null) { // No more candidates if (!resolved) { resolved = true; clearTimeout(timeout); pc.close(); // Retornar IP público se encontrou, senão local resolve(publicIP || localIP || undefined); } } }; // Criar um data channel para forçar a criação de candidatos pc.createDataChannel(''); pc.createOffer() .then((offer) => pc.setLocalDescription(offer)) .catch(() => { if (!resolved) { resolved = true; clearTimeout(timeout); pc.close(); resolve(publicIP || localIP || undefined); } }); } catch (error) { console.warn("Erro ao obter IP via WebRTC:", error); resolve(undefined); } }); } /** * Obtém informações completas do navegador */ export interface BrowserInfo { userAgent: string; ipAddress?: string; } export async function getBrowserInfo(): Promise { const userAgent = getUserAgent(); const ipAddress = await getLocalIP(); return { userAgent, ipAddress, }; }