← Назадconst express = require('express');
const router = express.Router();
const webpush = require('web-push');
const prisma = require('../services/db');
const logger = require('../utils/logger');
// Setup Web Push
const publicVapidKey = process.env.VAPID_PUBLIC_KEY;
const privateVapidKey = process.env.VAPID_PRIVATE_KEY;
if (publicVapidKey && privateVapidKey) {
webpush.setVapidDetails(
'mailto:test@szhub.space',
publicVapidKey,
privateVapidKey
);
logger.info('VAPID Web Push initialized properly.');
} else {
logger.error('VAPID keys are missing! Web push will not work.');
}
// Return the public VAPID key to the frontend so it can subscribe
router.get('/vapidPublicKey', (req, res) => {
res.json({ publicKey: publicVapidKey });
});
// Save the push subscription Object from the browser
router.post('/subscribe', async (req, res) => {
const subscription = req.body;
if (!subscription || !subscription.endpoint) {
return res.status(400).json({ error: 'Invalid subscription object' });
}
try {
// Save or update the subscription in the database
await prisma.pushSubscription.upsert({
where: { endpoint: subscription.endpoint },
update: {
p256dh: subscription.keys.p256dh,
auth: subscription.keys.auth,
},
create: {
endpoint: subscription.endpoint,
p256dh: subscription.keys.p256dh,
auth: subscription.keys.auth,
},
});
// Send a welcome push to confirm it worked!
const payload = JSON.stringify({
title: 'Уведомления Включены! 🚀',
body: 'Теперь вы будете первыми узнавать о новых сильных сигналах на рынке опционов.',
icon: '/pwa-192x192.png'
});
await webpush.sendNotification(subscription, payload).catch(err => logger.error(`Error sending welcome push: ${err.message}`));
logger.info(`New Web Push subscription saved successfully.`);
res.status(201).json({});
} catch (error) {
logger.error(`Error saving push subscription: ${error.message}`);
res.status(500).json({ error: 'Failed to subscribe' });
}
});
// Remove the push subscription
router.post('/unsubscribe', async (req, res) => {
const subscription = req.body;
if (!subscription || !subscription.endpoint) {
return res.status(400).json({ error: 'Invalid subscription object' });
}
try {
await prisma.pushSubscription.delete({
where: { endpoint: subscription.endpoint },
});
logger.info('User unsubscribed from push notifications.');
res.status(200).json({});
} catch (error) {
res.status(200).json({});
}
});
// Diagnostic Test Endpoint
router.post('/test', async (req, res) => {
logger.info('Executing diagnostic push notification broadcast...');
try {
await broadcastPushNotification({
title: 'Инструмент отладки ⚙️',
body: 'Связь с сервером установлена. Push-уведомления работают исправно!',
icon: '/pwa-192x192.png'
});
res.status(200).json({ success: true, message: 'Test broadcast fired.' });
} catch (error) {
logger.error(`Failed diagnostic test: ${error.message}`);
res.status(500).json({ error: 'Failed' });
}
});
async function broadcastPushNotification(payloadData) {
if (!publicVapidKey) return;
const payload = JSON.stringify(payloadData);
try {
const subscriptions = await prisma.pushSubscription.findMany();
const promises = subscriptions.map(sub => {
const pushSub = {
endpoint: sub.endpoint,
keys: { p256dh: sub.p256dh, auth: sub.auth }
};
return webpush.sendNotification(pushSub, payload).catch(err => {
if (err.statusCode === 404 || err.statusCode === 410) {
// The subscription has expired or been unsubscribed, remove from DB
logger.info(`Subscription expired. Removing endpoint: ${sub.id}`);
return prisma.pushSubscription.delete({ where: { id: sub.id } }).catch(e => logger.error(`Failed DB cleanup: ${e.message}`));
} else {
logger.error(`Push notification error for device ${sub.id}: ${err.message}`);
}
});
});
await Promise.allSettled(promises);
logger.info(`Successfully broadcasted push chunk to ${subscriptions.length} devices.`);
} catch (err) {
logger.error(`Error broadcasting push chunk: ${err.message}`);
}
}
module.exports = { router, broadcastPushNotification };