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}
-
-
+
+
+
+
-
+
{authStore.usuario?.nome}
{authStore.usuario?.role.nome}
@@ -233,19 +252,28 @@
-
-
+
+ {#if avatarUrlDoUsuario()}
+
})
+ {:else}
+
+
+ {/if}
-
+
@@ -295,36 +323,36 @@
-
+
{@render children?.()}
-
-
-
+
+
+