Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
825d8af
apply Bswup improvements #12408
msynk May 31, 2026
c646784
further improvements
msynk Jun 1, 2026
733a05a
resolve review comments
msynk Jun 2, 2026
de9c330
resolve review comments II
msynk Jun 4, 2026
23b29b3
resolve review comments III
msynk Jun 5, 2026
088c6d1
resolve review comments IV
msynk Jun 6, 2026
c161919
Merge branch 'develop' into 12408-bswup-improvements
msynk Jun 6, 2026
8c11506
resovle review comments V
msynk Jun 6, 2026
ea3d330
resolve review comments VI
msynk Jun 6, 2026
a71d04f
remove json serializer
msynk Jun 6, 2026
ddd6829
fix progress component
msynk Jun 6, 2026
9d73fbf
resolve local review findimgs
msynk Jun 14, 2026
6c96a71
Merge branch '12408-bswup-improvements' of https://github.com/msynk/b…
msynk Jun 14, 2026
ce3cd61
Merge branch 'develop' into 12408-bswup-improvements
msynk Jun 16, 2026
5377236
resolve review comments V
msynk Jun 21, 2026
a450c91
Merge branch 'develop' into 12408-bswup-improvements
msynk Jun 21, 2026
d6f1bd1
resolve review comments VI
msynk Jun 22, 2026
bd800d8
resolve review comments VII
msynk Jun 27, 2026
3623e25
Merge branch 'develop' into 12408-bswup-improvements
msynk Jun 27, 2026
754eb63
resolve review comments VIII
msynk Jun 27, 2026
70d385e
fix build issues
msynk Jun 28, 2026
a1ee5c5
resolve review comments IX
msynk Jun 28, 2026
f92cdaa
resolve review comments X
msynk Jun 28, 2026
20addce
resolve review comments XI
msynk Jun 28, 2026
73401e9
fix variable definition issue
msynk Jun 28, 2026
7721833
restructure demo projects
msynk Jun 29, 2026
3e6197a
improve FullDemo
msynk Jun 30, 2026
021176a
Merge branch 'develop' into 12408-bswup-improvements
msynk Jun 30, 2026
571f537
resolve review comments XII
msynk Jul 3, 2026
968fc99
resolve review comments XIII
msynk Jul 3, 2026
4a55113
Merge branch 'develop' into 12408-bswup-improvements
msynk Jul 6, 2026
3431b3c
add regex support for asset urls
msynk Jul 7, 2026
dea4803
Merge branch 'develop' into 12408-bswup-improvements
msynk Jul 20, 2026
4bd6741
fix local findings
msynk Jul 20, 2026
11c8887
add tests
msynk Jul 20, 2026
49b8e8e
add fetch resiliency
msynk Jul 21, 2026
e07870b
resolve local review findings
msynk Jul 23, 2026
4ee615c
resolve local review findings II
msynk Jul 24, 2026
6dd9fb9
resolve local findings in loop
msynk Jul 24, 2026
ae73eb8
resolve local loop findings
msynk Jul 25, 2026
3ddd5a1
fix FullDemo app
msynk Jul 28, 2026
3a63009
merge demo projects
msynk Jul 28, 2026
075c94d
rename BasicDemo
msynk Jul 28, 2026
0cc0a2b
add document website project
msynk Jul 28, 2026
6c294e8
add
msynk Jul 28, 2026
c04b464
fix version badge
msynk Jul 28, 2026
dc36feb
fix port numbers
msynk Jul 28, 2026
e503f15
Merge branch 'develop' of https://github.com/bitfoundation/bitplatfor…
msynk Jul 28, 2026
7bcb248
resolve review comments 15
msynk Jul 28, 2026
f718b2c
resolve review comments
msynk Jul 28, 2026
73ecd0d
resolve review comments 17
msynk Jul 28, 2026
b7269d0
resolve review comments 18
msynk Jul 28, 2026
a41f645
resolve review comments 19
msynk Jul 29, 2026
8e970da
resolve review comments 20
msynk Jul 29, 2026
03525eb
resolve review comments 21
msynk Jul 29, 2026
d8e7aa8
resolve review comments 22
msynk Jul 29, 2026
66a374c
resolve review comments 23
msynk Jul 29, 2026
f39a481
resolve review comments 24
msynk Jul 29, 2026
8953762
resolve review comments 25
msynk Jul 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/bit.ci.Bswup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,11 @@ jobs:

