Browsers Web Push notifications

Supporting browsers

Desktop

  • Chrome >= 50.0
  • FireFox >= 44.0
  • Safari >= 7

Mobile

  • Google Chrome (Android only)

WebPush is working only with HTTPs.

Chrome/FireFox examples

Register your app on console.developers.google.com, and enable Cloud Messaging service. After registration you must be have API KEY and APP ID.

  1. Add GCM_KEY with API KEY to the project settings:
GCM_KEY = 'AIza...'
  1. Create /static/manifest.json with the following lines:
{
  "gcm_sender_id": "``{APP ID}``",
  "name": "Web Push Notification",
  "short_name": "WebPush",
  "icons": [
    {
      "src": "/static/images/icon-192x192.png",
      "sizes": "192x192"
    }
  ],
  "start_url": "/?homescreen=1",
  "display": "standalone",
  "gcm_user_visible_only": true,
  "permissions": [
    "gcm"
  ]
}
  1. Add /static/manifest.json to meta on page
<head>
    <meta charset="UTF-8">
    ...
    <link rel="manifest" href="/static/manifest.json">
</head>
  1. Create service worker /static/js/service-worker.js file
self.addEventListener('push', function (event) {
    if (event.data) {
        var payload = event.data.json();

        return self.registration.showNotification(payload.title, {
            body: data.body,
            icon: '/static/images/icon-192x192.png',
            data: payload,
        });
    }
});

self.addEventListener('notificationclick', function (event) {
    event.notification.close();

    if (event.notification.data && event.notification.data.url) {
        event.waitUntil(clients.matchAll({
            type: "window"
        }).then(function () {
            if (clients.openWindow) {
                return clients.openWindow(event.notification.data.url);
            }
        }));
    }
});
  1. Register service worker to get permission and start background process
<head>
...
<script>
    function enableWebPush() {
        var is_chrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1;
        var is_ff = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
        if ((is_chrome || is_ff) && 'serviceWorker' in navigator) {
            navigator.serviceWorker.register('/service-worker.js').then(function () {
                navigator.serviceWorker.ready.then(function (serviceWorkerRegistration) {
                    serviceWorkerRegistration.pushManager.getSubscription().then(function (subscription) {
                        if (!subscription) {
                            serviceWorkerRegistration.pushManager.subscribe({userVisibleOnly: true}).then(function (subscription_info) {
                                var xhr = new XMLHttpRequest();
                                xhr.open("POST", "/dbmail/web-push/subscribe/", true);
                                xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
                                xhr.setRequestHeader("X-CSRFToken", "{{ request.META.CSRF_COOKIE }}");
                                xhr.send(JSON.stringify(subscription_info));

                                document.getElementById('subscription').innerHTML = JSON.stringify(subscription_info);
                            });
                        }
                        document.getElementById('subscription').innerHTML = JSON.stringify(subscription);
                    });

                });
            });
        }
    }
</script>
</head>
<body onload="enableWebPush()">
<div id="subscription"></div>
...
  1. Open page to setup notification
$ open '/Applications/Google Chrome.app' --args https://localhost:8000/web-push/
  1. Install pywebpush app
$ pip install 'pywebpush>=0.4.0'
  1. Add Server-side Endpoints to urls.py
urlpatterns += patterns(
    '', url(r'^dbmail/', include('dbmail.urls')),
)
  1. Add events receiver (subscribe/unsubscribe)
from dbmail import signals

def _web_push(**kwargs):
    # you must have your own function to store device token into db
    kwargs.pop('instance', None)
    kwargs.pop('sender', None)
    kwargs.pop('signal', None)
    print(kwargs)

signals.push_subscribe.connect(_web_push)
signals.push_unsubscribe.connect(_web_push)
  1. And finally you can send notification from backend
from dbmail.providers.google.browser import send

subscription_info = {
    'endpoint': 'https://android.googleapis.com/gcm/send/dVj-T5fXaEw:AP...',
     'keys': {
        'auth': 'X6Ek_...',
        'p256dh': 'BBo..'
     }
}

send(subscription_info, message="Hello, World!", event="Python", url="..")

Safari examples

  1. Register a Website Push ID (requires iOS developer license or Mac developer license)
  2. Download and import to KeyChain the push notification certificate
  3. Exporting Private Key .p12 from KeyChain
  4. Generate .pem certificate to send notification via APNs
$ openssl pkcs12 -in apns-cert.p12 -out apns-cert.pem -nodes -clcerts
  1. Check APNs connection
