82 lines
3.9 KiB
Plaintext
82 lines
3.9 KiB
Plaintext
The Consolidated "Bulletproof" Push Listener
|
|
To fix the "hit or miss" behavior on mobile, we need to move away from relying on the Firebase SDK's internal listener (which is a black box that doesn't always play nice with mobile power management) and instead wrap everything in the native push event using event.waitUntil.
|
|
|
|
Replace your current messaging.onBackgroundMessage and self.addEventListener('push') blocks with this unified version:
|
|
|
|
JavaScript
|
|
// ── Unified Push Handler (Optimized for Mobile) ──────────────────────────────
|
|
self.addEventListener('push', (event) => {
|
|
console.log('[SW] Push event received. Messaging Ready:', !!messaging);
|
|
|
|
// event.waitUntil is the "Keep-Alive" signal for mobile OS
|
|
event.waitUntil(
|
|
(async () => {
|
|
try {
|
|
let payload;
|
|
|
|
// 1. Try to parse the data directly from the push event (Fastest/Reliable)
|
|
if (event.data) {
|
|
try {
|
|
payload = event.data.json();
|
|
console.log('[SW] Raw push data parsed:', JSON.stringify(payload));
|
|
} catch (e) {
|
|
console.warn('[SW] Could not parse JSON, using text fallback');
|
|
payload = { notification: { body: event.data.text() } };
|
|
}
|
|
}
|
|
|
|
// 2. If the payload is empty, check if Firebase can catch it
|
|
// (This happens if your server sends "Notification" instead of "Data" messages)
|
|
if (!payload && messaging) {
|
|
// This is a last-resort wait for the SDK
|
|
payload = await new Promise((resolve) => {
|
|
const timeout = setTimeout(() => resolve(null), 2000);
|
|
messaging.onBackgroundMessage((bgPayload) => {
|
|
clearTimeout(timeout);
|
|
resolve(bgPayload);
|
|
});
|
|
});
|
|
}
|
|
|
|
// 3. Construct and show the notification
|
|
if (payload) {
|
|
const n = payload.notification || {};
|
|
const d = payload.data || {};
|
|
|
|
// Use the specific function you already defined
|
|
await showRosterChirpNotification({
|
|
title: n.title || d.title || 'New Message',
|
|
body: n.body || d.body || '',
|
|
url: d.url || d.link || '/', // some SDKs use 'link'
|
|
groupId: d.groupId || '',
|
|
});
|
|
} else {
|
|
// Fallback if we woke up for a "ghost" push with no data
|
|
await self.registration.showNotification('RosterChirp', {
|
|
body: 'You have a new update.',
|
|
tag: 'rosterchirp-fallback'
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('[SW] Critical Push Error:', error);
|
|
}
|
|
})()
|
|
);
|
|
});
|
|
Why this fixes your issues:
|
|
Eliminates the Race Condition: By wrapping the logic in an async function inside event.waitUntil, you tell Android/iOS: "Do not kill this process until the notification is actually rendered on the screen."
|
|
|
|
Bypasses SDK "Sleep": Sometimes the Firebase SDK takes too long to initialize. This code tries to parse the event.data directly first. Direct parsing is almost instantaneous, which is critical when a phone is in a deep sleep state.
|
|
|
|
Handles "Ghost" Pushes: Sometimes FCM sends a "heartbeat" or an empty push to wake the device. This script ensures that even if data is missing, the worker stays alive long enough to handle it gracefully rather than crashing.
|
|
|
|
One Final Server-Side Check
|
|
Since you are using HAProxy, check your server logs for the HTTP status code when you send a message to a mobile device.
|
|
|
|
If you see 200 OK but no notification: The issue was definitely this Service Worker "sleep" issue.
|
|
|
|
If you see 401 or 403: HAProxy might be stripping the Authorization header from your backend's outbound request to Google.
|
|
|
|
If you see 400: Ensure your backend is sending priority: "high" in the FCM JSON.
|
|
|
|
Would you like me to provide a Python or Node.js snippet to test sending a "High Priority" message with the correct v1 API headers? |