Skip to content

iOS Push Notifications - Technical Summary

The Problem

iOS Safari and iOS PWAs have specific requirements for push notifications that differ from Android/Desktop.

Key Facts

  1. No Web Push in Safari browser tabs: Push notifications do NOT work in regular Safari tabs on iOS - only in Home Screen installed PWAs.

  2. iOS 16.4+ Home Screen PWAs support Web Push: Apple added Web Push support in iOS/iPadOS 16.4 (March 2023) for PWAs installed to the Home Screen. This uses APNs under the hood but is accessed via standard Web Push APIs.

  3. FCM supports Safari (including iOS PWAs): Firebase Cloud Messaging's JS SDK now supports Safari, including iOS/iPadOS Home Screen PWAs. See Firebase Blog: FCM for Safari.

  4. No Apple Developer account required: Unlike native iOS apps, you do NOT need an Apple Developer Program membership ($99/year) for iOS/iPadOS web push. See WebKit Blog.


What We Fixed (Immediate Issue)

The service worker was unconditionally trying to initialize FCM, which could crash on unsupported environments:

javascript
// Original - could crash the service worker
firebase.initializeApp(firebaseConfig);
const messaging = firebase.messaging();

Current Fix (Too Blunt)

We currently skip FCM entirely on iOS:

javascript
const isIOS = /iPad|iPhone|iPod/.test(self.navigator?.userAgent || '');
if (!isIOS) {
  // Initialize FCM only on non-iOS
}

Problem: This also disables push for iOS 16.4+ Home Screen PWAs, which DO support push.

Instead of UA-detecting iOS, gate on FCM/Push API support:

In the page (client-side):

typescript
import { getMessaging, isSupported } from 'firebase/messaging';

// isSupported() is ASYNC - common footgun!
const messagingSupported = await isSupported();
if (messagingSupported) {
  const messaging = getMessaging(app);
  // ... proceed with FCM setup
}

In the service worker:

Important: WebKit recommends feature detection instead of browser/UA detection. Check for Push API, Notifications API, and Service Worker support rather than sniffing user agents.

javascript
// Feature detection (preferred over UA sniffing per WebKit guidance)
const hasPushAPI = 'PushManager' in self;
const hasNotificationAPI = 'Notification' in self;
const isStandalone = self.clients && 'matchAll' in self.clients;

// Log environment for debugging
console.log('[SW] Environment:', {
  hasPushAPI,
  hasNotificationAPI,
  userAgent: self.navigator?.userAgent?.substring(0, 50),
});

let messaging = null;

// Only initialize FCM if push capabilities exist
if (hasPushAPI && hasNotificationAPI) {
  try {
    importScripts('https://www.gstatic.com/firebasejs/10.12.0/firebase-app-compat.js');
    importScripts('https://www.gstatic.com/firebasejs/10.12.0/firebase-messaging-compat.js');

    firebase.initializeApp(firebaseConfig);
    messaging = firebase.messaging();

    messaging.onBackgroundMessage((payload) => {
      // Handle background messages
    });

    console.log('[SW] FCM initialized successfully');
  } catch (error) {
    console.warn('[SW] FCM initialization failed:', error);
    // Service worker continues to work for caching, etc.
  }
} else {
  console.log('[SW] Push not available - skipping FCM init');
}

Current Platform Support Status

PlatformPush NotificationsStatus
Android (Chrome)FCM✅ Working
Desktop (Chrome/Firefox/Edge)FCM✅ Working
macOS Safari (browser)FCM / Web Push✅ Working (since Safari 16.1)
iOS Safari (browser tab)Not supported❌ Not possible
iOS Home Screen PWA (16.4+)Web Push (FCM compatible)✅ Possible with proper setup

Note: On iOS/iPadOS, push requires an installed Home Screen web app. On macOS Safari, push works directly in the browser (since Safari 16.1). See WebKit Blog.


Requirements for iOS Home Screen PWA Push

What's Required

  1. iOS/iPadOS 16.4 or later on the user's device
  2. App installed to Home Screen (not running in Safari tab)
  3. Valid Web App Manifest with proper icons
  4. HTTPS (required for service workers and push)
  5. User gesture for Notification.requestPermission() - cannot be called on page load

