Push notification subscription in iOS PWA silently failing because of SW not controlling the app

Hi :waving_hand:

We ran into a weird behavior (or probably a bug…) with setting up push notifications in the iOS PWA. The UI acts as if the push notification subscription was set up, while in the background nothing happened yet and the user will never receive any push notification.

(tl;dr I think I know how to fix it and can provide a PR)

Steps to reproduce :footprints:

The problem is about enabling push notifications immediately after installing the PWA on iOS.

  1. Open your Discourse instance in mobile Safari on iOS
  2. Open up the share menu and choose “Add to home screen”
  3. Open the new PWA on your home screen
  4. You see a banner asking you whether you want enable push notification
  5. Click on the “enable notifications” link
    1. You see a system dialog “xyz” would like to send you notifications" - accept it.
  6. Navigating to your notification preferences, you’ll see live notifications not enabled.
    1. Checking on the server, you also won’t find a new PushSubscription record for the user.

Expected behavior :books:

After step 5, you’d already receive a push notification that notifications are enabled. There should also be a new PushSubscription record for your current user in the database and their setting to receive live notifications in the PWA should be enabled.

Problem :bug:

On a freshly launched PWA, the service worker has installed and activated, but does not yet control the page. With that, the check in lib/push-notifications.js, isPushNotificationsSupported(), will still return false, since navigation.serviceworker.controller returns null. These are the important two lines:

navigator.serviceWorker.controller &&
navigator.serviceWorker.controller.state === "activated"

With that, the UI suggests to the user that every is fine with their setup, but they’ll never receive any push notification.

On the first load, the PWA is uncontrolled and the call to subscribe() is never made, even though the user granted permission… :bug:

Users can fix the problem for themselves, if they know how:

  1. Fully close the PWA (swipe it away…)
  2. Open the PWA again
  3. Go to their notification preferences
  4. Enable live notifications again
  5. -> This time, there should be a new PushSubscription record and the notifications are set up.

Potential Fix :adhesive_bandage:

I worked around this problem by registering another service worker that calls skipWaiting() and clients.claim() upon install/activate. Doing so it immediately takes over control over the PWA and the subscription goes through and the respective PushSubscription record is created.

I deployed this as a “hotfix” as part of one of my custom plugins, so that I can fix the situation for my users without diving into a fork of Discourse core…

It’s just two changes:

# plugin.rb
register_service_worker "push-notification-setup.js"
// assets/push-notification-setup.js
self.addEventListener("install", function () {
  self.skipWaiting();
});
		
		
self.addEventListener("activate", function (event) {	
  event.waitUntil(self.clients.claim());
});

I think this fix belongs into Discourse core though. With that I’m doing, the plugin is taking over unnecessary control over the PWA. I can provide a PR that adjusts the checks in Discourse core accordingly so that subscribing actually works. I’ll like it here, but feel free to already voice your opinions on the topic.

1 Like

This is the PR with a fix to Discourse core → FIX: Allow push notification subscriptions on first launch of iOS PWA · Pull Request #39885 · discourse/discourse

It fixes the problem described here, with the subscription silently failing and not reaching the server. But what’s still missing then, to play absolutely save, is that the first received push notification will not be routable inside the app without the service worker controlling the app, so without the user restarting the app.

This is something I could also add on top of my PR. But I’m not 100% sure if the fix is what’s desired.

1 Like