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

@baryodev/pwa-kit

v0.5.0

Published

Drop-in PWA kit: install prompt (Android + iOS), a secure network-first service worker, and helpers. Framework-agnostic React.

Readme

Turn any web app into an installable, offline-capable one. Framework-agnostic (the React component uses inline styles, so no CSS setup needed).

Why

Turning a site into an installable, offline-capable app means three fiddly pieces every time: an install affordance (Android has a prompt event, iOS doesn't), a service worker with a sane caching strategy, and cache hygiene so one user's data isn't served to another. This packages all three.

Install

npm i @baryodev/pwa-kit

1. Service worker

Generate the worker source and write it to a file your site serves at the root (so its scope covers the whole app):

// scripts/gen-sw.mjs
import { writeFileSync } from "node:fs";
import { generateServiceWorker } from "@baryodev/pwa-kit";

writeFileSync(
  "public/sw.js",
  generateServiceWorker({
    cachePrefix: "myapp",          // -> myapp-shell-v1 / myapp-api-v1
    apiPrefix: "/api/",            // requests treated as live data
    skipPaths: ["/api/auth/", "/api/files/"], // never cached
    navigationFallback: "/",       // shown when offline with nothing cached
  }),
);
// package.json
"scripts": { "prebuild": "node scripts/gen-sw.mjs" }

Caching strategy

  • App shell / static assets: cache-first (fast, works offline).
  • Navigations: network-first, cached page as an offline fallback.
  • API GETs: network-first — online always gets fresh data; the last good response is kept only as an offline fallback, so lists still show like a native app with no signal.
  • Writes (POST/PUT/…) always hit the network.

Security

  • Auth and file paths are never cached.
  • Only same-origin 200 responses are cached.
  • Call clearApiCache() on sign-in and sign-out so cached data from one account is never served to another on a shared device (see below).

2. Register + cache hygiene

import { registerServiceWorker, clearApiCache } from "@baryodev/pwa-kit";

registerServiceWorker(); // defaults to "/sw.js"

// in your auth code:
function onLogin(session) { clearApiCache(); /* store token... */ }
function onLogout() { clearApiCache(); /* drop token... */ }

3. Install prompt (React)

import { InstallHint } from "@baryodev/pwa-kit";

export default function App() {
  return (
    <>
      {/* ...your app... */}
      <InstallHint appName="MyApp" iconUrl="/icon-192.png" accentColor="#2563eb" />
    </>
  );
}
  • Android/Chrome: shows an Install app button wired to beforeinstallprompt.
  • iOS Safari: shows Share → Add to Home Screen instructions (iOS has no install event).
  • Hidden when already installed; dismissible (remembered in localStorage).

4. Native feel when installed (React)

Browsers let users pinch- and double-tap-zoom any page — great for the web, but it makes an installed app feel like a website. Mount StandaloneViewport once near your root: when the app is launched as an installed PWA it locks the viewport so zoom is disabled; in a normal browser tab it does nothing, so accessibility zoom still works there.

import { StandaloneViewport } from "@baryodev/pwa-kit";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <StandaloneViewport />
      </body>
    </html>
  );
}

Not using React? Call the helper directly (e.g. in a client entry). It's a no-op on the server and in a browser tab, and returns a cleanup function:

import { lockViewportWhenStandalone } from "@baryodev/pwa-kit";

const cleanup = lockViewportWhenStandalone();

Your base viewport should still allow zoom (so the browser tab stays accessible) — Next.js:

export const viewport = { width: "device-width", initialScale: 1 };

5. Who installed it (adoption tracking)

There is no cross-browser API that answers "is this PWA installed?" from a normal browser tab. navigator.getInstalledRelatedApps() is Chromium-only and needs related_applications in your manifest; iOS has no install API at all. What every platform can tell you is whether the current launch is the installed app, so record that and keep it.

reportPwaStatus fires once on launch, and again if the user installs during the session (appinstalled). You supply send, so the report goes through your own authenticated API:

import { reportPwaStatus } from "@baryodev/pwa-kit/report";

reportPwaStatus((report) => api.post("/api/pwa/report", report));
// report: { deviceId, displayMode, platform, installed }

Want a one-off read instead of a subscription? Use pwaStatus(), which returns the same shape (or null on the server).

Import from @baryodev/pwa-kit/report, not the package root. The root re-exports the React components, so it pulls React in. This subpath carries only the reporting helpers: no dependencies, no React, and it builds to a single self-contained file. That means a non-React app, or a site with no bundler at all, can use it directly:

<script type="module">
  import { reportPwaStatus } from "/js/pwa-kit-report.js"; // copied from dist/report.js
  reportPwaStatus((r) => fetch("/api/pwa/report", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(r),
  }));
</script>

Things to design around:

  • installed means currently running as installed. Someone who installed but is in a browser tab reports false on that launch, which is why the server should store the first true it sees.
  • appinstalled is Chromium-only. On iOS you find out at the next home-screen launch, not at the moment of install.
  • deviceId is a random id in localStorage, so it is per browser, not per account. Private mode or cleared storage yields a new one and the same person can be counted twice.

6. Web manifest (you provide)

pwa-kit doesn't generate your manifest (icons/colors are yours), but it needs one. Minimum:

{
  "name": "MyApp",
  "short_name": "MyApp",
  "start_url": "/",
  "display": "standalone",
  "theme_color": "#2563eb",
  "background_color": "#ffffff",
  "icons": [
    { "src": "/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any maskable" },
    { "src": "/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" }
  ]
}

Link it and the Apple bits in your <head> (Next.js: use the metadata API):

<link rel="manifest" href="/manifest.webmanifest" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="MyApp" />

API

| Export | Description | |--------|-------------| | generateServiceWorker(options) | Returns the service-worker source as a string. | | registerServiceWorker(url?) | Registers the worker after load. No-op on the server. | | clearApiCache() | Tells the worker to drop cached API data. Call on sign-in/out. | | isStandalone() | true when running as an installed PWA. | | lockViewportWhenStandalone() | Disables zoom when installed (no-op in a browser tab). Returns a cleanup fn. | | <InstallHint /> | Dismissible install prompt (Android button / iOS instructions). | | <StandaloneViewport /> | Mounts lockViewportWhenStandalone for you. Renders nothing. |

License

MIT