Expand description
The service worker: opt-in, per application, and retired everywhere else.
§The setting
A worker is an application’s choice, made once at start, before the first
page renders and before /sw.js is served:
use forma_server::sw::{self, ServiceWorker};
// Ledger: pages register /sw.js, which serves the worker its build wrote.
sw::configure(ServiceWorker::for_app("ledger")?.enabled())?;Left unconfigured, the setting is ServiceWorker::off: no page registers
a worker, and /sw.js serves the retiring worker in place of whatever
the build embedded. A browser that installed a worker from an earlier
release checks /sw.js for an update on its next visits, receives the
retiring worker, which deletes Forma’s caches and unregisters, and from then
on loads every page and asset from the network. This is the kill switch
that keeps turning the worker on a two-way door; it is served through 3.x.
Rolling back an application’s worker is turning .enabled() off and
keeping the name: its retiring worker then deletes that application’s own
caches too. Turning it back on re-installs the worker on the next visit.
§The cache-name contract (forma-server 0.2.2, @getforma/build 0.3.1)
The build writes the production worker and the cache names; forma-server writes the registration and the retiring worker. They meet here:
- Application name: 1 to 63 of
a-z,0-9and-, starting with a letter or digit; the same string in the build’s configuration and inServiceWorker::for_app. - Cache name:
forma:<app>:<build>, where<build>is the build’s identity as the build writes it (today the first 12 hex digits ofbuild_hash) and carries no:. Theforma:prefix (a colon, not v1’s hyphen) is new, so no v1 or 0.3.0 worker, all of which deleteforma-names, ever deletes one. - v1’s names (builds up to 0.3.0):
forma-dev,forma-and 12 lowercase hex digits, and either with-on-demand-v1; exactly^forma-(?:dev|[0-9a-f]{12})(?:-on-demand-v1)?$. They cannot be traced to an application, so every Forma worker retires them. - The production worker of application
Xreads only its own cache, and on activation deletes v1’s names andforma:X:caches other than its own; never another application’s (forma:Y:,forma:Xy:) and never a cache outside these patterns. It calls neitherskipWaiting()norclients.claim(): a new worker waits until the previous one controls no tab. - The retiring worker deletes v1’s names and, for a named application,
forma:X:caches; then unregisters. It answers no request. It does callskipWaiting(), as a kill switch must: an open tab of the old build is handed to it at once and loses the old worker’s cache, so its requests go to the network (and to the skew path if its build’s assets are gone). That is the trade made on purpose: a planted entry stops being served from the navigation after the browser’s update check, not when the last old tab closes.
§One origin, one setting
/sw.js and its scope / belong to the origin, so every process serving
one origin must agree on the setting: during a rolling deploy from 0.2.1 a
browser can alternate between registering and retiring until every
instance runs the new release; and of two applications behind one proxy on
one origin, only one can enable the worker, under its own name.
tests/js/retiring_worker.mjs holds the contract’s vectors as a table;
the build’s tests are to share them.
Structs§
- Already
Configured configurewas called after the setting was fixed: by an earlierconfigure, or by the first page or/sw.jsrequest, which read the default.- AppName
Error - An application name the cache-name contract cannot carry.
- Service
Worker - An application’s service-worker setting. See the module docs.
Functions§
- configure
- Fix this process’s service-worker setting. Call it once, at start, before
the first page renders and before
/sw.jsis served. - configured
- This process’s setting: what
configureset, orServiceWorker::off. Reading it fixes it. - serve_
sw - Axum handler: serve
/sw.jswith no-cache headers.