- Updated type definitions in ChatWindow and MessageList components for better type safety. - Improved MessageInput to handle message responses, including a preview feature for replying to messages. - Enhanced the chat message handling logic to support message references and improve user interaction. - Refactored notification utility functions to support push notifications and rate limiting for email sending. - Updated backend schema to accommodate new features related to message responses and notifications.
94 lines
3.0 KiB
TypeScript
94 lines
3.0 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 webpush = await import("web-push");
|
|
|
|
// VAPID keys devem vir de variáveis de ambiente
|
|
const publicKey = process.env.VAPID_PUBLIC_KEY;
|
|
const privateKey = 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) {
|
|
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 };
|
|
}
|
|
},
|
|
});
|
|
|