- name: dotnet build
run: dotnet build src/Bswup/Bit.Bswup.slnx

# Runs against Bit.Bswup/wwwroot/*.js, which the build above produces, so this also covers
# the build step itself (e.g. the minifier re-introducing downleveled syntax).
- name: JS tests (service worker, page script, progress UI)
run: |
cd src/Bswup/Tests/bit-bswup-js
npm ci
npm test
11 changes: 7 additions & 4 deletions src/Bit.CI.Release.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,15 @@
</Folder>
<Folder Name="/Bswup/">
<File Path="Bswup/README.md" />
<Project Path="Bswup/Bit.Bswup.Demo/Bit.Bswup.Demo.csproj" />
<Project Path="Bswup/Bit.Bswup/Bit.Bswup.csproj" />
<Project Path="Bswup/Bit.Bswup.Demo/Bit.Bswup.Demo.csproj" />
</Folder>
<Folder Name="/Bswup/Demos/">
<Project Path="Bswup/Demos/BasicDemo/Bit.Bswup.BasicDemo.csproj" />
</Folder>
<Folder Name="/Bswup/FullDemo/">
<Project Path="Bswup/FullDemo/Client/Bit.Bswup.Demo.Client.csproj" />
<Project Path="Bswup/FullDemo/Server/Bit.Bswup.Demo.Server.csproj" />
<Folder Name="/Bswup/Demos/FullDemo/">
<Project Path="Bswup/Demos/FullDemo/Client/Bit.Bswup.FullDemo.Client.csproj" />
<Project Path="Bswup/Demos/FullDemo/Server/Bit.Bswup.FullDemo.Server.csproj" />
</Folder>
<Folder Name="/Brouter/">
<Project Path="Brouter/Bit.Brouter/Bit.Brouter.csproj" />
Expand Down
13 changes: 7 additions & 6 deletions src/Bit.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,15 @@
</Folder>
<Folder Name="/Bswup/">
<File Path="Bswup/README.md" />
<Project Path="Bswup/Bit.Bswup.Demo/Bit.Bswup.Demo.csproj" />
<Project Path="Bswup/Bit.Bswup.NewDemo/Bit.Bswup.NewDemo.Client/Bit.Bswup.NewDemo.Client.csproj" />
<Project Path="Bswup/Bit.Bswup.NewDemo/Bit.Bswup.NewDemo/Bit.Bswup.NewDemo.csproj" />
<Project Path="Bswup/Bit.Bswup/Bit.Bswup.csproj" />
<Project Path="Bswup/Bit.Bswup.Demo/Bit.Bswup.Demo.csproj" />
</Folder>
<Folder Name="/Bswup/Demos/">
<Project Path="Bswup/Demos/BasicDemo/Bit.Bswup.BasicDemo.csproj" />
</Folder>
<Folder Name="/Bswup/FullDemo/">
<Project Path="Bswup/FullDemo/Client/Bit.Bswup.Demo.Client.csproj" />
<Project Path="Bswup/FullDemo/Server/Bit.Bswup.Demo.Server.csproj" />
<Folder Name="/Bswup/Demos/FullDemo/">
<Project Path="Bswup/Demos/FullDemo/Client/Bit.Bswup.FullDemo.Client.csproj" />
<Project Path="Bswup/Demos/FullDemo/Server/Bit.Bswup.FullDemo.Server.csproj" />
</Folder>
<Folder Name="/Brouter/">
<File Path="Brouter/README.md" />
Expand Down
27 changes: 16 additions & 11 deletions src/Bswup/Bit.Bswup.Demo/App.razor
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
<LayoutView>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<p role="alert">Sorry, there's nothing at this address.</p>
</NotFound>
</Router>
</LayoutView>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<PageTitle>Not found - bit Bswup</PageTitle>
<div class="docs-not-found">
<h1>404</h1>
<p role="alert">Sorry, there's nothing at this address.</p>
<a class="button-primary" href="/">Back to the docs</a>
</div>
</LayoutView>
</NotFound>
Comment thread
msynk marked this conversation as resolved.
</Router>
2 changes: 1 addition & 1 deletion src/Bswup/Bit.Bswup.Demo/Bit.Bswup.Demo.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@
<ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" />
</ItemGroup>

