From aa3e3470cd21d80b33b81dfb7c3c43d831955848 Mon Sep 17 00:00:00 2001 From: deyvisonwanderley Date: Wed, 5 Nov 2025 06:14:52 -0300 Subject: [PATCH] feat: enhance push notification management and error handling - Implemented error handling for unhandled promise rejections related to message channels, improving stability during push notification operations. - Updated the PushNotificationManager component to manage push subscription registration with timeouts, preventing application hangs. - Enhanced the sidebar and chat components to display user avatars, improving user experience and visual consistency. - Refactored email processing logic to support scheduled email sending, integrating new backend functionalities for better email management. - Improved overall error handling and logging across components to reduce console spam and enhance debugging capabilities. --- .../components/PushNotificationManager.svelte | 91 +- apps/web/src/lib/components/Sidebar.svelte | 110 +- .../src/lib/components/chat/ChatList.svelte | 43 +- .../src/lib/components/chat/ChatWidget.svelte | 63 +- .../src/lib/components/chat/ChatWindow.svelte | 17 +- .../lib/components/chat/MessageList.svelte | 44 +- apps/web/src/lib/utils/notifications.ts | 117 +- .../routes/(dashboard)/perfil/+page.svelte | 80 +- .../ti/monitoramento-emails/+page.svelte | 220 +- .../(dashboard)/ti/notificacoes/+page.svelte | 42 +- packages/backend/convex/actions/email.ts | 3 +- .../convex/actions/pushNotifications.ts | 18 +- packages/backend/convex/actions/smtp.ts | 5 +- .../convex/actions/utils/nodeCrypto.ts | 1 + packages/backend/convex/chat.ts | 3114 +++++++++-------- packages/backend/convex/crons.ts | 15 +- packages/backend/convex/email.ts | 81 +- packages/backend/convex/pushNotifications.ts | 24 +- packages/backend/convex/types/web-push.d.ts | 46 + packages/backend/convex/usuarios.ts | 46 +- 20 files changed, 2515 insertions(+), 1665 deletions(-) create mode 100644 packages/backend/convex/types/web-push.d.ts diff --git a/apps/web/src/lib/components/PushNotificationManager.svelte b/apps/web/src/lib/components/PushNotificationManager.svelte index c1b47f9..be11326 100644 --- a/apps/web/src/lib/components/PushNotificationManager.svelte +++ b/apps/web/src/lib/components/PushNotificationManager.svelte @@ -12,51 +12,116 @@ const client = useConvexClient(); + // Capturar erros de Promise não tratados relacionados a message channel + // Este erro geralmente vem de extensões do Chrome ou comunicação com Service Worker + if (typeof window !== "undefined") { + window.addEventListener( + "unhandledrejection", + (event: PromiseRejectionEvent) => { + const reason = event.reason; + const errorMessage = + reason?.message || reason?.toString() || ""; + + // Filtrar apenas erros relacionados a message channel fechado + if ( + errorMessage.includes("message channel closed") || + errorMessage.includes("asynchronous response") || + (errorMessage.includes("message channel") && + errorMessage.includes("closed")) + ) { + // Prevenir que o erro apareça no console + event.preventDefault(); + // Silenciar o erro - é geralmente causado por extensões do Chrome + return false; + } + }, + { capture: true } + ); + } + onMount(async () => { + let checkAuth: ReturnType | null = null; + let mounted = true; + // Aguardar usuário estar autenticado - const checkAuth = setInterval(async () => { - if (authStore.usuario) { - clearInterval(checkAuth); - await registrarPushSubscription(); + checkAuth = setInterval(async () => { + if (authStore.usuario && mounted) { + clearInterval(checkAuth!); + checkAuth = null; + try { + await registrarPushSubscription(); + } catch (error) { + // Silenciar erros de push subscription para evitar spam no console + if (error instanceof Error && !error.message.includes("message channel")) { + console.error("Erro ao configurar push notifications:", error); + } + } } }, 500); // Limpar intervalo após 30 segundos (timeout) - setTimeout(() => { - clearInterval(checkAuth); + const timeout = setTimeout(() => { + if (checkAuth) { + clearInterval(checkAuth); + checkAuth = null; + } }, 30000); return () => { - clearInterval(checkAuth); + mounted = false; + if (checkAuth) { + clearInterval(checkAuth); + } + clearTimeout(timeout); }; }); async function registrarPushSubscription() { try { - // Solicitar subscription - const subscription = await solicitarPushSubscription(); + // Verificar se Service Worker está disponível antes de tentar + if (!("serviceWorker" in navigator) || !("PushManager" in window)) { + return; + } + + // Solicitar subscription com timeout para evitar travamentos + const subscriptionPromise = solicitarPushSubscription(); + const timeoutPromise = new Promise((resolve) => + setTimeout(() => resolve(null), 5000) + ); + + const subscription = await Promise.race([subscriptionPromise, timeoutPromise]); if (!subscription) { - console.log("ℹ️ Push subscription não disponível ou permissão negada"); + // Não logar para evitar spam no console quando VAPID key não está configurada return; } // Converter para formato serializável const subscriptionData = subscriptionToJSON(subscription); - // Registrar no backend - const resultado = await client.mutation(api.pushNotifications.registrarPushSubscription, { + // Registrar no backend com timeout + const mutationPromise = client.mutation(api.pushNotifications.registrarPushSubscription, { endpoint: subscriptionData.endpoint, keys: subscriptionData.keys, userAgent: navigator.userAgent, }); + const timeoutMutationPromise = new Promise<{ sucesso: false; erro: string }>((resolve) => + setTimeout(() => resolve({ sucesso: false, erro: "Timeout" }), 5000) + ); + + const resultado = await Promise.race([mutationPromise, timeoutMutationPromise]); + if (resultado.sucesso) { console.log("✅ Push subscription registrada com sucesso"); - } else { + } else if (resultado.erro && !resultado.erro.includes("Timeout")) { console.error("❌ Erro ao registrar push subscription:", resultado.erro); } } catch (error) { + // Ignorar erros relacionados a message channel fechado + if (error instanceof Error && error.message.includes("message channel")) { + return; + } console.error("❌ Erro ao configurar push notifications:", error); } } diff --git a/apps/web/src/lib/components/Sidebar.svelte b/apps/web/src/lib/components/Sidebar.svelte index 7ef45b5..6c0fc37 100644 --- a/apps/web/src/lib/components/Sidebar.svelte +++ b/apps/web/src/lib/components/Sidebar.svelte @@ -11,6 +11,7 @@ import ChatWidget from "$lib/components/chat/ChatWidget.svelte"; import PresenceManager from "$lib/components/chat/PresenceManager.svelte"; import { getBrowserInfo } from "$lib/utils/browserInfo"; + import { getAvatarUrl } from "$lib/utils/avatarGenerator"; let { children }: { children: Snippet } = $props(); @@ -19,6 +20,22 @@ // Caminho atual da página const currentPath = $derived(page.url.pathname); + // Função para obter a URL do avatar/foto do usuário + const avatarUrlDoUsuario = $derived(() => { + const usuario = authStore.usuario; + if (!usuario) return null; + + // Prioridade: fotoPerfilUrl > avatar > fallback com nome + if (usuario.fotoPerfilUrl) { + return usuario.fotoPerfilUrl; + } + if (usuario.avatar) { + return getAvatarUrl(usuario.avatar); + } + // Fallback: gerar avatar baseado no nome + return getAvatarUrl(usuario.nome); + }); + // Função para gerar classes do menu ativo function getMenuClasses(isActive: boolean) { const baseClasses = "group font-semibold flex items-center justify-center gap-2 text-center p-3.5 rounded-xl border-2 transition-all duration-300 shadow-md hover:shadow-lg hover:scale-105"; @@ -209,12 +226,14 @@

-
+
{#if authStore.autenticado} - - + +
+ +
-