Saving a jpg as base64 after resize, trying jsZip - Error

I would like to have a user use a file input to select a JPG, and then have it save the photo to my collection as base64 string. I’ve posted my event handler below. All images get resized to 1Mpixel

I can see the JPG fine in browser (works), but I have a script that exports all photos as a ZIP using jsZip… and now I get the error

Exception while invoking ‘exportData’ Error: Invalid base64 input, bad content length

<input type="file" id="uploadedFile" />
"change #uploadedFile": function(e, t){
    // create off-screen canvas
    var canvas = document.createElement('canvas');
    var ctx = canvas.getContext('2d');

    var img = new Image();
    img.onload = function() {
      if(img.width > img.height) { // landscape
        var ratio = 1280 / img.width;
        canvas.width = 1280;
        canvas.height = parseInt(ratio * img.height);
      }else{ // portrait
        var ratio = 1280 / img.height;
        canvas.height = 1280;
        canvas.width = parseInt(ratio * img.width);
      }
      ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
      imgData = canvas.toDataURL('image/jpeg', [0.0, 1.0]).split(',')[1]; // gets rid of the leading part of the URL
      var new_photo = {
        data: imgData,
        caption: "No caption added",
        form_id: Session.get("selected_form_id")
      }
      Meteor.call('createPhoto', new_photo);
    }
    img.src = URL.createObjectURL(e.target.files[0]);
  }

in server/export.js

Meteor.methods({
  exportData: function() {
    let zip = new jsZip();

    // add your photos
    let photos = Photos.find().fetch();
    if(photos) {
      photos.forEach(function(photo) {
        zip.file(photo.caption+".jpg", photo.data[0], {base64: true, createFolders: true});
      });
    }
    return makeArchive(zip);
});

async function makeArchive(archive) {
  let result = await archive.generateAsync({type: 'base64'});
  return result;
}

Maybe your base64 data contains type definition? Like data:base64...;? It is possible that expected input should contain only data chunk.

2 Likes

Thanks @mrzafod. I definitely considered that…

The following line should take off the image handle with the split(’,’)[1]

data:image/jpeg;base64,

imgData = canvas.toDataURL('image/jpeg', [0.0, 1.0]).split(',')[1]; // gets rid of the leading part of the URL

I just can’t figure it out.

I have a working jsFiddle of regular javascript that shows what I’m trying to do. Maybe it will help me solve my woes with this problem.

Here’s what I think…

The makeArchive function is asynchronous, so I have to use something like this:

makeArchive(zip).then(function(response) {
   // how do I return a value from in here? Help!
});

Thoughts?

Your original post looks like it does this correctly. However, you should also be using an async method:

Meteor.methods({
  async exportData() {
    const zip = new jsZip();

    // add your photos
    const photos = Photos.find().fetch();
    if (photos) {
      photos.forEach(photo => {
        zip.file(`${photo.caption}.jpg`, photo.data[0], { base64: true, createFolders: true });
      });
    }
    return await makeArchive(zip);
  },
});