$ openssl s_client -connect gateway.push.apple.com:2195 -CAfile apns-cert.pem
  1. Create contents of the Push Package
PushPackage.raw/
  icon.iconset
    icon_128x128@2x.png
    icon_128x128.png
    icon_32x32@2x.png
    icon_32x32.png
    icon_16x16@2x.png
    icon_16x16.png
  website.json
  1. Contents of website.json
{
    "websiteName": "Localhost",
    "websitePushID": "web.ru.lpgenerator",
    "allowedDomains": ["https://localhost:8000"],
    "urlFormatString": "https://localhost:8000/%@",
    "authenticationToken": "19f8d7a6e9fb8a7f6d9330dabe",
    "webServiceURL": "https://localhost:8000/dbmail/safari"
}
  1. Create Push Package (https://github.com/connorlacombe/Safari-Push-Notifications)
$ cp `php createPushPackage.php` pushPackages.zip
  1. Add Server-side Endpoints to urls.py
urlpatterns += patterns(
    '', url(r'^dbmail/', include('dbmail.urls')),
)
  1. Add events receiver (subscribe/unsubscribe/errors)
from dbmail import signals

def _safari_web_push(**kwargs):
    # you must have your own function to store device token into db
    kwargs.pop('instance', None)
    kwargs.pop('sender', None)
    kwargs.pop('signal', None)
    print(kwargs)

signals.safari_subscribe.connect(_safari_web_push)
signals.safari_unsubscribe.connect(_safari_web_push)
signals.safari_error_log.connect(_safari_web_push)
  1. Add APNS_GW_HOST and APNS_CERT_FILE to the project settings
APNS_GW_HOST = 'api.push.apple.com'
APNS_GW_PORT = 443
APNS_CERT_FILE = 'apns-cert.pem'
APNS_KEY_FILE = None
  1. Register service worker to get permission and start background process
<head>
...
<script>
    function enableSafariWebPush() {
        var websitePushID = "web.dev.localhost";
        var webServiceUrl = "https://localhost:8000/web-push/";
        var dataToIdentifyUser = {UserId: "123123"};

        var checkRemotePermission = function (permissionData) {
            if (permissionData.permission === 'default') {
                window.safari.pushNotification.requestPermission(
                        webServiceUrl,
                        websitePushID,
                        dataToIdentifyUser,
                        checkRemotePermission
                );
            }
            else if (permissionData.permission === 'denied') {
                console.dir(arguments);
                alert("Access denied. Please, enable push notification from Safari settings.");
            }
            else if (permissionData.permission === 'granted') {
                document.getElementById('subscription').innerHTML = JSON.stringify(permissionData.deviceToken);
            }
        };

        if ('safari' in window && 'pushNotification' in window.safari) {
            checkRemotePermission(
                window.safari.pushNotification.permission(websitePushID)
            );
        }
    }
</script>
</head>
<body onload="enableSafariWebPush()">
<div id="subscription"></div>
...
  1. Open page to setup notification
$ open '/Applications/Safari.app' --args https://localhost:8000/web-push/
  1. And finally you can send notification from backend
from dbmail import send_db_push

send_db_push(
    'welcome',
    '62B63D730C84E363627B95879CF13723B890249A4BA03BAC08004574DF17D2DA',
    use_celery=False,
    alert={
        "title": "Python",
        "body": "Hello, World!",
        "action": "View"
    }, **{"url-args": ["https://localhost:8000/admin/"]}
)

Local demo

You can test by demo which found on repo or use samples

  1. Run server
$ cd demo
$ python manage.py runsslserver
  1. Copy path to SSL certs
export CERT_PATH=`python -c 'import os, sslserver; print(os.path.dirname(sslserver.__file__) + "/certs/development.crt")'`
echo $(CERT_PATH)
  1. Import certs to KeyChain

Linux

sudo apt-get install -y ca-certificates
sudo cp "$CERT_PATH" /usr/local/share/ca-certificates/
sudo update-ca-certificates

OS X

sudo security add-trusted-cert -d -r trustRoot -k "/Library/Keychains/System.keychain" "$CERT_PATH"

Windows (possible will not working)

certutil -addstore "Root" "$CERT_PATH"
    # or
certmgr.exe -add -all -c "$CERT_PATH" -s -r localMachine Root
  1. Open demo url
$ open /Applications/Safari.app --args https://localhost:8000/web-push/
    # or
$ open '/Applications/Google Chrome.app' --args https://localhost:8000/web-push/