Files
sgse-app/apps/web/src/lib/utils/deviceInfo.ts

1005 lines
27 KiB
TypeScript

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<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 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<Response>((_, 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<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;
}
/**
* Solicita permissão para acesso aos sensores de movimento (iOS 13+)
*/
async function solicitarPermissaoSensor(): Promise<PermissionState> {
if (
typeof DeviceMotionEvent === 'undefined' ||
typeof (DeviceMotionEvent as { requestPermission?: () => Promise<PermissionState> })
.requestPermission !== 'function'
) {
// Permissão não necessária ou já concedida (navegadores modernos)
return 'granted';
}
try {
const requestPermission = (
DeviceMotionEvent as { requestPermission: () => Promise<PermissionState> }
).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<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, 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;
}