{"record":{"id":"b10f283d5fe48c1a","repo":"schollz/croc","slug":"streaming-browser-downloads-are-unavailable","errorCode":null,"errorMessage":"Streaming browser downloads are unavailable","messagePattern":"Streaming browser downloads are unavailable","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/src/protocol/storage.ts","lineNumber":176,"sourceCode":"  async commit() {\n    if (!this.blob) throw new Error(\"Destination must be finalized before download\");\n    if (this.committed) return;\n    this.committed = true;\n    this.onDownload(this.name, this.blob);\n  }\n\n  async abort() {\n    this.chunks.clear();\n    this.blob = undefined;\n  }\n}\n\nlet downloadWorker: Promise<ServiceWorker> | undefined;\n\nasync function streamingWorker() {\n  downloadWorker ??= (async () => {\n    if (!(\"serviceWorker\" in navigator) || typeof MessageChannel === \"undefined\") {\n      throw new Error(\"Streaming browser downloads are unavailable\");\n    }\n    const registration = await navigator.serviceWorker.register(\n      `${import.meta.env.BASE_URL}croc-download-sw.js`,\n      { scope: import.meta.env.BASE_URL },\n    );\n    await navigator.serviceWorker.ready;\n    const worker =\n      navigator.serviceWorker.controller ??\n      registration.active ??\n      registration.waiting ??\n      registration.installing;\n    if (!worker) throw new Error(\"Streaming download service did not start\");\n    return worker;\n  })();\n  return downloadWorker;\n}\n\nclass StreamingDownloadSink implements ReceiveSink {","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/schollz/croc/blob/e25f1bdc04f07f094d50b0a1bf67e2563944b57a/web/src/protocol/storage.ts#L158-L194","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Serve the app from a secure context: https://, or localhost/127.0.0.1 during development.","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.","For webview embedding, enable service worker support in the native shell (Android WebView: enable ServiceWorker via ServiceWorkerController, iOS WKWebView supports it in secure contexts).","In tests, stub navigator.serviceWorker and MessageChannel or skip the streaming path."],"exampleFix":"// before\nconst sink = new StreamingDownloadSink(...); // throws on http:// LAN host\n// after\nconst canStream = \"serviceWorker\" in navigator && typeof MessageChannel !== \"undefined\";\nconst sink = canStream ? new StreamingDownloadSink(...) : new MemoryBlobSink(...);","handlingStrategy":"fallback","validationCode":"function streamingDownloadsSupported(): boolean {\n  return typeof navigator !== \"undefined\" &&\n    \"serviceWorker\" in navigator &&\n    typeof MessageChannel !== \"undefined\" &&\n    window.isSecureContext;\n}","typeGuard":null,"tryCatchPattern":"try {\n  sink = new StreamingDownloadSink(await streamingWorker());\n} catch (error) {\n  if (error instanceof Error && error.message === \"Streaming browser downloads are unavailable\") {\n    sink = new MemoryBlobSink(); // degrade to buffered download with a size warning\n    return;\n  }\n  throw error;\n}","preventionTips":["Serve the app over https or localhost — service workers require a secure context.","Feature-detect serviceWorker + MessageChannel before offering the streaming path; provide a blob/memory fallback.","Test in the actual minimum browser matrix, including webviews and private modes."],"tags":["browser-support","service-worker","download","environment"],"backgroundTag":null,"analyzedSha":"e25f1bdc04f07f094d50b0a1bf67e2563944b57a","analyzedAt":"2026-08-15T12:53:39.096Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}