AbsoluteJS

PWA

@absolutejs/pwav0.7.3betaFrontend & UX

Framework-agnostic PWA and Web Push primitives — manifest, push service worker, VAPID sender, and browser subscription glue — for any Bun or Elysia app.

@absolutejs/pwa provides framework-agnostic primitives for turning any app into an installable, push-capable PWA: a web app manifest builder, a generated push service worker, a VAPID Web Push sender that flags dead endpoints, and browser glue for registration and subscription. It is storage- and framework-agnostic — you decide how subscriptions are stored and how routes are mounted, which makes it a natural fit for an Elysia server on Bun. Server helpers live at the package root; browser helpers at @absolutejs/pwa/client.

#Installation

BASH
bun add @absolutejs/pwa

#Capabilities

Overview

Framework-agnostic primitives for turning any app into an installable, push-capable PWA: a web app manifest, the push service worker, a VAPID Web Push sender that flags dead endpoints, and browser glue for service-worker registration + subscription.

It is storage- and framework-agnostic — _you_ decide how subscriptions are stored and how routes are mounted. Server helpers live at the root; browser helpers at @absolutejs/pwa/client.

Server

Mounting is yours. With Elysia:

Client

Install prompt

Capture the browser's install signal and drive it from your own button:

Embedded browsers and capability UX

Show 3 more

Feature-detect before offering browser-dependent actions, then use embedded- browser identity only to explain an unavailable capability:

Detection is deliberately conservative: Facebook, Instagram, and Messenger are identified only from their explicit host-app markers. Unknown WebViews return null; capability checks remain authoritative.

Every client function is feature-safe (no-ops when the APIs are missing or during SSR). subscribeToPush throws Error("notification-permission-denied") on a hard permission denial so you can message it.

Web app manifest

createWebAppManifest builds a typed web app manifest — icons, shortcuts, share targets — ready to serve as application/manifest+json.

Push service worker

pushServiceWorker generates the service worker source, with optional offline support that precaches an app shell and serves a fallback page.

VAPID sender

createWebPush signs and sends Web Push notifications, fans out to all of a user’s devices with sendMany, and reports gone endpoints for pruning.

Browser subscription glue

registerServiceWorker, subscribeToPush, unsubscribeFromPush, and getPushStatus handle the browser side; you POST the results to your own routes.

Install prompt control

initInstallPrompt captures the install signal so you can show your own install button and trigger promptInstall from a user gesture.

Feature-safe everywhere

Every client function no-ops when the APIs are missing or during SSR, and the sender no-ops when VAPID keys are unset — push degrades gracefully.

Capability-aware embedded browser UX

getBrowserCapabilities reports install, push, service-worker, passkey, media, clipboard, and sharing support. detectEmbeddedBrowser conservatively identifies Facebook, Instagram, and Messenger only when explicit host markers are present.

#Capability-first browser UX

Feature checks are authoritative. Embedded-browser identity should only explain why an action is unavailable.

  1. Detect
    Read the current browser capability snapshot.
  2. Offer
    Offer install, push, sharing, passkeys, or media only when supported.
  3. Explain
    Use conservative host-app detection to explain unavailable actions.
  4. Recover
    Provide an open-in-browser or alternate-channel fallback.

Outcomes

What you can build

Overview

Framework-agnostic primitives for turning any app into an installable, push-capable PWA: a web app manifest, the push service worker, a VAPID Web Push sender that flags dead endpoints, and browser glue for service-worker registration + subscription.

Server

Mounting is yours. With Elysia:

Client

Install prompt

Hardening checklist

Production guidance

Make every external boundary explicitPin the deployed @absolutejs/pwa version, replace example or memory-backed dependencies with durable implementations, bound external calls, protect credentials, and emit enough evidence to retry or recover safely.

Follow in order

Troubleshooting path

1
Trace from the first failed boundary
Reproduce the smallest canonical @absolutejs/pwa example, confirm the supported entry point and version in the API explorer, then inspect the first boundary that did not produce its documented result.

#@absolutejs/pwa quick start

Partial snippet

# @absolutejs/pwa

BASH
bun add @absolutejs/pwa

#Install prompt

Partial snippet

Capture the browser's install signal and drive it from your own button:

TS
import {
  initInstallPrompt,
  onInstallable,
  promptInstall,
  canInstall,
} from "@absolutejs/pwa/client";

initInstallPrompt(); // once at boot

// React to availability (show/hide your install button):
const off = onInstallable((installable) => setShowInstall(installable));

// From a click handler (must be a user gesture):
const accepted = await promptInstall();

#Server

Partial snippet

Working example for Server.

TS
import {
  createWebAppManifest,
  pushServiceWorker,
  createWebPush,
} from "@absolutejs/pwa";

const ICON = "/icons/app-512.png";

// Serve as application/manifest+json at /manifest.webmanifest
export const manifest = createWebAppManifest({
  name: "My App",
  shortName: "MyApp",
  themeColor: "#6366f1",
  icons: [
    { src: ICON, sizes: "192x192", type: "image/png", purpose: "any" },
    { src: ICON, sizes: "512x512", type: "image/png", purpose: "any" },
  ],
});

// Serve as text/javascript at /sw.js with header `Service-Worker-Allowed: /`.
// Pass `offline` to also precache an app shell + serve a fallback page when a
// navigation fails offline, and cache same-origin assets cache-first.
export const sw = pushServiceWorker({
  icon: ICON,
  offline: { fallback: "/offline.html", assetPrefix: "/assets/" },
});

