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

@jsenv/pwa

v6.2.6

Published

Service worker and other progressive web application helpers

Readme

@jsenv/pwa npm package

A toolkit to implement progressive web application (PWA) features in your website.

🏠 Add to home screen functionality
🔄 Service worker lifecycle management
📱 Display mode detection
🛠️ Simple APIs for complex PWA features

Complete usage examples live in docs/usage.md; every export also carries JSDoc in its source file.

Installation

npm install @jsenv/pwa

Add to Home Screen

Allow users to add your website to their device homescreen, running it in a standalone mode without browser UI.

Usage Example

<!doctype html>
<html>
  <head>
    <title>PWA Demo</title>
    <meta charset="utf-8" />
    <script type="importmap">
      {
        "imports": {
          "@jsenv/pwa": "./node_modules/@jsenv/pwa/src/main.js"
        }
      }
    </script>
  </head>
  <body>
    <button id="add-to-home-screen" disabled>Add to home screen</button>

    <!-- Capture beforeinstallprompt early, before any module loads -->
    <script>
      window.addEventListener(
        "beforeinstallprompt",
        (beforeinstallpromptEvent) => {
          beforeinstallpromptEvent.preventDefault();
          window.beforeinstallpromptEvent = beforeinstallpromptEvent;
        },
      );
    </script>

    <script type="module">
      import { addToHomescreen } from "@jsenv/pwa";

      const button = document.querySelector("#add-to-home-screen");

      // Called immediately with the current value, then on every change
      addToHomescreen.availableSignal.subscribe((available) => {
        button.disabled = !available;
      });

      button.onclick = async () => {
        const accepted = await addToHomescreen.prompt();
        console.log(accepted ? "User accepted" : "User declined");
      };
    </script>
  </body>
</html>

API Reference

addToHomescreen.availableSignal

A signal telling if the "Add to Home Screen" prompt can be shown. availableSignal.value is a boolean; availableSignal.subscribe(callback) calls the callback immediately with the current value and again on every change, and returns an unsubscribe function.

The prompt is available when the browser has fired beforeinstallprompt, the app is not already installed, and the page is not already running standalone.

addToHomescreen.prompt()

Prompts the user to add the website to their home screen. Returns a promise that resolves to a boolean indicating whether the user accepted.

import { addToHomescreen } from "@jsenv/pwa";

button.onclick = async () => {
  const userAccepted = await addToHomescreen.prompt();
  console.log(userAccepted ? "added to home screen" : "declined");
};

Important: This must be called inside a user interaction event handler (like click) to work properly.

listenAppInstalled(callback)

Calls callback when the app gets installed — whether the user accepted the prompt or installed from the browser toolbar. Returns a function removing the listener.

import { listenAppInstalled } from "@jsenv/pwa";

listenAppInstalled(() => {
  console.log("app installed");
});

displayModeStandaloneSignal

A signal telling if the page runs in standalone display mode (launched from the home screen).

import { displayModeStandaloneSignal } from "@jsenv/pwa";

displayModeStandaloneSignal.subscribe((standalone) => {
  console.log(`Running in ${standalone ? "standalone" : "browser"} mode`);
});

Service Worker

Service workers enable offline functionality and background updates for your web application. createServiceWorkerFacade wraps registration, update detection, update activation and messaging behind one object with reactive state.

Usage Example

import { createServiceWorkerFacade } from "@jsenv/pwa";

const swFacade = createServiceWorkerFacade();
swFacade.setRegistrationPromise(navigator.serviceWorker.register("/sw.js"));

// Check for updates on demand (browser also checks on navigation / every 24h)
updateCheckButton.onclick = async () => {
  const found = await swFacade.checkForUpdates();
  if (!found) {
    updateStatus.textContent = "No update found";
  }
};

// React to state: show an "update" button when a new version is installed
swFacade.subscribe(() => {
  const { update } = swFacade.state;
  updateActivateButton.hidden = update.readyState !== "installed";
});
updateActivateButton.onclick = async () => {
  await swFacade.activateUpdate();
  // the update controls the page; restarting is a separate, explicit step
  await swFacade.reloadClients();
};

API Reference

createServiceWorkerFacade({ scope, autoclaimOnFirstActivation })

Both parameters are optional. scope selects which registration to look up (defaults to the whole origin). autoclaimOnFirstActivation makes the very first service worker control the page as soon as it activates, instead of waiting for the next navigation.

The returned facade exposes:

  • state — reactive state object:

    {
      error, // Error/ErrorEvent, null while all good
      readyState, // "" | "registering" | "installing" | "installed" | "activating" | "activated" | "redundant"
      meta, // "inspect" meta of registration.active || waiting || installing
      update: {
        error,
        readyState, // same values plus "activation_pending"; "installed" means ready to activate
        meta,
        reloadRequired, // false when every changed resource has an update handler
      },
    }

    Both metas describe workers: state.meta is the worker the registration points at (use navigatorControllerSignal for the one controlling the page), and an "installed" update means different script bytes, not necessarily a version the page is not already running. Before announcing a version number, read the note under Service worker: updates.

  • stateSignal — the signal holding state, for consumers composing it with other signals.

  • subscribe(callback) — runs the callback immediately and again on every state change; returns an unsubscribe function.

  • setRegistrationPromise(promise) — hand it the return value of navigator.serviceWorker.register(url).

  • checkForUpdates() — async, resolves to true if an update was found.

  • activateUpdate() — async, activates the installed update (skipWaiting + claim) and resolves once it controls the page; rejects when the update is discarded or refuses. The browser switches only once the current worker has finished its in-flight events, which can take a while on a slow network: state.update.readyState reports the progress ("activation_pending", "activating", "activated"), so prefer drawing from it over keeping a control busy on the promise. The page is not reloaded.

  • reloadClients() — async, asks the service worker to tell every client tab (this page included) to reload.

  • sendMessage(message) — async, posts a message to the service worker and resolves with its response (see docs/usage.md for the service-worker-side snippet).

  • unregister() — async, unregisters the service worker.

  • defineResourceUpdateHandler(url, handler) — register how to update a resource in place during a service worker update instead of reloading the page.

An activated update needs a restart — it deleted the previous cache, so a tab still running the old build loses the versioned files it may still ask for. That restart is reloadClients(), called by the app when it suits the person using it, and it reloads every tab at once. state.update.reloadRequired says whether one is owed: it is false when every changed resource had a handler registered with defineResourceUpdateHandler and was replaced in place.

Messaging-based features ("inspect" meta, update diffing for defineResourceUpdateHandler) expect the service worker script to answer { action } messages on a MessageChannel port — @jsenv/service-worker implements this protocol. With a plain service worker script everything still works but degrades: meta stays empty, no resource can be hot-replaced and nobody relays reloadClients() to the other tabs — such an app reloads itself with window.location.reload().

navigatorControllerSignal

A signal exposing the service worker currently controlling the page: null when not controlled, otherwise { meta }.

pwaLogger

The package logs through pwaLogger, silent by default except warnings/errors. pwaLogger.setOptions({ logLevel: "debug" }) shows everything the package does.