jimmywarting / StreamSaver.js

StreamSaver writes stream to the filesystem directly asynchronous
https://jimmywarting.github.io/StreamSaver.js/example.html
MIT License
3.97k stars 413 forks source link

Download happening, but not getting saved anywhere #223

Closed jat255 closed 3 years ago

jat255 commented 3 years ago

Hi @jimmywarting, my code that I was using to zip up a number of remote files (obtained using the Fetch API) and deliver them to users has stopped working in modern versions of Chrome. What's odd is that the download is working (i.e. I can see network traffic and my progress tracker -- the TransformStream -- is getting updated), but the file is not showing up in the downloads bar or in the browser at all.

I'm not super understanding of how it all works (I basically hacked my solution together until it worked), but it appears that the download is supposed to appear when the .pipeTo() is called. Here's the basics of what I have (not a full MWE, but just to give you an idea):

let p = new TransformStream({
    transform(chunk, ctrl) {
        bytesDownloaded += chunk.byteLength
        updateProgressBar(bytesDownloaded,
        arrSum(zip_total_sizes) +
        arrSum(indiv_dl_sizes));
        ctrl.enqueue(chunk)
    }
});

var abortController = new AbortController();
var abortSignal = abortController.signal;

let ws = streamSaver.createWriteStream(
   zip_title, {
     size: zip_size
});

let z = new ZIP({
    pull(ctrl) {
        const it = files.next()
        if (it.done) {
            ctrl.close()
        } else {
            const [name, url] = it.value
            return fetch(url, {
                signal: abortSignal
            })
            .then(res => {
                ctrl.enqueue({
                    name,
                    stream: () => {
                        r = res.body;
                        return r
                     }
                 });
             });
         }
     }
    }).pipeThrough(p)
    .pipeTo(ws, {
        signal: abortSignal
     })

Any ideas why this would have stopped working (and how I might be able to fix it)?

jimmywarting commented 3 years ago

If it has worked before, just try to restart chrome. I saw this issue plenty of time with FileSaver.js It dose not look like something about your code is wrong

jat255 commented 3 years ago

Hmm, I tried that and also tried using a different machine (Windows, vs. my Linux dev machine) to see if that made a difference, but it's still not working. Oddly, the demos from https://jimmywarting.github.io/StreamSaver.js/example.html are working okay, so I suspect it's something to do with my specific implementation, but I'm not sure where to look, as there are no errors anywhere in the network log or console.

Do you have any tips on how I might be able to debug what's happening? I figured I'd try installing older versions of Chrome to see if I can spot when it broke.

jimmywarting commented 3 years ago

Do you have a demo/page that i can have a look at?

jat255 commented 3 years ago

Unfortunately, no, since it's all on an internal server, but the code is all here. The downloadFn() function is the one that does all the heavy lifting.

jat255 commented 3 years ago

Hmm, so some more progress; I copied the JS from https://jimmywarting.github.io/StreamSaver.js/examples/saving-multiple-files.html into my website, and I'm seeing the same problem (the code is running, and the "done writing" message gets written to the console, but no file shows up in the downloads folder). This example works on your public website however, so that leads me to think that perhaps the issue is in the libraries I'm loading (web-streams-adapter and web-streams-polyfill, although those should only be used in Firefox) or perhaps the communication with the service worker or something.

EDIT: a little more info...

I took a look at the Network tab in my devtools when trying to run the download using the JS from the "saving-multiple-files.html", and it appears the request is made via the mitm.html, but it stays in "pending" status indefinitely:

image

image

Could there be some server configuration causing the issue? Like a CORS thing or something?

jat255 commented 3 years ago

After a good deal of bisecting (using https://aur.archlinux.org/cgit/aur.git/log/?h=google-chrome-beta as a version source), I confirmed that this stopped working in v87.0.4280.20, but works in the previous version v86.0.4240.75.

Looking at https://en.wikipedia.org/wiki/Google_Chrome_version_history, this change smells suspicious:

Transferable Streams - ReadableStream, WritableStream, and TransformStream objects can now be passed as arguments to postMessage()

jat255 commented 3 years ago

Hmm, so there were some changes between what I had as StreamSaver.js and what was on the demo website. (green is your up to date version, red is what I had) This seems mostly related to the useBlobFallback stuff, which is false in my environments from initial look, but it appears this gets me working again in chrome (at least for the simple archive.zip example). Going to play around with it a bit more to see if I can get my full code working again.

diff --git i/static/libs/StreamSaver/StreamSaver.js w/static/libs/StreamSaver/StreamSaver.js
index c0c118b..fe48f22 100644
--- i/static/libs/StreamSaver/StreamSaver.js
+++ w/static/libs/StreamSaver/StreamSaver.js
@@ -14,7 +14,9 @@
   const test = fn => { try { fn() } catch (e) {} }
   const ponyfill = window.WebStreamsPolyfill || {}
   const isSecureContext = window.isSecureContext
-  let useBlobFallback = /constructor/i.test(window.HTMLElement) || !!window.safari
+  // TODO: Must come up with a real detection test (#69)
+  let useBlobFallback = /constructor/i.test(window.HTMLElement) || !!window.safari || !!window.WebKitPoint
   const downloadStrategy = isSecureContext || 'MozAppearance' in document.documentElement.style
     ? 'iframe'
     : 'navigate'
@@ -132,6 +134,11 @@
       readableStrategy: undefined
     }

+    let bytesWritten = 0 // by StreamSaver.js (not the service worker)
+    let downloadUrl = null
+    let channel = null
+    let ts = null
+
     // normalize arguments
     if (Number.isFinite(options)) {
       [ size, options ] = [ options, size ]
@@ -148,10 +155,8 @@
     if (!useBlobFallback) {
       loadTransporter()

-      var bytesWritten = 0 // by StreamSaver.js (not the service worker)
-      var downloadUrl = null
-      var channel = new MessageChannel()
-
+      channel = new MessageChannel()
+      
       // Make filename RFC5987 compatible
       filename = encodeURIComponent(filename.replace(/\//g, ':'))
         .replace(/['()]/g, escape)
@@ -190,7 +195,7 @@
             }
           }
         }
-        var ts = new streamSaver.TransformStream(
+        ts = new streamSaver.TransformStream(
           transformer,
           opts.writableStrategy,
           opts.readableStrategy
@@ -238,8 +243,7 @@

     let chunks = []

-    //(!useBlobFallback && ts && ts.writable) ||
-    return  new streamSaver.WritableStream({
+    return (!useBlobFallback && ts && ts.writable) || new streamSaver.WritableStream({
       write (chunk) {
         if (useBlobFallback) {
           // Safari... The new IE6
@@ -263,7 +267,6 @@
         // EDIT: Transfarable streams solvs this...
         channel.port1.postMessage(chunk)
         bytesWritten += chunk.length

         if (downloadUrl) {
           location.href = downloadUrl
@@ -294,4 +297,4 @@
   }

   return streamSaver
-})
+})
\ No newline at end of file
jat255 commented 3 years ago

Yup, not sure what it was about that changeset, but that got it working in my application, so I'll close this out. Sorry for the noise.