From 2d164958d834306c2cbc9ef8ad9bed7f1a924e43 Mon Sep 17 00:00:00 2001 From: Ricky Stretch Date: Sun, 22 Mar 2026 23:31:19 -0400 Subject: [PATCH] FCM test --- backend/src/routes/push.js | 43 ++++++++++++++++++ frontend/src/components/ProfileModal.jsx | 55 +++++++++++++++++++++++- frontend/src/pages/Chat.jsx | 16 +++++-- frontend/src/utils/api.js | 1 + 4 files changed, 111 insertions(+), 4 deletions(-) diff --git a/backend/src/routes/push.js b/backend/src/routes/push.js index b90443e..61521ff 100644 --- a/backend/src/routes/push.js +++ b/backend/src/routes/push.js @@ -112,4 +112,47 @@ router.post('/unsubscribe', authMiddleware, async (req, res) => { } catch (e) { res.status(500).json({ error: e.message }); } }); +// Send a test push to the requesting user's own device β€” for diagnosing FCM setup +router.post('/test', authMiddleware, async (req, res) => { + try { + const subs = await query(req.schema, + 'SELECT * FROM push_subscriptions WHERE user_id = $1 AND fcm_token IS NOT NULL', + [req.user.id] + ); + if (subs.length === 0) { + return res.status(404).json({ + error: 'No push subscription found for your account. Grant notification permission and reload the app first.', + }); + } + + const messaging = getMessaging(); + if (!messaging) { + return res.status(503).json({ error: 'Firebase Admin not initialised on server β€” check FIREBASE_SERVICE_ACCOUNT in .env' }); + } + + const results = []; + for (const sub of subs) { + try { + await messaging.send({ + token: sub.fcm_token, + data: { + title: 'RosterChirp Test', + body: 'Push notifications are working! πŸŽ‰', + url: '/', + groupId: '', + }, + android: { priority: 'high' }, + webpush: { headers: { Urgency: 'high' } }, + }); + results.push({ device: sub.device, status: 'sent' }); + console.log(`[Push] Test notification sent to user ${req.user.id} device=${sub.device}`); + } catch (err) { + results.push({ device: sub.device, status: 'failed', error: err.message, code: err.code }); + console.error(`[Push] Test notification failed for user ${req.user.id} device=${sub.device}:`, err.message); + } + } + res.json({ results }); + } catch (e) { res.status(500).json({ error: e.message }); } +}); + module.exports = { router, sendPushToUser }; diff --git a/frontend/src/components/ProfileModal.jsx b/frontend/src/components/ProfileModal.jsx index 0c33d2f..a3618f4 100644 --- a/frontend/src/components/ProfileModal.jsx +++ b/frontend/src/components/ProfileModal.jsx @@ -16,7 +16,9 @@ export default function ProfileModal({ onClose }) { const [newPw, setNewPw] = useState(''); const [confirmPw, setConfirmPw] = useState(''); const [loading, setLoading] = useState(false); - const [tab, setTab] = useState('profile'); // 'profile' | 'password' + const [tab, setTab] = useState('profile'); // 'profile' | 'password' | 'notifications' + const [pushTesting, setPushTesting] = useState(false); + const [pushResult, setPushResult] = useState(null); const [hideAdminTag, setHideAdminTag] = useState(!!user?.hide_admin_tag); const [allowDm, setAllowDm] = useState(user?.allow_dm !== 0); @@ -101,6 +103,7 @@ export default function ProfileModal({ onClose }) {
+
{tab === 'profile' && ( @@ -167,6 +170,56 @@ export default function ProfileModal({ onClose }) { )} + {tab === 'notifications' && ( +
+
+

Tap Send Test Notification to trigger a push to this device. The notification will arrive shortly if everything is configured correctly.

+

If it doesn't arrive, check:
+ β€’ Notification permission granted (browser prompt)
+ β€’ Android Settings β†’ Apps β†’ RosterChirp β†’ Notifications β†’ Enabled
+ β€’ App is backgrounded when the test fires +

+
+ + {pushResult && ( +
+ {pushResult.ok ? ( + pushResult.results.map((r, i) => ( +
+ {r.device}: {r.status === 'sent' ? 'βœ“ Sent β€” check your device for the notification' : `βœ— Failed β€” ${r.error}`} +
+ )) + ) : ( +
βœ— {pushResult.error}
+ )} +
+ )} +
+ )} + {tab === 'password' && (
diff --git a/frontend/src/pages/Chat.jsx b/frontend/src/pages/Chat.jsx index a6b7942..719830b 100644 --- a/frontend/src/pages/Chat.jsx +++ b/frontend/src/pages/Chat.jsx @@ -126,19 +126,29 @@ export default function Chat() { if (granted !== 'granted') return; } + console.log('[Push] Requesting FCM token...'); const fcmToken = await getToken(firebaseMessaging, { vapidKey, serviceWorkerRegistration: reg, }); - if (!fcmToken) return; + if (!fcmToken) { + console.warn('[Push] getToken() returned null β€” notification permission may not be granted at OS level, or VAPID key is wrong'); + return; + } + console.log('[Push] FCM token obtained:', fcmToken.slice(0, 30) + '...'); const token = localStorage.getItem('tc_token') || sessionStorage.getItem('tc_token'); - await fetch('/api/push/subscribe', { + const subRes = await fetch('/api/push/subscribe', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ fcmToken }), }); - console.log('[Push] FCM subscription registered'); + if (!subRes.ok) { + const err = await subRes.json().catch(() => ({})); + console.warn('[Push] Subscribe failed:', err.error || subRes.status); + } else { + console.log('[Push] FCM subscription registered successfully'); + } } catch (e) { console.warn('[Push] FCM subscription failed:', e.message); } diff --git a/frontend/src/utils/api.js b/frontend/src/utils/api.js index 68d5b6d..c7b437d 100644 --- a/frontend/src/utils/api.js +++ b/frontend/src/utils/api.js @@ -170,6 +170,7 @@ export const api = { getFirebaseConfig: () => req('GET', '/push/firebase-config'), subscribePush: (fcmToken) => req('POST', '/push/subscribe', { fcmToken }), unsubscribePush: () => req('POST', '/push/unsubscribe'), + testPush: () => req('POST', '/push/test'), // Link preview getLinkPreview: (url) => req('GET', `/link-preview?url=${encodeURIComponent(url)}`),