Files
skills/docs/foundation/data/packer-browser/src/service-worker/service-worker.md
2026-05-29 08:20:56 +01:00

3.4 KiB

service-worker

(no submodules)

Purpose

service-worker.ts is the production Service Worker entrypoint exposed via the /service-worker subpath. Its only public function, runPackedResourceServiceWorker(scope, opts?), registers four handlers — install, activate, fetch, and message — against the supplied scope. In production the scope is the real ServiceWorkerGlobalScope; in tests it is the in-memory shim from sw-shim.ts. Cluster A intentionally caches the packed bytes in the SW (un-tagged) and lets the PackedResourceManager on the page do the unpack; Cluster B adds SW-side unpack with range support.

Features

  • install → scope.skipWaiting() so the new worker activates on the first navigation rather than waiting for every tab to close.
  • activate → deletes every cache whose name is not the configured cacheName, then calls scope.clients.claim() so existing tabs start routing through the new worker immediately.
  • fetch → matches packedPatterns (default [/\.packed$/, /\?packed=true/]) and intercepts only those URLs. Cache hits are served verbatim; misses trigger an upstream fetch and a cache write tagged X-Unpacked: true.
  • message → handles CACHE_CLEAR (deletes the cache) and CACHE_STATUS (replies with { cacheSize, urls } on event.ports[0]).
  • Injectable fetch for tests — no vi.mock required.

Usage: Basic

In your production SW bundle (service-worker.js):

import { runPackedResourceServiceWorker } from "@suaveplan/packer-browser/service-worker";

runPackedResourceServiceWorker(
    self as unknown as ServiceWorkerGlobalScope
);

Usage: Advanced

import {
    createServiceWorkerShim,
    runPackedResourceServiceWorker,
} from "@suaveplan/packer-browser/service-worker";

const scope = createServiceWorkerShim();
runPackedResourceServiceWorker(scope, {
    cacheName: "test-cache",
    packedPatterns: [/^https:\/\/cdn\.example\.com\/.+\.packed/],
    fetch: async (url) => new Response(testBytes),
});

const request = new Request("https://cdn.example.com/a.packed");
const fetchEvent = makeFetchEventLike(request);
scope.dispatchEvent(fetchEvent);
const response = await fetchEvent.responsePromise;
// response.headers.get("X-Unpacked") === "true"

API Reference

  • runPackedResourceServiceWorker(scope, opts?: RunServiceWorkerOptions): void.
  • RunServiceWorkerOptions:
    • cacheName?: string — default "packed-resources-v1".
    • packedPatterns?: ReadonlyArray<RegExp> — default [/\.packed$/, /\?packed=true/].
    • fetch?: FetchLike — default globalThis.fetch.
  • Errors raised inside the fetch handler are PackerBrowserError with LOAD_FAILED (network) or RESOURCE_NOT_FOUND (non-2xx) codes; they propagate out of respondWith as a rejected promise.

Implementation Notes

The SW deliberately does not call into PackedResourceManager from inside its scope — the manager owns Blob URL allocation, which is a page-scope concern (a SW that calls URL.createObjectURL produces URLs the page cannot resolve). The SW's role is purely to cache the packed bytes and short-circuit re-fetch. The page-side manager sees the cached response (or a fresh network response) and runs the unpack. This split keeps the SW small enough to register on a flaky connection without bundling the entire Unpacker pipeline.