JJopgood PWAcore + adaptersv0.0.2GitHub

Quick Start

Construct a PWAManager, wrap your app with PushProvider, and drive push UI from a hook.

Construct a manager, wrap the app with the provider, and read state from a hook.

Steps

Install

npm install @jopgood/pwa-core @jopgood/react-pwa

Construct the manager outside React

Construct once, at module scope, so React isn't recreating it on every render. Construction is safe on the server.

// lib/pwa.ts
import { PWAManager } from "@jopgood/pwa-core";

export const pwaManager = new PWAManager({
  serviceWorkerUrl: "/sw.js",
  vapidPublicKey: process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY!,
  onSubscriptionChange: async (subscription) => {
    if (subscription) {
      await fetch("/api/push/register", {
        method: "POST",
        body: JSON.stringify(subscription),
      });
    }
  },
});

Wrap your app

The provider calls manager.mount() on mount and manager.unmount() on unmount.

import { PushProvider } from "@jopgood/react-pwa";
import { pwaManager } from "./lib/pwa";

export function App({ children }: { children: React.ReactNode }) {
  return <PushProvider manager={pwaManager}>{children}</PushProvider>;
}

Use a hook

Anywhere inside the provider, drive UI from the store:

"use client";
import { usePushNotifications } from "@jopgood/react-pwa";

export function PushToggle() {
  const { permission, isSubscribed, subscribe, unsubscribe, requestPermission } =
    usePushNotifications();

  if (permission === "default") {
    return <button onClick={requestPermission}>Enable notifications</button>;
  }
  if (permission === "denied") {
    return <p>Notifications are blocked in browser settings.</p>;
  }
  return isSubscribed ? (
    <button onClick={unsubscribe}>Disable</button>
  ) : (
    <button onClick={subscribe}>Subscribe</button>
  );
}

The hooks

usePushNotifications()

Every state field plus every action in one hook. Useful when a component needs more than one piece of state.

const {
  // state
  permission,
  isSupported,
  isSubscribed,
  subscription,
  swState,
  swUpdateAvailable,
  isLoading,
  error,
  // actions
  subscribe,
  unsubscribe,
  requestPermission,
  activateWaiting,
} = usePushNotifications();

usePermission()

Just the permission. Useful for a "request permission" button that doesn't care about the subscription.

const { permission, requestPermission } = usePermission();

useSWUpdate()

Just the update flag, for an "update available" banner.

function UpdateBanner() {
  const { swUpdateAvailable, activateWaiting } = useSWUpdate();
  if (!swUpdateAvailable) return null;
  return <button onClick={activateWaiting}>Reload to update</button>;
}
Equivalent to the core

Each hook is a useSelector over manager.store plus a method reference. Anything available here is also available on the manager directly.

Passing a manager explicitly

Every hook accepts an optional manager argument that bypasses context. Useful in tests:

const { permission } = usePushNotifications(testManager);