This project implements a backend for VAPID Web Push notifications using Netlify Functions. It allows you to send push notifications to subscribed clients from your serverless backend. Currently it only supports VAPID-based web push notification, so iOS devices is out of order.
- Serverless backend using Netlify Functions
- Handles push subscription and notification sending
- Uses web-push library for VAPID and payload encryption
- Example endpoints for subscribing and sending notifications
- Node.js > 18
- Netlify CLI (
npm install -g netlify-cli)
-
Clone the repository:
git clone https://github.com/yourusername/netlify-webpush.git cd netlify-webpush -
Install dependencies:
npm install
-
Set up environment variables:
- Add your VAPID keys and other configuration as needed to .env file.
Start the Netlify Functions server locally:
netlify devDeploy to Netlify using the CLI or by connecting your repository to Netlify.
-
POST /api/subscribe
Save a user's push subscription. -
POST /api/auth
Get a token, used for sending push notification. -
POST /api/blast
Send a push notification to all subscribers. -
POST /api/push-notification
Send a push notification to single subscriber.
-
Create a database & table in Supabase, with column:
Column Name Type id uuid created_at timestamp endpoint text keys json -
Example script on how to subscribe to notification for the front-end:
const vapidPublicKey = "YOUR_VAPID_PUBLIC_KEY"; function askNotificationPermission() { if (!("Notification" in window)) { console.error("Notifications not supported in this browser"); return; } Notification.requestPermission().then(permission => { if (permission === "granted") { navigator.serviceWorker.getRegistration().then(registration => { if (!registration) { return navigator.serviceWorker.register("/sw.js"); } return registration; }).then(registration => { if (!registration) { console.error("Service Worker registration failed."); return; } registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(vapidPublicKey) }).then(subscription => { if (!subscription) { console.error("Failed to subscribe to push notifications."); return; } fetch('/api/subscribe', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(subscription) }); }).catch(error => { console.error("Failed to subscribe:", error); }); }).catch(error => { console.error("Service Worker registration not found:", error); }); } else if (permission === "denied" || permission === "default") { console.warn("Notification permission denied or defaulted."); } }); } function urlBase64ToUint8Array(base64String) { const padding = '='.repeat((4 - base64String.length % 4) % 4); const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/'); const raw = atob(base64); return Uint8Array.from([...raw].map(c => c.charCodeAt(0))); }
-
Example service worker on how to handle push notification & notification click:
self.addEventListener('push', function (event) { let data = { title: "Hello!", body: "You have a new notification!", icon: "", image: "", badge: "", data: {}, actions: [] }; if (event.data) { try { const json = event.data.json(); data = { ...data, ...json }; } catch (e) { console.warn("Push data is not valid JSON, using as text:", e); data.body = event.data.text(); } } event.waitUntil( self.registration.showNotification(data.title, { body: data.body, icon: data.icon, image: data.image, badge: data.badge, data: data.data, actions: data.actions }) ); }); self.addEventListener('notificationclick', function (event) { event.notification.close(); const url = event.notification.data?.url; if (event.action === 'dismiss') { return; } if ((!event.action || event.action === 'open_url') && url) { event.waitUntil(clients.openWindow(url)); } });
-
Example on how to get token:
-
Add client to environment variables (.env file on local). It should be
CLIENT_${CLIENT_ID}=${SECRET}.So if you want the client id to be
functionand secret to beasdqwe123, then you need to addCLIENT_FUNCTION=asdqwe123. Please note there should beCLIENT_prefix before the actual client ID. -
To get the token, send [POST] to
/api/authcurl --location --request POST '{baseUrl}/api/auth' \ --header 'Authorization: Basic base64(${clientId}:${clientSecret})'
-
-
Example on how to blast messsage to all subscribers: Send [POST] to
/api/blast, with payload (don't forget the bearer token){ "title": "Some Title", "body": "Some body", "image": "/image.jpg", "url": "https://example.com", "actions": [ { "action": "open_url", "title": "Open" }, { "action": "dismiss", "title": "Dismiss" } ] } -
Example on how to send push notification to single subscriber: Send [POST] to
/api/push-notification, with payload (don't forget the bearer token). To get the subscription object, you can check the database{ "title": "Some Title", "body": "Some body", "url": "https://example.com", "actions": [ { "action": "open_url", "title": "Open" }, { "action": "dismiss", "title": "Dismiss" } ], "subscription": { "endpoint": "https://the-subscription-endpoint", "keys": { "p256dh": "p256dh keys", "auth": "auth keys" } } }
Contributions are welcome! Please submit a pull request or open an issue to discuss any changes.