Whenever I try to create a zip file using JSZip, I either get an error indicating the images may not be an instance of Blob (they are) or the result is corrupted/cannot be extracted.
I have a back-end API which returns a .png image given an id corresponding to the image from an authenticated endpoint.
In a server component, I obtain a set of image ids from a separate endpoint, then map it to an array of .png images (as Blob objects, using await res.blob()
). Then I create a zip file from these images using JSZip, which I return as a Buffer object.
In a client component, I receive this Buffer object, create a Blob object from it, then an URL which I click.
The server component:
const download = async (file, format: string) => {
"use server"
const imageArray = await makeImageArray(file.id, format);
const zip = new JSZip();
await Promise.all(imageArray.map(async (rendered) => {
console.log("blob", rendered.data)
zip.file(`${rendered.name.replaceAll(" ", "_")}.${rendered.type}`, Buffer.from(await rendered.data.arrayBuffer()).toString("base64"), {base64: true});
return rendered.data;
}));
const zipped = await zip.generateAsync({type: "blob"})
const arrayBuffer = await zipped.arrayBuffer();
const buffer = Buffer.from(arrayBuffer)
return buffer
}
The client component:
const clickAZip = async (file, format) => {
const a = document.createElement("a");
const zip = await onDownload(file, format)
a.href = URL.createObjectURL(new Blob(zip,{type: 'application/zip'}))
a.download=`${file.name}.zip`
a.click()
}
Note, the code here downloads a corrupted zip. If I replace rendered.data.arrayBuffer()).toString("base64")
with rendered.data
I get the following error in the server component:
Internal error: Error: Can't read the data of 'Generic_image.png'. Is it in a supported JavaScript type (String, Blob, ArrayBuffer, etc) ?
Edit:
The code for the makeImageArray
function, which might be the source of the corrupted file:
const makeImageArray = async (id: string, format: string) => {
"use server"
return await authenticatedFetch(process.env.NEXT_PUBLIC_API_URL + `/project/sheets/all/${id}`, "GET", null)
.then(async (response) => {
return await Promise.all(response
.map(async (sheet) => {
return {
data: await blobFetch(process.env.NEXT_PUBLIC_API_URL + '/private/render', "POST", JSON.stringify({
text: sheet.text,
format: format
})), name: sheet.name, type: format
}
}))
}).then(res => res.filter(r => !r.data?.error))
}
The function receives an array of ids (Strings) from the first fetch, then maps it onto an array of .png images via another fetch.
It seems unlikely the images themselves are corrupted, as the zip as a whole is corrupted, not the individual files contained.
2
Answers
Well, I found the problem:
Essentially, the client component was receiving a buffer and then attempting to process it as an array, producing a corrupted file.
The solution was instantiating an Uint8Array object from the buffer before processing this array instead:
Server component:
Client component:
The problem is you’r doing Blob directly to the JSZip file without any conversion like base64.
Try this:
The server component:
The client component:
Just handle the Blob objects correctly and create a zip file. Please check this