103 lines
3.3 KiB
TypeScript
103 lines
3.3 KiB
TypeScript
'use node';
|
|
|
|
import { action } from '../_generated/server';
|
|
import { v } from 'convex/values';
|
|
import { internal } from '../_generated/api';
|
|
|
|
/**
|
|
* 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<void>;
|
|
}
|
|
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 };
|
|
}
|
|
}
|
|
});
|