FileSaver.js icon indicating copy to clipboard operation
FileSaver.js copied to clipboard

Downloading jpeg on Chrome iOS only results in 1) opening image in new tab or 2) downloading file named "document" with no extension

Open jparismorgan opened this issue 4 years ago • 11 comments

Hello, I am having trouble downloading an image on Chrome iOS 87.0.4280.77. I am using an iPhone 12 running iOS 14.3-beta. I am taking a screenshot with MediaRecorder and then creating a blob with const currentBlob = new Blob([buffer], {type: 'image/jpeg'}). My goal is to be able to trigger a download of the image, with the name and file extension set.

Issue: Testing with FileSaver.js with code from v2.0.4 or master

When I call saveAs(currentBlob, "name.jpg"), the file pops up in a new tab. I can long press on it and get the ability to save to photos (and other things), but I want a direct download.

For both versions of FileSaver.js I have tried changing the type of the blob (b/c I have read that may force Chrome to automatically download the file), but that results in no tab being opened and no download pop-up:

const currentBlob = new Blob([buffer], {type: 'image/jpeg'})
const rawBlob = new Blob([currentBlob])
const octetBlob = new Blob([currentBlob], {type: 'application/octet-stream'})
saveAs(blob / rawBlob / octetBlob, "name.jpg")

For contrast, Safari iOS shows the system dialog for a download in all three of these cases, with both versions of FileSaver.js.

Solution Attempt 1: Using a to download the image

The basic way to download a file is below. This results in the image being opened in a new window with no download prompt:

const currentBlob = new Blob([buffer], {type: 'image/jpeg'})
const a = document.createElement('a')
a.download = currentName
a.href = URL.createObjectURL(currentBlob)
a.rel = 'noopener'
document.body.appendChild(a)
click(a)
document.body.removeChild(a)

If I instead encode the Blob into a Base64 encoded string with readAsDataURL, I can get the file to download, but it is named 'document' and has no file extension (though if I rename the file to "name.jpg" then I can see it is in fact an image):

const currentBlob = new Blob([buffer], {type: 'image/jpeg'})
const reader = new FileReader()
reader.onloadend = () => {
    const a = document.createElement('a')
    a.download = currentName
    a.href = reader.result // I.e. data:image/jpeg;base64,/9j/4AA...
    a.rel = 'noopener'
    document.body.appendChild(a)
    click(a)
    document.body.removeChild(a)
}
reader.readAsDataURL(currentBlob)
alt text alt text

I have also tried changing the Base64 encoded string in several ways to try to rename the file, but to no effect.

reader.onloadend = () => {
    ...
    const url = reader.result
    const urlWithHeaders = url.replace(';', `;name=${currentFilename};headers=Content-Disposition%3A%20attachment%3B%20filename=%22${currentFilename}%22;`)
    const urlFile = url.replace(/^data:[^;]*;/, 'data:attachment/file;')
    const urlOctet = url.replace('image/jpeg', 'binary/octet-stream')
    a.href = url / urlWithHeaders / urlFile / urlOctet
    ...
}

Solution Attempt 2: Using a new window

I have also tried by opening a new window, but it just opens the image in a new window and does not prompt a download:

const currentBlob = new Blob([buffer], {type: 'image/jpeg'})
const newWindow = window.open(URL.createObjectURL(currentBlob), '_blank')

Questions

  • Is Chrome iOS downloading supported with FileSaver.js?
  • Is this a known limitation of Chrome iOS?

There are several issues that seem potentially related, but all of them are quite old and haven't focused on images specifically: https://github.com/eligrey/FileSaver.js/issues/506, https://github.com/eligrey/FileSaver.js/issues/576, https://github.com/eligrey/FileSaver.js/issues/343

I have also tried with an mp4 but, just like these questions describe, it is not working:

  • https://stackoverflow.com/questions/63848034/anchor-download-attribute-created-with-javascript-not-working-on-ios-chrome
  • https://stackoverflow.com/questions/64969672/unable-to-download-files-with-the-correct-name-in-chrome-on-ios

jparismorgan avatar Dec 02 '20 19:12 jparismorgan

Having the same issue here with Chrome for iOS, any workaround?

mauriblint avatar Dec 28 '20 15:12 mauriblint

Hello, I am having trouble downloading an image on Chrome iOS 87.0.4280.77. I am using an iPhone 12 running iOS 14.3-beta. I am taking a screenshot with MediaRecorder and then creating a blob with const currentBlob = new Blob([buffer], {type: 'image/jpeg'}). My goal is to be able to trigger a download of the image, with the name and file extension set.

Issue: Testing with FileSaver.js with code from v2.0.4 or master

When I call saveAs(currentBlob, "name.jpg"), the file pops up in a new tab. I can long press on it and get the ability to save to photos (and other things), but I want a direct download.

For both versions of FileSaver.js I have tried changing the type of the blob (b/c I have read that may force Chrome to automatically download the file), but that results in no tab being opened and no download pop-up:

const currentBlob = new Blob([buffer], {type: 'image/jpeg'})
const rawBlob = new Blob([currentBlob])
const octetBlob = new Blob([currentBlob], {type: 'application/octet-stream'})
saveAs(blob / rawBlob / octetBlob, "name.jpg")

For contrast, Safari iOS shows the system dialog for a download in all three of these cases, with both versions of FileSaver.js.

