Skip to content

Add option to enable service worker#2062

Merged
victorlin merged 2 commits into
masterfrom
victorlin/service-worker
Jul 2, 2026
Merged

Add option to enable service worker#2062
victorlin merged 2 commits into
masterfrom
victorlin/service-worker

Conversation

@victorlin

@victorlin victorlin commented Jun 4, 2026

Copy link
Copy Markdown
Member

Use Workbox's webpack plugin to install a simple service worker that enables offline use of the index page.

This is added primarily for auspice.us, but should work for any Auspice server where assets are served at the root path. Gated behind a build-time env var setting to prevent unintended service worker installation (e.g. on nextstrain.org).

Review threads

Checklist

@victorlin

victorlin commented Jun 4, 2026

Copy link
Copy Markdown
Member Author

auspice.us review app works:

  1. Open the review app to "install" the service worker
  2. Disconnect from internet
  3. Open the review app in a new tab/window
  4. Drag/drop an Auspice JSON

@victorlin victorlin force-pushed the victorlin/service-worker branch from f86ffe5 to b28ad85 Compare June 11, 2026 23:36
@victorlin victorlin force-pushed the victorlin/service-worker branch 2 times, most recently from 0417918 to f169a23 Compare June 26, 2026 20:44
@victorlin victorlin force-pushed the victorlin/service-worker branch 2 times, most recently from 1fa0c40 to 8edb28c Compare June 26, 2026 22:26
@victorlin victorlin changed the title Add service worker Add option to enable service worker Jun 26, 2026
@victorlin victorlin force-pushed the victorlin/service-worker branch 3 times, most recently from 7bf5e3e to 5a56fc0 Compare June 29, 2026 18:59
@victorlin

victorlin commented Jun 29, 2026

Copy link
Copy Markdown
Member Author

Demo using auspice.us (0:00-0:25) vs. review app (0:25-0:53):

service.worker.demo.mov

@victorlin victorlin marked this pull request as ready for review June 29, 2026 19:20

@jameshadfield jameshadfield left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed this with claude as I'm not an expert on service workers. It seems to work well (localhost & the auspice.us review app. I've read stories which make me wary of them due to the difficulty in undoing mistakes that have gone out, but I do see the advantage for auspice.us. Some general comments on the PR

  1. We need more dev docs - especially around what requests will be intercepted (bundles defined in index.html and all navigation requests in the domain) and which won't (e.g. map tiles, charon), how to test them, how to remove them etc.
  2. If a user has an auspice.us tab open (with the service worker running), then refreshing the tab won't get an updated auspice.us version as far as I can tell. Something to be aware of, but probably not a deal breaker for auspice.us
  3. Is there any anticipated use-case beyond auspice.us? The worker won't handle the charon API, so at best it's giving an auspice error page. Should we use navigateFallbackAllowlist: [/^\/(\?.*)?$/], so that it only works for the root URL?

Comment thread webpack.config.js
Comment thread CHANGELOG.md Outdated
Comment thread src/index.js
Comment on lines +38 to +40
navigator.serviceWorker.register('/service-worker.js')
// Check for updates on each network-connected page load.
.then((registration) => registration.update())

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a user has an auspice.us tab open (with the service worker running), then refreshing the tab won't get an updated auspice.us version as far as I can tell.

It should! This is controlled by the precache, which contains all JS bundles and assets. You can see the full list in the compiled service-worker.js file which looks like:

function(e) {
    C().precache(e)
}([{
    url: "/dist/09172b1910dff418491f.woff2",
    revision: null
}, {
// ...
    url: "/dist/auspice.chunk.163.bundle.f2dc78be857ccb8ba92e.js",
    revision: null
}, {
// ...
    url: "/dist/index.html",
    revision: "4cb1bfc61bc2a453d2d5"
}]),

and is checked on each network-connected page load:

auspice/src/index.js

Lines 38 to 40 in 5a56fc0

