schollz/croc · error · Error

Streaming browser downloads are unavailable

Error message

Streaming browser downloads are unavailable

What it means

streamingWorker() lazily registers the download service worker that enables streamed file writes; it throws immediately when navigator.serviceWorker or MessageChannel is unavailable. Without those APIs the browser cannot intercept download URLs or pump chunks cross-context, so streaming downloads cannot be offered at all.

Source

Thrown at web/src/protocol/storage.ts:176

  async commit() {
    if (!this.blob) throw new Error("Destination must be finalized before download");
    if (this.committed) return;
    this.committed = true;
    this.onDownload(this.name, this.blob);
  }

  async abort() {
    this.chunks.clear();
    this.blob = undefined;
  }
}

let downloadWorker: Promise<ServiceWorker> | undefined;

async function streamingWorker() {
  downloadWorker ??= (async () => {
    if (!("serviceWorker" in navigator) || typeof MessageChannel === "undefined") {
      throw new Error("Streaming browser downloads are unavailable");
    }
    const registration = await navigator.serviceWorker.register(
      `${import.meta.env.BASE_URL}croc-download-sw.js`,
      { scope: import.meta.env.BASE_URL },
    );
    await navigator.serviceWorker.ready;
    const worker =
      navigator.serviceWorker.controller ??
      registration.active ??
      registration.waiting ??
      registration.installing;
    if (!worker) throw new Error("Streaming download service did not start");
    return worker;
  })();
  return downloadWorker;
}

class StreamingDownloadSink implements ReceiveSink {

View on GitHub (pinned to e25f1bdc04)

Solutions

  1. Serve the app from a secure context: https://, or localhost/127.0.0.1 during development.
  2. Feature-detect before choosing the sink: fall back to an in-memory/blob or File System Access API sink when 'serviceWorker' in navigator is false.
  3. For webview embedding, enable service worker support in the native shell (Android WebView: enable ServiceWorker via ServiceWorkerController, iOS WKWebView supports it in secure contexts).
  4. In tests, stub navigator.serviceWorker and MessageChannel or skip the streaming path.

Example fix

// before
const sink = new StreamingDownloadSink(...); // throws on http:// LAN host
// after
const canStream = "serviceWorker" in navigator && typeof MessageChannel !== "undefined";
const sink = canStream ? new StreamingDownloadSink(...) : new MemoryBlobSink(...);
Defensive patterns

Strategy: fallback

Validate before calling

function streamingDownloadsSupported(): boolean {
  return typeof navigator !== "undefined" &&
    "serviceWorker" in navigator &&
    typeof MessageChannel !== "undefined" &&
    window.isSecureContext;
}

Try / catch

try {
  sink = new StreamingDownloadSink(await streamingWorker());
} catch (error) {
  if (error instanceof Error && error.message === "Streaming browser downloads are unavailable") {
    sink = new MemoryBlobSink(); // degrade to buffered download with a size warning
    return;
  }
  throw error;
}

Prevention

When it happens

Trigger: Calling into the streaming download path (StreamingDownloadSink / streamingWorker) in a context lacking ServiceWorker support: non-secure origins (plain http on a non-localhost host), old/embedded browsers/webviews (some Android WebViews, in-app browsers), worker/sandboxed iframe contexts, or where MessageChannel is missing.

Common situations: Deploying the web client over http:// on a LAN IP (service workers require a secure context); iOS in-app browsers and other webviews; serving from file://; corporate-embedded browsers; testing in jsdom which lacks real serviceWorker registration.

Related errors


AI-assisted analysis of schollz/croc@e25f1bdc04 (2026-08-15). Data as JSON: /api/errors/b10f283d5fe48c1a. Report an issue: GitHub.