npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pushforge/builder

v2.0.1

Published

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.

Readme

PushForge Builder

A lightweight, dependency-free Web Push library built on the standard Web Crypto API.

npm version License: MIT TypeScript

Send push notifications from any JavaScript runtime · Zero dependencies

GitHub · npm · Report Bug

Try the Live Demo →


npm install @pushforge/builder

Live Demo

Try PushForge in your browser at pushforge.draphy.org — a live test site running on 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

Why PushForge?

| | PushForge | web-push | |---|:---:|:---:| | Dependencies | 0 | 5+ (with nested deps) | | Cloudflare Workers | Yes | No | | Vercel Edge | Yes | No | | Convex | Yes | No | | Deno / Bun | Yes | Limited | | TypeScript | First-class | @types package |

Traditional web push libraries rely on Node.js-specific APIs (crypto.createECDH, https.request) that don't work in modern edge runtimes. PushForge uses the standard Web Crypto API, making it portable across all JavaScript environments.

Quick Start

1. Generate VAPID Keys

npx @pushforge/builder vapid

This outputs a public key (for your frontend) and a private key in JWK format (for your server).

2. Subscribe Users (Frontend)

Use the VAPID public key to subscribe users to push notifications:

// In your frontend application
const registration = await navigator.serviceWorker.ready;

const subscription = await registration.pushManager.subscribe({
  userVisibleOnly: true,
  applicationServerKey: 'YOUR_VAPID_PUBLIC_KEY' // From step 1
});

// Send this subscription to your server
// subscription.toJSON() returns:
// {
//   endpoint: "https://fcm.googleapis.com/fcm/send/...",
//   keys: {
//     p256dh: "BNcRd...",
//     auth: "tBHI..."
//   }
// }
await fetch('/api/subscribe', {
  method: 'POST',
  body: JSON.stringify(subscription)
});

3. Send Notifications (Server)

import { buildPushHTTPRequest } from "@pushforge/builder";

// Your VAPID private key (JWK format from step 1)
const privateJWK = {
  kty: "EC",
  crv: "P-256",
  x: "...",
  y: "...",
  d: "..."
};

// The subscription object from the user's browser
const subscription = {
  endpoint: "https://fcm.googleapis.com/fcm/send/...",
  keys: {
    p256dh: "BNcRd...",
    auth: "tBHI..."
  }
};

// Build and send the notification
const { endpoint, headers, body } = await buildPushHTTPRequest({
  privateJWK,
  subscription,
  message: {
    payload: {
      title: "New Message",
      body: "You have a new notification!",
      icon: "/icon.png"
    },
    adminContact: "mailto:[email protected]"
  }
});

const response = await fetch(endpoint, {
  method: "POST",
  headers,
  body
});

if (response.status === 201) {
  console.log("Notification sent");
}

Understanding Push Subscriptions

When a user subscribes to push notifications, the browser returns a PushSubscription object:

{
  // The unique URL for this user's browser push service
  endpoint: "https://fcm.googleapis.com/fcm/send/dAPT...",

  keys: {
    // Public key for encrypting messages (base64url)
    p256dh: "BNcRdreALRFXTkOOUHK1EtK2wtaz5Ry4YfYCA...",

    // Authentication secret (base64url)
    auth: "tBHItJI5svbpez7KI4CCXg=="
  }
}

| Field | Description | |-------|-------------| | endpoint | The push service URL. Each browser vendor has their own (Google FCM, Mozilla autopush, Apple APNs). | | p256dh | The user's public key for ECDH P-256 message encryption. | | auth | A shared 16-byte authentication secret. |

Store these securely on your server. You'll need them to send notifications to this user.

API Reference

buildPushHTTPRequest(options)

Builds an HTTP request for sending a push notification.

const { endpoint, headers, body } = await buildPushHTTPRequest({
  privateJWK,    // Your VAPID private key (JWK object or JSON string)
  subscription,  // User's push subscription
  message: {
    payload,       // Any JSON-serializable data
    adminContact,  // Contact email (mailto:...) or URL
    options: {     // Optional
      ttl,         // Time-to-live in seconds (default: 86400)
      urgency,     // "very-low" | "low" | "normal" | "high"
      topic        // Topic for notification coalescing
    }
  }
});

