@molecule/api-push-notifications
v1.0.3
Published
Push notification provider interface for molecule.dev
Downloads
0
Readme
@molecule/api-push-notifications
Push notification provider interface for molecule.dev.
Provides an abstract push notification interface that can be backed by any
push notification library. Use setProvider to bond a concrete implementation
such as @molecule/api-push-notifications-web-push.
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Quick Start
import { configure, sendMany, getPublicKey } from '@molecule/api-push-notifications'
configure({ email: '[email protected]', publicKey: VAPID_PUBLIC, privateKey: VAPID_PRIVATE })
// Browser subscribes with the PUBLIC key only; the API stores the subscription per user.
router.post('/push/subscribe', async (req, res) => {
await saveSubscription(getUserId(res), req.body.subscription) // scoped to the user
res.json({ ok: true })
})
// Server sends, then prunes subscriptions the push service reports as gone.
// A dead endpoint REJECTS (it lands in r.error with a statusCode), it does not
// resolve into r.result — checking r.result for 404/410 would never prune anything.
const rows = await subscriptionsFor(userId)
const results = await sendMany(
rows.map((r) => r.subscription),
{ title: 'Hi', options: { body: '…' } },
)
results.forEach((r, i) => {
const gone = (r.error as { statusCode?: number } | undefined)?.statusCode
if (gone === 404 || gone === 410) deleteSubscription(rows[i].id)
})Type
core
Installation
npm install @molecule/api-push-notifications @molecule/api-bondAPI
Interfaces
PushSubscription
A push subscription representing a client endpoint.
interface PushSubscription {
endpoint: string
keys: {
p256dh: string
auth: string
}
}NotificationPayload
Notification payload structure.
interface NotificationPayload {
title: string
options?: {
body?: string
icon?: string
badge?: string
image?: string
tag?: string
data?: Record<string, unknown>
actions?: Array<{
action: string
title: string
icon?: string
}>
requireInteraction?: boolean
silent?: boolean
}
}SendResult
Result of sending a single push notification.
interface SendResult {
statusCode: number
headers: Record<string, string>
body: string
}SendManyResult
Result entry when sending to multiple subscriptions.
Exactly one of result / error is set per entry. A dead subscription
(HTTP 404/410 from the push service) is a FAILURE: it arrives in error
(provider errors such as web-push's WebPushError carry a statusCode),
never as a resolved result — prune stored subscriptions by inspecting
error, not result.
interface SendManyResult {
subscription: PushSubscription
result?: SendResult
error?: Error
}VapidConfig
VAPID configuration for push notification authentication.
interface VapidConfig {
email: string
publicKey: string
privateKey: string
}VapidKeys
VAPID key pair returned by key generation.
interface VapidKeys {
publicKey: string
privateKey: string
}PushNotificationProvider
Push notification provider interface that all implementations must satisfy.
interface PushNotificationProvider {
/**
* Configure VAPID credentials for push notifications.
*/
configure(config?: VapidConfig): void
/**
* Sends a push notification to a single subscription endpoint.
*/
send(subscription: PushSubscription, payload: NotificationPayload): Promise<SendResult>
/**
* Sends a push notification to multiple subscriptions, handling
* partial failures gracefully.
*/
sendMany(
subscriptions: PushSubscription[],
payload: NotificationPayload,
): Promise<SendManyResult[]>
/**
* Generates a new VAPID key pair for push notification authentication.
*/
generateVapidKeys(): VapidKeys
/**
* Returns the public VAPID key for client-side subscription requests,
* or `undefined` if VAPID is not configured.
*/
getPublicKey(): string | undefined
}Functions
setProvider(provider)
Registers a push notification provider as the active singleton. Called by bond packages during application startup.
function setProvider(provider: PushNotificationProvider): voidprovider— The push notification provider implementation to bond.
getProvider()
Retrieves the bonded push notification provider, or undefined if none is bonded.
function getProvider(): PushNotificationProvider | undefinedReturns: The bonded push notification provider, or undefined.
hasProvider()
Checks whether a push notification provider is currently bonded.
function hasProvider(): booleanReturns: true if a push notification provider is bonded.
requireProvider()
Retrieves the bonded push notification provider, throwing if none is configured.
function requireProvider(): PushNotificationProviderReturns: The bonded push notification provider.
configure(config)
Configures VAPID credentials on the bonded push notification provider.
function configure(config?: VapidConfig): voidconfig— The VAPID configuration containing email, public key, and private key.
send(subscription, payload)
Sends a push notification to a single subscription endpoint.
function send(subscription: PushSubscription, payload: NotificationPayload): Promise<SendResult>subscription— The push subscription containing the client endpoint and encryption keys.payload— The notification payload with title and optional display options.
Returns: The send result containing status code, headers, and response body.
sendMany(subscriptions, payload)
Sends a push notification to multiple subscription endpoints. Handles partial failures gracefully — each result includes either the send result or the error for that subscription.
function sendMany(
subscriptions: PushSubscription[],
payload: NotificationPayload,
): Promise<SendManyResult[]>subscriptions— The push subscriptions to send to.payload— The notification payload with title and optional display options.
Returns: One result entry per subscription, each containing either result or error.
generateVapidKeys()
Generates a new VAPID key pair for push notification authentication.
function generateVapidKeys(): VapidKeysReturns: An object containing publicKey and privateKey strings.
getPublicKey()
Returns the public VAPID key for client-side push subscription requests.
function getPublicKey(): string | undefinedReturns: The public VAPID key string, or undefined if not configured.
Available Providers
| Provider | Package |
| ----------------- | ------------------------------------------- |
| Capture | @molecule/api-push-capture |
| Molecule (hosted) | @molecule/api-push-notifications-molecule |
| Web Push (VAPID) | @molecule/api-push-notifications-web-push |
Injection Notes
Requirements
Peer dependencies:
@molecule/api-bond^1.0.1
Runtime Dependencies
@molecule/api-bondThe VAPID private key is a server secret — it stays in the API (config/secrets). Only the public key ({@link getPublicKey}) goes to the browser for
subscribe(). Generate the pair ONCE with {@link generateVapidKeys} and persist it in env; do NOT regenerate per boot — new keys invalidate every existing subscription.Store each {@link PushSubscription} server-side, scoped to its user, and send from the server with {@link send}/{@link sendMany}. The browser never sends notifications and never holds another user's subscription.
Prune dead endpoints: a gone subscription (HTTP 404/410 from the push service) surfaces as a failed send — with the web-push bond the per-subscription
errorin {@link SendManyResult} (aWebPushErrorcarryingstatusCode), NOT a resolved {@link SendResult}. Checkr.errorfor 404/410 and delete that row so you stop pushing to it. (send()for a single subscription throws in that case.)configure()once at startup with your {@link VapidConfig} (email + keys).
E2E Tests
Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual screens/flows, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:
- [ ] The UI offers an enable-notifications control; activating it triggers the browser permission prompt and, once granted, the subscription is stored (the UI still shows "enabled" after a full reload).
- [ ] An event this app notifies about actually delivers a push to the
subscribed session, with a readable title/body (not raw JSON). The sandbox
CAPTURES outbound pushes instead of delivering — read the captured message
with the
read_activitytool (filter type 'push'); never mock the flow or modify production code to expose it. - [ ] Clicking the delivered notification opens/focuses the relevant screen (when the app claims deep-linking).
- [ ] Denying the permission leaves the app fully usable and truthful about the state (no crash, no false "enabled").
- [ ] Disabling/unsubscribing stops deliveries, and the disabled state persists across a reload.