// VAPID sender — pass empty/unset keys and it no-ops (isConfigured() === false),
// so push degrades gracefully to your email/in-app fallback.
const push = createWebPush({
  publicKey: process.env.VAPID_PUBLIC_KEY,
  privateKey: process.env.VAPID_PRIVATE_KEY,
  subject: "mailto:you@example.com",
});

// Fan out to a user's devices; prune whatever it reports gone.
const { gone } = await push.sendMany(subscriptions, {
  title: "New match",
  body: "Acme Co. just replied.",
  url: "/inbox",
});
await pruneEndpoints(gone); // your storage

#Server 2

Partial snippet

Mounting is yours. With Elysia:

TS
new Elysia()
  .get("/manifest.webmanifest", ({ set }) => {
    set.headers["content-type"] = "application/manifest+json";
    return manifest;
  })
  .get("/sw.js", ({ set }) => {
    set.headers["content-type"] = "text/javascript";
    set.headers["service-worker-allowed"] = "/";
    return sw;
  });

#Server

Partial snippet

Serve the manifest and service worker from Elysia, then send Web Push notifications with the VAPID sender.

TS
import {
	createWebAppManifest,
	createWebPush,
	pushServiceWorker
} from '@absolutejs/pwa';
import { Elysia } from 'elysia';

const ICON = '/icons/app-512.png';

const manifest = createWebAppManifest({
	icons: [
		{ purpose: 'any', sizes: '192x192', src: ICON, type: 'image/png' },
		{ purpose: 'any', sizes: '512x512', src: ICON, type: 'image/png' }
	],
	name: 'My App',
	shortName: 'MyApp',
	themeColor: '#6366f1'
});

// Pass `offline` to also precache an app shell and serve a fallback
// page when a navigation fails offline.
const sw = pushServiceWorker({
	icon: ICON,
	offline: { assetPrefix: '/assets/', fallback: '/offline.html' }
});

new Elysia()
	.get('/manifest.webmanifest', ({ set }) => {
		set.headers['content-type'] = 'application/manifest+json';

		return manifest;
	})
	.get('/sw.js', ({ set }) => {
		set.headers['content-type'] = 'text/javascript';
		set.headers['service-worker-allowed'] = '/';

		return sw;
	});

// VAPID sender — with empty/unset keys it no-ops, so push degrades
// gracefully to your email or in-app fallback.
const push = createWebPush({
	privateKey: process.env.VAPID_PRIVATE_KEY,
	publicKey: process.env.VAPID_PUBLIC_KEY,
	subject: 'mailto:you@example.com'
});

// Fan out to a user's devices; prune whatever it reports gone.
const { gone } = await push.sendMany(subscriptions, {
	body: 'Acme Co. just replied.',
	title: 'New match',
	url: '/inbox'
});
await pruneEndpoints(gone); // your storage

#Client

Partial snippet

Register the service worker and manage push subscriptions from the browser; you own the routes the results are posted to.

TS
import {
	getPushStatus,
	registerServiceWorker,
	subscribeToPush,
	unsubscribeFromPush
} from '@absolutejs/pwa/client';

// At boot:
await registerServiceWorker(); // defaults to "/sw.js"

// Toggle on: returns the subscription JSON — POST it to your own route.
const subscription = await subscribeToPush(vapidPublicKey);
await fetch('/push/subscribe', {
	body: JSON.stringify(subscription),
	method: 'POST'
});

// Toggle off: returns the endpoint to drop server-side.
const endpoint = await unsubscribeFromPush();
await fetch('/push/unsubscribe', {
	body: JSON.stringify({ endpoint }),
	method: 'POST'
});

const status = await getPushStatus();
// { supported, permission, subscribed }

#Install Prompt

Partial snippet

Capture the browser install signal and drive installation from your own button instead of the default mini-infobar.

TS
import {
	canInstall,
	initInstallPrompt,
	onInstallable,
	promptInstall
} from '@absolutejs/pwa/client';

initInstallPrompt(); // once at boot

// React to availability (show/hide your install button):
const off = onInstallable((installable) => setShowInstall(installable));

// From a click handler (must be a user gesture):
const accepted = await promptInstall();

#Embedded Browser Capabilities

Partial snippet

Feature-detect before offering browser-dependent actions, then use host-app identity only to explain an unavailable capability.

TS
import {
	detectEmbeddedBrowser,
	getBrowserCapabilities
} from '@absolutejs/pwa/client';

const capabilities = getBrowserCapabilities();
if (!capabilities.pushNotifications && capabilities.embeddedBrowser) {
	showOpenInBrowserHelp(capabilities.embeddedBrowser.app);
}

// Pure classification is available for SSR and tests.
detectEmbeddedBrowser(request.headers.get('user-agent') ?? '');
Bring your own routes and storage
Mounting is yours: serve the manifest as application/manifest+json and the service worker as text/javascript with a Service-Worker-Allowed header, from any framework. Subscriptions land in whatever storage you already use.

#API reference

Search the declarations exported by the current package type files. Expand a symbol to inspect its source-backed signature.

19 symbols
PwaIcontypePermalink
TS
type PwaIcon = {
    src: string;
    sizes: string;
    type: string;
    purpose?: string;
};
Exported from @absolutejs/pwa

Current package surface

What ships today

@absolutejs/pwav0.7.3 · betaFrontend & UXnpmSource
4entry points40symbols

Import surface · click to copy