</Project>
</Project>
52 changes: 52 additions & 0 deletions src/Bswup/Bit.Bswup.Demo/Pages/CleanupPage.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
@page "/cleanup"

<PageTitle>Backing Out of Bswup - bit Bswup</PageTitle>

<h1>Backing Out of Bswup</h1>
<p class="docs-lead">
Fully remove Bswup from a deployed app - dropping offline support, or recovering clients stuck on a
broken worker or cache - with the self-destructing cleanup worker.
</p>

<p>
Replace the <em>content</em> of your <code>service-worker.js</code> <strong>and</strong>
<code>service-worker.published.js</code> (the file deployed builds actually ship, via the
<code>ServiceWorker</code> item's <code>PublishedContent</code> mapping) with:
</p>
<CodeBlock Language="javascript" Code="self.importScripts('_content/Bit.Bswup/bit-bswup.sw-cleanup.js');" />

<h2>What happens next</h2>
<p>On its next update check, every client installs this self-destructing worker instead. It:</p>
<ol>
<li>activates immediately,</li>
<li>purges this app's Bswup and Blazor caches,</li>
<li>unregisters its own registration, and</li>
<li>signals open tabs to detach.</li>
</ol>
<p>
Tabs the previous worker controlled reload once; everything afterwards runs purely from the network,
even while the page keeps referencing <code>bit-bswup.js</code> - each later load just repeats the
register/self-unregister cycle silently, with no reloads.
</p>
<Callout Type="note" Title="When can the script tag go?">
Once no client has loaded the old app for as long as your cache headers require, the
<code>bit-bswup.js</code> script tag can be removed from the host document too.
</Callout>

<h2>Client-side reset without backing out</h2>
<p>
If you only need to recover a <em>single</em> broken client (not remove Bswup from the deployment),
prefer <a href="javascript-api#forcerefresh"><code>BitBswup.forceRefresh()</code></a> - it clears this
app's caches, unregisters its worker, and reloads, without touching the deployed service worker file.
</p>

<div class="pager">
<a href="playground">
<span class="pager-label">Previous</span>
<b>← Live Playground</b>
</a>
<a class="pager-next" href="migration">
<span class="pager-label">Next</span>
<b>Migrating to 10-6-0 →</b>
</a>
</div>
220 changes: 220 additions & 0 deletions src/Bswup/Bit.Bswup.Demo/Pages/EventsPage.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
@page "/events"

<PageTitle>Events &amp; Handler - bit Bswup</PageTitle>

<h1>Events &amp; Handler</h1>
<p class="docs-lead">
Bswup reports its whole lifecycle through a single global handler function. Use the built-in
<a href="progress-ui">progress UI</a>, or write your own handler and drive any markup you like.
</p>

<p>
The handler's name is configured through the <a href="script-options#handler"><code>handler</code></a>
script attribute (default <code>bitBswupHandler</code>). It receives <code>(type, data)</code> where
<code>type</code> is one of the <code>BswupMessage</code> constants:
</p>

<h2>Event catalog</h2>
<div class="docs-table-wrapper">
<table>
<thead>
<tr><th>BswupMessage</th><th>Value</th><th>Raised when</th></tr>
</thead>
<tbody>
<tr><td><code>updateFound</code></td><td><code>UPDATE_FOUND</code></td><td>The browser found a new service worker version and started installing it.</td></tr>
<tr><td><code>stateChanged</code></td><td><code>STATE_CHANGED</code></td><td>The installing worker's lifecycle state changed (<code>data.currentTarget.state</code>).</td></tr>
<tr><td><code>downloadStarted</code></td><td><code>DOWNLOAD_STARTED</code></td><td>Asset download began. <code>data.version</code>, <code>data.firstInstall</code>.</td></tr>
<tr><td><code>downloadProgress</code></td><td><code>DOWNLOAD_PROGRESS</code></td><td>An asset finished. <code>data.percent</code> (0-100), <code>data.index</code> (1-based count), <code>data.asset</code> (<code>url</code>, <code>reqUrl</code>, <code>hash</code>).</td></tr>
<tr><td><code>downloadFinished</code></td><td><code>DOWNLOAD_FINISHED</code></td><td>All assets are staged. <code>data.firstInstall</code>, <code>data.reload()</code>, <code>data.cleanup()</code>.</td></tr>
<tr><td><code>updateReady</code></td><td><code>UPDATE_READY</code></td><td>A fully staged update is waiting to be activated (<code>data.reload()</code>).</td></tr>
<tr><td><code>activate</code></td><td><code>ACTIVATE</code></td><td>A new version activated (<code>data.version</code>).</td></tr>
<tr><td><code>updateNotFound</code></td><td><code>UPDATE_NOT_FOUND</code></td><td>An update check completed and the app is already on the latest version.</td></tr>
<tr><td><code>updateCheckFailed</code></td><td><code>UPDATE_CHECK_FAILED</code></td><td>An update check itself failed transiently (offline, server hiccup). Non-blocking - the app keeps running.</td></tr>
<tr><td><code>error</code></td><td><code>ERROR</code></td><td>A structured install failure - see <a href="#error-payload">below</a>.</td></tr>
</tbody>
</table>
</div>

<h2>A complete handler</h2>
<CodeBlock Language="javascript" Code="@HandlerCode" />

<h2 id="download-finished">Finishing a download: <code>reload</code> and <code>cleanup</code></h2>
<ul>
<li>
<code>data.reload()</code> activates the staged version. On a <strong>first install</strong> it
claims the clients and starts Blazor <em>with no reload</em>; on an <strong>update</strong> it
performs <code>SKIP_WAITING</code> and reloads.
</li>
<li>
<code>data.cleanup()</code> (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.
</li>
</ul>

<Callout Type="warning" Title="Breaking change: updates no longer auto-reload by default">
Since v-10-6-0, the built-in <code>BswupProgress</code> component's <code>AutoReload</code> parameter
defaults to <code>false</code>: 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 <code>AutoReload="true"</code> to restore the old behavior. First
installs are unaffected: they always complete the seamless claim-and-start flow (no reload).
</Callout>

<h2>Multi-tab updates</h2>
<p>
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
<em>other</em> tab reloads itself automatically (via <code>controllerchange</code>) 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.
</p>

<h2 id="error-payload">The <code>error</code> payload</h2>
<p>Install failures are structured:</p>
<div class="docs-table-wrapper">
<table>
<thead>
<tr><th>Field</th><th>Meaning</th></tr>
</thead>
<tbody>
<tr>
<td><code>reason</code></td>
<td>
One of <code>manifest</code>, <code>integrity</code>, <code>fetch</code>,
<code>cache</code>, <code>request</code>, <code>install-incomplete</code>,
<code>install-aborted</code>, or <code>install-infra</code> (the install died
before/while touching CacheStorage - storage pressure, a broken private mode - always
fatal).
</td>
</tr>
Comment thread
msynk marked this conversation as resolved.
<tr><td><code>message</code></td><td>Human-readable description.</td></tr>
<tr><td><code>url</code> / <code>hash</code></td><td>The offending asset, when known.</td></tr>
<tr>
<td><code>fatal</code></td>
<td>
Whether the install actually stopped. Under the default <code>lax</code> tolerance a
failed asset is reported with <code>fatal: false</code> - 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. <code>fatal: true</code> means no usable staged version is available to this
page - though a worker may still have been installed (a lax
<code>install-infra</code> failure resolves the install so the worker can keep serving
as a network pass-through).
</td>
</tr>
<tr>
<td><code>firstInstall</code></td>
<td>
Where a fatal failure landed. <code>true</code>: before the app ever booted - Bswup
starts the app without a service worker so it still boots. <code>false</code>: a
background <em>update</em> failed - the app keeps running on the current version (the
previous worker keeps serving).
</td>
</tr>
</tbody>
</table>
</div>
<CodeBlock Language="javascript" Code="@ErrorHandlerCode" />

<div class="pager">
<a href="service-worker">
<span class="pager-label">Previous</span>
<b>← Service Worker Settings</b>
</a>
<a class="pager-next" href="progress-ui">
<span class="pager-label">Next</span>
<b>Progress UI →</b>
</a>
</div>

@code {
private const string HandlerCode = @"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;
}
}";

private const string ErrorHandlerCode = @"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;";
}
Loading
Loading