Continuously Push Events From Web Server to Browser
The Web Push API lets you send push notifications to web browsers and APIs. While most of the logic happens in the browser, you still need a server-side component to generate your notifications. Here's how to implement a Web Push backend using PHP.
Prerequisites
For the purposes of this tutorial, we'll assume you're familiar with the basics of creating HTTP APIs in PHP. You'll need to expose a few public endpoints using your web framework. These will be called by your in-browser JavaScript to register and unregister devices.
This article won't touch on the browser-side code or how it works. You'll need to put together a service worker that responds to incoming push events and displays a notification to the user.
At a high-level, the Web Push flow looks like this:
- A push subscription is registered in the browser. The browser issues a unique endpoint URL to your JavaScript.
- Your JavaScript sends the subscription data to your server and identifies the user it applies to.
- When your backend needs to send a push notification, create a payload and send it to the endpoint URL reported as part of the subscription data.
- The user's browser will receive the payload via the vendor's notification delivery platform. Your JavaScript service worker handles the consequent event and uses the browser's notification API to alert the user.
Here's how to implement the server-side aspects of steps 1 to 3.
Getting Setup
We'll use the web-push
Packagist package by minishlink. This abstracts the interactions with each browser notification platform so you don't have to manually distinguish between endpoint types.
Add the package to your project using Composer:
composer require minishlink/web-push
To use the latest version, you need PHP 7.2 or greater with the gmp
, mbstring
, curl
, and openssl
extensions. If you must use an older PHP release, lock the package to an earlier version to maintain compatibility.
The library exposes a core WebPush
class with methods that let you send notifications individually or as batches. Subscriptions are represented by instances of the Subscription
class.
Providing VAPID Keys
Trust in the standards-compliant Web Push ecosystem is enforced through the use of VAPID keys. Your server needs a VAPID key pair so it can authenticate itself to browsers. The public key should be exposed via an API endpoint.
You can generate a VAPID key set using the web-push
package:
use MinishlinkWebPushVAPID; $keyset = VAPID:: createVapidKeys ( ) ; // public key - this needs to be accessible via an API endpoint echo $keyset [ "publicKey" ] ; // private key - never expose this! echo $keyset [ "privateKey" ] ; file_put_contents ( "vapid.json" , json_encode ( $keyset ) ) ;
Generate keys for your system and store them to a persistent location. Add an API endpoint so your client-side JavaScript can retrieve the public key. This will be used to setup the browser's push subscription. The user's device will accept incoming push events if they've been signed using the corresponding VAPID private key.
Registering Push Subscriptions
The next step in the sequence is receiving push subscription requests from your clients. Once the browser's confirmed a new push subscription, your JavaScript should send the subscription's endpoint URL and associated authentication keys to your server. Store these details alongside the user's ID so you can retrieve all the push-enrolled devices linked to the user later on.
We're omitting code samples for this step as the implementation depends on your data storage layer and the values your JavaScript sends up. Typically, this will be a JSON representation of a PushSubscription
object. You need a simple set of database-backed CRUD API endpoints to create a subscription, replace an existing one, and request a deletion when the user unsubscribes.
Preparing Subscriptions
Once a client's successfully registered you can start sending notifications using the web-push
library. Begin by creating an instance of the WebPush
class:
use MinishlinkWebPushWebPush; $webPush = new WebPush( [ "VAPID" => [ "subject" => "https://example.com" , "publicKey" => "VAPID_Public_Key_Here" , "privateKey" => "VAPID_Private_Key_Here" ] ] ) ;
You can reuse one WebPush
instance each time you send a notification. The library needs to be configured with the VAPID key set you generated earlier. Keys should be encoded as Base64 but this is handled for you if you create them with the library.
The VAPID subject
is used to identify your server and its contact details. You can supply a website URL or a mailto:
email address link.
Next you need to retrieve the push subscription you'll be sending to. Use your data access system to lookup the push endpoint URLs associated with the user you want to send to. Convert each subscription to a Subscription
instance:
use MinishlinkWebPushSubscription; // Get user's push data... // SELECT * FROM push_subscriptions WHERE user_id = 123456 $subscription = Subscription:: create ( [ "endpoint" => "https://fcm.google.com/..." , "contentEncoding" => "aesgcm" , "authToken" => "<auth token from JavaScript PushSubscription object>" "keys" => [ "auth" => "<auth token from JavaScript PushSubscription object>" , "p256dh" => "<p256dh token from JavaScript PushSubscription object>" ] ] ) ;
The auth
property of the PushSubscription
is repeated twice to cope with two different versions of the spec used by browser services. The P256DH property is another public key which should be supplied when set on the subscription.
The web-push
library is compatible with Chrome and Firefox push endpoints. It'll also work with any other Web Push implementation that meets the current standard.
Sending a Notification
Now combine your WebPush
and Subscription
instances to send a notification:
$result = $webPush -> sendOneNotification ( $subscription , json_encode ( [ "message" => "Demo notification" , "foo" => "bar" ] ) ) ;
Calling sendOneNotification()
provides immediate delivery for a single notification. The payload in this case is a JSON-encoded array with two properties. It's up to you what data you send and the format you use – your JavaScript client receives it as-is and can interpret it as necessary.
Sending a notification returns a result class that lets you check whether the operation succeeded:
if ( $result -> isSuccess ( ) ) { // all good } else { // something went wrong error_log ( $result -> getReason ( ) ) ; // provides raw HTTP response data error_log ( $result -> getResponse ( ) ) ; }
You can take action to retry or cancel the delivery if an error occurs.
Notification subscriptions can also expire. Call the isSubscriptionExpired()
method on a result class to determine whether this is the reason for the failure. You could delete the subscription from your database in this scenario, ensuring you don't send anything else to a dead endpoint.
Batching Notifications
Notifications can be batched together for delivery with one method call:
$webPush -> queueNotification ( $subscription , [ "msg" => "first" ] ) ; $webPush -> queueNotification ( $subscription , [ "msg" => "second" ] ) ; foreach ( $webPush -> flush ( ) as $i => $result ) { echo ( "Notification $i was " . ( $result -> isSuccess ( ) ? "sent" : "not sent" ) ) ; }
This is useful when you know you'll be sending a large number of notifications in a short timeframe. Queue all your payloads and let web-push
deliver them in the optimal way.
You can limit the number of notifications sent in a single flush()
by passing an integer to the method:
$webPush -> flush ( 100 ) ; // send 100 messages
The default value is 1000
.
Notification Options
sendOneNotification()
and queueNotification()
accept the following options as a third array argument:
-
TTL
– Controls how long the browser's notification platform will hold onto the notification if it can't be passed to the user's device immediately. If the user's device is offline, platforms default to trying to deliver it for the next four weeks. If you're sending a notification that won't be relevant next week, adjust the TTL accordingly so the user doesn't see outdated content. -
urgency
– Acceptsnormal
,low
orvery-low
as values. Some platforms may use this to adjust the frequency of notification delivery. Devices that enter a battery saving mode may suspend delivery of non-urgent notifications. -
batchSize
– This has the same effect as the argument toflush()
described above.
You can configure default option values using the second argument to the WebPush
constructor:
$webPush = new WebPush( [ "VAPID" => [ ... ] ] , [ "TTL" => 3600 ] ) ;
Summary
The web-push
library makes it easy to send Web Push notifications using PHP. You get an abstraction layer atop the various browser platforms that supports batching, error handling, and all Web Push features.
The Web Push mechanism is an unusual browser system as it's reliant on remote server-side components you supply yourself. This can make it seem opaque and technical. In practice, creating a simple PHP backend is quick and easy; the frontend implementation is usually the more time-consuming aspect, particularly if you're not already using service worker features.
Source: https://www.howtogeek.com/devops/how-to-send-web-push-notifications-with-php/