From b3cc9727e4f7c3d3f8ab65646150ee4289ad46d5 Mon Sep 17 00:00:00 2001 From: Ricky Stretch Date: Mon, 23 Mar 2026 11:46:22 -0400 Subject: [PATCH] v0.12.4 windsurf changes --- backend/package.json | 2 +- build.sh | 2 +- frontend/package.json | 2 +- frontend/public/sw copy.js | 126 +++++++++++++++++++++++++++++++++++++ frontend/public/sw.js | 60 +++++++++++------- 5 files changed, 166 insertions(+), 26 deletions(-) create mode 100644 frontend/public/sw copy.js diff --git a/backend/package.json b/backend/package.json index 2664f74..4b8c022 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "rosterchirp-backend", - "version": "0.12.3", + "version": "0.12.4", "description": "RosterChirp backend server", "main": "src/index.js", "scripts": { diff --git a/build.sh b/build.sh index 2dc47ee..94e60c6 100644 --- a/build.sh +++ b/build.sh @@ -13,7 +13,7 @@ # ───────────────────────────────────────────────────────────── set -euo pipefail -VERSION="${1:-0.12.3}" +VERSION="${1:-0.12.4}" ACTION="${2:-}" REGISTRY="${REGISTRY:-}" IMAGE_NAME="rosterchirp" diff --git a/frontend/package.json b/frontend/package.json index 439095b..ef7a550 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "rosterchirp-frontend", - "version": "0.12.3", + "version": "0.12.4", "private": true, "scripts": { "dev": "vite", diff --git a/frontend/public/sw copy.js b/frontend/public/sw copy.js new file mode 100644 index 0000000..1300512 --- /dev/null +++ b/frontend/public/sw copy.js @@ -0,0 +1,126 @@ +// ── Firebase Messaging (background push for Android PWA) ────────────────────── +// Fill in the values below from Firebase Console → Project Settings → General → Your apps +// Leave apiKey as '__FIREBASE_API_KEY__' if not using FCM (push will be disabled). +importScripts('https://www.gstatic.com/firebasejs/10.14.1/firebase-app-compat.js'); +importScripts('https://www.gstatic.com/firebasejs/10.14.1/firebase-messaging-compat.js'); + +const FIREBASE_CONFIG = { + apiKey: "AIzaSyDx191unzXFT4WA1OvkdbrIY_c57kgruAU", + authDomain: "rosterchirp-push.firebaseapp.com", + projectId: "rosterchirp-push", + storageBucket: "rosterchirp-push.firebasestorage.app", + messagingSenderId: "126479377334", + appId: "1:126479377334:web:280abdd135cf7e0c50d717" +}; + +// Only initialise Firebase if the config has been filled in +let messaging = null; +if (FIREBASE_CONFIG.apiKey !== '__FIREBASE_API_KEY__') { + firebase.initializeApp(FIREBASE_CONFIG); + messaging = firebase.messaging(); +} + +// ── Cache ───────────────────────────────────────────────────────────────────── +const CACHE_NAME = 'rosterchirp-v1'; +const STATIC_ASSETS = ['/']; + +self.addEventListener('install', (event) => { + event.waitUntil( + caches.open(CACHE_NAME).then((cache) => cache.addAll(STATIC_ASSETS)) + ); + self.skipWaiting(); +}); + +self.addEventListener('activate', (event) => { + event.waitUntil( + caches.keys().then((keys) => + Promise.all(keys.filter(k => k !== CACHE_NAME).map(k => caches.delete(k))) + ) + ); + self.clients.claim(); +}); + +self.addEventListener('fetch', (event) => { + const url = event.request.url; + if (url.includes('/api/') || url.includes('/socket.io/') || url.includes('/manifest.json')) { + return; + } + event.respondWith( + fetch(event.request).catch(() => caches.match(event.request)) + ); +}); + +// ── Badge counter ───────────────────────────────────────────────────────────── +let badgeCount = 0; + +function showRosterChirpNotification(data) { + console.log('[SW] showRosterChirpNotification:', JSON.stringify(data)); + badgeCount++; + if (self.navigator?.setAppBadge) self.navigator.setAppBadge(badgeCount).catch(() => {}); + + return self.registration.showNotification(data.title || 'New Message', { + body: data.body || '', + icon: '/icons/icon-192.png', + badge: '/icons/icon-192-maskable.png', + data: { url: data.url || '/' }, + tag: data.groupId ? `rosterchirp-group-${data.groupId}` : 'rosterchirp-message', + renotify: true, + vibrate: [200, 100, 200], + }); +} + +// ── FCM background messages ─────────────────────────────────────────────────── +if (messaging) { + messaging.onBackgroundMessage((payload) => { + console.log('[SW] onBackgroundMessage received, data:', JSON.stringify(payload.data)); + return showRosterChirpNotification(payload.data || {}); + }); +} else { + console.warn('[SW] Firebase messaging not initialised — push notifications disabled'); +} + +// ── Raw push event (fallback diagnostic) ───────────────────────────────────── +// Fires for every push event BEFORE the Firebase SDK handles it. +// Log it so chrome://inspect shows whether the SW is even waking up. +self.addEventListener('push', (event) => { + console.log('[SW] push event received, hasData:', !!event.data, 'text:', event.data?.text?.()?.slice(0, 120)); + // Note: Firebase compat SDK registers its own push listener and handles display. + // This listener is diagnostic only — do not call showNotification() here. +}); + +// ── Notification click ──────────────────────────────────────────────────────── +self.addEventListener('notificationclick', (event) => { + event.notification.close(); + badgeCount = 0; + if (self.navigator?.clearAppBadge) self.navigator.clearAppBadge().catch(() => {}); + event.waitUntil( + clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => { + const url = event.notification.data?.url || '/'; + for (const client of clientList) { + if (client.url.includes(self.location.origin) && 'focus' in client) { + client.focus(); + return; + } + } + return clients.openWindow(url); + }) + ); +}); + +// ── Badge control messages from main thread ─────────────────────────────────── +self.addEventListener('message', (event) => { + if (event.data?.type === 'CLEAR_BADGE') { + badgeCount = 0; + if (self.navigator?.clearAppBadge) self.navigator.clearAppBadge().catch(() => {}); + } + if (event.data?.type === 'SET_BADGE') { + badgeCount = event.data.count || 0; + if (self.navigator?.setAppBadge) { + if (badgeCount > 0) { + self.navigator.setAppBadge(badgeCount).catch(() => {}); + } else { + self.navigator.clearAppBadge().catch(() => {}); + } + } + } +}); diff --git a/frontend/public/sw.js b/frontend/public/sw.js index 1300512..b7dfe2f 100644 --- a/frontend/public/sw.js +++ b/frontend/public/sw.js @@ -1,24 +1,35 @@ // ── Firebase Messaging (background push for Android PWA) ────────────────────── -// Fill in the values below from Firebase Console → Project Settings → General → Your apps -// Leave apiKey as '__FIREBASE_API_KEY__' if not using FCM (push will be disabled). +// Dynamically fetch Firebase config from backend to ensure consistency importScripts('https://www.gstatic.com/firebasejs/10.14.1/firebase-app-compat.js'); importScripts('https://www.gstatic.com/firebasejs/10.14.1/firebase-messaging-compat.js'); -const FIREBASE_CONFIG = { - apiKey: "AIzaSyDx191unzXFT4WA1OvkdbrIY_c57kgruAU", - authDomain: "rosterchirp-push.firebaseapp.com", - projectId: "rosterchirp-push", - storageBucket: "rosterchirp-push.firebasestorage.app", - messagingSenderId: "126479377334", - appId: "1:126479377334:web:280abdd135cf7e0c50d717" -}; +let FIREBASE_CONFIG = null; +let VAPID_KEY = null; -// Only initialise Firebase if the config has been filled in +// Fetch Firebase config and initialise messaging let messaging = null; -if (FIREBASE_CONFIG.apiKey !== '__FIREBASE_API_KEY__') { - firebase.initializeApp(FIREBASE_CONFIG); - messaging = firebase.messaging(); -} +let firebaseConfigPromise = fetch('/api/push/firebase-config') + .then(res => res.json()) + .then(config => { + FIREBASE_CONFIG = { + apiKey: config.apiKey, + authDomain: config.authDomain || `${config.projectId}.firebaseapp.com`, + projectId: config.projectId, + storageBucket: config.storageBucket || `${config.projectId}.firebasestorage.app`, + messagingSenderId: config.messagingSenderId, + appId: config.appId + }; + VAPID_KEY = config.vapidKey; + + if (FIREBASE_CONFIG.apiKey) { + firebase.initializeApp(FIREBASE_CONFIG); + messaging = firebase.messaging(); + console.log('[SW] Firebase initialized with dynamic config'); + } + }) + .catch(err => { + console.warn('[SW] Failed to fetch Firebase config:', err); + }); // ── Cache ───────────────────────────────────────────────────────────────────── const CACHE_NAME = 'rosterchirp-v1'; @@ -70,14 +81,17 @@ function showRosterChirpNotification(data) { } // ── FCM background messages ─────────────────────────────────────────────────── -if (messaging) { - messaging.onBackgroundMessage((payload) => { - console.log('[SW] onBackgroundMessage received, data:', JSON.stringify(payload.data)); - return showRosterChirpNotification(payload.data || {}); - }); -} else { - console.warn('[SW] Firebase messaging not initialised — push notifications disabled'); -} +// Wait for Firebase config to be loaded before setting up message handler +firebaseConfigPromise.then(() => { + if (messaging) { + messaging.onBackgroundMessage((payload) => { + console.log('[SW] onBackgroundMessage received, data:', JSON.stringify(payload.data)); + return showRosterChirpNotification(payload.data || {}); + }); + } else { + console.warn('[SW] Firebase messaging not initialised — push notifications disabled'); + } +}); // ── Raw push event (fallback diagnostic) ───────────────────────────────────── // Fires for every push event BEFORE the Firebase SDK handles it.