BackgroundSync
Defer work until the user has connectivity. Wraps the Background Sync API (SyncManager) plus the related Periodic Background Sync - register tags from C# and let the service worker do the actual work when its sync event fires.
@inject Bit.Butil.BackgroundSync backgroundSyncMDN reference
IsSupported / IsPeriodicSupported before relying on them.
/sw.js first - its
sync handler logs every fired tag to the DevTools console.
IsSupported is true when the runtime exposes ServiceWorkerRegistration.sync; IsPeriodicSupported checks for ServiceWorkerRegistration.periodicSync.
var oneShot = await backgroundSync.IsSupported();
var periodic = await backgroundSync.IsPeriodicSupported();Background sync tags are stored on a service worker registration, so one must exist before Register or GetTags can succeed. This button registers the same minimal /sw.js used across this category.
Bit.Butil.ServiceWorker serviceWorker
<button @onclick="RegisterWorker">Register /sw.js</button>
{
private async Task RegisterWorker() => await serviceWorker.Register("/sw.js");
}// A sync fires when the page that registered it may well be gone, so the work has to live here.
self.addEventListener('install', () => self.skipWaiting());
self.addEventListener('activate', event => event.waitUntil(self.clients.claim()));Register queues a one-shot sync under a tag; the service worker's sync event fires for that tag once the device is online (immediately, if it already is). GetTags lists the tags currently registered. Registering the same tag twice coalesces into one pending sync.
Bit.Butil.BackgroundSync backgroundSync
{
private async Task Queue()
{
// Registering the same tag twice coalesces into one pending sync, so this is safe to call
// on every failed send rather than tracking whether one is already queued.
var ok = await backgroundSync.Register("sync-outbox");
string[] tags = await backgroundSync.GetTags();
}
}// The other half. The browser wakes the worker for the tag once the device is online - the page
// that registered it is usually closed by then, which is the whole point of registering it.
self.addEventListener('sync', event => {
if (event.tag !== 'sync-outbox') return;
// The tag stays registered and the browser retries later if this rejects, so the work has to be
// safe to run more than once.
event.waitUntil(sendOutbox());
});
async function sendOutbox() {
// Whatever the page queued - typically read back out of IndexedDB, which is the only storage a
// worker can reach.
const response = await fetch('/api/outbox', { method: 'POST', body: '[]' });
if (!response.ok) throw new Error('retry me');
}Periodic sync wakes the service worker at a browser-chosen cadence, never more often than the minimum interval you pass (in milliseconds). It requires the periodic-background-sync permission, which Chromium only grants to installed web apps (PWAs) with sufficient site engagement - expect a rejection when running this demo from a plain tab.
Bit.Butil.BackgroundSync backgroundSync
{
private async Task Schedule()
{
// minimum interval: 1 hour (the browser may extend it)
var ok = await backgroundSync.RegisterPeriodic("refresh-feed", 60 * 60 * 1000);
string[] tags = await backgroundSync.GetPeriodicTags();
}
private async Task Cancel() => await backgroundSync.UnregisterPeriodic("refresh-feed");
}// Fires at a cadence the browser chooses, never more often than the minimum interval - and only for
// an installed app the browser considers engaged enough. Treat it as an optimization, never as the
// only path by which content gets refreshed.
self.addEventListener('periodicsync', event => {
if (event.tag !== 'refresh-feed') return;
event.waitUntil(refreshFeed());
});
async function refreshFeed() {
const response = await fetch('/api/feed');
const cache = await caches.open('feed-v1');
await cache.put('/api/feed', response);
}{
"name": "Bit.Butil demo",
"short_name": "Butil",
"start_url": "/",
"display": "standalone"
}sync / periodicsync handlers - the demo worker simply logs the tag, so open
the DevTools console (and, in Chromium, DevTools, Application, Service workers, where you can also
dispatch a sync event manually) to observe it firing.
API reference
ValueTask<bool> IsSupported()ValueTask<bool> IsPeriodicSupported()ValueTask<bool> Register(string tag)ValueTask<string[]> GetTags()ValueTask<bool> RegisterPeriodic(string tag, long minInterval)ValueTask<string[]> GetPeriodicTags()ValueTask<bool> UnregisterPeriodic(string tag)