@swimmesberger/elarion-pwa
v0.3.2
Published
The installable-app shell for Elarion frontends: install-prompt capture with iOS Home Screen instructions, a deploy-update watcher that reloads only on a safe page change, renewal of an expired authenticating-proxy session, service-worker registration, a
Readme
@swimmesberger/elarion-pwa
The installable-app shell for Elarion frontends — what turns "Add to Home Screen" into an app that opens offline, loads its assets from the cache, and moves to a new deploy without throwing away what the user was typing. Guide: Installable apps (PWA).
- Install —
createInstallPrompt()captures Chromium'sbeforeinstallpromptand reportsavailability():'prompt' | 'ios' | 'none'(ios= show the "Share → Add to Home Screen" instructions).isStandalone(),isAppleTouchDevice()and the pureinstallOption()are exported too. - Updates —
createUpdateWatcher({ currentVersion })asks the server which web build is live when the app returns to the foreground and reloads into a new one on the next navigation that changed the path, never on the foreground return itself, never while a dialog is open or a field is focused, at most once a minute. - Session renewal —
createSessionRenewal()for an app behind an authenticating proxy (Cloudflare Access, IAP, oauth2-proxy): itsfetch(for the JSON-RPC client) marks calls so the proxy answers an expired session with401instead of a login redirect, itseventSource(for the client-events client) probes a failing stream, and an expired session reloads the page once so the proxy renews it — at most once a minute, thenonSignedOut. - Registration —
registerServiceWorker('/sw.js')withupdateViaCache: 'none'; resolvesnullinstead of throwing where there is no service worker. /sw—registerShellRouter(self, …): network-only or network-first navigations, cache-first hashed assets, stale-while-revalidate icons and manifest; never touches the API (ELARION_SERVER_PREFIXES), non-GET, cross-origin orno-storerequests, and never stores a redirected, opaque, partial,no-store, or (outside the shell) HTML answer. Composes withregisterWebPushHandlers(self)from@swimmesberger/elarion-webpush/swin the same worker./vite—appVersionFile({ version, define })emits/app-version.jsonand bakes the same version into the bundle.
Page
import { createInstallPrompt, createSessionRenewal, createUpdateWatcher, registerServiceWorker } from '@swimmesberger/elarion-pwa'
if (import.meta.env.PROD) void registerServiceWorker('/sw.js')
export const install = createInstallPrompt() // at module load: the prompt event fires early
export const updates = createUpdateWatcher({ currentVersion: __APP_VERSION__, enabled: import.meta.env.PROD })
router.subscribe('onResolved', (event) => updates.navigationResolved({ pathChanged: event.pathChanged }))
// Behind an authenticating proxy:
const session = createSessionRenewal({ onSignedOut: () => showSignedOut() })
export const rpc = createRpcApi({ url: '/rpc', fetch: session.fetch })
export const events = createElarionEvents({ url: '/events', eventSource: session.eventSource })Service worker
import { ELARION_SERVER_PREFIXES, registerShellRouter } from '@swimmesberger/elarion-pwa/sw'
import { registerWebPushHandlers } from '@swimmesberger/elarion-webpush/sw'
registerShellRouter(self, {
cacheName: 'myapp-v1',
serverPrefixes: [...ELARION_SERVER_PREFIXES, '/auth'],
navigation: { mode: 'network-only', offlineUrl: '/offline.html' }, // or { mode: 'network-first' } for an SPA shell
staticFiles: ['/manifest.webmanifest', '/icon-192.png', '/icon-512.png', '/apple-touch-icon.png'],
})
registerWebPushHandlers(self, { icon: '/icon-192.png' })The module is ESM: bundle the worker into one classic script served at /sw.js (for example a second
vite build with build.lib.formats: ['iife']), or register it with { type: 'module' }.
Vite
import { appVersionFile } from '@swimmesberger/elarion-pwa/vite'
const version = process.env.APP_VERSION?.trim() || 'dev'
export default defineConfig({ plugins: [appVersionFile({ version, define: '__APP_VERSION__' })] })