← Back
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 };

📜 Git History

06783dafix: options-screener-v2 — 9 bug fixes, 5 strategy improvements, 2 infra enhancements3 months ago
2581c69feat(ops): add winston logger, jest tests for whale analysis, and push diagnostic endpoint4 months ago
ef8d444feat(push): allow toggle push notifications via the ui with unsubscribe logic4 months ago
fc018c2feat(push): implement web-push vapid backend, db registry, and frontend bell subscription toggle4 months ago
Show last diff
Loading...