Solution Attempt 1: Using a to download the image

The basic way to download a file is below. This results in the image being opened in a new window with no download prompt:

const currentBlob = new Blob([buffer], {type: 'image/jpeg'})
const a = document.createElement('a')
a.download = currentName
a.href = URL.createObjectURL(currentBlob)
a.rel = 'noopener'
document.body.appendChild(a)
click(a)
document.body.removeChild(a)

If I instead encode the Blob into a Base64 encoded string with readAsDataURL, I can get the file to download, but it is named 'document' and has no file extension (though if I rename the file to "name.jpg" then I can see it is in fact an image):

const currentBlob = new Blob([buffer], {type: 'image/jpeg'})
const reader = new FileReader()
reader.onloadend = () => {
    const a = document.createElement('a')
    a.download = currentName
    a.href = reader.result // I.e. data:image/jpeg;base64,/9j/4AA...
    a.rel = 'noopener'
    document.body.appendChild(a)
    click(a)
    document.body.removeChild(a)
}
reader.readAsDataURL(currentBlob)
alt text alt text

I have also tried changing the Base64 encoded string in several ways to try to rename the file, but to no effect.

reader.onloadend = () => {
    ...
    const url = reader.result
    const urlWithHeaders = url.replace(';', `;name=${currentFilename};headers=Content-Disposition%3A%20attachment%3B%20filename=%22${currentFilename}%22;`)
    const urlFile = url.replace(/^data:[^;]*;/, 'data:attachment/file;')
    const urlOctet = url.replace('image/jpeg', 'binary/octet-stream')
    a.href = url / urlWithHeaders / urlFile / urlOctet
    ...
}

Solution Attempt 2: Using a new window

I have also tried by opening a new window, but it just opens the image in a new window and does not prompt a download:

const currentBlob = new Blob([buffer], {type: 'image/jpeg'})
const newWindow = window.open(URL.createObjectURL(currentBlob), '_blank')

Questions

  • Is Chrome iOS downloading supported with FileSaver.js?
  • Is this a known limitation of Chrome iOS?

There are several issues that seem potentially related, but all of them are quite old and haven't focused on images specifically: #506, #576, #343

I have also tried with an mp4 but, just like these questions describe, it is not working:

  • https://stackoverflow.com/questions/63848034/anchor-download-attribute-created-with-javascript-not-working-on-ios-chrome
  • https://stackoverflow.com/questions/64969672/unable-to-download-files-with-the-correct-name-in-chrome-on-ios

Do you have a try to add the response header -----Content-Disposition:attachment;filename='xxx.xxx'?

xiaoluntian avatar Jan 05 '21 06:01 xiaoluntian

This issue is still present today it seems? Latest Chrome on latest iPadOS. How can we set the filename of a file download in Chrome for iOS?

av01d avatar Jul 26 '21 06:07 av01d

Do you have a try to add the response header -----Content-Disposition:attachment;filename='xxx.xxx'?

Do you understand the issue? How do you set a Content-Disposition header in client side javascript?

av01d avatar Jul 26 '21 06:07 av01d

@av01d I think @xiaoluntian is referring to something like urlWithHeaders, which if you read the Solution Attempt 1 section above I mention trying unsuccessfully:

I have also tried changing the Base64 encoded string in several ways to try to rename the file, but to no effect. reader.onloadend = () => { ... const url = reader.result const urlWithHeaders = url.replace(';', ;name=${currentFilename};headers=Content-Disposition%3A%20attachment%3B%20filename=%22${currentFilename}%22;) const urlFile = url.replace(/^data:[^;]*;/, 'data:attachment/file;') const urlOctet = url.replace('image/jpeg', 'binary/octet-stream') a.href = url / urlWithHeaders / urlFile / urlOctet ... }

But I don't remember much of what I tried beyond what is written above, so you may want to give it a go yourself, @av01d.

To answer your original question @xiaoluntian, yes, I tried setting a header and it didn't work 😕 . Is the code snippet above how you suggest doing it?

jparismorgan avatar Jul 26 '21 20:07 jparismorgan

This issue still persists, anyone found a fix?

Gbahdeyboh avatar Sep 21 '21 20:09 Gbahdeyboh

It is working in chrome after setting the Content Disposition response header.

vanga avatar Feb 23 '22 01:02 vanga

@vanga Could you share example code? Thanks!

jparismorgan avatar Feb 23 '22 04:02 jparismorgan

@jparismorgan My images are stored in S3, so I just set the metadata so that content disposition response header is sent. I have set it to Content-disposition: attachment like suggested in few of the above comments in this thread.

vanga avatar Mar 04 '22 05:03 vanga

Found a solution for setting the filename and extension, (had this problem with various browsers when obtaining the base64 dynamically). I had to add target="_blank".

On Solution 1, before appending the element to the body, add: a.target = '_blank'

I'm not using this library, but my solution is the same as Solution 1, so hopefully it helps you.

Ismael-S avatar Jul 04 '23 16:07 Ismael-S

Is there a solution to this yet? It's been 4 years, I'm hoping something has happened.

Changing the delivery from s3 isn't a viable option, as I want it to display in the browser nomrally, but when they click a download button, I want to download the image.

jfederer avatar Jul 17 '24 22:07 jfederer