Web Push library for PHP
composer require minishlink/web-push
WebPush can be used to send notifications to endpoints which server delivers web push notifications as described in the Web Push protocol. As it is standardized, you don't have to worry about what server type it relies on.
Notifications with payloads are supported with this library on Firefox 46+ and Chrome 50+.
You'll need at least PHP 5.6, and PHP 7.1 is recommended for much better performance.
<?php
use Minishlink\WebPush\WebPush;
// array of notifications
$notifications = array(
array(
'endpoint' => 'https://updates.push.services.mozilla.com/push/abc...', // Firefox 43+
'payload' => 'hello !',
'userPublicKey' => 'BPcMbnWQL5GOYX/5LKZXT6sLmHiMsJSiEvIFvfcDvX7IZ9qqtq68onpTPEYmyxSQNiH7UD/98AUcQ12kBoxz/0s=', // base 64 encoded, should be 88 chars
'userAuthToken' => 'CxVX6QsVToEGEcjfYPqXQw==', // base 64 encoded, should be 24 chars
), array(
'endpoint' => 'https://android.googleapis.com/gcm/send/abcdef...', // Chrome
'payload' => null,
'userPublicKey' => null,
'userAuthToken' => null,
), array(
'endpoint' => 'https://example.com/other/endpoint/of/another/vendor/abcdef...',
'payload' => '{msg:"test"}',
'userPublicKey' => '(stringOf88Chars)',
'userAuthToken' => '(stringOf24Chars)',
),
);
$webPush = new WebPush();
// send multiple notifications with payload
foreach ($notifications as $notification) {
$webPush->sendNotification(
$notification['endpoint'],
$notification['payload'], // optional (defaults null)
$notification['userPublicKey'], // optional (defaults null)
$notification['userAuthToken'] // optional (defaults null)
);
}
$webPush->flush();
// send one notification and flush directly
$webPush->sendNotification(
$notifications[0]['endpoint'],
$notifications[0]['payload'], // optional (defaults null)
$notifications[0]['userPublicKey'], // optional (defaults null)
$notifications[0]['userAuthToken'], // optional (defaults null)
true // optional (defaults false)
);
There are several good examples and tutorials on the web:
- Mozilla's ServiceWorker Cookbooks (don't mind the
server.js
file: it should be replaced by your PHP server code with this library) - Google's introduction to push notifications (as of 03-20-2016, it doesn't mention notifications with payload)
- you may want to take a look at my own implementation: sw.js and app.js
Browsers need to verify your identity. A standard called VAPID can authenticate you for all browsers. You'll need to create and provide a public and private key for your server. These keys must be safely stored and should not change.
You can specify your authentication details when instantiating WebPush. The keys can be passed directly (recommended), or you can load a PEM file or its content:
<?php
use Minishlink\WebPush\WebPush;
$endpoint = 'https://android.googleapis.com/gcm/send/abcdef...'; // Chrome
$auth = array(
'GCM' => 'MY_GCM_API_KEY', // deprecated and optional, it's here only for compatibility reasons
'VAPID' => array(
'subject' => 'mailto:[email protected]', // can be a mailto: or your website address
'publicKey' => '~88 chars', // (recommended) uncompressed public key P-256 encoded in Base64-URL
'privateKey' => '~44 chars', // (recommended) in fact the secret multiplier of the private key encoded in Base64-URL
'pemFile' => 'path/to/pem', // if you have a PEM file and can link to it on your filesystem
'pem' => 'pemFileContent', // if you have a PEM file and want to hardcode its content
),
);
$webPush = new WebPush($auth);
$webPush->sendNotification(...);
In order to generate the uncompressed public and secret key, encoded in Base64, enter the following in your Linux bash:
$ openssl ecparam -genkey -name prime256v1 -out private_key.pem
$ openssl ec -in private_key.pem -pubout -outform DER|tail -c 65|base64|tr -d '=' |tr '/+' '_-' >> public_key.txt
$ openssl ec -in private_key.pem -outform DER|tail -c +8|head -c 32|base64|tr -d '=' |tr '/+' '_-' >> private_key.txt
If you can't access a Linux bash, you can print the output of the createVapidKeys
function:
var_dump(VAPID::createVapidKeys()); // store the keys afterwards
On the client-side, don't forget to subscribe with the VAPID public key as the applicationServerKey
: (urlBase64ToUint8Array
source here)
serviceWorkerRegistration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(vapidPublicKey)
})
Each notification can have a specific Time To Live, urgency, and topic.
You can change the default options with setDefaultOptions()
or in the constructor:
<?php
use Minishlink\WebPush\WebPush;
$defaultOptions = array(
'TTL' => 300, // defaults to 4 weeks
'urgency' => 'normal', // protocol defaults to "normal"
'topic' => 'new_event', // not defined by default
);
// for every notifications
$webPush = new WebPush(array(), $defaultOptions);
$webPush->setDefaultOptions($defaultOptions);
// or for one notification
$webPush->sendNotification($endpoint, $payload, $userPublicKey, $userAuthToken, $flush, array('TTL' => 5000));
Time To Live (TTL, in seconds) is how long a push message is retained by the push service (eg. Mozilla) in case the user browser is not yet accessible (eg. is not connected). You may want to use a very long time for important notifications. The default TTL is 4 weeks. However, if you send multiple nonessential notifications, set a TTL of 0: the push notification will be delivered only if the user is currently connected. For other cases, you should use a minimum of one day if your users have multiple time zones, and if they don't several hours will suffice.
Urgency can be either "very-low", "low", "normal", or "high". If the browser vendor has implemented this feature, it will save battery life on mobile devices (cf. protocol).
Similar to the old collapse_key
on legacy GCM servers, this string will make the vendor show to the user only the last notification of this topic (cf. protocol).
You can see what the browser vendor's server sends back in case it encountered an error (push subscription expiration, wrong parameters...).
sendNotification()
(with flush as true) and flush()
returns true if there were no errors. If there are errors it returns an array like the following.
The expired
key can be useful to clean your database of expired endpoints.
$res = array(
array( // first notification
'success' => false,
'endpoint' => $theEndpointToDeleteInYourDatabaseIfExpired
'statusCode' => $responseStatusCode,
'headers' => $responseHeaders,
'content' => $responseContent, // you may have more infos here
'expired' => $isTheEndpointWrongOrExpired,
),
array( // second notification
...
), ...
);
Payloads are encrypted by the library. The maximum payload length is theoretically 4078 bytes (or ASCII characters). For compatibility reasons though, your payload should be less than 3052 bytes long.
The library pads the payload by default. This is more secure but it decreases performance for both your server and your user's device.
When you encrypt a string of a certain length, the resulting string will always have the same length, no matter how many times you encrypt the initial string. This can make attackers guess the content of the payload. In order to circumvent this, this library adds some null padding to the initial payload, so that all the input of the encryption process will have the same length. This way, all the output of the encryption process will also have the same length and attackers won't be able to guess the content of your payload.
Encrypting more bytes takes more runtime on your server, and also slows down the user's device with decryption. Moreover, sending and receiving the packet will take more time. It's also not very friendly with users who have limited data plans.
You can customize automatic padding in order to better fit your needs.
Here are some ideas of settings:
- (default)
Encryption::MAX_COMPATIBILITY_PAYLOAD_LENGTH
(3052 bytes) for compatibility purposes with Firefox for Android Encryption::MAX_PAYLOAD_LENGTH
(4078 bytes) for maximum securityfalse
for maximum performance- If you know your payloads will not exceed
X
bytes, then set it toX
for the best balance between security and performance.
<?php
use Minishlink\WebPush\WebPush;
$webPush = new WebPush();
$webPush->setAutomaticPadding(false); // disable automatic padding
$webPush->setAutomaticPadding(512); // enable automatic padding to 512 bytes (you should make sure that your payload is less than 512 bytes, or else an attacker could guess the content)
$webPush->setAutomaticPadding(true); // enable automatic padding to default maximum compatibility length
By default, WebPush will use MultiCurl
, allowing to send multiple notifications in parallel.
You can change the client to any client extending \Buzz\Client\AbstractClient
.
Timeout is configurable in the constructor.
<?php
use Minishlink\WebPush\WebPush;
$client = new \Buzz\Client\Curl();
$timeout = 20; // seconds
$webPush = new WebPush(array(), array(), $timeout, $client);
You have access to the inner browser if you want to configure it further.
<?php
use Minishlink\WebPush\WebPush;
$webPush = new WebPush();
/** @var $browser \Buzz\Browser */
$browser = $webPush->getBrowser();
The following are available:
- Symfony: MinishlinkWebPushBundle
- Laravel: laravel-notification-channels/webpush
Feel free to add your own!
Not until the Push API spec is finished.
Payload is encrypted according to the Message Encryption for Web Push standard, using the user public key and authentication secret that you can get by following the Web Push API specification.
Internally, WebPush uses the phpecc Elliptic Curve Cryptography library to create
local public and private keys and compute the shared secret.
Then, if you have a PHP >= 7.1, WebPush uses openssl
in order to encrypt the payload with the encryption key.
Otherwise, if you have PHP < 7.1, it uses Spomky-Labs/php-aes-gcm, which is slower.
Your installation lacks some certificates.
- Download cacert.pem.
- Edit your
php.ini
: after[curl]
, typecurl.cainfo = /path/to/cacert.pem
.
You can also force using a client without peer verification.
See issue #58.
WebPush is for web apps. You need something like RMSPushNotificationsBundle (Symfony).
This library was inspired by the Node.js marco-c/web-push library.
See CONTRIBUTING.md.