Back to Knowledge Hub
Deployment

Service Worker Updates and Client Coherence

A practical model for discovering service worker updates, coordinating waiting and active versions, reloading controlled clients safely, and keeping rollback evidence.

BotBrowser Team

Documentation

Want the structured docs for Deployment?

This article lives in the editorial library. For step-by-step setup, reference material, and ongoing updates, jump into the docs section.

Flow from update discovery through installing and waiting states to activation, controller change, coordinated reload, validation, and rollback evidence

BotBrowser can create controlled browser contexts and expose the registration and client state that a test observes. It does not change the browser's service worker update algorithm, make a waiting worker active, or decide when a page is safe to reload. The application owns the coordination contract; BotBrowser supplies repeatable contexts and observation points.

TL;DR

An update is a sequence, not a single download. Discover the candidate with ServiceWorkerRegistration.update(), observe installing, waiting, and active states, identify which clients are controlled, and coordinate a reload only after the new version is ready for those clients. Record the old and new script identities, client states, user decision, and post-reload result. If the new version misbehaves, stop promoting it and restore the last known-good script, then verify the old version with fresh evidence. This workflow does not promise that every browser receives an update at the same moment.

Contents

Model the update states

The browser keeps an existing active service worker while it evaluates a candidate script. A registration can therefore expose three distinct references: installing while the candidate is being installed, waiting after installation has completed but before it can take over, and active for the version currently serving controlled clients. Treat these as observed states, not as synonyms for “the server has the new file.”

registration.update() asks the browser to check the registration's script URL for a newer version. The MDN reference documents the returned promise: it resolves with the registration after the check, and rejects when the update cannot be completed. A resolved promise is not proof that a new version was found or activated. Read registration.installing, registration.waiting, and registration.active after the promise, and subscribe to updatefound for a candidate that appears asynchronously.

The statechange event on the installing worker is the useful boundary for a user-facing update message. installing means that the candidate is not ready for a coordinated reload. With an existing active worker or controlled clients, installed can remain in waiting; during a first installation with no active worker, it proceeds to activating and activated. installed is not itself active. activating is a transition, not a stable release identity. activated is evidence that the registration has a new active worker, but a page may still need a reload before it becomes controlled.

Use a small state table in application code so each transition has one meaning:

Observed stateSafe interpretationNext action
installingA candidate is being preparedKeep the current page; wait for statechange
waitingCandidate is ready but existing clients may still use the old active versionShow a reload choice with a compatibility message
active with unchanged controllerRegistration is active, but this page may still be controlled by the previous versionWait for controllerchange or a deliberate next navigation
controllerchangeThis page's controlling version changedReconcile page state, then reload once if required

Do not infer state from a timestamp, a request that returned 200, or a new script URL alone. The browser can check at a different time, and a client can remain under the previous controller while another client has moved on.

Keep controlled clients coherent

navigator.serviceWorker.controller tells a page which active worker currently controls it, or is null when the page is not controlled. Capture the controller's script URL or a release identifier exposed by the application, alongside the registration state. This makes it possible to distinguish “candidate installed” from “this page is running under the candidate.”

An application with several tabs needs a single decision about when to move forward. One tab can observe waiting and broadcast a neutral “update ready” signal to the other controlled clients. Each tab should show the same release identifier and allow the person to postpone the reload. A tab that is busy with an unsaved form must not be reloaded just because another tab is ready.

The update message should carry facts, not commands that hide a transition: candidate release, current controller release, readiness state, and a request identifier. When a tab receives a message, it reads its own registration and controller again. This avoids acting on a stale message after a second update has arrived.

Compatibility is the deciding rule. A candidate that changes response formats, navigation assumptions, or page protocol should not take over a still-open page without a migration path. Mark the candidate as “reload together” when old and new page code cannot coexist. A candidate that is backward compatible can use a less disruptive prompt, but it still needs an explicit controller check.

BotBrowser helps by opening separate controlled contexts and capturing each page's registration state. It cannot make two contexts share a controller, and it cannot decide whether a product's page protocol is compatible. The test must state which clients are expected to move together and which may remain on the previous version.

Coordinate a safe reload

First, announce readiness in the page UI without forcing navigation. Include the candidate release and a short reason to reload. Keep the choice available until the page is safe: no unsaved form, upload, payment step, or other operation that would be lost. Provide a later navigation path for a person who chooses “later.”

Second, ask the page to confirm the state immediately before reloading. Read registration.waiting, the active worker, and navigator.serviceWorker.controller; then compare the identifiers with the message that opened the prompt. If they differ, close the prompt and run the check again instead of reloading on stale information.

Third, coordinate the transition. The page can listen for controllerchange, stop accepting new work, persist only the application state that the product already permits, and reload once. Guard the reload with a per-page flag so a burst of events does not create a loop. A page that receives controllerchange after it has already navigated should simply complete the navigation and record the resulting controller.

A minimal coordination sequence looks like this:

let reloadIssued = false;
let reloadApproved = false;

function approveReload() {
  reloadApproved = true;
}

navigator.serviceWorker.addEventListener('controllerchange', () => {
  if (reloadIssued || !reloadApproved) return;
  if (document.querySelector('[data-unsaved-work="true"]')) return;
  reloadIssued = true;
  window.location.reload();
});

async function checkForUpdate(registration) {
  await registration.update();
  return {
    installing: registration.installing?.state ?? null,
    waiting: Boolean(registration.waiting),
    active: registration.active?.scriptURL ?? null,
    controller: navigator.serviceWorker.controller?.scriptURL ?? null,
  };
}

The snippet observes state; it does not force activation. The activation policy must fit the page protocol and the user's current work. For critical forms, let the user finish first and verify the controller after the next navigation. For a read-only shell, an immediate choice may be appropriate when compatibility is proven.

Make rollback evidence useful

Rollback is a release decision backed by observations. Preserve the last known-good script identity, candidate identity, browser release, context identifier, and the exact journey that failed. Add timestamps for update discovery, activation, controller change, and reload completion. Redact account data and request bodies; the evidence needs state transitions, not user content.

Use a failure record with four checkpoints:

CheckpointEvidence to retainDecision
Before promotionKnown-good script identity and passing journey resultBaseline is restorable
Candidate discoveryupdate() result, registration states, candidate identityCandidate was actually observed
After reloadController identity, page release, journey resultClient moved coherently or did not
After rollbackRestored script identity and a fresh journey resultRollback is effective, not merely configured

When a journey fails, stop new promotion and compare the controller identity with the expected release. Restore the last known-good script through the normal deployment path, then open a fresh context and repeat the same journey. A page that was already controlled by the candidate may need a coordinated navigation before it can demonstrate the restored version; record that fact rather than calling the rollback complete early.

Do not treat “the old file is reachable” as rollback evidence. The evidence is a controlled client that reports the restored active version and passes the journey that failed. Keep the candidate artifact and failure record available for diagnosis, while the user-facing path serves only the accepted version.

Validate a release

Release validation should cover the complete sequence in a deterministic context:

  1. Load the baseline and record registration, active script, controller, and a representative journey result.
  2. Deploy the candidate script and call registration.update() from a page that is already controlled.
  3. Record installing, waiting, active, updatefound, and statechange observations until the candidate reaches a stable state.
  4. Open a second controlled client. Confirm whether it remains on the old controller or moves according to the declared compatibility rule.
  5. Choose reload in one client, capture controllerchange, and verify that the page reloads at most once.
  6. Repeat the journey and compare the result with the baseline. A mismatch pauses promotion.
  7. Run the rollback path with the same context matrix and retain the restored-version evidence.

BotBrowser can make these checks repeatable across controlled contexts and browser releases. It cannot certify an application's compatibility contract, choose a user's reload moment, or guarantee a universal update schedule. Those claims require product-specific evidence from the pages and release process.

Read the result without guessing.

The useful release record is intentionally small. For each context, write down the browser release, the registration script URL, the observed installing, waiting, and active states, the controller before and after the prompt, and the result of one representative journey. Add whether the person chose “now” or “later,” because a delayed choice is a valid outcome rather than a failed update. A record that contains only “update succeeded” cannot tell whether the candidate installed, became active, or actually controlled the page.

