jimmywarting / StreamSaver.js

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

Turbo rails, secure context: TypeError: Cannot read properties of null (reading 'postMessage') in iframe postMessage handler #343

Open malakutsko opened 5 months ago

malakutsko commented 5 months ago

We have a rails application with https://turbo.hotwired.dev/ on the frontend. We are using StreamSaver v2.0.4 to handle file downloads in secure context.

We have the next issue with StreamSaver, reproducible with these steps:

  1. User clicks Download link
  2. StreamSaver creates an iframe and downloads the file through it, everything is ok.
  3. User navigates to another page, and there is another Download link
  4. User clicks Download, but nothing happens
  5. User is unable to download anything until they fully reload the page.

Console shows the error:

Uncaught (in promise) TypeError: Cannot read properties of null (reading 'postMessage')
    at iframe.postMessage (StreamSaver.js:50:60)
    at Object.createWriteStream (StreamSaver.js:251:25)

After some debugging I came to conclusion that this happens because StreamSaver is currently incompatible with turbo-rails, but it can be easily fixed.

The problem is that on the step 3 turbo replaces the dom, and the iframe created on the step 2 gets removed, but the reference to it still lives as mitmTransporter: https://github.com/jimmywarting/StreamSaver.js/blob/2.0.4/StreamSaver.js#L116

Then on step 4 the StreamSaver checks the existence of mitmTransporter, and it's indeed defined, so it skips creating iframe, but the postMessage to it fails, because it's not in the dom anymore.

I would propose to change this condition https://github.com/jimmywarting/StreamSaver.js/blob/2.0.4/StreamSaver.js#L115 so that in secure context it also checks existence of mitmTransporter.contentWindow. Then it will create the iframe again if it's not in the dom.

Does this make sense? I would create a PR.