2

I have many files in folders and subfolders in my Firebase Storage bucket. I have a list of DownloadURLs of all of these files in my Realtime Firebase Database. I'm trying to do a bulk download, ideally creating a .ZIP folder for the end user to download at once.

Several other posts (eg How to download entire folder from Firebase Storage?) note that there is no built in API to do bulk downloads, such as downloading an entire folder from a bucket. As far as I know this has not changed.

However a related answer (How to download entire folder from Firebase Storage?) suggests that creating a .ZIP folder may be possible, but does not elaborate on how.

How can I either...

... A) use pure JS on the client side to allow the end user to collect all the DownloadURLs and download the files at once?...

...OR...

... B) Use a Cloud Function written in Node.JS to use the DownloadURLs to create a .ZIP file that can be downloaded by the end user as one file. (I'm assuming a .ZIP file can be stored in the Storage bucket as another file with its own DownloadURL and be downloaded like any other file, then unzipped locally and all the original files be extracted. Please correct me if this is not the case)

... Or are neither of those approaches feasible? If not why, what functionality is missing?

Thanks in advance for any insights!

Jonathan
  • 73
  • 1
  • 2
  • 11

2 Answers2

1

Better late than never, you can use JSZip which provides a simple API usage.

After the zip file has been generated you can then use FileSaver to save/download it.

This is how I generated a zip file (from the client-side)

import JSZip from "jszip";
import saveAs from "file-saver";

generateZipFile(photosToDownload) {

  var jszip = new JSZip();

  //Looping through to get the download url for each image
  for (let i = 0; i < photosToDownload.length; i++) {
    let photoName = photosToDownload[i].photo_image_name;
    let photoImageURL = photosToDownload[i].generated_image_url;

    let photoImageExtension = photoName.substr(photoName.length - 4);

    jszip.file(
      photoName + photoImageExtension, //in my case, this produces something like 'my image.jpg'
      this.downloadUrlAsPromise(photoImageURL) //generates the file we need to download
    );

    //If we have reached the last image in the loop, then generate the zip file
    if (i == photosToDownload.length - 1) {
      jszip.generateAsync({ type: "blob" }).then(function(content) {
        saveAs(content, "MyZipFile.zip");
      });
    }
  }
},

downloadUrlAsPromise(url) {
  return new Promise(function(resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    xhr.responseType = "arraybuffer";
    xhr.onreadystatechange = function(evt) {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          resolve(xhr.response);
        } else {
          reject(new Error("Error for " + url + ": " + xhr.status));
        }
      }
    };
    xhr.send();
  });
}, 
Oush
  • 3,090
  • 23
  • 22
0

Here is a simple TypeScript code sample with ESNext syntax that uses the same packages mentioned by @Oush above:

import JSZip from 'jszip';
import firebase from 'firebase/app';
import { saveAs } from 'file-saver';

export const downloadFolderAsZip = async (folderPath: string) => {
    const jszip = new JSZip();
    const folderRef = firebase.storage().ref(folderPath);
    const files = (await folderRef.listAll()).items;
    const downloadUrls: Array<string> = await Promise.all(
        files.map(({ name }) => folderRef.child(name).getDownloadURL())
    );
    const downloadedFiles = await Promise.all(downloadUrls.map(url => fetch(url).then(res => res.blob())));
    downloadedFiles.forEach((file, i) => jszip.file(files[i].name, file));
    const content = await jszip.generateAsync({ type: 'blob' });
    saveAs(content, folderPath);
};

Note that you must configure CORS for Cloud Storage first so that this can work. By default, Cloud Storage has no CORS config (you can use the gsutil cors get gs://your-bucket-name.appspot.com command to find out), therefore, browsers will reject the fetch() call due to no CORS header returned from the gcs.

Further reading:

Son Nguyen
  • 2,991
  • 2
  • 19
  • 21