73 lines
2.5 KiB
TypeScript
73 lines
2.5 KiB
TypeScript
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<string, PushSubscription[]> = 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();
|
|
|
|
|