Events & Handler
Bswup reports its whole lifecycle through a single global handler function. Use the built-in progress UI, or write your own handler and drive any markup you like.
The handler's name is configured through the handler
script attribute (default bitBswupHandler). It receives (type, data) where
type is one of the BswupMessage constants:
Event catalog
| BswupMessage | Value | Raised when |
|---|---|---|
updateFound | UPDATE_FOUND | The browser found a new service worker version and started installing it. |
stateChanged | STATE_CHANGED | The installing worker's lifecycle state changed (data.currentTarget.state). |
downloadStarted | DOWNLOAD_STARTED | Asset download began. data.version, data.firstInstall. |
downloadProgress | DOWNLOAD_PROGRESS | An asset finished. data.percent (0-100), data.index (1-based count), data.asset (url, reqUrl, hash). |
downloadFinished | DOWNLOAD_FINISHED | All assets are staged. data.firstInstall, data.reload(), data.cleanup(). |
updateReady | UPDATE_READY | A fully staged update is waiting to be activated (data.reload()). |
activate | ACTIVATE | A new version activated (data.version). |
updateNotFound | UPDATE_NOT_FOUND | An update check completed and the app is already on the latest version. |
updateCheckFailed | UPDATE_CHECK_FAILED | An update check itself failed transiently (offline, server hiccup). Non-blocking - the app keeps running. |
error | ERROR | A structured install failure - see below. |
A complete handler
const appEl = document.getElementById('app');
const bswupEl = document.getElementById('bit-bswup');
const progressBar = document.getElementById('bit-bswup-progress-bar');
const reloadButton = document.getElementById('bit-bswup-reload');
function bitBswupHandler(type, data) {
switch (type) {
case BswupMessage.updateFound: return console.log('an update found.');
case BswupMessage.stateChanged: return console.log('state:', data.currentTarget.state);
case BswupMessage.activate: return console.log('new version activated:', data.version);
case BswupMessage.downloadStarted:
// A background update downloads behind the running app - only a first
// install owns the screen (firstInstall rides on every message).
if (data?.firstInstall === false) return;
appEl.style.display = 'none';
bswupEl.style.display = 'block';
return console.log('downloading assets started:', data?.version);
case BswupMessage.downloadProgress:
progressBar.style.width = `${Math.round(data.percent)}%`;
return console.log('asset downloaded:', data.asset.url, data);
case BswupMessage.downloadFinished:
if (data.firstInstall) {
// First install: claim + start Blazor, no page reload. Reveal the app even if
// the claim/start stalls or rejects - a hidden #app with no way out is worse
// than an app shown behind a stale splash.
const reveal = () => {
appEl.style.display = 'block';
bswupEl.style.display = 'none';
};
const failSafe = setTimeout(reveal, 10000);
data.reload()
.catch(err => console.error('Bswup first install failed to start:', err))
.then(() => { clearTimeout(failSafe); reveal(); });
} else {
// Update: let the user accept it.
reloadButton.style.display = 'block';
reloadButton.onclick = data.reload;
}
return console.log('downloading assets finished.');
case BswupMessage.updateReady:
reloadButton.style.display = 'block';
reloadButton.onclick = data.reload;
return console.log('new update ready.');
case BswupMessage.updateNotFound:
return console.log('already on the latest version.');
case BswupMessage.updateCheckFailed:
return console.warn('could not check for updates right now:', data);
case BswupMessage.error:
if (data.fatal === false) {
// lax tolerance: the install continued; the asset lazy-fills later.
return console.warn('Bswup asset skipped:', data.reason, data.message);
}
console.error('Bswup install error:', data.reason, data.message, data);
if (data.firstInstall) {
// Fatal first-install failure: Bswup force-starts Blazor from the network;
// reveal the app instead of leaving it booted behind the splash.
appEl.style.display = 'block';
bswupEl.style.display = 'none';
}
return;
}
}Finishing a download: reload and cleanup
data.reload()activates the staged version. On a first install it claims the clients and starts Blazor with no reload; on an update it performsSKIP_WAITINGand reloads.data.cleanup()(optional) asks the active service worker to prune this app's stale cache buckets right away. It is safe to call at any time - the worker declines while an update is staged or staging (pruning then happens automatically on activation), and it never touches another app's caches. Most apps never need it: the same pruning already runs on activation and after every accepted update.
BswupProgress component's AutoReload parameter
defaults to false: when an update finishes downloading, the reload button is shown and the
new version activates when the user accepts it - an unprompted reload discards whatever in-page state
the user has mid-session. Set AutoReload="true" to restore the old behavior. First
installs are unaffected: they always complete the seamless claim-and-start flow (no reload).
Multi-tab updates
Service workers are single-instance per origin, so accepting an update in one tab activates the new
version for every open tab. When that happens, Bswup has the new worker claim all clients and each
other tab reloads itself automatically (via controllerchange) onto the new
version. This keeps every tab consistent and avoids the classic failure where an old tab keeps running
old app code while its asset requests are served from the new version's cache (mismatched boot config /
DLL hashes). The first install is exempt: claiming a client for the first time starts Blazor and does
not trigger a reload.
The error payload
Install failures are structured:
| Field | Meaning |
|---|---|
reason |
One of manifest, integrity, fetch,
cache, request, install-incomplete,
install-aborted, or install-infra (the install died
before/while touching CacheStorage - storage pressure, a broken private mode - always
fatal).
|
message | Human-readable description. |
url / hash | The offending asset, when known. |
fatal |
Whether the install actually stopped. Under the default lax tolerance a
failed asset is reported with fatal: false - the install still succeeds and
the asset is fetched from the network on first use - so treat it as a warning, not a
dead app. fatal: true means no usable staged version is available to this
page - though a worker may still have been installed (a lax
install-infra failure resolves the install so the worker can keep serving
as a network pass-through).
|
firstInstall |
Where a fatal failure landed. true: before the app ever booted - Bswup
starts the app without a service worker so it still boots. false: a
background update failed - the app keeps running on the current version (the
previous worker keeps serving).
|
case BswupMessage.error:
if (data.fatal === false) {
console.warn('Bswup asset skipped:', data.reason, data.message, data);
return;
}
if (data.firstInstall === false) {
// A background update failed - the running app is untouched.
return;
}
// Fatal first-install failure: Bswup force-starts Blazor from the network;
// reveal the app instead of leaving it booted behind the splash.
appEl.style.display = 'block';
bswupEl.style.display = 'none';
return;