import { getLocalIP } from './browserInfo'; export interface DadosAcelerometro { x: number; y: number; z: number; movimentoDetectado: boolean; magnitude: number; variacao: number; // Variância entre leituras timestamp: number; } export interface DadosGiroscopio { alpha: number; beta: number; gamma: number; } export interface InformacoesDispositivo { ipAddress?: string; ipPublico?: string; ipLocal?: string; userAgent?: string; browser?: string; browserVersion?: string; engine?: string; sistemaOperacional?: string; osVersion?: string; arquitetura?: string; plataforma?: string; latitude?: number; longitude?: number; precisao?: number; altitude?: number | null; altitudeAccuracy?: number | null; heading?: number | null; speed?: number | null; confiabilidadeGPS?: number; // 0-1 suspeitaSpoofing?: boolean; motivoSuspeita?: string; endereco?: string; cidade?: string; estado?: string; pais?: string; timezone?: string; deviceType?: string; deviceModel?: string; screenResolution?: string; coresTela?: number; idioma?: string; isMobile?: boolean; isTablet?: boolean; isDesktop?: boolean; connectionType?: string; memoryInfo?: string; acelerometro?: DadosAcelerometro; giroscopio?: DadosGiroscopio; sensorDisponivel?: boolean; // Indica se o sensor está disponível no dispositivo permissaoNegada?: boolean; // Indica se a permissão foi negada pelo usuário } /** * Detecta informações do navegador */ function detectarNavegador(): { browser: string; browserVersion: string; engine: string; } { if (typeof navigator === 'undefined') { return { browser: 'Desconhecido', browserVersion: '', engine: '' }; } const ua = navigator.userAgent; let browser = 'Desconhecido'; let browserVersion = ''; let engine = ''; // Detectar engine if (ua.includes('Edg/')) { engine = 'EdgeHTML'; } else if (ua.includes('Chrome/')) { engine = 'Blink'; } else if (ua.includes('Firefox/')) { engine = 'Gecko'; } else if (ua.includes('Safari/') && !ua.includes('Chrome/')) { engine = 'WebKit'; } // Detectar navegador if (ua.includes('Edg/')) { browser = 'Edge'; const match = ua.match(/Edg\/(\d+)/); browserVersion = match ? match[1]! : ''; } else if (ua.includes('Chrome/') && !ua.includes('Edg/')) { browser = 'Chrome'; const match = ua.match(/Chrome\/(\d+)/); browserVersion = match ? match[1]! : ''; } else if (ua.includes('Firefox/')) { browser = 'Firefox'; const match = ua.match(/Firefox\/(\d+)/); browserVersion = match ? match[1]! : ''; } else if (ua.includes('Safari/') && !ua.includes('Chrome/')) { browser = 'Safari'; const match = ua.match(/Version\/(\d+)/); browserVersion = match ? match[1]! : ''; } else if (ua.includes('Opera/') || ua.includes('OPR/')) { browser = 'Opera'; const match = ua.match(/(?:Opera|OPR)\/(\d+)/); browserVersion = match ? match[1]! : ''; } return { browser, browserVersion, engine }; } /** * Detecta informações do sistema operacional */ function detectarSistemaOperacional(): { sistemaOperacional: string; osVersion: string; arquitetura: string; plataforma: string; } { if (typeof navigator === 'undefined') { return { sistemaOperacional: 'Desconhecido', osVersion: '', arquitetura: '', plataforma: '' }; } const ua = navigator.userAgent; const platform = navigator.platform || ''; let sistemaOperacional = 'Desconhecido'; let osVersion = ''; let arquitetura = ''; const plataforma = platform; // Detectar OS if (ua.includes('Windows NT')) { sistemaOperacional = 'Windows'; const match = ua.match(/Windows NT (\d+\.\d+)/); if (match) { const version = match[1]!; const versions: Record = { '10.0': '10/11', '6.3': '8.1', '6.2': '8', '6.1': '7' }; osVersion = versions[version] || version; } } else if (ua.includes('Mac OS X') || ua.includes('Macintosh')) { sistemaOperacional = 'macOS'; const match = ua.match(/Mac OS X (\d+[._]\d+)/); if (match) { osVersion = match[1]!.replace('_', '.'); } } else if (ua.includes('Linux')) { sistemaOperacional = 'Linux'; osVersion = 'Linux'; } else if (ua.includes('Android')) { sistemaOperacional = 'Android'; const match = ua.match(/Android (\d+(?:\.\d+)?)/); osVersion = match ? match[1]! : ''; } else if (ua.includes('iPhone') || ua.includes('iPad')) { sistemaOperacional = 'iOS'; const match = ua.match(/OS (\d+[._]\d+)/); if (match) { osVersion = match[1]!.replace('_', '.'); } } // Detectar arquitetura (se disponível) if ('cpuClass' in navigator) { arquitetura = (navigator as unknown as { cpuClass: string }).cpuClass; } return { sistemaOperacional, osVersion, arquitetura, plataforma }; } /** * Detecta tipo de dispositivo */ function detectarTipoDispositivo(): { deviceType: string; isMobile: boolean; isTablet: boolean; isDesktop: boolean; } { if (typeof navigator === 'undefined') { return { deviceType: 'Desconhecido', isMobile: false, isTablet: false, isDesktop: true }; } const ua = navigator.userAgent; const isMobile = /Mobile|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua); const isTablet = /iPad|Android(?!.*Mobile)|Tablet/i.test(ua); const isDesktop = !isMobile && !isTablet; let deviceType = 'Desktop'; if (isTablet) { deviceType = 'Tablet'; } else if (isMobile) { deviceType = 'Mobile'; } return { deviceType, isMobile, isTablet, isDesktop }; } /** * Obtém informações da tela */ function obterInformacoesTela(): { screenResolution: string; coresTela: number; } { if (typeof screen === 'undefined') { return { screenResolution: 'Desconhecido', coresTela: 0 }; } const screenResolution = `${screen.width}x${screen.height}`; const coresTela = screen.colorDepth || 24; return { screenResolution, coresTela }; } /** * Obtém informações de conexão */ async function obterInformacoesConexao(): Promise { if (typeof navigator === 'undefined' || !('connection' in navigator)) { return 'Desconhecido'; } const connection = (navigator as unknown as { connection?: { effectiveType?: string } }) .connection; if (connection?.effectiveType) { return connection.effectiveType; } return 'Desconhecido'; } /** * Obtém informações de memória (se disponível) */ function obterInformacoesMemoria(): string { if (typeof navigator === 'undefined' || !('deviceMemory' in navigator)) { return 'Desconhecido'; } const deviceMemory = (navigator as unknown as { deviceMemory?: number }).deviceMemory; if (deviceMemory) { return `${deviceMemory} GB`; } return 'Desconhecido'; } /** * Calcula distância entre duas coordenadas (fórmula de Haversine) * Retorna distância em metros */ function calcularDistancia(lat1: number, lon1: number, lat2: number, lon2: number): number { const R = 6371000; // Raio da Terra em metros const dLat = ((lat2 - lat1) * Math.PI) / 180; const dLon = ((lon2 - lon1) * Math.PI) / 180; const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos((lat1 * Math.PI) / 180) * Math.cos((lat2 * Math.PI) / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; } /** * Obtém timezone aproximado por coordenadas */ function obterTimezonePorCoordenadas(latitude: number, longitude: number): string { // Pernambuco está em UTC-3 (America/Recife) if (longitude >= -45 && longitude <= -30 && latitude >= -10 && latitude <= 5) { return 'America/Recife'; // UTC-3 } // Fallback: usar timezone do sistema try { return Intl.DateTimeFormat().resolvedOptions().timeZone; } catch { return 'America/Recife'; // Default } } /** * Captura uma única leitura de localização com todas as propriedades disponíveis */ async function capturarLocalizacaoUnica( enableHighAccuracy: boolean = true, timeout: number = 10000 ): Promise<{ latitude?: number; longitude?: number; precisao?: number; altitude?: number | null; altitudeAccuracy?: number | null; heading?: number | null; speed?: number | null; timestamp?: number; confiabilidade: number; // 0-1 }> { return new Promise((resolve) => { if (typeof navigator === 'undefined' || !navigator.geolocation) { resolve({ confiabilidade: 0 }); return; } const timeoutId = setTimeout(() => { resolve({ confiabilidade: 0 }); }, timeout + 1000); navigator.geolocation.getCurrentPosition( (position) => { clearTimeout(timeoutId); const coords = position.coords; const { latitude, longitude, accuracy } = coords; // Validar coordenadas básicas if ( isNaN(latitude) || isNaN(longitude) || latitude === 0 || longitude === 0 || latitude < -90 || latitude > 90 || longitude < -180 || longitude > 180 ) { resolve({ confiabilidade: 0 }); return; } // Calcular score de confiabilidade baseado em propriedades do GPS real const sinaisGPSReal = { temAltitude: coords.altitude !== null && coords.altitude !== 0, temAltitudeAccuracy: coords.altitudeAccuracy !== null && coords.altitudeAccuracy > 0, temHeading: coords.heading !== null && !isNaN(coords.heading), temSpeed: coords.speed !== null && !isNaN(coords.speed), precisaoBoa: accuracy < 20, // GPS real geralmente < 20m precisaoMedia: accuracy >= 20 && accuracy < 100, timestampPreciso: position.timestamp > 0 }; // Calcular confiabilidade: cada sinal adiciona pontos let pontos = 0; const maxPontos = 7; if (sinaisGPSReal.temAltitude) pontos += 1; if (sinaisGPSReal.temAltitudeAccuracy) pontos += 1; if (sinaisGPSReal.temHeading) pontos += 0.5; if (sinaisGPSReal.temSpeed) pontos += 0.5; if (sinaisGPSReal.precisaoBoa) pontos += 2; if (sinaisGPSReal.precisaoMedia) pontos += 1; if (sinaisGPSReal.timestampPreciso) pontos += 1; const confiabilidade = Math.min(pontos / maxPontos, 1); resolve({ latitude, longitude, precisao: accuracy, altitude: coords.altitude ?? null, altitudeAccuracy: coords.altitudeAccuracy ?? null, heading: coords.heading ?? null, speed: coords.speed ?? null, timestamp: position.timestamp, confiabilidade }); }, (error) => { clearTimeout(timeoutId); console.warn('Erro ao obter localização:', error.code, error.message); resolve({ confiabilidade: 0 }); }, { enableHighAccuracy, timeout, maximumAge: 0 // Sempre obter nova leitura } ); }); } /** * Obtém localização via GPS com múltiplas leituras para detectar spoofing * Apps de spoofing geralmente retornam valores idênticos em todas as leituras */ async function obterLocalizacaoMultipla(): Promise<{ latitude?: number; longitude?: number; precisao?: number; altitude?: number | null; altitudeAccuracy?: number | null; heading?: number | null; speed?: number | null; confiabilidade: number; // 0-1 suspeitaSpoofing: boolean; motivoSuspeita?: string; }> { if (typeof navigator === 'undefined' || !navigator.geolocation) { return { confiabilidade: 0, suspeitaSpoofing: true, motivoSuspeita: 'Geolocalização não suportada' }; } // Capturar 3 leituras com intervalo de 2 segundos entre elas const leituras: Array<{ lat: number; lon: number; precisao: number; altitude: number | null; confiabilidade: number; }> = []; for (let i = 0; i < 3; i++) { const leitura = await capturarLocalizacaoUnica(true, 8000); if (leitura.latitude && leitura.longitude && leitura.confiabilidade > 0) { leituras.push({ lat: leitura.latitude, lon: leitura.longitude, precisao: leitura.precisao || 999, altitude: leitura.altitude ?? null, confiabilidade: leitura.confiabilidade }); } // Aguardar 2 segundos entre leituras (exceto na última) if (i < 2) { await new Promise((resolve) => setTimeout(resolve, 2000)); } } if (leituras.length === 0) { return { confiabilidade: 0, suspeitaSpoofing: true, motivoSuspeita: 'Não foi possível obter localização' }; } // Se tivermos menos de 2 leituras, usar única leitura com baixa confiança if (leituras.length < 2) { const unica = leituras[0]; return { latitude: unica.lat, longitude: unica.lon, precisao: unica.precisao, altitude: unica.altitude, altitudeAccuracy: null, heading: null, speed: null, confiabilidade: unica.confiabilidade * 0.5, // Reduzir confiança por ter apenas 1 leitura suspeitaSpoofing: true, motivoSuspeita: 'Apenas uma leitura obtida' }; } // Verificar se todas as leituras são idênticas (suspeito de spoofing) const primeiraLeitura = leituras[0]; const todasIguais = leituras.every( (l) => Math.abs(l.lat - primeiraLeitura.lat) < 0.00001 && // ~1 metro Math.abs(l.lon - primeiraLeitura.lon) < 0.00001 ); if (todasIguais && leituras.length === 3) { // GPS real varia alguns metros, se todas são idênticas pode ser spoofing return { latitude: primeiraLeitura.lat, longitude: primeiraLeitura.lon, precisao: primeiraLeitura.precisao, altitude: primeiraLeitura.altitude, altitudeAccuracy: null, heading: null, speed: null, confiabilidade: primeiraLeitura.confiabilidade * 0.4, // Reduzir drasticamente confiança suspeitaSpoofing: true, motivoSuspeita: 'Todas as leituras são idênticas (GPS real varia alguns metros)' }; } // Calcular média das leituras e variância const mediaLat = leituras.reduce((sum, l) => sum + l.lat, 0) / leituras.length; const mediaLon = leituras.reduce((sum, l) => sum + l.lon, 0) / leituras.length; const mediaConfianca = leituras.reduce((sum, l) => sum + l.confiabilidade, 0) / leituras.length; // Calcular distância máxima entre leituras let distanciaMaxima = 0; for (let i = 0; i < leituras.length; i++) { for (let j = i + 1; j < leituras.length; j++) { const dist = calcularDistancia( leituras[i].lat, leituras[i].lon, leituras[j].lat, leituras[j].lon ); distanciaMaxima = Math.max(distanciaMaxima, dist); } } // Se distância máxima for muito grande (> 100m), pode indicar problemas const suspeitoPorDistancia = distanciaMaxima > 100; return { latitude: mediaLat, longitude: mediaLon, precisao: primeiraLeitura.precisao, altitude: primeiraLeitura.altitude, altitudeAccuracy: null, heading: null, speed: null, confiabilidade: suspeitoPorDistancia ? mediaConfianca * 0.6 : mediaConfianca, suspeitaSpoofing: suspeitoPorDistancia, motivoSuspeita: suspeitoPorDistancia ? `Variação muito grande entre leituras (${Math.round(distanciaMaxima)}m)` : undefined }; } /** * Obtém localização via GPS de forma rápida (uma única leitura, sem reverse geocoding) * Usado para login - não bloqueia o fluxo */ export async function obterLocalizacaoRapida(): Promise<{ latitude?: number; longitude?: number; precisao?: number; endereco?: string; cidade?: string; estado?: string; pais?: string; }> { if (typeof navigator === 'undefined' || !navigator.geolocation) { return {}; } try { // Uma única leitura rápida com timeout curto const leitura = await capturarLocalizacaoUnica(true, 3000); // 3 segundos máximo if (!leitura.latitude || !leitura.longitude || leitura.confiabilidade === 0) { return {}; } // Tentar obter endereço via reverse geocoding (com timeout curto) let endereco = ''; let cidade = ''; let estado = ''; let pais = ''; try { const geocodePromise = fetch( `https://nominatim.openstreetmap.org/reverse?format=json&lat=${leitura.latitude}&lon=${leitura.longitude}&zoom=18&addressdetails=1`, { headers: { 'User-Agent': 'SGSE-App/1.0' } } ); const geocodeTimeout = new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 2000) ); const response = await Promise.race([geocodePromise, geocodeTimeout]); if (response.ok) { const data = (await response.json()) as { address?: { road?: string; house_number?: string; city?: string; town?: string; state?: string; country?: string; }; }; if (data.address) { const addr = data.address; if (addr.road) { endereco = `${addr.road}${addr.house_number ? `, ${addr.house_number}` : ''}`; } cidade = addr.city || addr.town || ''; estado = addr.state || ''; pais = addr.country || ''; } } } catch (error) { // Ignorar erro de geocoding - não é crítico console.warn('Erro ao obter endereço (não crítico):', error); } return { latitude: leitura.latitude, longitude: leitura.longitude, precisao: leitura.precisao, endereco, cidade, estado, pais }; } catch (error) { console.warn('Erro ao obter localização rápida:', error); return {}; } } /** * Obtém localização via GPS com múltiplas tentativas e validações anti-spoofing */ export async function obterLocalizacao(): Promise<{ latitude?: number; longitude?: number; precisao?: number; altitude?: number | null; altitudeAccuracy?: number | null; heading?: number | null; speed?: number | null; confiabilidadeGPS?: number; suspeitaSpoofing?: boolean; motivoSuspeita?: string; endereco?: string; cidade?: string; estado?: string; pais?: string; }> { if (typeof navigator === 'undefined' || !navigator.geolocation) { console.warn('Geolocalização não suportada'); return {}; } // Usar múltiplas leituras para detectar spoofing const localizacaoMultipla = await obterLocalizacaoMultipla(); if (!localizacaoMultipla.latitude || !localizacaoMultipla.longitude) { console.warn('Não foi possível obter localização'); return { confiabilidadeGPS: 0, suspeitaSpoofing: true, motivoSuspeita: 'Não foi possível obter localização' }; } const { latitude, longitude, precisao, altitude, altitudeAccuracy, heading, speed, confiabilidade, suspeitaSpoofing, motivoSuspeita } = localizacaoMultipla; // Tentar obter endereço via reverse geocoding let endereco = ''; let cidade = ''; let estado = ''; let pais = ''; try { const response = await fetch( `https://nominatim.openstreetmap.org/reverse?format=json&lat=${latitude}&lon=${longitude}&zoom=18&addressdetails=1`, { headers: { 'User-Agent': 'SGSE-App/1.0' } } ); if (response.ok) { const data = (await response.json()) as { address?: { road?: string; house_number?: string; city?: string; town?: string; state?: string; country?: string; }; }; if (data.address) { const addr = data.address; if (addr.road) { endereco = `${addr.road}${addr.house_number ? `, ${addr.house_number}` : ''}`; } cidade = addr.city || addr.town || ''; estado = addr.state || ''; pais = addr.country || ''; } } } catch (error) { console.warn('Erro ao obter endereço:', error); } // Validar timezone vs localização if (typeof navigator !== 'undefined') { const timezoneAtual = Intl.DateTimeFormat().resolvedOptions().timeZone; const timezoneEsperado = obterTimezonePorCoordenadas(latitude, longitude); // Se timezone é muito diferente, pode ser suspeito if ( timezoneAtual !== timezoneEsperado && timezoneAtual !== 'America/Recife' && timezoneEsperado !== 'America/Recife' ) { console.warn(`Timezone inconsistente: esperado ${timezoneEsperado}, atual ${timezoneAtual}`); } } console.log('Localização obtida com validações:', { latitude, longitude, confiabilidade: confiabilidade.toFixed(2), suspeitaSpoofing, motivoSuspeita }); return { latitude, longitude, precisao, altitude, altitudeAccuracy, heading, speed, confiabilidadeGPS: confiabilidade, suspeitaSpoofing: suspeitaSpoofing || false, motivoSuspeita, endereco, cidade, estado, pais }; } /** * Obtém IP público */ export async function obterIPPublico(): Promise { try { const response = await fetch('https://api.ipify.org?format=json'); if (response.ok) { const data = (await response.json()) as { ip: string }; return data.ip; } } catch (error) { console.warn('Erro ao obter IP público:', error); } return undefined; } /** * Solicita permissão para acesso aos sensores de movimento (iOS 13+) */ async function solicitarPermissaoSensor(): Promise { if ( typeof DeviceMotionEvent === 'undefined' || typeof (DeviceMotionEvent as { requestPermission?: () => Promise }) .requestPermission !== 'function' ) { // Permissão não necessária ou já concedida (navegadores modernos) return 'granted'; } try { const requestPermission = ( DeviceMotionEvent as { requestPermission: () => Promise } ).requestPermission; const resultado = await requestPermission(); return resultado; } catch (error) { console.warn('Erro ao solicitar permissão de sensor:', error); return 'denied'; } } /** * Obtém dados de acelerômetro e giroscópio durante um período * @param duracaoMs Duração da coleta em milissegundos (padrão: 5000ms = 5 segundos) */ async function obterDadosAcelerometro(duracaoMs: number = 5000): Promise<{ acelerometro?: DadosAcelerometro; giroscopio?: DadosGiroscopio; sensorDisponivel: boolean; permissaoNegada: boolean; }> { // Verificar se DeviceMotionEvent está disponível if (typeof DeviceMotionEvent === 'undefined' || typeof DeviceOrientationEvent === 'undefined') { return { sensorDisponivel: false, permissaoNegada: false }; } // Solicitar permissão (especialmente necessário no iOS 13+) const permissao = await solicitarPermissaoSensor(); if (permissao === 'denied') { return { sensorDisponivel: true, permissaoNegada: true }; } return new Promise((resolve) => { const leiturasAcelerometro: Array<{ x: number; y: number; z: number; timestamp: number }> = []; const leiturasGiroscopio: Array<{ alpha: number; beta: number; gamma: number; timestamp: number; }> = []; const timeoutId = setTimeout(() => { window.removeEventListener('devicemotion', handleDeviceMotion); window.removeEventListener('deviceorientation', handleDeviceOrientation); // Processar dados de acelerômetro let acelerometro: DadosAcelerometro | undefined; if (leiturasAcelerometro.length > 0) { const ultimaLeitura = leiturasAcelerometro[leiturasAcelerometro.length - 1]!; // Calcular magnitude média const magnitudes = leiturasAcelerometro.map((l) => Math.sqrt(l.x * l.x + l.y * l.y + l.z * l.z) ); const magnitude = magnitudes.reduce((sum, m) => sum + m, 0) / magnitudes.length; // Calcular variância para detectar movimento const mediaX = leiturasAcelerometro.reduce((sum, l) => sum + l.x, 0) / leiturasAcelerometro.length; const mediaY = leiturasAcelerometro.reduce((sum, l) => sum + l.y, 0) / leiturasAcelerometro.length; const mediaZ = leiturasAcelerometro.reduce((sum, l) => sum + l.z, 0) / leiturasAcelerometro.length; const variacoes = leiturasAcelerometro.map( (l) => Math.pow(l.x - mediaX, 2) + Math.pow(l.y - mediaY, 2) + Math.pow(l.z - mediaZ, 2) ); const variacao = variacoes.reduce((sum, v) => sum + v, 0) / variacoes.length; // Detectar movimento: se variância > 0.01, há movimento const movimentoDetectado = variacao > 0.01; acelerometro = { x: ultimaLeitura.x, y: ultimaLeitura.y, z: ultimaLeitura.z, movimentoDetectado, magnitude, variacao, timestamp: ultimaLeitura.timestamp }; } // Processar dados de giroscópio let giroscopio: DadosGiroscopio | undefined; if (leiturasGiroscopio.length > 0) { const ultimaLeitura = leiturasGiroscopio[leiturasGiroscopio.length - 1]!; giroscopio = { alpha: ultimaLeitura.alpha || 0, beta: ultimaLeitura.beta || 0, gamma: ultimaLeitura.gamma || 0 }; } resolve({ acelerometro, giroscopio, sensorDisponivel: true, permissaoNegada: false }); }, duracaoMs); function handleDeviceMotion(event: DeviceMotionEvent) { if (event.accelerationIncludingGravity) { const acc = event.accelerationIncludingGravity; if (acc.x !== null && acc.y !== null && acc.z !== null) { leiturasAcelerometro.push({ x: acc.x, y: acc.y, z: acc.z, timestamp: Date.now() }); } } } function handleDeviceOrientation(event: DeviceOrientationEvent) { if (event.alpha !== null && event.beta !== null && event.gamma !== null) { leiturasGiroscopio.push({ alpha: event.alpha, beta: event.beta, gamma: event.gamma, timestamp: Date.now() }); } } window.addEventListener('devicemotion', handleDeviceMotion); window.addEventListener('deviceorientation', handleDeviceOrientation); }); } /** * Obtém todas as informações do dispositivo */ export async function obterInformacoesDispositivo(): Promise { const informacoes: InformacoesDispositivo = {}; // Informações básicas if (typeof navigator !== 'undefined') { informacoes.userAgent = navigator.userAgent; informacoes.idioma = navigator.language || navigator.languages?.[0]; informacoes.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; } // Informações do navegador const navegador = detectarNavegador(); informacoes.browser = navegador.browser; informacoes.browserVersion = navegador.browserVersion; informacoes.engine = navegador.engine; // Informações do sistema const sistema = detectarSistemaOperacional(); informacoes.sistemaOperacional = sistema.sistemaOperacional; informacoes.osVersion = sistema.osVersion; informacoes.arquitetura = sistema.arquitetura; informacoes.plataforma = sistema.plataforma; // Tipo de dispositivo const dispositivo = detectarTipoDispositivo(); informacoes.deviceType = dispositivo.deviceType; informacoes.isMobile = dispositivo.isMobile; informacoes.isTablet = dispositivo.isTablet; informacoes.isDesktop = dispositivo.isDesktop; // Informações da tela const tela = obterInformacoesTela(); informacoes.screenResolution = tela.screenResolution; informacoes.coresTela = tela.coresTela; // Informações de conexão, memória e localização (assíncronas) const [connectionType, memoryInfo, ipPublico, ipLocal, localizacao, dadosSensores] = await Promise.all([ obterInformacoesConexao(), Promise.resolve(obterInformacoesMemoria()), obterIPPublico(), getLocalIP(), obterLocalizacao(), obterDadosAcelerometro(5000) // Coletar dados por 5 segundos ]); informacoes.connectionType = connectionType; informacoes.memoryInfo = memoryInfo; informacoes.ipPublico = ipPublico; informacoes.ipLocal = ipLocal; informacoes.latitude = localizacao.latitude; informacoes.longitude = localizacao.longitude; informacoes.precisao = localizacao.precisao; informacoes.altitude = localizacao.altitude ?? null; informacoes.altitudeAccuracy = localizacao.altitudeAccuracy ?? null; informacoes.heading = localizacao.heading ?? null; informacoes.speed = localizacao.speed ?? null; informacoes.confiabilidadeGPS = localizacao.confiabilidadeGPS; informacoes.suspeitaSpoofing = localizacao.suspeitaSpoofing; informacoes.motivoSuspeita = localizacao.motivoSuspeita; informacoes.endereco = localizacao.endereco; informacoes.cidade = localizacao.cidade; informacoes.estado = localizacao.estado; informacoes.pais = localizacao.pais; // Dados de sensores informacoes.acelerometro = dadosSensores.acelerometro; informacoes.giroscopio = dadosSensores.giroscopio; informacoes.sensorDisponivel = dadosSensores.sensorDisponivel; informacoes.permissaoNegada = dadosSensores.permissaoNegada; // IP address (usar público se disponível, senão local) informacoes.ipAddress = ipPublico || ipLocal; return informacoes; }