Picture upload to s3 and store URL in mongo

I’m trying to upload a picture taken with mdg:camera on the client to s3 and then store the image URL in mongo with some other user input using autoform. I can get the file to s3 using Meteor.call, and can print out the s3 URL on the server console. But it seems to behave in a synchronous mode and my callback data doesn’t get returned, even when specifying the async version. I’ve instrumented timers on each side and compared time stamps and the client message prints out immediately.

Q1: is this the best approach? I suspect it might be a race condition, since the user could be ready to submit the form before the fileURL comes back.

Q2: what am I doing wrong with the callbacks?

Thanks!

Template.reportIssue.events({
‘click .capture’: function(){
MeteorCamera.getPicture({}, function(error,data) {
Meteor.call(‘base64tos3’, data, function(err,results) {
if (err) {
console.log(‘error in base64 call’+err.message);
} else {
console.log('reportIsue S3 url = '+results);
Session.set(‘imageURL’,results);
}
}
);
});
}

if (Meteor.isServer) {
Meteor.methods({
‘base64tos3’ : function(photo, clientcallback){
/* … */
AWS.config.update({accessKeyId: AWS_KEY_ID, secretAccessKey: AWS_SECRET_KEY, region: AWS_REGION});
buf = new Buffer(photo.replace(/^data:image/\w+;base64,/, “”),‘base64’)
str = +new Date + Math.floor((Math.random() * 100) + 1)+ “.jpg”;
var params = {
Bucket: ‘xyz’,
Key: str,
Body: buf,
ACL:‘public-read’,
ContentEncoding: ‘base64’,
ContentType: ‘image/jpeg’
};

var s3 = new AWS.S3();
s3.putObject(params, function(err, data) {
  if (err) {
      throw new Meteor.Error("oopsie", "Error.");
    }
    else {
      var urlParams = {Bucket: 'xyz', Key: str};
      s3.getSignedUrl('getObject', urlParams, function(err, url){
        console.log('the url of the image is ' +  url);
      clientcallback(null,url);
      });
    }
  });

}
});

Hi @RockoDragon, perhaps you could try out https://atmospherejs.com/edgee/slingshot, that way the file gets uploaded directly to AWS, not via your server.

3 Likes

+1 on slingshot


Thanks for the excellent suggestion. Makes a lot more sense. I have tried implementing that, but ran into another problem. Since I took the picture with the camera, I don’t have a ‘file’ to upload. I have this resource URI.

data:image/jpeg;base64,...

I was trying to read it using the code below, but get an error that window.resolveLocalFileSystemURL is not a function. I’ve searched and found some discussion about CORS and a local http server.I’m guessing there must be an easier way to convert the URI to a file using some other function?

window.resolveLocalFileSystemURL(data, function(fileEntry) {
          fileEntry.file(function(file) {
              file.name = filename;
              template.cordovaFile = file;
          });
      });

You can create a File object by just doing:

function dataUriToFile(dataUri, fileName) {
  // https://en.wikipedia.org/wiki/Data_URI_scheme
  // create a pattern to match the data uri
  var patt = /^data:([^\/]+\/[^;]+)?(;charset=([^;]+))?(;base64)?,/i,
    matches = dataUri.match(patt);
  if (matches == null){
    throw new Error("data: uri did not match scheme")
  }
  var 
    prefix = matches[0],
    contentType = matches[1],
    // var charset = matches[3]; -- not used.
    isBase64 = matches[4] != null,
    // remove the prefix
    encodedBytes = dataUri.slice(prefix.length),
    // decode the bytes
    decodedBytes = isBase64 ? atob(encodedBytes) : encodedBytes,
    // return the file object
    props = {};
  if (contentType) {
    props.type = contentType;
  }
  return new File([decodedBytes], fileName, props);
}
1 Like

How can I use this file in Slingshot?

I have set the file to a var instead of returning it, and tried to pass it into SlingShot but I keep getting:
Uncaught TypeError: Cannot read property 'files' of null

I guess I’m just not accessing it right, but I have tried lots of different ways.

...
  if (contentType) {
    props.type = contentType;
  }
  var imageFile = new File([decodedBytes], fileName, props);

           var upload = new Slingshot.Upload("myImageUploads");          
           upload.send(document.getElementById(imageFile).files[0], function (error, url) {
             uploader.set();
             if (error) {
               console.error('Error uploading');
               alert (error);
                }
             else{
               console.log("Success!");
                 }
             });
             uploader.set(upload);
        }
  });

upload.send(document.getElementById(imageFile).files[0] ...implies you’re looking for a file input on the document. Since you’re not doing that, you should be able to just upload.send(imageFile, function(){...})