From 810e0130eb85840e24d7e59320f551e0aef0f7a5 Mon Sep 17 00:00:00 2001 From: BordedDev <> Date: Sat, 1 Feb 2025 22:22:40 +0100 Subject: [PATCH] Initial commit --- README.md | 121 ++++++++++++++++++++++++++++++++++++++++++++++ example.html | 42 ++++++++++++++++ service-worker.js | 31 ++++++++++++ 3 files changed, 194 insertions(+) create mode 100644 README.md create mode 100644 example.html create mode 100644 service-worker.js diff --git a/README.md b/README.md new file mode 100644 index 0000000..c7649d7 --- /dev/null +++ b/README.md @@ -0,0 +1,121 @@ +# 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"); + +const privateKeyBuffer = fs.readFileSync('vapid_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: + +```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) \ No newline at end of file diff --git a/example.html b/example.html new file mode 100644 index 0000000..dfa9beb --- /dev/null +++ b/example.html @@ -0,0 +1,42 @@ + + +
+ +