Push Notification Issues | Blue Frog Docs

Push Notification Issues

Troubleshoot web push notification permissions, delivery, and display problems

Push Notification Issues

What This Means

Web push notifications allow you to re-engage users even when they're not on your site. Common issues:

  • Permission prompts not appearing
  • Notifications not being delivered
  • Notifications not displaying correctly
  • Users not receiving notifications after granting permission

Prerequisites

Push notifications require:

  1. HTTPS (mandatory)
  2. Service worker registered
  3. User permission granted
  4. Push subscription created
  5. Backend push service configured

How to Diagnose

1. Check Permission Status

// Check current permission
console.log('Notification permission:', Notification.permission);
// 'default' - not asked yet
// 'granted' - permission given
// 'denied' - permission blocked

2. Service Worker Status

DevTools > Application > Service Workers:

  • Verify SW is active
  • Check for push event handler

3. Push Subscription

// Check if push is subscribed
navigator.serviceWorker.ready.then(registration => {
  registration.pushManager.getSubscription().then(subscription => {
    console.log('Push subscription:', subscription);
  });
});

4. Browser Support

// Check push support
const supported = 'PushManager' in window;
console.log('Push supported:', supported);

General Fixes

Request Permission Properly

// Request permission with user interaction
async function requestNotificationPermission() {
  // Check if already granted/denied
  if (Notification.permission === 'granted') {
    return true;
  }

  if (Notification.permission === 'denied') {
    console.log('Notifications blocked by user');
    return false;
  }

  // Request permission
  const permission = await Notification.requestPermission();
  return permission === 'granted';
}

// Trigger on user action (required in modern browsers)
document.getElementById('enable-notifications').addEventListener('click', async () => {
  const granted = await requestNotificationPermission();
  if (granted) {
    await subscribeToPush();
    showSuccessMessage();
  }
});

Subscribe to Push

// Subscribe user to push notifications
async function subscribeToPush() {
  const registration = await navigator.serviceWorker.ready;

  // Get VAPID public key from your server
  const vapidPublicKey = 'YOUR_VAPID_PUBLIC_KEY';
  const convertedKey = urlBase64ToUint8Array(vapidPublicKey);

  const subscription = await registration.pushManager.subscribe({
    userVisibleOnly: true, // Required: must show notification
    applicationServerKey: convertedKey
  });

  // Send subscription to your server
  await fetch('/api/push/subscribe', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(subscription)
  });

  return subscription;
}

// Helper function
function urlBase64ToUint8Array(base64String) {
  const padding = '='.repeat((4 - base64String.length % 4) % 4);
  const base64 = (base64String + padding)
    .replace(/-/g, '+')
    .replace(/_/g, '/');
  const rawData = window.atob(base64);
  const outputArray = new Uint8Array(rawData.length);
  for (let i = 0; i < rawData.length; ++i) {
    outputArray[i] = rawData.charCodeAt(i);
  }
  return outputArray;
}

Service Worker Push Handler

// sw.js - Handle push events
self.addEventListener('push', (event) => {
  console.log('Push received:', event);

  let data = { title: 'Notification', body: 'You have a new message' };

  if (event.data) {
    try {
      data = event.data.json();
    } catch (e) {
      data.body = event.data.text();
    }
  }

  const options = {
    body: data.body,
    icon: '/icons/notification-icon.png',
    badge: '/icons/badge-icon.png',
    image: data.image,
    vibrate: [100, 50, 100],
    data: {
      url: data.url || '/',
      dateOfArrival: Date.now()
    },
    actions: [
      { action: 'open', title: 'Open' },
      { action: 'dismiss', title: 'Dismiss' }
    ]
  };

  event.waitUntil(
    self.registration.showNotification(data.title, options)
  );
});

// Handle notification click
self.addEventListener('notificationclick', (event) => {
  event.notification.close();

  if (event.action === 'dismiss') {
    return;
  }

  const url = event.notification.data?.url || '/';

  event.waitUntil(
    clients.matchAll({ type: 'window' }).then(clientList => {
      // Focus existing tab if open
      for (const client of clientList) {
        if (client.url === url && 'focus' in client) {
          return client.focus();
        }
      }
      // Open new tab
      return clients.openWindow(url);
    })
  );
});

Backend Push Service (Node.js)

// server.js
const webpush = require('web-push');

// Generate VAPID keys (run once)
// const vapidKeys = webpush.generateVAPIDKeys();

webpush.setVapidDetails(
  'mailto:admin@example.com',
  process.env.VAPID_PUBLIC_KEY,
  process.env.VAPID_PRIVATE_KEY
);

async function sendPushNotification(subscription, payload) {
  try {
    await webpush.sendNotification(
      subscription,
      JSON.stringify(payload)
    );
    console.log('Push sent successfully');
  } catch (error) {
    if (error.statusCode === 410) {
      // Subscription expired, remove from database
      await removeSubscription(subscription.endpoint);
    }
    throw error;
  }
}

// Example: Send notification
app.post('/api/push/send', async (req, res) => {
  const { userId, title, body, url } = req.body;
  const subscriptions = await getSubscriptionsForUser(userId);

  await Promise.all(
    subscriptions.map(sub =>
      sendPushNotification(sub, { title, body, url })
    )
  );

  res.json({ success: true });
});

Permission UI Best Practices

// Don't immediately ask for permission
// Show value proposition first

function showNotificationPromo() {
  const promo = document.getElementById('notification-promo');
  promo.innerHTML = `
    <h3>Stay Updated</h3>
    <p>Get notified about price drops and new features</p>
    <button id="enable-push">Enable Notifications</button>
    <button id="maybe-later">Maybe Later</button>
  `;
  promo.style.display = 'block';

  document.getElementById('enable-push').onclick = async () => {
    promo.style.display = 'none';
    const granted = await requestNotificationPermission();
    if (granted) {
      await subscribeToPush();
    }
  };

  document.getElementById('maybe-later').onclick = () => {
    promo.style.display = 'none';
    localStorage.setItem('push-promo-dismissed', Date.now());
  };
}

Verification

  1. Permission granted: Notification.permission === 'granted'
  2. Subscription exists in PushManager
  3. Test notification from backend arrives
  4. Clicking notification opens correct URL

Common Mistakes

Mistake Fix
Requesting permission on page load Wait for user interaction
Missing userVisibleOnly: true Required in subscription options
Not handling expired subscriptions Check for 410 status
No notification click handler Add notificationclick listener

Further Reading

// SYS.FOOTER