import webpush from 'web-push'; import db from '../../database/models/index.js'; import { getIO } from './socket.js'; import logger from './logger.js'; const { Notification, PushSubscription } = db; // Initialize VAPID keys if (process.env.VAPID_PUBLIC_KEY && process.env.VAPID_PRIVATE_KEY) { webpush.setVapidDetails( `mailto:${process.env.VAPID_EMAIL || 'admin@royalenfield.com'}`, process.env.VAPID_PUBLIC_KEY, process.env.VAPID_PRIVATE_KEY ); } /** * Sends a notification to a specific user via multiple channels. */ export const sendNotification = async (params: { userId: string; title: string; message: string; type?: string; link?: string; data?: any; }) => { const { userId, title, message, type = 'info', link, data } = params; logger.info(`Starting sendNotification for user: ${userId}`); try { // 1. Create In-App Notification (Database) const notification = await Notification.create({ userId, title, message, type, link, isRead: false }); logger.info(`Notification created in DB with ID: ${notification.id}`); // 2. Real-time update via Socket.io try { const io = getIO(); const roomName = `user_${userId}`; logger.info(`Emitting real-time notification to room: ${roomName}`); // Emit to a private room for the user io.to(roomName).emit('notification', { id: notification.id, title, message, type, link, createdAt: notification.createdAt }); } catch (socketError: any) { logger.warn(`Socket.io emit failed: ${socketError.message}`); } // 3. Web Push Notification const subscriptions = await PushSubscription.findAll({ where: { userId } }); for (const sub of subscriptions) { try { const pushConfig = { endpoint: sub.endpoint, keys: { p256dh: sub.p256dh, auth: sub.auth } }; const payload = JSON.stringify({ title, body: message, icon: '/re-logo.png', // Fallback icon path data: { url: link || '/', ...data } }); await webpush.sendNotification(pushConfig, payload); } catch (error: any) { logger.error(`Error sending Web Push to subscription ${sub.id}:`, error.message); // If subscription is expired or invalid, remove it if (error.statusCode === 410 || error.statusCode === 404) { await sub.destroy(); logger.info(`Removed invalid push subscription: ${sub.id}`); } } } return notification; } catch (error) { logger.error('Error in sendNotification:', error); throw error; } }; /** * Notifies multiple users (e.g., participants in a request). */ export const notifyUsers = async (userIds: string[], params: any) => { return Promise.all(userIds.map(userId => sendNotification({ ...params, userId }))); };