Search code examples
javascriptamazon-s3multipartform-datafilereaderarraybuffer

Using JS FileReader with FormData


I'm trying to upload files to S3. My UI supports drag-drop and a file input element, so my plan is to start with the blobs returned by drop and by input events, use FileReader to load their contents, and then process using FormData.

The issue is at *****. It looks as though .result is an arraybuffer of some nonzero size, but what I find on S3 is just a 20b file called ${filename} with contents [object ArrayBuffer].

What am I missing in this flow

function sendSigned(fileObj, cb) {
    console.log("send signed", fileObj)
    let formData = new FormData();
    const host = "http://localhost:4000"

    formData.append("key", fileObj.signature.stem + "${filename}");
    formData.append("x-amz-algorithm", "AWS4-HMAC-SHA256");
    formData.append("x-amz-credential", fileObj.signature.credential);
    formData.append("x-amz-date", fileObj.signature.date);
    formData.append("success_action_redirect", host + "/upload/success");
    formData.append("policy", fileObj.signature.policy);
    formData.append("x-amz-signature", fileObj.signature.signature);

    let fileReader = new FileReader();
    fileReader.onload = function(evt) {
        console.log(evt);
        // ******************************************
        // formData.append("file", evt.target.result);
        formData.append("file", fileReader.result);

        jQuery.ajax({
            url: 'http:\/\/uploads.s3.amazonaws.com',
            data: formData,
            cache: false,
            contentType: false,
            processData: false,
            type: 'POST',
            success: (res) => {
                console.log(res);
                let s3Conf = Object.assign(fileObj, {
                    confirmation: res.ETag,
                    zipname : fileObj.signature.stem + fileObj.nativeFiles[0].name,
                    status: "Done"
                });
                cb(s3Conf);
            },
            error: (err) => {
                console.error(err);
                let s3Conf = Object.assign(fileObj, {
                    error: err,
                    status: "Done"
                });
                cb(s3Conf);
            }
        });
    }
    fileReader.readAsArrayBuffer(fileObj.nativeFiles[0].blob);
}

Solution

  • You cannot send an array buffer with a FormData object(you can however send it as the entire post body). FormData accepts blobs so you can just pass it that.

    function sendSigned(fileObj, cb) {
        console.log("send signed", fileObj)
        let formData = new FormData();
        const host = "http://localhost:4000"
    
        formData.append("key", fileObj.signature.stem + "${filename}");
        formData.append("x-amz-algorithm", "AWS4-HMAC-SHA256");
        formData.append("x-amz-credential", fileObj.signature.credential);
        formData.append("x-amz-date", fileObj.signature.date);
        formData.append("success_action_redirect", host + "/upload/success");
        formData.append("policy", fileObj.signature.policy);
        formData.append("x-amz-signature", fileObj.signature.signature);
        formData.append("file", fileObj.nativeFiles[0].blob, fileObj.nativeFiles[0].name);
    
        jQuery.ajax({
            url: 'http:\/\/uploads.s3.amazonaws.com',
            data: formData,
            cache: false,
            contentType: false,
            processData: false,
            type: 'POST',
            success: (res) => {
                console.log(res);
                let s3Conf = Object.assign(fileObj, {
                    confirmation: res.ETag,
                    zipname : fileObj.signature.stem + fileObj.nativeFiles[0].name,
                    status: "Done"
                });
                cb(s3Conf);
            },
            error: (err) => {
                console.error(err);
                let s3Conf = Object.assign(fileObj, {
                    error: err,
                    status: "Done"
                });
                cb(s3Conf);
            }
        });
    
    }
    

    Note your server side logic will need to read the field name as a file field.