navigator.serviceWorker.register('/service-worker.js')
// Check for updates on each network-connected page load.
.then((registration) => registration.update())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that while registration.update() runs on a page refresh, if it updates the service worker (from A to B) then B becomes ready in a "waiting" state but the tab continues to use A. I believe index.html will also stay version A, because service worker A just does cache.match() without any revalidation. I don't have enough experience with service workers to give concrete advice here. Maybe this is all just fine?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added skipWaiting: true so that B will immediately take control in all existing tabs.

auspice/webpack.config.js

Lines 131 to 133 in 56e3f03

// Control all clients immediately (no reload required).
clientsClaim: true,
skipWaiting: true,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. For completeness, this is what can go wrong with skipWaiting: true but I don't think it applies to us as requests will fallthrough to the network if not handled by the service worker

If version-A HTML lazy-loads a hashed chunk after SW B has activated — say a dynamic import, or an image the A page requests on interaction — that request goes to SW B, which no longer has version A's assets in precache. If your precache-only strategy returns nothing for an unknown URL (and the network copy is also gone because you shipped B), that fetch fails. This is the classic "skipWaiting breaks in-flight pages" hazard, and it's specifically a problem for cache-first/precache setups with content-hashed assets.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this does apply to us:

the network copy is also gone because you shipped B

though it's very specifically for when someone has an old tab open, opens a new tab, and tries to use the old tab again without refreshing*.

I guess this is why clientsClaim and skipWaiting default to false. There is a flip side though, because the browser can only have one active service worker per origin: for the user who has an old tab open, they'll be stuck with the old version on the new tab too, until all tabs are closed to end the waiting phase.

In our case, I think it's better that new tabs use the latest version while breaking old tabs, than to have all tabs stuck on the old version. I'll keep both options set to true.

* could be mitigated by auto-refresh on ChunkLoadError?

Comment thread webpack.config.js Outdated
Comment thread docs/customise-client/api.rst Outdated
@victorlin victorlin force-pushed the victorlin/service-worker branch from 5a56fc0 to 56e3f03 Compare June 30, 2026 23:44

@joverlee521 joverlee521 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will use my own time to understand service workers, but the user facing changes, both the offline behavior and the envvar to enable service workers, look good to me.

Comment thread CHANGELOG.md Outdated
@victorlin

Copy link
Copy Markdown
Member Author

I'll hold off merging until at least after dev chat tomorrow.

Pushed some tests for the functionality: 8f58dd8

victorlin added 2 commits July 2, 2026 15:49
Use Workbox's webpack plugin to install a simple service worker that
enables offline use of the index page.

This is added primarily for auspice.us, but should work for any Auspice
server where assets are served at the root path. Gated behind a
build-time env var setting to prevent unintended service worker
installation (e.g. on nextstrain.org).
Simplifies configuration of workbox-webpack-plugin (see removed TODO).

Of all the breaking changes¹, only the script loading behavior should
apply to Auspice. Preserving existing behavior with
scriptLoading='blocking'. We may want to consider switching to the new
default ('defer') in the future.

¹ https://github.com/jantimon/html-webpack-plugin/blob/v5.0.0/CHANGELOG.md#-breaking-changes
@victorlin victorlin force-pushed the victorlin/service-worker branch from 8f58dd8 to 949ee54 Compare July 2, 2026 22:51
@victorlin

Copy link
Copy Markdown
Member Author

Tests in 8f58dd8 significantly changed CI run time: +1m of smoke testing and +2m of unit testing (before and after).

I've dropped that commit in the rebase and will open a separate PR to explore ideas for testing.

@victorlin victorlin merged commit 9308a5c into master Jul 2, 2026
18 checks passed
@victorlin victorlin deleted the victorlin/service-worker branch July 2, 2026 23:39
@victorlin victorlin mentioned this pull request Jul 3, 2026
3 tasks
@jameshadfield jameshadfield mentioned this pull request Jul 6, 2026
1 task
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants