diff --git a/apps/web/src/lib/components/Sidebar.svelte b/apps/web/src/lib/components/Sidebar.svelte index 1780901..f63f9f7 100644 --- a/apps/web/src/lib/components/Sidebar.svelte +++ b/apps/web/src/lib/components/Sidebar.svelte @@ -193,11 +193,10 @@ if (result.data) { // Registrar tentativa de login bem-sucedida // Fazer de forma assíncrona para não bloquear o login + // Não tentamos buscar getCurrentUser aqui porque pode causar timeout + // O useQuery no componente já busca o usuário automaticamente quando a sessão estiver pronta (async () => { try { - // Aguardar um pouco para o usuário ser sincronizado no Convex - await new Promise((resolve) => setTimeout(resolve, 500)); - // Tentar obter GPS se já estiver disponível (não esperar) let localizacaoGPS: any = {}; try { @@ -209,40 +208,21 @@ // Ignorar se GPS não estiver pronto } - // Buscar o usuário no Convex usando getCurrentUser - const usuario = await convexClient.query(api.auth.getCurrentUser, {}); - - if (usuario && usuario._id) { - await convexClient.mutation(api.logsLogin.registrarTentativaLogin, { - usuarioId: usuario._id, - matriculaOuEmail: matricula.trim(), - sucesso: true, - userAgent: userAgent, - ipAddress: ipPublico, - latitudeGPS: localizacaoGPS.latitude, - longitudeGPS: localizacaoGPS.longitude, - precisaoGPS: localizacaoGPS.precisao, - enderecoGPS: localizacaoGPS.endereco, - cidadeGPS: localizacaoGPS.cidade, - estadoGPS: localizacaoGPS.estado, - paisGPS: localizacaoGPS.pais - }); - } else { - // Se não encontrou o usuário, registrar sem usuarioId (será atualizado depois) - await convexClient.mutation(api.logsLogin.registrarTentativaLogin, { - matriculaOuEmail: matricula.trim(), - sucesso: true, - userAgent: userAgent, - ipAddress: ipPublico, - latitudeGPS: localizacaoGPS.latitude, - longitudeGPS: localizacaoGPS.longitude, - precisaoGPS: localizacaoGPS.precisao, - enderecoGPS: localizacaoGPS.endereco, - cidadeGPS: localizacaoGPS.cidade, - estadoGPS: localizacaoGPS.estado, - paisGPS: localizacaoGPS.pais - }); - } + // Registrar log sem usuarioId - será atualizado depois quando o usuário estiver disponível + // Isso evita timeouts que ocorrem quando tentamos buscar getCurrentUser imediatamente após login + await convexClient.mutation(api.logsLogin.registrarTentativaLogin, { + matriculaOuEmail: matricula.trim(), + sucesso: true, + userAgent: userAgent, + ipAddress: ipPublico, + latitudeGPS: localizacaoGPS.latitude, + longitudeGPS: localizacaoGPS.longitude, + precisaoGPS: localizacaoGPS.precisao, + enderecoGPS: localizacaoGPS.endereco, + cidadeGPS: localizacaoGPS.cidade, + estadoGPS: localizacaoGPS.estado, + paisGPS: localizacaoGPS.pais + }); } catch (err) { console.error('Erro ao registrar tentativa de login:', err); // Não bloquear o login se houver erro ao registrar diff --git a/apps/web/src/lib/components/chat/MessageList.svelte b/apps/web/src/lib/components/chat/MessageList.svelte index 4cda322..7c64094 100644 --- a/apps/web/src/lib/components/chat/MessageList.svelte +++ b/apps/web/src/lib/components/chat/MessageList.svelte @@ -38,48 +38,63 @@ }); // Atualizar lista de mensagens quando a query mudar + // Usar untrack para evitar loops infinitos + let processandoMensagens = false; $effect(() => { + if (processandoMensagens) return; if (!mensagensQuery?.data) return; const resultado = mensagensQuery.data as { mensagens: any[]; hasMore: boolean; nextCursor: Id<'mensagens'> | null }; const novasMensagens = resultado.mensagens || []; - // Evitar atualizações desnecessárias comparando IDs das mensagens - if (cursor === null) { - // Primeira carga: verificar se realmente mudou - const idsAtuais = todasMensagens.map(m => m?._id).filter(Boolean); - const idsNovos = novasMensagens.map(m => m?._id).filter(Boolean); - const idsIguais = idsAtuais.length === idsNovos.length && - idsAtuais.every((id, i) => id === idsNovos[i]); - - if (!idsIguais) { - todasMensagens = novasMensagens; + // Comparação simples usando JSON para evitar loops + const idsAtuais = todasMensagens.map(m => String(m?._id)).sort().join(','); + const idsNovos = novasMensagens.map(m => String(m?._id)).sort().join(','); + + // Só atualizar se realmente mudou + if (idsAtuais !== idsNovos) { + processandoMensagens = true; + try { + if (cursor === null) { + todasMensagens = novasMensagens; + } else { + // Carregamento adicional: adicionar no início + const idsExistentes = new Set(todasMensagens.map(m => String(m?._id))); + const novasParaAdicionar = novasMensagens.filter(m => m?._id && !idsExistentes.has(String(m._id))); + if (novasParaAdicionar.length > 0) { + todasMensagens = [...novasParaAdicionar, ...todasMensagens]; + } + } + hasMore = resultado.hasMore || false; + carregandoMais = false; + } finally { + processandoMensagens = false; } } else { - // Carregamento adicional: adicionar no início (mensagens mais antigas) - // Verificar se já não foram adicionadas - const idsExistentes = new Set(todasMensagens.map(m => m?._id).filter(Boolean)); - const novasParaAdicionar = novasMensagens.filter(m => m?._id && !idsExistentes.has(m._id)); - - if (novasParaAdicionar.length > 0) { - todasMensagens = [...novasParaAdicionar, ...todasMensagens]; - } + hasMore = resultado.hasMore || false; + carregandoMais = false; } - - hasMore = resultado.hasMore || false; - carregandoMais = false; }); // Resetar quando mudar de conversa let conversaIdAnterior = $state(null); + let resetandoConversa = false; $effect(() => { + if (resetandoConversa) return; + const conversaIdAtual = String(conversaId); if (conversaIdAnterior !== null && conversaIdAnterior !== conversaIdAtual) { - cursor = null; - todasMensagens = []; - hasMore = true; - carregandoMais = false; - mensagensComConteudo = []; + resetandoConversa = true; + try { + cursor = null; + todasMensagens = []; + hasMore = true; + carregandoMais = false; + mensagensComConteudo = []; + ultimoProcessamento = ''; + } finally { + resetandoConversa = false; + } } conversaIdAnterior = conversaIdAtual; }); @@ -542,7 +557,10 @@ // Processar mensagens para descriptografar as criptografadas let ultimoProcessamento = $state(''); + let processandoDescriptografia = false; $effect(() => { + if (processandoDescriptografia) return; + const mensagens = todasMensagens; // Se não há mensagens, limpar e retornar @@ -554,13 +572,14 @@ return; } - // Criar hash das mensagens para evitar reprocessamento desnecessário - const hashMensagens = mensagens.map(m => `${m._id}-${m.criptografado ? '1' : '0'}`).join('|'); - if (hashMensagens === ultimoProcessamento) { + // Criar hash simples das mensagens para evitar reprocessamento + const hashMensagens = mensagens.map(m => `${String(m._id)}-${m.criptografado ? '1' : '0'}`).join('|'); + if (hashMensagens === ultimoProcessamento && mensagensComConteudo.length === mensagens.length) { return; // Já foi processado } ultimoProcessamento = hashMensagens; + processandoDescriptografia = true; const processarMensagens = async () => { const processadas: Mensagem[] = []; @@ -638,6 +657,7 @@ } mensagensComConteudo = processadas; + processandoDescriptografia = false; }; processarMensagens().catch((error) => { @@ -648,6 +668,7 @@ conteudoDescriptografado: msg.criptografado ? '🔒 Erro ao descriptografar' : msg.conteudo || '', arquivoUrlDescriptografado: msg.criptografado ? null : msg.arquivoUrl || null })); + processandoDescriptografia = false; }); }); @@ -826,7 +847,7 @@ bind:this={messagesContainer} onscroll={handleScroll} > - {#if todasMensagens.length > 0 || mensagensComConteudo.length > 0} + {#if todasMensagens.length > 0} {@const mensagensParaExibir = mensagensComConteudo.length > 0 ? mensagensComConteudo : todasMensagens} {@const gruposPorDia = agruparMensagensPorDia(mensagensParaExibir)} {#each Object.entries(gruposPorDia) as [dia, mensagensDia]} diff --git a/packages/backend/convex/auth.ts b/packages/backend/convex/auth.ts index 4f341c1..d579430 100644 --- a/packages/backend/convex/auth.ts +++ b/packages/backend/convex/auth.ts @@ -40,32 +40,39 @@ export const createAuth = ( export const getCurrentUser = query({ args: {}, handler: async (ctx) => { - const authUser = await authComponent.safeGetAuthUser(ctx); - if (!authUser) { + try { + const authUser = await authComponent.safeGetAuthUser(ctx); + if (!authUser) { + return; + } + + const user = await ctx.db + .query('usuarios') + .withIndex('authId', (q) => q.eq('authId', authUser._id)) + .unique(); + + if (!user) { + return; + } + + // Buscar foto de perfil e role em paralelo para melhor performance + const [fotoPerfilUrl, role] = await Promise.all([ + user.fotoPerfil ? ctx.storage.getUrl(user.fotoPerfil).catch(() => null) : Promise.resolve(null), + user.roleId + ? ctx.db + .query('roles') + .withIndex('by_id', (q) => q.eq('_id', user.roleId)) + .unique() + .catch(() => null) + : Promise.resolve(null) + ]); + + return { ...user, role: role || null, fotoPerfilUrl }; + } catch (error) { + // Log do erro mas não falhar completamente - retornar null para permitir retry + console.error('Erro ao buscar usuário atual:', error); return; } - - const user = await ctx.db - .query('usuarios') - .withIndex('authId', (q) => q.eq('authId', authUser._id)) - .unique(); - - if (!user) { - return; - } - - const fotoPerfilUrl = user.fotoPerfil ? await ctx.storage.getUrl(user.fotoPerfil) : null; - - if (!user.roleId) { - return { ...user, role: null, fotoPerfilUrl }; - } - - const role = await ctx.db - .query('roles') - .withIndex('by_id', (q) => q.eq('_id', user.roleId)) - .unique(); - - return { ...user, role, fotoPerfilUrl }; } });