import webpush from 'web-push'; import logger from '@utils/logger'; import { Subscription } from '@models/Subscription'; type PushSubscription = any; // Web Push protocol JSON class NotificationService { private userIdToSubscriptions: Map = new Map(); configure(vapidPublicKey?: string, vapidPrivateKey?: string, mailto?: string) { const pub = vapidPublicKey || process.env.VAPID_PUBLIC_KEY || ''; const priv = vapidPrivateKey || process.env.VAPID_PRIVATE_KEY || ''; const contact = mailto || process.env.VAPID_CONTACT || 'mailto:admin@example.com'; if (!pub || !priv) { logger.warn('VAPID keys are not configured. Push notifications are disabled.'); return; } webpush.setVapidDetails(contact, pub, priv); logger.info('Web Push configured'); } async addSubscription(userId: string, subscription: PushSubscription, userAgent?: string) { // Persist to DB (upsert by endpoint) try { const endpoint: string = subscription?.endpoint || ''; const keys = subscription?.keys || {}; if (!endpoint || !keys?.p256dh || !keys?.auth) throw new Error('Invalid subscription payload'); await Subscription.upsert({ userId, endpoint, p256dh: keys.p256dh, auth: keys.auth, userAgent: userAgent || null, } as any); } catch (e) { logger.error('Failed to persist subscription', e); } const list = this.userIdToSubscriptions.get(userId) || []; const already = list.find((s) => JSON.stringify(s) === JSON.stringify(subscription)); if (!already) { list.push(subscription); this.userIdToSubscriptions.set(userId, list); } logger.info(`Subscription stored for user ${userId}. Total: ${list.length}`); } async sendToUsers(userIds: string[], payload: any) { const message = JSON.stringify(payload); for (const uid of userIds) { let subs = this.userIdToSubscriptions.get(uid) || []; // Load from DB if memory empty if (subs.length === 0) { try { const rows = await Subscription.findAll({ where: { userId: uid } }); subs = rows.map((r: any) => ({ endpoint: r.endpoint, keys: { p256dh: r.p256dh, auth: r.auth } })); } catch {} } for (const sub of subs) { try { await webpush.sendNotification(sub, message); } catch (err) { logger.error(`Failed to send push to ${uid}:`, err); } } } } } export const notificationService = new NotificationService(); notificationService.configure();