Files
suaveplan/packages/foundation/data/pwa/src/sync-queue/background-sync.md
2026-05-27 23:47:53 +01:00

2.6 KiB

sync-queue/background-sync.ts

Purpose

Bridges the standard W3C Background Sync API (SyncManager.register(tag)) into the @suaveplan/pwa package. Where the runtime supports it (every recent Chromium-based browser), the bridge registers a tag whose corresponding sync event the consumer's service worker handles. Where it does not (Firefox and Safari), the bridge falls back to a visibilitychange listener that invokes the supplied flush callback every time the tab transitions to the visible state — a close-enough approximation of "do work when the user comes back".

Features

  • Detects native support by inspecting the supplied registration for registration.sync rather than navigator.serviceWorker, so the bridge also works in worker contexts.
  • Visibility-change fallback fires exactly once per transition to visible (no spurious second invocations when the document is already visible).
  • unregister() removes the visibility listener; native registrations have no programmatic deregistration in the W3C spec, so the call is a no-op for that path.
  • tag getter exposes the currently-registered tag for diagnostic surfaces.

Usage: Basic

import { BackgroundSyncBridge } from "@suaveplan/pwa";

const bridge = new BackgroundSyncBridge({ registration, document });
await bridge.register("retry-pending", () => queue.process());

Usage: Advanced

Inspect the returned mode to differ telemetry per platform:

import { BackgroundSyncBridge } from "@suaveplan/pwa";

const bridge = new BackgroundSyncBridge({ registration, document });
const mode = await bridge.register("retry-pending", flushQueue);
metrics.bsyncRegistered.add(1, { mode });

API Reference

  • new BackgroundSyncBridge(options: BackgroundSyncBridgeOptions).
  • register(tag, flush): Promise<"native" | "visibility">.
  • unregister(): void.
  • tag: string | null.
  • SyncManagerLike, SyncCapableRegistration, VisibilityDocumentLike — injectable seams.

Implementation Notes

The visibility-fallback listener compares the current visibilityState against a private _lastState so it only fires when the document transitions into the visible state from any other state, not on the no-op event that fires when a tab is opened in the foreground. This keeps the flush callback's invocation count proportional to actual user activity rather than to the number of visibilitychange events the browser happens to dispatch. The register() body intentionally requires either a sync-capable registration or a document seam — passing neither is a programming error and surfaces as a thrown Error rather than silently no-opping.