'use node'; import { v } from 'convex/values'; import { internal } from '../_generated/api'; import { action } from '../_generated/server'; /** * Enviar push notification usando Web Push API */ export const enviarPush = action({ args: { subscriptionId: v.id('pushSubscriptions'), titulo: v.string(), corpo: v.string(), data: v.optional( v.object({ conversaId: v.optional(v.string()), mensagemId: v.optional(v.string()), tipo: v.optional(v.string()) }) ) }, returns: v.object({ sucesso: v.boolean(), erro: v.optional(v.string()) }), handler: async (ctx, args) => { try { // Buscar subscription const subscription = await ctx.runQuery(internal.pushNotifications.getSubscriptionById, { subscriptionId: args.subscriptionId }); if (!subscription || !subscription.ativo) { return { sucesso: false, erro: 'Subscription não encontrada ou inativa' }; } // Web Push requer VAPID keys (deve estar em variáveis de ambiente) // Por enquanto, vamos usar uma implementação básica // Em produção, você precisará configurar VAPID keys const webpushModule = await import('web-push'); // web-push pode exportar como default ou named exports // Usar a declaração de tipo do módulo web-push interface WebPushType { setVapidDetails: (subject: string, publicKey: string, privateKey: string) => void; sendNotification: ( subscription: { endpoint: string; keys: { p256dh: string; auth: string }; }, payload: string | Buffer ) => Promise; } const webpush: WebPushType = (webpushModule.default || webpushModule) as WebPushType; // VAPID keys devem vir de variáveis de ambiente const publicKey: string | undefined = process.env.VAPID_PUBLIC_KEY; const privateKey: string | undefined = process.env.VAPID_PRIVATE_KEY; if (!publicKey || !privateKey) { console.warn('⚠️ VAPID keys não configuradas. Push notifications não funcionarão.'); // Em desenvolvimento, podemos retornar sucesso sem enviar return { sucesso: true }; } webpush.setVapidDetails('mailto:suporte@sgse.app', publicKey, privateKey); // Preparar payload da notificação const payload = JSON.stringify({ title: args.titulo, body: args.corpo, icon: '/favicon.png', badge: '/favicon.png', data: args.data || {}, tag: args.data?.conversaId || 'default', requireInteraction: args.data?.tipo === 'mencao' // Menções requerem interação }); // Enviar push notification await webpush.sendNotification( { endpoint: subscription.endpoint, keys: { p256dh: subscription.keys.p256dh, auth: subscription.keys.auth } }, payload ); console.log(`✅ Push notification enviada para ${subscription.endpoint}`); return { sucesso: true }; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); console.error('❌ Erro ao enviar push notification:', errorMessage); // Se subscription inválida, marcar como inativa if (errorMessage.includes('410') || errorMessage.includes('expired')) { await ctx.runMutation(internal.pushNotifications.marcarSubscriptionInativa, { subscriptionId: args.subscriptionId }); } return { sucesso: false, erro: errorMessage }; } } });