Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)**

</div>

---

## 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

Expand Down Expand Up @@ -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
}
}
});

Expand Down
149 changes: 136 additions & 13 deletions packages/builder/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)**

</div>

Expand All @@ -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?

Expand Down Expand Up @@ -176,16 +179,48 @@ 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
}
}
});
```

**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
Expand Down Expand Up @@ -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:
Expand All @@ -309,17 +412,33 @@ 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
})
);
});

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));
});
```

Expand Down Expand Up @@ -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)
55 changes: 20 additions & 35 deletions packages/builder/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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": {
Expand Down Expand Up @@ -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"
]
}
2 changes: 1 addition & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading