rastrillo / pwa Public

Clone
git clone https://amadan.net/rastrillo/pwa

Plain git — no account needed to clone.

Download

Download this file

Rastrillo PWA

Add an installable app manifest, a public offline page and controlled worker updates to a web app. This optional module has no runtime dependencies. Web Push composes through aviso.

The kit does not store pages, API responses, messages, keys or pending writes. Offline navigation shows a public fallback. Offline reading and editing need an application data model and synchronisation contract; see docs/offline.md.

Try it

From this checkout:

cd examples/basic
go run .

Open http://127.0.0.1:8080, reload once after the worker has installed, then disconnect and reload. The worker returns an offline page with a retry link. Connect again and retry to return to the app. The sample serves valid PNG icons as placeholders; replace them with your app's icons.

The sample imports aviso's worker helper and wires notification handlers. It does not enrol subscriptions or send push messages. Follow aviso's own skill to provision its server and browser enrolment.

Add it to an app

Read SKILL.md. Install a reviewed version of amadan.net/rastrillo/pwa. Create pwa.Manifest with a permanent ID, name, start URL, scope ending in /, and your icons. Call .Handler() at boot and handle its error. Serve the manifest and worker assets without authentication redirects. Mount the returned handler at /manifest.webmanifest. Mount assets with:

mux.Handle("/pwa/", http.StripPrefix("/pwa", pwa.Assets()))

Add the manifest link, theme colour and Apple touch icon to the page head:

<link rel="manifest" href="/manifest.webmanifest">
<meta name="theme-color" content="#234d45">
<link rel="apple-touch-icon" href="/static/icon-180.png">

Serve your own /sw.js with Content-Type: text/javascript and Cache-Control: no-cache:

importScripts("/pwa/worker.js");
RastrilloPWA.install();

Then register it from your page's JavaScript:

import { register } from "/pwa/client.mjs";
await register({onUpdate: () => { updateNotice.hidden = false; }});

Define updateNotice in the app. Suggested text: “An update is ready. Save your work in all tabs, then close and reopen the app.” The helper neither reloads pages nor automatically activates a waiting worker. Browser support is detected; registration resolves to null without service workers.

activateUpdate(registration) explicitly asks a waiting worker to activate and returns whether there was one. Activation affects all tabs in its scope. Use it only when the app has resolved unsaved work across those tabs; handle controllerchange in the app if a reload is appropriate. Closing all tabs allows normal browser activation without this helper.

Worker contract

RastrilloPWA.install({offlineHTML}) attaches navigation and update-message handlers once. offlineHTML is optional, trusted build-time public HTML. The default is a self-contained English page. Provide an app-owned public translation if needed; neither account details nor keys belong in it.

Only same-origin, in-scope GET navigations are intercepted. A network failure returns the fallback with status 503 and Cache-Control: no-store. HTTP errors remain unchanged. API calls and mutations are untouched. The fallback permits inline styles but no scripts, forms or external resources.

The worker and imported scripts are persisted by the browser's worker installation; the kit never writes Cache Storage or IndexedDB. With no clients.claim, the first page stays uncontrolled until its next navigation. Removing the worker registration removes the fallback capability.

Use one worker registration per app scope. To add push, import aviso's helper into this same sw.js, attach its push/click/subscription-change handlers, and pass the same registration to its browser module. Read the version-pinned aviso skill for its authentication and key-rotation contracts. Keep notification payloads and encryption in the app.

Production needs HTTPS. Installation UI differs by browser; on iOS guide the person to add the app to the Home Screen and sign in inside that copy before enabling push. Registration alone does not prompt installation or grant notification permission.

Validation

make ci runs Go and JavaScript tests and a real Chromium/WebKit browser drive. Install the matching Playwright browsers first with npx playwright install chromium webkit after npm ci. Missing browsers fail the gate. The nested example is built and tested by the gate too.

The browser drive covers navigation failures, preservation of HTTP errors, API failure behaviour, empty caches and updates across two edited tabs. Chromium uses its offline switch; WebKit uses a dropped network connection because its automation switch can abort before calling the worker. Device installation and real iOS push delivery remain manual checks; this gate does not claim them.

MPL-2.0; see LICENSE.