Push Notifications Not Working in iOS App or Safari Browser Created via PWA Builder

Push Notification Issue in iOS PWA App

We’ve been trying to implement push notifications in our iOS app, which is a wrapper around a Vite PWA built using PWA Builder. Here's a detailed overview of the issues we’re facing:

Problem Summary

We originally had a working Vite PWA and used Firebase Cloud Messaging (FCM) for push notifications. When converting this PWA to an iOS app using PWA Builder:

  • The notification permission prompt did not behave as expected in Safari.
  • Even after requesting permission via a user gesture (e.g., button press), FCM token was not received.
  • On Safari (both Mac and Windows), permission sometimes works, but the token isn’t saved until Safari is closed and reopened.
  • In the iOS PWA app, the FCM token never gets retrieved.

We tried the same process on Chrome, and everything works flawlessly there.

What We’ve Tried

  • Wrapped the permission request and FCM token logic in a user gesture (e.g., button click), as recommended.
  • Confirmed our manifest includes all necessary fields (see below).
  • Tested across macOS, Windows, Safari (desktop), and the iOS app.

Manifest.json

{
  "name": "Periscopio",
  "short_name": "Periscopio",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#f2f2f2",
  "lang": "en",
  "scope": "/",
  "description": "Facilitates the collection of primary data for market research purposes.",
  "icons": [
    {
      "src": "/android-chrome-192x192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "any"
    },
    {
      "src": "/android-chrome-512x512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "any"
    },
    {
      "src": "/maskable_icon_x192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "maskable"
    },
    {
      "src": "/maskable_icon_x512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "maskable"
    }
  ],
  "edge_side_panel": {
    "preferred_width": 400
  },
  "display_override": [
    "window-controls-overlay",
    "standalone"
  ],
  "theme_color": "#08244c",
  "orientation": "portrait"
}

Core Logic (Plain TypeScript)

1. Request Notification Permission + FCM Token

async function handleRequestPermission(): Promise<string | null> {
  try {
    console.log("Requesting notification permission...");
    const permission = await Notification.requestPermission();
    console.log("Notification permission result:", permission);

    if (permission === "denied") {
      console.error("Notification permission was denied.");
      return null;
    }

    const token = await requestFCMToken();
    console.log("FCM Token:", token);

    if (token) {
      console.log("Notification setup successful.");
      return token;
    } else {
      console.error("Failed to retrieve FCM token.");
      return null;
    }
  } catch (error) {
    console.error("Error requesting FCM token:", error);
    return null;
  }
}

2. FCM Token Logic

async function requestFCMToken(): Promise<string | undefined> {
  try {
    let permission = Notification.permission;

    if (permission === "default") {
      console.log("Requesting notification permission...");
      permission = await Notification.requestPermission();
    }

    if (permission === "granted") {
      console.log("Notification permission granted.");

      const isSupportedBrowser = await isSupported();
      if (!isSupportedBrowser) {
        console.error("This browser does not support FCM.");
        return;
      }

      const registration = await navigator.serviceWorker.register("/firebase-messaging-sw.js");
      console.log("Service Worker registered:", registration);

      const token = await getToken(cloudMessaging, {
        vapidKey: "YOUR_PUBLIC_VAPID_KEY_HERE",
        serviceWorkerRegistration: registration,
      });

      if (token) {
        console.log("FCM Token:", token);
        localStorage.setItem("fcmToken", token);
        return token;
      } else {
        console.warn("No registration token available. Request permission to generate one.");
        return;
      }

    } else if (permission === "denied") {
      console.warn("Permission to notify was denied.");
      return;
    } else {
      console.warn("Notification permission not granted.");
      return;
    }
  } catch (error) {
    console.error("Error getting FCM token:", error);
    return;
  }
}

Request for Help

We’d really appreciate support from anyone who’s successfully implemented FCM push notifications in a Vite PWA wrapped as an iOS app using PWA Builder.

  • Is there something we’re missing about how iOS Safari handles push permissions in PWA mode?
  • Could there be an issue with the service worker or the manifest setup that causes the token not to register?
  • Any Safari-specific quirks to be aware of?

Thanks in advance!

Push Notifications Not Working in iOS App or Safari Browser Created via PWA Builder
 
 
Q