- Refactored the RegistroPonto component to improve the layout and user experience, including a new section for displaying standard hours. - Updated RelogioSincronizado to include GMT offset adjustments for accurate time display. - Introduced new location validation logic in the backend to ensure point registrations are within allowed geofenced areas. - Enhanced the device information schema to capture additional GPS data, improving the reliability of location checks. - Added new endpoints for managing allowed marking addresses, facilitating better control over where points can be registered.
712 lines
20 KiB
TypeScript
712 lines
20 KiB
TypeScript
import { getLocalIP } from './browserInfo';
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* 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<string, string> = {
|
|
'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<string> {
|
|
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 com múltiplas tentativas e validações anti-spoofing
|
|
*/
|
|
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
|
|
*/
|
|
async function obterIPPublico(): Promise<string | undefined> {
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Obtém todas as informações do dispositivo
|
|
*/
|
|
export async function obterInformacoesDispositivo(): Promise<InformacoesDispositivo> {
|
|
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 e memória (assíncronas)
|
|
const [connectionType, memoryInfo, ipPublico, ipLocal, localizacao] = await Promise.all([
|
|
obterInformacoesConexao(),
|
|
Promise.resolve(obterInformacoesMemoria()),
|
|
obterIPPublico(),
|
|
getLocalIP(),
|
|
obterLocalizacao(),
|
|
]);
|
|
|
|
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;
|
|
|
|
// IP address (usar público se disponível, senão local)
|
|
informacoes.ipAddress = ipPublico || ipLocal;
|
|
|
|
return informacoes;
|
|
}
|
|
|