Compare identifiers, not just labels. A friendly version name can be reused or mistyped; a script URL with an immutable release identifier gives the page and the test something concrete to compare. If the application exposes a release value in its UI, capture that value beside the controller value. When they disagree, keep the client on its current page and repeat the state check. Do not turn an uncertain observation into a forced navigation.

The same rule applies to several clients. A second tab may discover the candidate later, may already be controlled by it, or may have no controller at all. Record each tab's state separately before declaring the group coherent. A context-level pass is useful only when the expected relationship between its clients is written down: for example, “all dashboard tabs reload after the same controller change” or “the editor tab waits until its draft is saved.”

Use a bounded retry for a transient check. Call update() again only after the previous promise settles, and keep the number of checks visible in the record. Repeated calls do not make activation faster, and an endless loop can hide a server or compatibility problem. When the bound is reached, retain the observed state and move to the release decision.

A compact acceptance checklist.

An update prompt is part of the application contract, so its wording should match the observed state. Say “A new version is ready” only when waiting or a new active identity was actually observed. Say “Checking for an update” while update() is pending. If the promise rejects, explain that the check could not finish and leave the current page usable. These messages keep a network or browser timing problem from being mistaken for a completed release.

The same distinction helps support teams. A report that says “the new script was downloaded” is incomplete unless it also names the registration state and the page controller. Ask for the release identity shown by the page, the controller identity read from the browser, and whether the page emitted controllerchange. Those three facts usually separate a candidate that is waiting from a candidate that has taken control.

Keep the reload choice reversible at the user-interface level. “Reload now” can be disabled while an upload or payment is in progress, while “Later” can remain available. The page can show the candidate identity again after the operation completes and re-check the registration before navigation. This is more predictable than silently reloading when focus changes or when another tab sends a message.

Do not let a background check mutate page state by itself. A timer may call update() at a product-defined interval, but the result should update an indicator and the registration snapshot, not discard the user's work. The browser may also perform its own checks during navigation or functional events. Treat both paths as observations that enter the same state model.

Release evidence benefits from a stable vocabulary. Use “candidate” for the installing or waiting version, “active” for the registration's active reference, and “controller” for the version that currently controls a page. Avoid calling all three “the current version.” A precise vocabulary makes a rollback conversation shorter and makes the same fixture useful across browser releases.

When a candidate is rejected, keep the user path on the accepted version while the release is investigated. Record the rejected identity and the state at which it failed, then repeat the failing journey after restoration. A later candidate should receive a new identity so that its evidence cannot be confused with the rejected attempt. This is a release hygiene rule, not a promise that the browser will discard every old observation immediately.

Finally, make the acceptance record readable by someone who did not run the test. Include one sentence describing the page journey, one line for each controller transition, and one explicit result for rollback. Avoid raw console dumps and sensitive page content. A compact record is easier to compare across contexts and easier to attach to a release decision.

QuestionPassing evidence
Was a candidate discovered?update() settled and the registration state was read afterward
Is the candidate ready?waiting or active was observed with its script identity
Which pages will move?Each controlled client has a recorded compatibility rule
Was reload safe?The page confirmed current identifiers and had no protected unsaved action
Did the page move once?One controllerchange, at most one reload, and a post-reload controller record
Can the change be reversed?The known-good identity and a fresh passing journey are retained

This checklist is evidence about one release and one declared context matrix. It is not a claim that another browser, profile, or page has the same state. Repeat the check whenever the script, page protocol, or browser release changes.

The boundary is useful in daily operations: BotBrowser can replay the observation, while the product team decides whether the page contract and the user's work are ready for the next version.

For a wider browser release baseline, see Browser Release Validation. For recovery after a disrupted session, see Browser Session Recovery After Network Interruption.

Practical conclusion

Treat service worker updates as coordination between a registration, a candidate, an active version, and the clients that it controls. Call update() to discover changes, read the actual states, make compatibility explicit, and let each client reload only after it confirms the transition. Keep rollback evidence tied to a real controller and a repeatable journey. BotBrowser is useful for reproducing those observations; it is not the owner of the update policy.

Sources

#Service Workers#Browser Updates#Release Validation#Client Coordination

Take BotBrowser from research to production

The guides cover the model first, then move into cross-platform validation, isolated contexts, and scale-ready browser deployment.