@specno-marlon/convex-web-push
v0.1.1
Published
Web Push notifications as a Convex component — subscribe, send, and track delivery with zero infrastructure.
Maintainers
Readme
convex-web-push
Web push notifications for Convex apps. Send notifications to subscribers with a simple API, automatic subscription management, and built-in delivery tracking.
Features
- VAPID-secured push delivery — industry-standard Web Push Protocol
- Automatic subscription lifecycle — handles permission requests, subscription rotation, and cleanup
- Delivery tracking — built-in stats, notification history, and subscriber management
- Retry with exponential backoff — auto-recovers from transient delivery failures
- Dead subscription cleanup — automatically removes 404/410 responses
- React hook + service worker handlers — zero-config notification UI and handlers
Quick Start
1. Install package
npm install @specno-marlon/convex-web-push
# or: pnpm add @specno-marlon/convex-web-push2. Generate VAPID keys
npx web-push generate-vapid-keysStore the keys in your Convex environment variables:
# .env.local (or your deployment platform's env var settings)
CONVEX_VAPID_PUBLIC_KEY=<public key>
CONVEX_VAPID_PRIVATE_KEY=<private key>3. Backend setup
In convex/convex.config.ts, add the component:
import { defineProject } from "convex/server";
import webPush from "@specno-marlon/convex-web-push/convex.config";
export default defineProject({
modules: [webPush],
});Create convex/push.ts to export the client:
import { WebPushClient } from "@specno-marlon/convex-web-push";
export const push = new WebPushClient();4. Frontend setup
Register a service worker in your app entry point:
// app/main.tsx or pages/_app.tsx
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/sw.js");
}Create public/sw.js:
// public/sw.js — handles incoming push events
importScripts(
"https://cdn.jsdelivr.net/npm/@specno-marlon/convex-web-push/dist/sw/bundle.js"
);
const { handlePush, handleClick, handleSubscriptionChange } = ConvexWebPush;
self.addEventListener("push", handlePush);
self.addEventListener("notificationclick", handleClick);
self.addEventListener("pushsubscriptionchange", handleSubscriptionChange);5. Hook into React component
import { usePushNotifications } from "@specno-marlon/convex-web-push/react";
import { push } from "./convex/api";
export function NotificationButton() {
const { isSupported, isSubscribed, subscribe, unsubscribe, permission } =
usePushNotifications(
{ subscribe: push.subscribe, unsubscribe: push.unsubscribe, updateSubscription: push.updateSubscription },
{
vapidPublicKey: import.meta.env.VITE_VAPID_PUBLIC_KEY,
onNotification: (payload) => console.log("Push received:", payload),
onNotificationClick: (payload) => console.log("Notification clicked:", payload),
}
);
if (!isSupported) return <p>Push notifications not supported</p>;
return (
<button onClick={isSubscribed ? unsubscribe : subscribe}>
{isSubscribed ? "Unsubscribe" : "Subscribe to Notifications"}
</button>
);
}API Reference
WebPushClient
Backend API wrapper for sending notifications and managing subscribers.
import { WebPushClient } from "@specno-marlon/convex-web-push";
const push = new WebPushClient();| Method | Signature | Description |
|--------|-----------|-------------|
| subscribe | (endpoint, keys_p256dh, keys_auth) => Promise<{subscriberId}> | Register a new subscriber. Called by hook; rarely used directly. |
| unsubscribe | (subscriberId) => Promise<void> | Remove a subscriber. Called by hook on unsubscribe. |
| updateSubscription | (subscriberId, endpoint, keys_p256dh, keys_auth) => Promise<void> | Update keys after rotation. Called by hook; called automatically on subscription change. |
| send | (title, body, url?, icon?, data?, subscriberIds?) => Promise<{notificationId, totalSent}> | Broadcast to all subscribers or target specific ones. Delivery is async (scheduled). |
| getStats | () => Promise<{totalSubscribers, notificationsSent, acceptanceRate}> | Get aggregated delivery stats. |
| getNotification | (notificationId) => Promise<Notification\|null> | Retrieve a sent notification by ID. |
| listSubscribers | (paginationOpts: PaginationOptions) => Promise<PaginationResult<Subscriber>> | List active subscribers. Uses Convex cursor-based pagination. |
usePushNotifications Hook
Frontend React hook for subscription management and notification callbacks.
const {
isSupported, // boolean — device supports Web Push
permission, // NotificationPermission — "default" | "granted" | "denied"
isSubscribed, // boolean — user is currently subscribed
subscriberId, // string | null — unique subscriber ID
subscribe, // () => Promise<void> — request permission and subscribe
unsubscribe, // () => Promise<void> — unsubscribe and remove locally
} = usePushNotifications(api, options);Parameters:
api: Object withsubscribe,unsubscribe,updateSubscriptionmutations from WebPushClientoptions:vapidPublicKey(string, required) — public key for encryptiononNotification(optional) — callback when push arrives; payload is{title, body, url?, icon?, data?}onNotificationClick(optional) — callback when user clicks notification
Service Worker Handlers
Export from convex-web-push/sw; re-export in your service worker.
import { handlePush, handleClick, handleSubscriptionChange } from "convex-web-push/sw";
self.addEventListener("push", handlePush);
self.addEventListener("notificationclick", handleClick);
self.addEventListener("pushsubscriptionchange", handleSubscriptionChange);| Handler | Event | What it does |
|---------|-------|-------------|
| handlePush | PushEvent | Parse notification payload and display + notify open clients |
| handleClick | NotificationEvent | Close notification, open URL if present, notify clients |
| handleSubscriptionChange | PushSubscriptionChangeEvent | Notify clients that subscription rotated (hook handles update) |
Environment Variables
Required for backend notification delivery:
| Variable | Source | Used for |
|----------|--------|----------|
| VAPID_PUBLIC_KEY | npx web-push generate-vapid-keys | Client-side subscription encryption; shared with frontend |
| VAPID_PRIVATE_KEY | npx web-push generate-vapid-keys | Backend signing of push messages; kept server-side only |
| VAPID_SUBJECT | e.g. mailto:[email protected] | Identifies the sender to push services; required by VAPID spec |
How It Works
- Subscribe: Browser requests notification permission, generates a push subscription via
PushManager, and sends endpoint + keys to backend. - Deliver: Backend signs and sends a message to the subscription endpoint using the Web Push Protocol. Failed requests retry with exponential backoff.
- Notify: When a push arrives, the service worker decrypts it, displays the notification, and posts a message to all open clients (for in-app callbacks).
- Track: Every delivery attempt is logged. Dead subscriptions (404/410) are auto-cleaned. Stats API aggregates delivery success rate.
License
MIT
