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

@profullstack/notifications

v0.1.4

Published

Web Push that works everywhere, once: VAPID + RFC 8291 encryption from node:crypto (zero dependencies), a browser client that fetches the key at runtime and says exactly why push is unavailable, and service-worker handlers.

Readme

@profullstack/notifications

Web Push that works the first time, in every app.

  • Zero dependencies. VAPID (RFC 8292) and message encryption (RFC 8291) come straight from node:crypto. Keys are in the same format as the web-push package, so you can switch without regenerating anything.
  • The public key is fetched at runtime. The most common way push breaks in production is a NEXT_PUBLIC_VAPID_PUBLIC_KEY that was missing when the client bundle was built. It compiles to an empty string, and every browser then reports "push notifications are not supported". Here the browser asks your server for the key when it subscribes.
  • "Not supported" always comes with a reason. pushSupport() tells you which one it is, with a sentence to show the user:
    • not on HTTPS;
    • iPhone or iPad without the site added to the Home Screen;
    • notifications blocked in the browser's settings;
    • no service worker support;
    • the server has no key configured;
    • the browser cannot reach its own push service (Chromium's "Registration failed - push service error"), with the exact setting to change on Brave.
npm install @profullstack/notifications

Server

import {
  vapidKeysFromEnv,
  vapidPublicKeyResponse,
  parseSubscription,
  sendPushToMany,
} from '@profullstack/notifications/server';

const keys = vapidKeysFromEnv(); // VAPID_PUBLIC_KEY (or NEXT_PUBLIC_VAPID_PUBLIC_KEY, VAPID_PUBLIC) + VAPID_PRIVATE_KEY (or VAPID_PRIVATE), read at run time

// GET /api/push/vapid-public-key
export const GET = () => vapidPublicKeyResponse(keys);

// POST /api/push/subscribe
const subscription = parseSubscription(await request.json()); // null if malformed
// ...store it against the user...

// Sending
await sendPushToMany(userSubscriptions, { title: 'You are live', body: 'Tap to open', url: '/live' }, {
  keys,
  subject: 'mailto:[email protected]',
  onGone: (endpoint) => db.deleteSubscription(endpoint), // 404/410 from the push service
});

Messages the push service can't deliver yet are kept for ttl seconds, 24 hours by default. The web-push package defaulted to 4 weeks, so pass ttl: 28 * 24 * 3600 when migrating if late delivery matters.

New keys: node -e "import('@profullstack/notifications/server').then(m => console.log(m.generateVapidKeys()))".

Browser

import { pushSupport, subscribe, unsubscribe } from '@profullstack/notifications/client';

const support = pushSupport();
if (!support.supported) showMessage(support.message); // e.g. the iPhone Home Screen instructions

button.onclick = async () => {
  try {
    await subscribe({ saveUrl: '/api/push/subscribe' }); // asks permission, fetches the key, registers /sw.js if needed
  } catch (error) {
    showMessage(error.message); // error.reason: 'denied' | 'no-server-key' | ...
  }
};

Options worth knowing: timeoutMs stops subscribe() waiting forever on Chrome's quiet permission prompt or Brave (it throws reason 'timeout'); a save that was redirected (a lost session sent to the sign-in page) throws 'save-failed' rather than passing; unsubscribe({ removeUrl, removeMethod: 'POST' }) suits a POST /unsubscribe route (the default is DELETE).

subscribe() replaces a subscription that was made with a different key, so rotating keys doesn't silently stop delivery.

Service worker

import { installPushHandlers } from '@profullstack/notifications/sw';
installPushHandlers(self, { icon: '/icon-192.png' });

A plain-script (non-module) service worker loads the same code with importScripts; serve @profullstack/notifications/sw-classic (src/sw-classic.js) from your app:

importScripts('/vendor/notifications-sw.js');
self.PushHandlers.installPushHandlers(self, { icon: '/icon-192.png' });

It shows { title, body, url, icon, tag } payloads. Clicking a notification focuses a tab already on that URL, or opens one.

License

MIT