109 lines
3.4 KiB
TypeScript
109 lines
3.4 KiB
TypeScript
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 })));
|
|
};
|