Search code examples
javascriptblobchunks

Push ArrayBuffer in Array for constructing a Blob


I've got an array of URLs [URL1, URL2, URL3,...] : each element is a link to one of the chunks of the same file. Each chunk is separately encrypted, with the same key as all the other chunks.

I download each chunk (in a forEach function) with a XMLHttpRequest. onload :

  1. each chunk is first decrypted
  2. then each chunk is converted to an ArrayBuffer (source)
  3. each ArrayBuffer is pushed to an array (source)
  4. when the three first steps are done for each chunk (callback by a var incremented on step#1 === the array.length), a blob is constructed with the array
  5. the blob is saved as file with FileReader API & filesaver.js

If it's a one chunk's file, everything works fine.

But with multiple chunks, steps #1 & #2 are ok, but only the last ArrayBuffer seems to be pushed to the array. What am I missing?

Below my code

// var for incrementation in forEach funtion
var chunkdownloaded = 0;
// 'clearfileurl' is the array of url's chunks :[URL1, URL2, URL3,...]
clearfileurl.forEach(function(entry) {
    var xhr = new XMLHttpRequest();
    var started_at = new Date();
    xhr.open('GET', entry, true);
    xhr.responseType = 'text';

    // request progress
    xhr.onprogress = function(pe) {
        if (pe.lengthComputable) {
            downloaderval.set((pe.loaded / pe.total) * 100);
        }
    };

    // on request's success
    xhr.onload = function(e) {
        if (this.status == 200) {

            chunkdownloaded+=1;
            var todecrypt = this.response;

            // decrypt request's response: get a dataURI
            try { 
                var bytesfile  = CryptoJS.AES.decrypt(todecrypt.toString(), userKey);
                var decryptedfile = bytesfile.toString(CryptoJS.enc.Utf8);
            } catch(err) {
                console.log (err);
                return false;
            }

            //convert a dataURI to a Blob
            var MyBlobBuilder = function() {
                this.parts = [];
            }

            MyBlobBuilder.prototype.append = function(dataURI) {
            //function dataURItoBlob(dataURI) {
                // convert base64 to raw binary data held in a string
                var byteString = atob(dataURI.split(',')[1]);

                // separate out the mime component
                // var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

                // write the bytes of the string to an ArrayBuffer
                var ab = new ArrayBuffer(byteString.length);
                var ia = new Uint8Array(ab);
                for (var i = 0; i < byteString.length; i++) {
                    ia[i] = byteString.charCodeAt(i);
                }
                this.parts.push(ab);
                console.log('parts', this.parts)
                this.blob = undefined; // Invalidate the blob
            }

            MyBlobBuilder.prototype.getBlob = function() {
                if (!this.blob) {
                    console.log (this.parts);
                    this.blob = new Blob(this.parts);
                }
                return this.blob;
            };

            var myBlobBuilder = new MyBlobBuilder();
            myBlobBuilder.append(decryptedfile);

            // if all chunks are downloaded
            if (chunkdownloaded === clearfileurl.length) {
                // get the blob
                var FinalFile = myBlobBuilder.getBlob();

                // launch consturction of a file with'FinalFile' inside FileReader API
                var reader = new FileReader();
                reader.onload = function(e){
                    // build & save on client the final file with 'file-saver' library
                    var FileSaver = require('file-saver');
                    var file = new File([FinalFile], clearfilename, {type: clearfiletype});
                    FileSaver.saveAs(file); 
                };
                reader.readAsText(FinalFile);

            } else {
                console.log('not yet');
            }
        } 
    };
    // sending XMLHttpRequest
    xhr.send();
});

Solution

  • You need to take out the declaration of MyBlobBuilder, try this:

    // var for incrementation in forEach funtion
    var chunkdownloaded = 0;
    
    //convert a dataURI to a Blob
    var MyBlobBuilder = function() {
        this.parts = [];
    }
    
    MyBlobBuilder.prototype.append = function(dataURI, index) {
    //function dataURItoBlob(dataURI) {
        // convert base64 to raw binary data held in a string
        var byteString = atob(dataURI.split(',')[1]);
    
        // separate out the mime component
        // var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
    
        // write the bytes of the string to an ArrayBuffer
        var ab = new ArrayBuffer(byteString.length);
        var ia = new Uint8Array(ab);
        for (var i = 0; i < byteString.length; i++) {
            ia[i] = byteString.charCodeAt(i);
        }
        this.parts[index] = ab;
        console.log('parts', this.parts)
        this.blob = undefined; // Invalidate the blob
    }
    
    MyBlobBuilder.prototype.getBlob = function() {
        if (!this.blob) {
            console.log (this.parts);
            this.blob = new Blob(this.parts);
        }
        return this.blob;
    };
    
    var myBlobBuilder = new MyBlobBuilder();
    
    // 'clearfileurl' is the array of url's chunks :[URL1, URL2, URL3,...]
    clearfileurl.forEach(function(entry, index) {
        var xhr = new XMLHttpRequest();
        var started_at = new Date();
        xhr.open('GET', entry, true);
        xhr.responseType = 'text';
    
        // request progress
        xhr.onprogress = function(pe) {
            if (pe.lengthComputable) {
                downloaderval.set((pe.loaded / pe.total) * 100);
            }
        };
    
        // on request's success
        xhr.onload = function(e) {
            if (this.status == 200) {
    
                chunkdownloaded+=1;
                var todecrypt = this.response;
    
                // decrypt request's response: get a dataURI
                try { 
                    var bytesfile  = CryptoJS.AES.decrypt(todecrypt.toString(), userKey);
                    var decryptedfile = bytesfile.toString(CryptoJS.enc.Utf8);
                } catch(err) {
                    console.log (err);
                    return false;
                }
    
    
                myBlobBuilder.append(decryptedfile, index);
    
                // if all chunks are downloaded
                if (chunkdownloaded === clearfileurl.length) {
                    // get the blob
                    var FinalFile = myBlobBuilder.getBlob();
    
                    // launch consturction of a file with'FinalFile' inside FileReader API
                    var reader = new FileReader();
                    reader.onload = function(e){
                        // build & save on client the final file with 'file-saver' library
                        var FileSaver = require('file-saver');
                        var file = new File([FinalFile], clearfilename, {type: clearfiletype});
                        FileSaver.saveAs(file); 
                    };
                    reader.readAsText(FinalFile);
    
                } else {
                    console.log('not yet');
                }
            } 
        };
        // sending XMLHttpRequest
        xhr.send();
    });
    

    *edit I also updated the append function to ensure that the files are in the correct order