Notes from attempting to setup web push notifications
Files included
service-worker.js
- Service worker file, it listens for push events and displays notificationsexample.html
- Main file, it will immediately ask for permission to send notifications and log the subscription object to the console. You might have to refresh after giving permissions to see the subscription object.
Steps to setup
Browser setup
- run
npx serve
- Open the browser and go to
http://localhost:3000
- Open the console and you should see the subscription object
Server setup
(Or you can use web-push package)
Generate VAPID keys
- Run
openssl ecparam -name prime256v1 -genkey -noout -out private.pem
- You might also want to generate a PKCS8 key for the private key
- Run
openssl pkcs8 -topk8 -nocrypt -in private.pem -out private.pkcs8.pem
- Run
- You might also want to generate a PKCS8 key for the private key
- Run
openssl ec -in private.pem -pubout -out public.pem
Now you can your favourite http request library to send a POST request to the url returned by the
pushManager.subscribe
method.
When you do you should include the following header (and no body!):
TTL: 60
Authorization: `Bearer ${jwtToken}`
Crypto-Key: `p256ecdsa=${publicKey}`
Topic: `An optional topic`
Urgency: `An optional urgency`
The jwtToken
is a JWT token that you can generate using the private key you generated earlier. The payload should
include the aud
field which should be the url returned by the pushManager.subscribe
method.
For example:
const {default: fs} = await import("fs");
const {default: crypto} = await import("crypto");
const {default: jwt} = await import("jsonwebtoken");
const privateKeyBuffer = fs.readFileSync('private.pem');
const privateKey = crypto.createPrivateKey(privateKeyBuffer);
const payload = {
aud: 'https://fcm.googleapis.com',
sub: 'mailto:example@example.com',
};
const jwtToken = jwt.sign(payload, privateKey, {algorithm: 'ES256', expiresIn: '1h'});
console.log(jwtToken);
It has to use the ES256 algorithm and the exp
field should be set to a timestamp in the future.
The publicKey
is the public key you generated earlier however it should be encoded as JWK and then base64 url encoded.
You can use the following code to do that:
const {default: fs} = await import("fs");
const {default: crypto} = await import("crypto");
const publicKeyBuffer = fs.readFileSync('public.pem');
const publicKey = crypto.createPublicKey(publicKeyBuffer);
const jwk = {
kty: 'EC',
crv: 'P-256',
x: publicKey.export({format: 'jwk'}).x,
y: publicKey.export({format: 'jwk'}).y,
};
const base64UrlEncode = (str) => {
return str.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
};
const base64UrlEncodedPublicKey = base64UrlEncode(publicKey.export({format: 'jwk'}).x) + '.' + base64UrlEncode(publicKey.export({format: 'jwk'}).y);
console.log(base64UrlEncodedPublicKey);
This should be enough to get you started with web push notifications.
Notes
-
The
pushManager.subscribe
method may require depending on the browser auserVisibleOnly
field orapplicationServerKey
field. TheuserVisibleOnly
field should be set totrue
and theapplicationServerKey
field should be set to the public key you generated earlier. -
For sending a payload additional changes need to happen, you can't just send a payload with the request.
- For it to work you will have to update the headers:
- Encoding the payload, it's complex and I haven't done it yet but you can look at the Mozilla Blog for more information.
- The
Crypto-Key
header should be set to 3 values:p256ecdsa=${publicKey}
- The public key you generated earlierdh=${publicHalfKey}
- The first half of the shared secret when the payload is encodedkeyid=p256dh
- The key id
- The
Encryption
header should be set to 2 values:salt=${salt}
- The salt used to generate the shared secretkeyid=p256dh
- The key id
- The
Content-Encoding
header should be set toaesgcm
- For it to work you will have to update the headers:
For more information you can check the useful links below.