24

I've created a function that takes a blob and fileName which is supposed to download that blob implemented as follows:

const blobToBase64 = (blob, callback) => {
  const reader = new FileReader();
  reader.onloadend = () => {
    const base64 = reader.result;
    console.log({ base64 });
    callback(base64);
  };
  reader.readAsDataURL(blob);
};

const downloadFile = (blob, fileName) => () => {
  const link = document.createElement('a');
  blobToBase64(blob, (base64) => {
    link.href = base64;
    link.download = fileName;
    link.click();
  });
};


downloadFile(myBlob, myFileName);

To try debug this I've made a console.log to log out the value of base64 which is created by reader.result.

That base64 value is data:application/octet-stream;base64,Mzc4MDY4...

My PDF file get's downloaded but it's corrupted. What am I doing wrong in my file download implementation?

Let me know if there are any additional details that might help with this? I'm 100% sure that the blob itself is not a corrupted file.

6
  • The type of your File should be application/pdf, not application/octet-stream Commented Oct 15, 2018 at 12:56
  • 2
    Replacing application/octet-stream with application/pdf doesn't fix it. Commented Oct 15, 2018 at 13:47
  • Then your blob might simply be invalid. Did you make sure it wasn't? Commented Oct 15, 2018 at 13:47
  • I'm pretty sure it's not invalid, been looking into it :/ Commented Oct 15, 2018 at 13:52
  • @BarryMichaelDoyle did you get solution for your problem ? Commented Aug 31, 2021 at 11:02

2 Answers 2

55

I can't tell for sure why your code doesn't work, but I can tell for sure that what you are doing is useless at best.

Do not convert a Blob to a dataURI, 99%* of the time, what you want to do with this dataURI can be done directly with the original Blob and a blobURI.

*The remaining 1% being when you need to create standalone documents that will include binary data, it happens but not that often.

Here, once again what you want to do (set an anchor to point to your Blob's data) can be done with the Blob directly: simply create a blobURI (which is just a pointer to the data in memory) by calling URL.createObjectURL(blob).

const downloadFile = (blob, fileName) => {
  const link = document.createElement('a');
  // create a blobURI pointing to our Blob
  link.href = URL.createObjectURL(blob);
  link.download = fileName;
  // some browser needs the anchor to be in the doc
  document.body.append(link);
  link.click();
  link.remove();
  // in case the Blob uses a lot of memory
  setTimeout(() => URL.revokeObjectURL(link.href), 7000);
};


downloadFile(new Blob(['random data']), "myfile.txt");

Sign up to request clarification or add additional context in comments.

4 Comments

For whatever reason iOS is really iffy with this implementation. Someone said it was about synthetic tap events, so I created an <a> on the page with the href being the object URL. This works on Safari + Firefox on iOS but Chrome is not having it. Anyone encountered this issue?
@3stacks, it could be because of the revokeObjectURL called in focus, did edit with a better solution for that, could you confirm if it works for you (I don't have an iOS device at hand).
@Kaiido thanks for responding so quickly mate. I added the revokeObjectURL and got some different behaviour. It actually works in iOS Safari, but iOS Firefox just opens the blob in the same window, and iOS Chrome does nothing. I tried a few different approaches like generating the object URL on mount and then adding that as the href to the DOM which works for safari and firefox but chrome has problems. edit: I think this may just be iOS being wack, honestly. desktop macOS and android browsers are working fine.
... Unfortunately as I said I don't have an iOS device to test this out... This should work per the standards. Safari for iOS added support in v13, and I can't find anything related to FF or Chrome for iOS. I'm sorry but we will probably need a third person to help here. Ps: Just looked at Eligrey's FileSaver and they are using an ugly isChromeIOS UA sniffing to switch to href=dataURI...
16

I tried using Fetch API to download the PDF file from server which is giving octet-stream content as response. So, if you check the response, you will get characters like these %PDF-1.4

Here is the solution:

function download(pdfUrl) {
        fetch(pdfUrl).then(resp => resp.arrayBuffer()).then(resp => {

            // set the blog type to final pdf
            const file = new Blob([resp], {type: 'application/pdf'});

            // process to auto download it
            const fileURL = URL.createObjectURL(file);
            const link = document.createElement('a');
            link.href = fileURL;
            link.download = "FileName" + new Date() + ".pdf";
            link.click();
        });
    }

You can use the same method and decode the content of the octet-stream before creating the blob.

3 Comments

For me your code is ok, but I am not using createObjectURL() two times, just once (see const fileURL = ... and link.href = ... lines). I did not tried the double createObjectURL version above, so I can not say if it works anyway or not
@DanieleCruciani I guess you're right. I did it twice while making code more readable. Updated the code. Thanks.
you will likely to get CORS error as it tries to download from server

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.