29

I am working on a web app that opens binary files and allows them to be edited.

This process is basically ondrop -> dataTransfer.files[0] -> FileReader -> Uint8Array

Essentially, I want to be able to save the modified file back as a binary file. Ideally as a file download with a specified file name.

There doesn't seem to be any standard method of doing this, and that sucks, because everything up to that point is well supported.

I am currently converting the array to a string using String.fromCharCode(), base64 encoding that, and using a data uri in a hyperlink like data:application/octet-stream;base64,.., along with the download attribute to specify filename.

It seems to work, but it's quite hacky and I think converting the raw bytes to a string might introduce encoding issues depending on the byte values. I don't want the data to become corrupt or break the string.

Barring that, is there a better/proper method for getting an array of bytes as a binary file to the user?

tshepang
  • 12,111
  • 21
  • 91
  • 136
bryc
  • 12,710
  • 6
  • 41
  • 61

2 Answers2

37

These are utilities that I use to download files cross-browser. The nifty thing about this is that you can actually set the download property of a link to the name you want your filename to be.

FYI the mimeType for binary is application/octet-stream

var downloadBlob, downloadURL;

downloadBlob = function(data, fileName, mimeType) {
  var blob, url;
  blob = new Blob([data], {
    type: mimeType
  });
  url = window.URL.createObjectURL(blob);
  downloadURL(url, fileName);
  setTimeout(function() {
    return window.URL.revokeObjectURL(url);
  }, 1000);
};

downloadURL = function(data, fileName) {
  var a;
  a = document.createElement('a');
  a.href = data;
  a.download = fileName;
  document.body.appendChild(a);
  a.style = 'display: none';
  a.click();
  a.remove();
};

Usage:

downloadBlob(myBinaryBlob, 'some-file.bin', 'application/octet-stream');
ivanfeli
  • 230
  • 2
  • 9
Stefan
  • 3,962
  • 4
  • 34
  • 39
  • This confuses me, the parameters in downloadURL are different to the parameters used to call it. – Hans Jan 27 '18 at 11:33
  • the example uses `downloadBlob` (the first function) that then calls `downloadURL` so the args reflect the first function not the 2nd one – Stefan Jan 27 '18 at 16:41
  • You are calling `downloadURL(url, filename, mimeType)` from within `downloadBlob` but the function is `downloadURL(data, filename)`, so unless there is same magic happening that I don't understand, it's not matching. I think it's a case of just removing the `mimeType` but when first trying to understand it it's still confusing. – Hans Jan 27 '18 at 23:22
  • 2
    [FileSaver.js](https://github.com/eligrey/FileSaver.js) is a pretty solid library for handling file downloads across multiple browsers. Under the hood it does essentially the same thing as your code but adjusts for different browsers that don't support the method. – bryc Aug 27 '18 at 12:53
  • this is saving a list of integers separated by commas... – HAL9000 Aug 15 '22 at 15:29
  • Awesome! I was able to simply paste this code into the browser console while stopped at a breakpoint and download a UInt8Array to a file in Safari on Mac. – Ken Aspeslagh Jul 25 '23 at 20:10
17

(shorter) ES6 version of the top answer:

const downloadURL = (data, fileName) => {
  const a = document.createElement('a')
  a.href = data
  a.download = fileName
  document.body.appendChild(a)
  a.style.display = 'none'
  a.click()
  a.remove()
}

const downloadBlob = (data, fileName, mimeType) => {

  const blob = new Blob([data], {
    type: mimeType
  })

  const url = window.URL.createObjectURL(blob)

  downloadURL(url, fileName)

  setTimeout(() => window.URL.revokeObjectURL(url), 1000)
}
nagy.zsolt.hun
  • 6,292
  • 12
  • 56
  • 95
v1rtl
  • 185
  • 3
  • 8
  • 1
    Even shorter if data is always Blob :) `DL=(d,f)=>{let a=document.createElement("a"),u=URL.createObjectURL(d);a.download=f,a.href=u,a.click(),setTimeout(()=>URL.revokeObjectURL(u))}` – bryc Nov 30 '22 at 17:56