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

@@ -4,12 +4,25 @@ import { getCurrentUserFunction } from './auth';
import type { Id } from './_generated/dataModel';
import { api, internal } from './_generated/api';
/**
* Tipo de retorno da configuração do relógio
*/
type ConfiguracaoRelogioRetorno = {
servidorNTP?: string | undefined;
portaNTP?: number | undefined;
usarServidorExterno: boolean;
fallbackParaPC: boolean;
ultimaSincronizacao: number | null;
offsetSegundos: number | null;
gmtOffset: number;
};
/**
* Obtém a configuração do relógio
*/
export const obterConfiguracao = query({
args: {},
handler: async (ctx) => {
handler: async (ctx): Promise<ConfiguracaoRelogioRetorno> => {
// Buscar todas as configurações e pegar a mais recente (por atualizadoEm)
const configs = await ctx.db
.query('configuracaoRelogio')
@@ -35,6 +48,46 @@ export const obterConfiguracao = query({
return {
...config,
ultimaSincronizacao: config.ultimaSincronizacao ?? null, // Converter undefined para null
offsetSegundos: config.offsetSegundos ?? null, // Converter undefined para null
gmtOffset: config.gmtOffset ?? -3, // Padrão GMT-3 para Brasília se não configurado
};
},
});
/**
* Obtém a configuração do relógio (internal) - usado por actions para evitar referência circular
*/
export const obterConfiguracaoInternal = internalQuery({
args: {},
handler: async (ctx): Promise<ConfiguracaoRelogioRetorno> => {
// Buscar todas as configurações e pegar a mais recente (por atualizadoEm)
const configs = await ctx.db
.query('configuracaoRelogio')
.collect();
// Pegar a configuração mais recente (ordenar por atualizadoEm desc)
const config = configs.length > 0
? configs.sort((a, b) => (b.atualizadoEm || 0) - (a.atualizadoEm || 0))[0]
: null;
if (!config) {
// Retornar configuração padrão (GMT-3 para Brasília)
return {
servidorNTP: 'pool.ntp.org',
portaNTP: 123,
usarServidorExterno: false,
fallbackParaPC: true,
ultimaSincronizacao: null,
offsetSegundos: null,
gmtOffset: -3, // GMT-3 para Brasília
};
}
return {
...config,
ultimaSincronizacao: config.ultimaSincronizacao ?? null, // Converter undefined para null
offsetSegundos: config.offsetSegundos ?? null, // Converter undefined para null
gmtOffset: config.gmtOffset ?? -3, // Padrão GMT-3 para Brasília se não configurado
};
},
@@ -119,15 +172,26 @@ export const obterTempoServidor = query({
},
});
/**
* Tipo de retorno da sincronização
*/
type SincronizacaoRetorno = {
sucesso: boolean;
timestamp: number;
usandoServidorExterno: boolean;
offsetSegundos: number;
aviso?: string;
};
/**
* Sincroniza tempo com servidor NTP (via action)
* Nota: NTP real requer biblioteca específica, aqui fazemos uma aproximação
*/
export const sincronizarTempo = action({
args: {},
handler: async (ctx) => {
// Buscar configuração diretamente do banco usando query pública
const config = await ctx.runQuery(api.configuracaoRelogio.obterConfiguracao, {});
handler: async (ctx): Promise<SincronizacaoRetorno> => {
// Buscar configuração usando query interna para evitar referência circular
const config: ConfiguracaoRelogioRetorno = await ctx.runQuery(internal.configuracaoRelogio.obterConfiguracaoInternal, {});
if (!config.usarServidorExterno) {
return {
@@ -145,66 +209,42 @@ export const sincronizarTempo = action({
const servidorNTP = config.servidorNTP || 'pool.ntp.org';
let serverTime: number;
// Mapear servidores NTP conhecidos para APIs HTTP que retornam UTC
// Todos os servidores NTP retornam UTC, então usamos APIs que retornam UTC
if (servidorNTP.includes('pool.ntp.org') || servidorNTP.includes('ntp.org') || servidorNTP.includes('ntp.br')) {
// pool.ntp.org e servidores .org/.br - usar API que retorna UTC
const response = await fetch('https://worldtimeapi.org/api/timezone/Etc/UTC');
if (!response.ok) {
throw new Error('Falha ao obter tempo do servidor');
}
const data = (await response.json()) as { unixtime: number; datetime: string };
// unixtime está em segundos, converter para milissegundos
serverTime = data.unixtime * 1000;
} else if (servidorNTP.includes('time.google.com') || servidorNTP.includes('google')) {
// Google NTP - usar API que retorna UTC
// Se o servidor configurado for uma URL HTTP/HTTPS, tentar usar diretamente
if (servidorNTP.startsWith('http://') || servidorNTP.startsWith('https://')) {
try {
const response = await fetch('https://worldtimeapi.org/api/timezone/Etc/UTC');
const response = await fetch(servidorNTP);
if (!response.ok) {
throw new Error('Falha ao obter tempo');
throw new Error('Falha ao obter tempo do servidor configurado');
}
const data = (await response.json()) as { unixtime: number };
serverTime = data.unixtime * 1000;
} catch {
// Fallback para outra API UTC
const data = (await response.json()) as { unixtime?: number; unixTime?: number; unixtimestamp?: number };
// Tentar diferentes formatos de resposta
if (data.unixtime) {
serverTime = data.unixtime * 1000; // Converter segundos para milissegundos
} else if (data.unixTime) {
serverTime = data.unixTime * 1000;
} else if (data.unixtimestamp) {
serverTime = data.unixtimestamp * 1000;
} else {
throw new Error('Formato de resposta não reconhecido');
}
} catch (error) {
// Se falhar, tentar APIs genéricas como fallback
throw new Error(`Falha ao usar servidor configurado: ${error}`);
}
} else {
// Para servidores NTP tradicionais (sem HTTP), usar APIs genéricas que retornam UTC
// Não usar worldtimeapi.org hardcoded - usar timeapi.io como primeira opção
try {
const response = await fetch('https://timeapi.io/api/Time/current/zone?timeZone=UTC');
if (!response.ok) {
throw new Error('Falha ao obter tempo do servidor');
throw new Error('Falha ao obter tempo');
}
const data = (await response.json()) as { unixTime: number };
serverTime = data.unixTime * 1000;
}
} else if (servidorNTP.includes('time.windows.com') || servidorNTP.includes('windows')) {
// Windows NTP - usar API que retorna UTC
const response = await fetch('https://worldtimeapi.org/api/timezone/Etc/UTC');
if (!response.ok) {
throw new Error('Falha ao obter tempo do servidor');
}
const data = (await response.json()) as { unixtime: number };
serverTime = data.unixtime * 1000;
} else {
// Para outros servidores NTP, usar API genérica que retorna UTC
// Tentar worldtimeapi primeiro
try {
const response = await fetch('https://worldtimeapi.org/api/timezone/Etc/UTC');
if (!response.ok) {
throw new Error('Falha ao obter tempo');
}
const data = (await response.json()) as { unixtime: number };
serverTime = data.unixtime * 1000;
} catch {
// Fallback para timeapi.io
try {
const response = await fetch('https://timeapi.io/api/Time/current/zone?timeZone=UTC');
if (!response.ok) {
throw new Error('Falha ao obter tempo');
}
const data = (await response.json()) as { unixTime: number };
serverTime = data.unixTime * 1000;
} catch {
// Último fallback: usar tempo do servidor Convex (já está em UTC)
serverTime = Date.now();
}
// Fallback: usar tempo do servidor Convex (já está em UTC)
// Não usar worldtimeapi.org como fallback automático
serverTime = Date.now();
}
}
@@ -234,7 +274,7 @@ export const sincronizarTempo = action({
} catch (error) {
// Sempre usar fallback como última opção, mesmo se desabilitado
// Isso evita que o sistema trave completamente se o servidor externo não estiver disponível
const aviso = config.fallbackParaPC
const aviso: string = config.fallbackParaPC
? 'Falha ao sincronizar com servidor externo, usando relógio do PC'
: 'Falha ao sincronizar com servidor externo. Fallback desabilitado, mas usando relógio do PC como última opção.';