feat: enhance call and point registration features with sensor data integration

- Updated the CallWindow component to include connection quality states and reconnection attempts, improving user experience during calls.
- Enhanced the ChatWindow to allow starting audio and video calls in a new window, providing users with more flexibility.
- Integrated accelerometer and gyroscope data collection in the RegistroPonto component, enabling validation of point registration authenticity.
- Improved error handling and user feedback for sensor permissions and data validation, ensuring a smoother registration process.
- Updated backend logic to validate sensor data and adjust confidence scores for point registration, enhancing security against spoofing.
This commit is contained in:
2025-11-22 20:49:52 -03:00
parent fc4b5c5ba5
commit f818756efc
15 changed files with 2100 additions and 275 deletions

View File

@@ -1,5 +1,21 @@
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;
@@ -37,6 +53,10 @@ export interface InformacoesDispositivo {
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
}
/**
@@ -637,6 +657,145 @@ async function obterIPPublico(): Promise<string | undefined> {
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
*/
@@ -675,13 +834,14 @@ export async function obterInformacoesDispositivo(): Promise<InformacoesDisposit
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([
// 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;
@@ -703,6 +863,12 @@ export async function obterInformacoesDispositivo(): Promise<InformacoesDisposit
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;