How to bypass browser restriction and attach two or more files at once: multi-file attachment

Hello, Habr!



Let's solve a non-trivial problem. Imagine that you need to download data through the interface in an elementary way, for example, click on the "Download files" button.



Let's take the default Chrome v.88. The task sounds like this:



  • Generate client side files.
  • Download all generated files with one click.


It can be anything: a bunch of binaries, large archives with backups, a gallery of pictures, and so on. We will talk specifically about the download mechanism as such, so we will take the download of text and images as an example.





Of course, you can solve this problem by simply compressing all the necessary files into one ZIP archive, and then downloading it. It turns out that the user downloads a single file, which he then unzips on his own. For example, you can use the jszip library , which allows you to download a set of files by compressing them.



Here's a small example of a precompressed download from the docs:



var zip = new JSZip();
zip.file("Hello.txt", "Hello World\n");
var img = zip.folder("images");
img.file("smile.gif", imgData, {base64: true}); zip.generateAsync({type:"blob"}).then(function(content) {
// see FileSaver.js
saveAs(content, "example.zip"); });
      
      





"Where is the non-triviality here?" - you ask. And you will be right. And if we are talking about the simultaneous downloading of two, three or ten files from the site? For example: there is a list in the select, where you can select a certain number of files to download. Let's introduce an additional condition: the user does not have an installed archiver, so we discard the option with compression to the archive. How to solve this problem?



First, let's prepare the browser. Chrome prohibits downloading multifiles by default. This is for security reasons. Therefore, this function must first be unlocked in the browser settings:



  1. We go to the site settings: chrome: // settings / content.
  2. Go to Additional permissions.
  3. Choose Automatic downloads.
  4. Add the required site to the Allow category.








Great, now your browser has become less secure and allows you to download multiple files on a particular site at once.



Approach # 1 - FileReader



Let's look at the first approach using the example of generating files using FileReader and the base64 reading API. I note right away that FileReader has a fairly extensive API, so choose what you like best: text, arrayBuffer or binaryString.



(function () {
  const button = document.getElementById("download_with_reader");
  const content = ["content-1", "content-2", "content-3"];
    const createLink = () => {
    let link = document.createElement('a');
    link.download = 'hello.txt';
    return link;
  }
    const generateBlob = () => {
    for (const [index, value] of content.entries()) {
      const blob = new Blob([value], { type: "text/plain" });
      download(blob, index);
    }
  }
  const download = (blob, index) => {
    const link = createLink();
    let reader = new FileReader();
    reader.readAsDataURL(blob);
    reader.onload = function () {
      link.href = reader.result;
      link.download = `content-${index+1}.txt`;
      link.click();
    }
  }
    button.addEventListener("click", generateBlob);
}) ();

      
      





[code on Gitlab]



Approach # 2 - createObjectURL



You can also use createObjectURL - it allows you to store File objects or Blobs.



(function () {
  const button = document.getElementById("download_with_url_object");
  const content = ["content-1", "content-2", "content-3"];
  
  const createLink = () => {
    let link = document.createElement('a');
    link.download = 'hello.txt';
    return link;
  }
  const generateBlob = () => {
    for (const [index, value] of content.entries()) {
      const blob = new Blob([value], { type: "text/plain" });
      download(blob, index);
    }
  }
  const download = (blob, index) => {
    const link = createLink();
    link.href = URL.createObjectURL(blob);
    link.download = `content-${index+1}.txt`;
    link.click();
    URL.revokeObjectURL(link.href);
  }
   button.addEventListener("click", generateBlob);
}) ();

      
      





[code on Gitlab]



Approach # 3 - Download from URL



The two options above generate client side files. Of course, this will not always be the case, from time to time we receive files from the backend via direct links. This can be done by downloading from a URL. Chrome requires latency, so implementation of artificial latency will become a feature of this method.



(function () {
(function () {
  const button = document.getElementById("download_with_request");
  const urls = ["images/image-1.jpg", "images/image-2.jpg", "images/image-3.jpg"];
  
  const delay = () => new Promise(resolve => setTimeout(resolve, 1000));
  
  const downloadWithRequest = async () => {
    for await (const [index, url] of urls.entries()) {
      await delay();
      const link = document.createElement("a");
      link.href = url;
      link.download = `image-${index+1}`;
      link.click();
    }
  }
  
  button.addEventListener("click", downloadWithRequest);
}) ();


      
      





[code on Gitlab]



Total



Here are three fairly simple ways that you can download multiple files from a site at the same time and do it quickly. The main thing is to turn on the necessary permissions for a specific site, and then choose the method you like.



All Articles