From 211a7a8287c88af5dceb1d56d219064b25950589 Mon Sep 17 00:00:00 2001 From: draphy Date: Thu, 23 Apr 2026 21:12:18 +0530 Subject: [PATCH] fix: [WPN-32] Add playground documentation and complete API reference --- README.md | 24 ++++-- packages/builder/README.md | 149 +++++++++++++++++++++++++++++++--- packages/builder/package.json | 55 +++++-------- pnpm-lock.yaml | 2 +- 4 files changed, 172 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index a15f870..9ff0e8f 100644 --- a/README.md +++ b/README.md @@ -15,22 +15,23 @@ Zero dependencies · Works everywhere · TypeScript-first [Documentation](packages/builder) · [npm](https://www.npmjs.com/package/@pushforge/builder) · [Report Bug](https://github.com/draphy/pushforge/issues) -**[Try the Live Demo →](https://pushforge.draphy.org)** +**[Try the Playground →](https://pushforge.draphy.org)** --- -## Live Demo +## Playground -See PushForge in action at **[pushforge.draphy.org](https://pushforge.draphy.org)** — a fully working test site powered by PushForge on Cloudflare Workers. +Test PushForge at **[pushforge.draphy.org](https://pushforge.draphy.org)** — an interactive playground for testing push notifications, powered by Cloudflare Workers. -- **Enable push notifications** on your device with a single toggle -- **Send a test notification** to all active devices — anyone visiting the page can send and receive -- **See it working across browsers** — Chrome, Firefox, Edge, Safari 16+, and more -- Subscriptions auto-expire after 5 minutes — no permanent data stored +- **Quick Test** — enable notifications, send test messages, see them arrive instantly +- **Topic Channels** — test targeted notifications by subscribing to channels +- **Notification Customization** — experiment with title, body, icon, image, action buttons, vibration +- **Push Options** — test urgency (battery hints) and TTL (message expiry) +- **Cross-Browser Testing** — works on Chrome, Firefox, Edge, Safari 16+ -The entire backend is a single Cloudflare Worker using `buildPushHTTPRequest()` from `@pushforge/builder` with zero additional dependencies. +Subscriptions auto-expire (5 min for quick test, 1 hour for topics) — no permanent data stored. The backend is a single Cloudflare Worker using `buildPushHTTPRequest()` with zero dependencies. ## The Problem @@ -103,7 +104,12 @@ const { endpoint, headers, body } = await buildPushHTTPRequest({ subscription, message: { payload: { title: "New Message", body: "You have a notification!" }, - adminContact: "mailto:admin@example.com" + adminContact: "mailto:admin@example.com", + options: { // Optional push delivery settings + ttl: 3600, // Message expires in 1 hour + urgency: "high", // Battery priority hint + topic: "alerts" // Replace pending message with same topic + } } }); diff --git a/packages/builder/README.md b/packages/builder/README.md index 3f0fa04..115d043 100644 --- a/packages/builder/README.md +++ b/packages/builder/README.md @@ -15,7 +15,7 @@ Send push notifications from any JavaScript runtime · Zero dependencies [GitHub](https://github.com/draphy/pushforge) · [npm](https://www.npmjs.com/package/@pushforge/builder) · [Report Bug](https://github.com/draphy/pushforge/issues) -**[Try the Live Demo →](https://pushforge.draphy.org)** +**[Try the Playground →](https://pushforge.draphy.org)** @@ -25,14 +25,17 @@ Send push notifications from any JavaScript runtime · Zero dependencies npm install @pushforge/builder ``` -## Live Demo +## Playground -Try PushForge in your browser at **[pushforge.draphy.org](https://pushforge.draphy.org)** — a live test site running on Cloudflare Workers. +Test PushForge in your browser at **[pushforge.draphy.org](https://pushforge.draphy.org)** — an interactive playground for testing push notifications, powered by Cloudflare Workers. -- Toggle push notifications on, send a test message, and see it arrive in real time -- Works across all supported browsers — Chrome, Firefox, Edge, Safari 16+ -- The backend is a single Cloudflare Worker using `buildPushHTTPRequest()` with zero additional dependencies -- Subscriptions auto-expire after 5 minutes — no permanent data stored +- **Quick Test** — enable notifications, send a test message, see it arrive in real time +- **Topic Channels** — test targeted notifications by subscribing to specific channels +- **Notification Customization** — experiment with title, body, icon, image, action buttons, vibration, click URL +- **Push Options** — test urgency levels (battery hints) and TTL (message expiry) +- **Cross-Browser** — test across Chrome, Firefox, Edge, Safari 16+ +- Subscriptions auto-expire (5 min for quick test, 1 hour for topics) — no permanent data stored +- The backend is a single Cloudflare Worker using `buildPushHTTPRequest()` with zero dependencies ## Why PushForge? @@ -176,9 +179,9 @@ const { endpoint, headers, body } = await buildPushHTTPRequest({ payload, // Any JSON-serializable data adminContact, // Contact email (mailto:...) or URL options: { // Optional - ttl, // Time-to-live in seconds (default: 86400) + ttl, // Time-to-live in seconds (default: 86400, max: 86400) urgency, // "very-low" | "low" | "normal" | "high" - topic // Topic for notification coalescing + topic // Topic for notification replacement } } }); @@ -186,6 +189,38 @@ const { endpoint, headers, body } = await buildPushHTTPRequest({ **Returns:** `{ endpoint: string, headers: Headers, body: ArrayBuffer }` +#### Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `privateJWK` | JsonWebKey \| string | Yes | Your VAPID private key (JWK object or JSON string) | +| `subscription` | PushSubscription | Yes | User's push subscription with `endpoint` and `keys` | +| `message.payload` | any | Yes | Any JSON-serializable data to send (see [Notification Payload](#notification-payload)) | +| `message.adminContact` | string | Yes | Contact for push service (`mailto:you@example.com` or URL) | +| `message.options` | object | No | Push delivery options (see below) | + +#### Push Options (Web Push Protocol Headers) + +These options control how the push service handles message delivery: + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `ttl` | number | 86400 | Time-to-live in seconds. How long the push service retains the message if user is offline. Max 24 hours. | +| `urgency` | string | - | Battery hint: `"very-low"` (ads), `"low"` (topic updates), `"normal"` (chat), `"high"` (calls/time-sensitive). | +| `topic` | string | - | Topic identifier. New message with same topic replaces pending one at push service level (before delivery). | + +#### TypeScript Types + +For TypeScript users, these types are exported: + +```typescript +import type { + BuilderOptions, // Parameter type for buildPushHTTPRequest + PushMessage, // The message object type + PushSubscription // The subscription object type +} from "@pushforge/builder"; +``` + ## Platform Examples ### Cloudflare Workers @@ -295,6 +330,74 @@ const { endpoint, headers, body } = await buildPushHTTPRequest({ await fetch(endpoint, { method: "POST", headers, body }); ``` +## How It Works + +``` +Your Server (PushForge) → Push Service (FCM/APNs) → Service Worker → User's Device +``` + +**PushForge handles:** +- Encrypts payload +- Signs with VAPID +- Sets ttl/urgency/topic headers + +**Your service worker handles:** +- Displays notification (title, body, icon, actions, etc.) +- Handles clicks + +## Notification Payload + +The `payload` field accepts any JSON-serializable data — PushForge encrypts and delivers it as-is. Your service worker receives this payload and passes it to the browser's `showNotification()` API. + +> **Note:** These are standard [Web Notifications API](https://developer.mozilla.org/en-US/docs/Web/API/Notification) options, not PushForge-specific. PushForge handles the transport; your service worker handles the display. + +Common fields: + +| Field | Type | Description | +|-------|------|-------------| +| `title` | string | Notification title (required) | +| `body` | string | Notification body text | +| `icon` | string | URL for the notification icon | +| `badge` | string | URL for the badge (small monochrome icon) | +| `image` | string | URL for a large image | +| `dir` | string | Text direction: `"auto"`, `"ltr"`, or `"rtl"` | +| `lang` | string | Language tag (e.g., `"en-US"`, `"es"`) | +| `tag` | string | Tag for notification replacement (same tag = replace, not stack) | +| `renotify` | boolean | Vibrate/alert again when replacing a notification with same tag | +| `requireInteraction` | boolean | Keep notification visible until user interacts | +| `silent` | boolean | Suppress sound and vibration | +| `timestamp` | number | Timestamp in milliseconds (e.g., `Date.now()`) | +| `vibrate` | number[] | Vibration pattern `[vibrate, pause, vibrate, ...]` | +| `actions` | array | Action buttons (max 2): `[{ action: "id", title: "Label", icon?: "url" }]` | +| `data` | object | Custom data (e.g., `{ url: "/page" }` for click handling) | + +Example with full options: + +```typescript +const { endpoint, headers, body } = await buildPushHTTPRequest({ + privateJWK, + subscription, + message: { + payload: { + title: "New Message", + body: "John: Hey, are you free?", + icon: "/icons/chat.png", + badge: "/icons/badge.png", + image: "/images/preview.jpg", + tag: "chat-john", + renotify: true, + actions: [ + { action: "reply", title: "Reply" }, + { action: "dismiss", title: "Dismiss" } + ], + data: { url: "/chat/john", messageId: "123" } + }, + adminContact: "mailto:admin@example.com", + options: { urgency: "high", ttl: 3600 } + } +}); +``` + ## Service Worker Setup Handle incoming push notifications in your service worker: @@ -309,7 +412,17 @@ self.addEventListener('push', (event) => { body: data.body, icon: data.icon, badge: data.badge, - data: data.url + image: data.image, + dir: data.dir, + lang: data.lang, + tag: data.tag, + renotify: data.renotify, + requireInteraction: data.requireInteraction, + silent: data.silent, + timestamp: data.timestamp, + vibrate: data.vibrate, + actions: data.actions, + data: data.data }) ); }); @@ -317,9 +430,15 @@ self.addEventListener('push', (event) => { self.addEventListener('notificationclick', (event) => { event.notification.close(); - if (event.notification.data) { - event.waitUntil(clients.openWindow(event.notification.data)); + // Handle action button clicks + if (event.action === 'reply') { + clients.openWindow('/chat?action=reply'); + return; } + + // Handle main notification click + const url = event.notification.data?.url || '/'; + event.waitUntil(clients.openWindow(url)); }); ``` @@ -362,6 +481,10 @@ PushForge validates all inputs before processing: - Payload size (max 4KB per Web Push spec) - TTL bounds (max 24 hours per VAPID spec) +## Contributing + +Contributions welcome! See [CONTRIBUTING.md](https://github.com/draphy/pushforge/blob/master/CONTRIBUTING.md) for guidelines. + ## License -MIT +MIT © [David Raphi](https://github.com/draphy) diff --git a/packages/builder/package.json b/packages/builder/package.json index fe55871..8372e14 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,7 +1,7 @@ { "name": "@pushforge/builder", "version": "0.0.0-development", - "description": "A robust, cross-platform Web Push notification library that handles VAPID authentication and payload encryption following the Web Push Protocol standard. Works in Node.js 16+, Browsers, Deno, Bun and Cloudflare Workers.", + "description": "Zero-dependency Web Push library for Cloudflare Workers, Vercel Edge, Convex, Deno, Bun, and Node.js 20+. A web-push alternative that uses Web Crypto API instead of Node.js crypto.", "private": false, "main": "dist/lib/main.js", "module": "dist/lib/main.js", @@ -20,7 +20,7 @@ "type": "git", "url": "https://github.com/draphy/pushforge" }, - "homepage": "https://github.com/draphy/pushforge", + "homepage": "https://pushforge.draphy.org", "license": "MIT", "type": "module", "exports": { @@ -52,42 +52,27 @@ "vitest": "^3.1.2" }, "keywords": [ - "David Raphi", - "David", - "draphy", - "david", - "draphi", - "draphy.org", - "www.draphy.org", - "non profit organization", - "open source", - "web push", "pushforge", - "@pushforge/builder", - "pushforge builder", - "draphy pushforge builder", - "draphy pushforge", - "web push builder", - "web push notification", - "web push protocol", - "pushforge npm", - "web push service", - "@pushforge/builder npm package", - "web push npm package", - "web push vapid", - "notification", - "cross-platform", - "cross platform", "web-push", - "webcrypto", + "web-push alternative", + "web push", + "push notifications", "vapid", - "web-push-protocol", + "cloudflare workers", + "vercel edge", + "convex", + "deno", + "bun", + "nodejs", + "edge runtime", + "edge functions", + "zero dependencies", + "web crypto api", + "crypto.createECDH", "typescript", - "node", - "cloudflare", - "browser", - "ecdh", - "hkdf", - "push-service" + "fcm", + "apns", + "service worker", + "pwa" ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7c5922f..115c8a6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -39,7 +39,7 @@ importers: specifier: ^3.1.2 version: 3.1.2(@types/node@22.14.1) - sites/test: + sites/playground: dependencies: '@pushforge/builder': specifier: workspace:*