Notes and a working demo for learning how browser push notifications actually work — written while figuring it out, so it answers the questions you get stuck on rather than the ones a spec answers.
No framework, no build step, no third-party push platform. Express + plain HTML, ~850 lines total, running on localhost in two minutes.
cd demo
npm install
node generate-keys.js # once: generates the VAPID pair into .env
npm start # http://localhost:3456localhost is exempt from the HTTPS requirement, so no certificate needed.
Reading about push is much less convincing than watching these two happen:
1. Push is not a WebSocket. Hit Send push (5s) and close the entire browser before the five seconds are up. The notification arrives anyway. A WebSocket dies with the tab; a service worker does not. That is the only reason push exists.
2. Your traffic goes through Google. Enable notifications in Chrome and watch the server console:
Push service for THIS browser: fcm.googleapis.com
Then cat demo/subscriptions.json and read the endpoint in plain text. This project holds
zero Google credentials — grep -ri firebase demo/*.js demo/public/*.js returns nothing — and the
notifications still travel through Google's servers. That is exactly what VAPID buys you: the
use of Google's infrastructure without being Google's customer.
Now repeat the whole thing in Firefox, without changing a line of code. The host becomes
updates.push.services.mozilla.com. That is the standard earning its keep.
| 01 — Concepts | Web Push (RFC 8030/8291/8292), VAPID, FCM, APNs, OneSignal, and the web-push library. What each one actually is and how they relate. |
| 02 — Direction and the cloud | Does push work both ways? (No.) Does it always go through Google? (Yes, and here is why you cannot avoid it.) |
| 03 — Pitfalls | Thirteen ways to get this wrong, in rough order of how often they happen, plus a pre-ship checklist. |
demo/ is deliberately small and heavily commented. It also does the things most
implementations get wrong, correctly, with the anti-pattern spelled out in a comment right
next to each one:
| Common mistake | What this demo does | |
|---|---|---|
| Error 401/403 | Deletes the subscription | Logs it and keeps it |
pushsubscriptionchange |
Not handled | Re-subscribes and re-registers |
| Sending | await inside the request handler |
Responds first, sends after |
| Missing VAPID keys | Generates new ones and continues | Fails loudly |
| Unsubscribe order | Server first, browser second | Browser first (self-healing) |
| Permission | Requested on page load | Pre-prompt first |
See demo/README.md for the six experiments worth running, including how
to verify all of this from the command line with no browser at all.
If you are starting cold: run the demo first, do the two experiments above, then read 01 — Concepts. The concepts land much better once you have watched a notification arrive at a closed browser.
MIT.