/webpush-ios-example

WebPush for IOS demo and code: VAPID, Home Screen, gcm_sender_id, serviceworker, iPhone, iPad

Primary LanguageJavaScript

WebPush for iOS sample code and demo site

WebPush - is browser technology that allows site developer send notifications from backend to subscribers. Now at iPhone and iPad!

Demo https://andreinwald.github.io/webpush-ios-example

Iphone prompt example



TL;DR iOS WebPush specifics

  • user required to add your site to Home Screen of his iPhone/iPad
  • manifest.json is required to set display: standalone
  • you don't need to register at apple.com to receive something like GCM_SENDER_ID
  • instead, you need to generate VAPID (pair of public and private keys)

webpush_safari_ios_wwdc_2022.mp4

More info

Basic WebPush subscription code

Example of basic subscription code, that works in Google Chrome and Firefox.

<html>
<body>
<button onclick="subscribe()">Subscribe</button>

<script>
    // You can use serviceworker.js from this repo
    // It should contain listeners for push and notificationclick events
    navigator.serviceWorker.register('/serviceworker.js');

    function subscribe() {
        navigator.serviceWorker.ready.then(async function (serviceWorker) {
            if (!serviceWorker.pushManager) {
                // Maybe iOS on iPhone or iPad - should ask for adding to Home Screen
                alert('pushManager is not enabled');
                return;
            }            
            let subscriptionOptions = {
                userVisibleOnly: true,
                applicationServerKey: '____INSERT_VAPID_PUBLIC_KEY_HERE_____'
            };
            let subscription = await serviceWorker.pushManager.subscribe(subscriptionOptions);
            console.log('Subscription token:', subscription.toJSON());
        });
    }
</script>
</body>
</html>

You can run it locally by creating index.html and serviceworker.js files with a simple html server:

npx http-server

Generating VAPID key

In example above you need to replace VAPID_PUBLIC_KEY to your own.

You don't need to register at apple.com to receive something like GCM_SENDER_ID, just generate VAPID key

  • All subscription tokens associated with that key, so if you change it - you may lose old subscribers
  • You MUST need generate your own VAPID keys!
  • Newer share your PRIVATE_VAPID_KEY. It should be stored in a safe storage

Run these commands in your terminal:
openssl ecparam -genkey -name prime256v1 -out vapid_private.pem
openssl ec -in vapid_private.pem -pubout -outform DER|tail -c 65|base64|tr -d '=' |tr '/+' '_-' >> vapid_public.txt
echo 'VAPID public:' ; cat vapid_public.txt
# Example: BCa4t85iJ0AYDG__5r48lo-HNdpi_29458t8R6zRTsF1OUi1QyvCRd_tOyXVkqH3nzsZdMzSRLlKJTXQyN7QI4s

openssl ec -in vapid_private.pem -outform DER|tail -c +8|head -c 32|base64|tr -d '=' |tr '/+' '_-' >> vapid_private.txt
echo 'VAPID private:' ; cat vapid_private.txt
# Example: Mz8GQ4Fx16dI-iEUZTp6KTLVsUrcIOfJmWWXlKb0Qgo

Then use it:

const VAPID_PUBLIC_KEY = 'BAwUJxIa7mJZMqu78Tfy2...';
let subscriptionOptions = {
    userVisibleOnly: true,
    applicationServerKey: VAPID_PUBLIC_KEY
};

See full example in frontend.js

Installing PWA on iOS by adding to Home Screen

WebPush is Progressive Web App(PWA) feature so you need to ask user to enable PWA mode first.
On iOs devices it can be made with button "Add to Home Screen" in browser.

Require adding to Home Screen

Also don't forget to set display mode in manifest.json!
Manifest.json required to set "display: standalone", that called by Apple "Home Screen web app"
PushManager will appear in serviceWorker object only after adding site to Home screen at your iPhone.

<html>
<head>
    <link rel="manifest" href="manifest.json"/>
</head>
# ...

manifest.json:

{
  "name": "WebPush iOS example",
  "display": "standalone"
}

Next you can check that PWA is installed by this code:

if (window.navigator.standalone) {
    // now we can ask for subscription by pushManager.subscribe()
} else {
    // we should ask user to add our site home screen
}

Subscription and saving token

(Displaying Prompt with permission)
After registering Service Worker and providing VAPID_PUBLIC_KEY you can request user to subscribe.
Best practice will be to ask user about subscription in html popup first.
Then you can call:

let subscription = await pushManager.subscribe(subscriptionOptions);

See full example in frontend.js

After receiving subscription you're going to send it to backend via fetch or something.
You will need that for sending push message from backend
Examples how subscription token looks:

For desktop and mobile Safari:

{
  "endpoint": "https://web.push.apple.com/QGuQyavXutnMH...",
  "keys": {
    "p256dh": "BF6-hyiRMKKKiiH...",
    "auth": "lM6vKjBJ1UX..."
  }
}

And this will be for Google Chrome (FCM):

{
  "endpoint": "https://fcm.googleapis.com/fcm/send/eEsw5ryoAzo:APA91bHC...",
  "expirationTime": null,
  "keys": {
    "p256dh": "BKDBx7wkagZSlDsaT...",
    "auth": "zKa3taDY2VWoM4..."
  }
}

Service worker

For receiving, displaying push message and processing click on it - you need to use these service worker methods:

self.addEventListener('push', (event) => {
    let pushData = event.data.json();
    // ...
    self.registration.showNotification(pushData.title, pushData)
});

self.addEventListener('notificationclick', function (event) {
    clients.openWindow(event.notification.data.url)
    // ...
    // You can send fetch request to your analytics API fact that push was clicked
});

See full example in serviceworker.js

Sending push message

You can send WebPush from frontend:

const title = "Push title";
const options = {
    body: "Additional text with some description",
    icon: "https://andreinwald.github.io/webpush-ios-example/images/favicon.png",
    image: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/68/Orange_tabby_cat_sitting_on_fallen_leaves-Hisashi-01A.jpg/1920px-Orange_tabby_cat_sitting_on_fallen_leaves-Hisashi-01A.jpg",
    data: {
        "url": "https://andreinwald.github.io/webpush-ios-example/success.html",
        "message_id": "your_internal_unique_message_id_for_tracking"
    },
};
navigator.serviceWorker.ready.then(function (serviceWorker) {
    serviceWorker.showNotification(title, options);
});

Or from Backend, for example by using Node.js web-push library
See example in backend-sender.js

Resources:

Keywords:

  • ServiceWorkerRegistration.pushManager is undefined
  • applicationServerKey is not properly base64url-encoded
  • undefined is not an object pushManager.subscribe
  • User denied push permission