121 lines
4.7 KiB
Markdown
Raw Normal View History

2025-02-01 21:22:40 +00:00
# Notes from attempting to setup web push notifications
## Files included
- `service-worker.js` - Service worker file, it listens for push events and displays notifications
- `example.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
1. run `npx serve`
2. Open the browser and go to `http://localhost:3000`
3. Open the console and you should see the subscription object
### Server setup
(Or you can use [web-push](https://www.npmjs.com/package/web-push) package)
#### Generate VAPID keys
1. Run `openssl ecparam -name prime256v1 -genkey -noout -out private.pem`
1. 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`
2. 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!):
```http
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:
```javascript
const {default: fs} = await import("fs");
const {default: crypto} = await import("crypto");
const {default: jwt} = await import("jsonwebtoken");
2025-02-01 21:26:42 +00:00
const privateKeyBuffer = fs.readFileSync('private.pem');
2025-02-01 21:22:40 +00:00
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:
```javascript
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 a `userVisibleOnly` field or `applicationServerKey` field. The `userVisibleOnly`
field should be set to `true` and the `applicationServerKey` 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](https://blog.mozilla.org/services/2016/08/23/sending-vapid-identified-webpush-notifications-via-mozillas-push-service/) for more information.
- The `Crypto-Key` header should be set to 3 values:
- `p256ecdsa=${publicKey}` - The public key you generated earlier
- `dh=${publicHalfKey}` - The first half of the shared secret when the payload is encoded
- `keyid=p256dh` - The key id
- The `Encryption` header should be set to 2 values:
- `salt=${salt}` - The salt used to generate the shared secret
- `keyid=p256dh` - The key id
- The `Content-Encoding` header should be set to `aesgcm`
For more information you can check the useful links below.
### Useful links
- [The Web Push Protocol ](https://web.dev/articles/push-notifications-web-push-protocol)
- [Web Push Book](https://web-push-book.gauntface.com/)
- [JWT.io](https://jwt.io/)
- [WebPushDataTestPage](https://mozilla-services.github.io/WebPushDataTestPage/)
- [Mozilla Blog](https://blog.mozilla.org/services/2016/08/23/sending-vapid-identified-webpush-notifications-via-mozillas-push-service/)
- [Mozilla docs](https://developer.mozilla.org/en-US/docs/Web/API/PushSubscription)