What's NOT Required (for iOS/iPadOS Web Push specifically)

  • ❌ Apple Developer Program membership ($99/year)
  • ❌ APNs certificates or keys upload
  • ❌ Native app wrapper

Note: These exemptions apply to web push for PWAs. Native iOS apps using FCM still require Apple Developer membership and APNs configuration.

Client-Side Implementation

1. Ensure proper feature detection

typescript
// services/fcm/FCMService.ts
import { getMessaging, getToken, isSupported } from 'firebase/messaging';

export const requestFCMToken = async (): Promise<string | null> => {
  try {
    // isSupported() is ASYNC!
    const supported = await isSupported();
    if (!supported) {
      console.log('[FCM] Not supported in this environment');
      return null;
    }

    const messaging = getMessaging();
    const token = await getToken(messaging, { vapidKey: VAPID_KEY });
    return token;
  } catch (error) {
    console.error('[FCM] Error:', error);
    return null;
  }
};

2. Request permission from user gesture

typescript
// Must be called from button click, tap, etc.
async function enableNotifications() {
  const permission = await Notification.requestPermission();
  if (permission === 'granted') {
    const token = await requestFCMToken();
    if (token) {
      await saveTokenToFirestore(userId, token);
    }
  }
}

// In your component:
<Button onClick={enableNotifications}>Enable Notifications</Button>

3. Update manifest.json

json
{
  "name": "SeedStart",
  "short_name": "SeedStart",
  "display": "standalone",
  "start_url": "/",
  "scope": "/",
  "icons": [
    {
      "src": "/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

4. Add iOS-specific meta tags

html
<!-- index.html -->
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="SeedStart">
<link rel="apple-touch-icon" href="/icons/icon-192x192.png">

User Experience Considerations

Detect and prompt iOS users to install PWA

typescript
function shouldPromptIOSInstall(): boolean {
  const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
  const isStandalone = (window.navigator as any).standalone === true;
  const isPWA = window.matchMedia('(display-mode: standalone)').matches;

  // User is on iOS but NOT in Home Screen PWA mode
  return isIOS && !isStandalone && !isPWA;
}

function getPushSupportMessage(): string {
  if (shouldPromptIOSInstall()) {
    return 'Add this app to your Home Screen to enable push notifications';
  }
  return 'Enable notifications to stay updated';
}

Show install instructions for iOS users

For iOS users in Safari, show instructions:

  1. Tap the Share button
  2. Tap "Add to Home Screen"
  3. Open the app from Home Screen
  4. Enable notifications when prompted

Alternative Pathways

If Web Push doesn't meet requirements, consider:

Option 1: Native iOS App with Capacitor

AspectDetails
ProsFull push support, background refresh, badges, App Store presence
ConsRequires App Store submission, additional maintenance
ImplementationWrap existing React app with Capacitor

Option 2: In-App Notifications Only

AspectDetails
ProsWorks everywhere, no platform restrictions
ConsOnly works when app is open
Current stateAlready implemented via Firestore real-time listeners

Option 3: Email/SMS Fallback

AspectDetails
ProsReliable delivery to all users
ConsLess immediate, additional cost (SMS)
ImplementationDetect unsupported environments, offer email/SMS preferences

Action Items for SeedStart

  1. Update service worker to use try-catch instead of iOS UA detection
  2. Verify isSupported() is called correctly (it's async!)
  3. Add iOS install prompt UI for Safari users
  4. Test on physical iOS 16.4+ device installed to Home Screen
  5. Verify VAPID key is properly configured in deployed builds

Testing Requirements

  • Physical iOS device (iOS 16.4+) - Simulators don't fully support push
  • App must be installed to Home Screen
  • HTTPS required (production URL or ngrok for testing)
  • Test notification permission flow with user gesture

Current SeedStart SDK Setup

ComponentVersion / Type
Firebase JS SDK10.12.0
Service Workercompat (firebase-app-compat.js, firebase-messaging-compat.js)
Client-sidemodular (firebase/messaging)

Note: The compat SDK is used in the service worker because ES modules aren't fully supported in all service worker contexts. The modular SDK is used client-side for tree-shaking benefits.


References

Internal docs — access restricted via Cloudflare Zero Trust.