Files
rosterchirp/backend/src/routes/push.js

116 lines
4.4 KiB
JavaScript

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