Skip to main content

Module sw

Module sw 

Source
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 with JS entries register /sw.js, which serves the built worker.
// After loading and validating the manifest, before binding:
sw::configure_for_manifest(ServiceWorker::for_app("ledger")?.enabled(), manifest)?;

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.

Static SSR pages emit no registration even when this setting is enabled. A static-only app therefore does not newly install a worker. Existing clients may still check the unchanged endpoint through the browser’s worker lifecycle.

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 it on the next JS-enabled page 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-9 and -, starting with a letter or digit; the same string in the build’s configuration and in ServiceWorker::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 of build) and carries no :. The forma: prefix (a colon, not v1’s hyphen) is new, so no v1 or 0.3.0 worker, all of which delete forma- 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 X reads only its own cache, and on activation deletes v1’s names and forma:X: caches other than its own; never another application’s (forma:Y:, forma:Xy:) and never a cache outside these patterns. It calls neither skipWaiting() nor clients.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 call skipWaiting(), 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§

AlreadyConfigured
configure was called after the setting was fixed: by an earlier configure, or by the first page or /sw.js request, which read the default.
AppNameError
An application name the cache-name contract cannot carry.
ServiceWorker
An application’s service-worker setting. See the module docs.

Enums§

ConfigureError
A refused startup setting; the current setting is never changed on error.

Functions§

configure
Configure an off worker at startup. Enabled workers require configure_for_manifest so application identity is checked before binding.
configure_for_manifest
Validate the manifest and match an enabled worker’s application before fixing the setting. Call at startup and refuse to bind on error. Validation does not read configured() or seal an unconfigured process’s default.
configured
This process’s setting: what configure set, or ServiceWorker::off. Reading it fixes it.
serve_sw
Axum handler: serve /sw.js with no-cache headers.