skip to Main Content

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


  1. Chosen as BEST ANSWER

    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:

    const download = async (file, format) => {
            "use server";
    
            const imageArray = await makeImageArray(file.id, format);
    
            const zip = new JSZip();
    
            await Promise.all(imageArray.map(async (rendered) => {
                const arrayBuffer = await rendered.data.arrayBuffer();
                zip.file(`${rendered.name.replaceAll(" ", "_")}.${rendered.type}`, arrayBuffer);
            }));
    
            const zipped = await zip.generateAsync({type: "arraybuffer"});
            return Buffer.from(zipped)
        }
    

    Client component:

    const clickAZip = async (file, format) => {
            const a = document.createElement("a");
            const zipBuffer = await onDownload(file, format);
            const zipArr = new Uint8Array(zipBuffer);
            const blob = new Blob([zipArr.buffer], {type: 'application/zip'});
            a.href = URL.createObjectURL(blob);
            a.download = `${file.name}.zip`;
            a.click();
        }
    

  2. The problem is you’r doing Blob directly to the JSZip file without any conversion like base64.

    Try this:

    The server component:

    const download = async (file, format) => {
        "use server";
    
        const imageArray = await makeImageArray(file.id, format);
    
        const zip = new JSZip();
    
        await Promise.all(imageArray.map(async (rendered) => {
            const arrayBuffer = await rendered.data.arrayBuffer();
            zip.file(`${rendered.name.replaceAll(" ", "_")}.${rendered.type}`, arrayBuffer);
        }));
    
        const zipped = await zip.generateAsync({type: "arraybuffer"});
        const buffer = Buffer.from(zipped);
        return buffer;
    }
    

    The client component:

    const clickAZip = async (file, format) => {
        const a = document.createElement("a");
        const zipBuffer = await onDownload(file, format);
        const blob = new Blob([zipBuffer], {type: 'application/zip'});
        a.href = URL.createObjectURL(blob);
        a.download = `${file.name}.zip`;
        a.click();
    }
    

    Just handle the Blob objects correctly and create a zip file. Please check this

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search