Returns: { endpoint: string, headers: Headers, body: ArrayBuffer }

Platform Examples

Cloudflare Workers

export default {
  async fetch(request, env) {
    const subscription = await request.json();

    const { endpoint, headers, body } = await buildPushHTTPRequest({
      privateJWK: JSON.parse(env.VAPID_PRIVATE_KEY),
      subscription,
      message: {
        payload: { title: "Hello from the Edge!" },
        adminContact: "mailto:[email protected]"
      }
    });

    return fetch(endpoint, { method: "POST", headers, body });
  }
};

Vercel Edge Functions

import { buildPushHTTPRequest } from "@pushforge/builder";

export const config = { runtime: "edge" };

export default async function handler(request: Request) {
  const subscription = await request.json();

  const { endpoint, headers, body } = await buildPushHTTPRequest({
    privateJWK: JSON.parse(process.env.VAPID_PRIVATE_KEY!),
    subscription,
    message: {
      payload: { title: "Edge Notification" },
      adminContact: "mailto:[email protected]"
    }
  });

  await fetch(endpoint, { method: "POST", headers, body });
  return new Response("Sent", { status: 200 });
}

Convex

import { action } from "./_generated/server";
import { buildPushHTTPRequest } from "@pushforge/builder";
import { v } from "convex/values";

export const sendPush = action({
  args: { subscription: v.any(), title: v.string(), body: v.string() },
  handler: async (ctx, { subscription, title, body }) => {
    const { endpoint, headers, body: reqBody } = await buildPushHTTPRequest({
      privateJWK: JSON.parse(process.env.VAPID_PRIVATE_KEY!),
      subscription,
      message: {
        payload: { title, body },
        adminContact: "mailto:[email protected]"
      }
    });

    await fetch(endpoint, { method: "POST", headers, body: reqBody });
  }
});

Deno

import { buildPushHTTPRequest } from "npm:@pushforge/builder";

const { endpoint, headers, body } = await buildPushHTTPRequest({
  privateJWK: JSON.parse(Deno.env.get("VAPID_PRIVATE_KEY")!),
  subscription,
  message: {
    payload: { title: "Hello from Deno!" },
    adminContact: "mailto:[email protected]"
  }
});

await fetch(endpoint, { method: "POST", headers, body });

Bun

import { buildPushHTTPRequest } from "@pushforge/builder";

const { endpoint, headers, body } = await buildPushHTTPRequest({
  privateJWK: JSON.parse(Bun.env.VAPID_PRIVATE_KEY!),
  subscription,
  message: {
    payload: { title: "Hello from Bun!" },
    adminContact: "mailto:[email protected]"
  }
});

await fetch(endpoint, { method: "POST", headers, body });

Service Worker Setup

Handle incoming push notifications in your service worker:

// sw.js
self.addEventListener('push', (event) => {
  const data = event.data?.json() ?? {};

  event.waitUntil(
    self.registration.showNotification(data.title, {
      body: data.body,
      icon: data.icon,
      badge: data.badge,
      data: data.url
    })
  );
});

self.addEventListener('notificationclick', (event) => {
  event.notification.close();

  if (event.notification.data) {
    event.waitUntil(clients.openWindow(event.notification.data));
  }
});

Requirements

Node.js 20+ or any runtime with Web Crypto API support.

| Environment | Status | |-------------|--------| | Node.js 20+ | Fully supported | | Cloudflare Workers | Fully supported | | Vercel Edge | Fully supported | | Deno | Fully supported | | Bun | Fully supported | | Convex | Fully supported | | Modern Browsers | Fully supported |

import { webcrypto } from "node:crypto";
globalThis.crypto = webcrypto;

import { buildPushHTTPRequest } from "@pushforge/builder";

Or: node --experimental-global-webcrypto your-script.js

Security

PushForge validates all inputs before processing:

  • VAPID key structure (EC P-256 curve with required x, y, d parameters)
  • Subscription endpoint (must be valid HTTPS URL)
  • p256dh key format (65-byte uncompressed P-256 point)
  • Auth secret length (exactly 16 bytes)
  • Payload size (max 4KB per Web Push spec)
  • TTL bounds (max 24 hours per VAPID spec)

License

MIT