5

I am trying to download the splitted zip file coming from api call in parts (.zip, .z01, .z02) etc. In Javascript I want to download these files on the fly and create a combined .zip file and then save the unzipped file. (i don't need to create a combined zip) I simply need test.pdf file which which has been splitted to multiple zip. I want to download that pdf file.

e.g. test.pdf (18 mb) is splitted into two zip files of (.zip 10mb) and (.z01 8mb). currently I am downloading them as blob and then merging them. the resulting zip is currupted. below is the code I tried.

Promise.all([
  axios.get(`https://testapi.com/folder/file.zip`, {
    responseType: "blob",
    headers: {
      Accept: "application/json",
    },
  }),
  axios.get(`https://testapi.com/folder/file.z01`, {
    responseType: "blob",
    headers: {
      Accept: "application/json",
    },
  }),
]).then((obj) => {
  let blob: any = [];
  obj.forEach((e: any) => {
    blob.push(e.data);
  });
  const newBlob = new Blob(blob, { type: "octet/stream" });
  saveAs(newBlob, "zip.zip"); // but it's currupted
});
diedu
  • 19,277
  • 4
  • 32
  • 49
Taj Khan
  • 579
  • 2
  • 7
  • 22

2 Answers2

7

You could use JSZip and FileSaver in order to achieve this. Note that the files are ordered alphabetically in the input so this snippet requires your files to be named accordingly. I have used an input in order to make testing easier, you could easily adapt it to axios requests for each file.

The snippet actually doesn't work on this web page because the downloads are disallowed by the snippet iframe's policy so you have to try it by other means.

const objectWithoutProperties = (obj, keys) => {
  const target = {};
  for (let i in obj) {
    if (keys.indexOf(i) >= 0) continue;
    if (!Object.prototype.hasOwnProperty.call(obj, i)) continue;
    target[i] = obj[i];
  }
  return target;
}

const mergeFiles = (a, b) => {
  if (!a) return b;
  const intersect = Object.keys(a.files).filter((x) =>
    Object.keys(b.files).includes(x)
  );

  a.files = { ...a.files, ...objectWithoutProperties(b.files, intersect) };

  return Promise.all(
    intersect.map((file) =>
      Promise.all([
        a.file(file).async("arraybuffer"),
        b.file(file).async("arraybuffer"),
      ])
        .then((x) => new Blob(x))
        .then((x) => a.file(file, x))
    )
  ).then(() => a);
};

const MultipleZipMerger = () => {
  function change(e) {
    Promise.all([...e.target.files].map(JSZip.loadAsync))
      .then((x) =>
        x.reduce(
          (acc, cur) => acc.then((x) => mergeFiles(x, cur)),
          Promise.resolve()
        )
      )
      .then((x) => x.generateAsync({ type: "blob" }))
      .then((x) => saveAs(x, "hello.zip"));
  }
  
  return <input onChange={change} type="file" multiple />;
};

ReactDOM.render(<MultipleZipMerger />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.6.0/jszip.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip-utils/0.1.0/jszip-utils.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.0/FileSaver.min.js"></script>

<div id="root"></div>

This is a standalone version of this code which you can test as a local file:

<!doctype html>

<html>
</head>

<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.6.0/jszip.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip-utils/0.1.0/jszip-utils.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.0/FileSaver.min.js"></script>

<div id="root"></div>
<script>
  
const objectWithoutProperties = (obj, keys) => {
  const target = {};
  for (let i in obj) {
    if (keys.indexOf(i) >= 0) continue;
    if (!Object.prototype.hasOwnProperty.call(obj, i)) continue;
    target[i] = obj[i];
  }
  return target;
}

const mergeFiles = (a, b) => {
  if (!a) return b;
  const intersect = Object.keys(a.files).filter((x) =>
    Object.keys(b.files).includes(x)
  );

  a.files = { ...a.files, ...objectWithoutProperties(b.files, intersect) };

  return Promise.all(
    intersect.map((file) =>
      Promise.all([
        a.file(file).async("arraybuffer"),
        b.file(file).async("arraybuffer"),
      ])
        .then((x) => new Blob(x))
        .then((x) => a.file(file, x))
    )
  ).then(() => a);
};

const MultipleZipMerger = () => {
  function change(e) {
    Promise.all([...e.target.files].map(JSZip.loadAsync))
      .then((x) =>
        x.reduce(
          (acc, cur) => acc.then((x) => mergeFiles(x, cur)),
          Promise.resolve()
        )
      )
      .then((x) => x.generateAsync({ type: "blob" }))
      .then((x) => saveAs(x, "hello.zip"));
  }

  return React.createElement("input", {
    onChange: change,
    type: "file",
    multiple: true,
  });
};

ReactDOM.render(
  React.createElement(MultipleZipMerger, null),
  document.getElementById("root")
);

  
</script>
</body>
</html>
Guerric P
  • 30,447
  • 6
  • 48
  • 86
0

Instead of downloading a file in pieces, I suggest you to do it in one piece through single endpoint.

Anyways, you can try this solution, I am not completely sure if it works as i do not have the access to real API of yours, and what kind of data it is sending.

You just need to correct the response_type of your axios call.

Promise.all([
  axios.get(
    `https://testapi.com/folder/file.zip`,
    { responseType: 'stream', headers: { Accept: "application/json",} }
  ), 
  axios.get(
    `https://testapi.com/folder/file.z01`,
    { responseType: 'stream', headers: { Accept: "application/json",} }
  )
]).then((response) => {
  let blob:any = []
    response.forEach((e:any) => {
      blob.push(e.data)
    })
    const newBlob = new Blob(blob, {type: "octet/stream"});
    saveAs(newBlob, "zip.zip")
  });

I hope this would work with the correct response type.

Gaurav Sharma
  • 107
  • 1
  • 7
  • It's having the same issue, the resulted zip is corrupt. The concept of splitted zip is little different as we have to make all the zip files in one folder and then only need to extract the main .zip file. The merging eventually going to be corrupt. I am not really sure whether it's possible to do it on a client side or not. – Taj Khan Jun 08 '21 at 06:02
  • The above or my solution will work if it's not a zip, but on zip file it does not. I guess we cannot merge .z01 or .z02 with main zip. Tried a lot.. just cant figure out how to do it. – Taj Khan Jun 08 '21 at 06:04
  • You need to figure out a way to generate the complete zip at once in the backend and then just send it across the API as a stream. – Gaurav Sharma Jun 08 '21 at 06:40
  • I don't have control over it. That is the API I have to work with. Let forget about the API and can you simple merge a .zip file and .z01 file in Javascript? – Taj Khan Jun 09 '21 at 14:16