const express = require('express'); const router = express.Router(); const { query, queryOne, exec } = require('../models/db'); const { authMiddleware } = require('../middleware/auth'); // ── Firebase Admin ───────────────────────────────────────────────────────────── let firebaseAdmin = null; let firebaseApp = null; function getMessaging() { if (firebaseApp) return firebaseAdmin.messaging(firebaseApp); const json = process.env.FIREBASE_SERVICE_ACCOUNT; if (!json) return null; try { firebaseAdmin = require('firebase-admin'); const svc = JSON.parse(json); firebaseApp = firebaseAdmin.initializeApp({ credential: firebaseAdmin.credential.cert(svc), }); console.log('[Push] Firebase Admin initialised'); return firebaseAdmin.messaging(firebaseApp); } catch (e) { console.error('[Push] Firebase Admin init failed:', e.message); return null; } } // ── Helpers ──────────────────────────────────────────────────────────────────── // Called from index.js socket push notifications async function sendPushToUser(schema, userId, payload) { const messaging = getMessaging(); if (!messaging) return; try { const subs = await query(schema, 'SELECT * FROM push_subscriptions WHERE user_id = $1 AND fcm_token IS NOT NULL', [userId] ); for (const sub of subs) { try { await messaging.send({ token: sub.fcm_token, data: { title: payload.title || 'New Message', body: payload.body || '', url: payload.url || '/', groupId: payload.groupId ? String(payload.groupId) : '', }, android: { priority: 'high' }, webpush: { headers: { Urgency: 'high' } }, }); } catch (err) { // Remove stale tokens const stale = [ 'messaging/registration-token-not-registered', 'messaging/invalid-registration-token', 'messaging/invalid-argument', ]; if (stale.includes(err.code)) { await exec(schema, 'DELETE FROM push_subscriptions WHERE id = $1', [sub.id]); } } } } catch (e) { console.error('[Push] sendPushToUser error:', e.message); } } // ── Routes ───────────────────────────────────────────────────────────────────── // Public — frontend fetches this to initialise the Firebase JS SDK router.get('/firebase-config', (req, res) => { const apiKey = process.env.FIREBASE_API_KEY; const projectId = process.env.FIREBASE_PROJECT_ID; const messagingSenderId = process.env.FIREBASE_MESSAGING_SENDER_ID; const appId = process.env.FIREBASE_APP_ID; const vapidKey = process.env.FIREBASE_VAPID_KEY; if (!apiKey || !projectId || !messagingSenderId || !appId) { return res.status(503).json({ error: 'FCM not configured' }); } res.json({ apiKey, projectId, messagingSenderId, appId, vapidKey }); }); // Register / refresh an FCM token for the logged-in user router.post('/subscribe', authMiddleware, async (req, res) => { const { fcmToken } = req.body; if (!fcmToken) return res.status(400).json({ error: 'fcmToken required' }); try { const device = req.device || 'desktop'; await exec(req.schema, 'DELETE FROM push_subscriptions WHERE user_id = $1 AND device = $2', [req.user.id, device] ); await exec(req.schema, 'INSERT INTO push_subscriptions (user_id, device, fcm_token) VALUES ($1, $2, $3)', [req.user.id, device, fcmToken] ); res.json({ success: true }); } catch (e) { res.status(500).json({ error: e.message }); } }); // Remove the FCM token for the logged-in user / device router.post('/unsubscribe', authMiddleware, async (req, res) => { try { const device = req.device || 'desktop'; await exec(req.schema, 'DELETE FROM push_subscriptions WHERE user_id = $1 AND device = $2', [req.user.id, device] ); res.json({ success: true }); } catch (e) { res.status(500).json({ error: e.message }); } }); module.exports = { router, sendPushToUser };