After spending way too much time figuring out how to upload stuff (like videos and photos) to S3 in React Native with modules written by others (like react-native-uploader or react-native-fs), I decided to fall back to the good old XMLHttpRequest.

Also this is not strictly related to React Native as it can be used anywhere, but it’s where I used recently.

I am using a couple of variables here:

presignedUrl is the destination URL I get from our API (it is a really long string that looks something like the URL I’m putting at the bottom)
filePath is the full path to the file (including file://).
fileType is the mime type of the file (like video/mp4, or image/jpeg).
fileName is the file name with extension (something.jpg).

And then, drumroll, the super simple code snippet:

const xhr = new XMLHttpRequest()
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4) {
    if (xhr.status === 200) {
      // Successfully uploaded the file.
    } else {
      // The file could not be uploaded.
    }
  }
}
xhr.open('PUT', presignedUrl)
xhr.setRequestHeader('X-Amz-ACL', 'public-read')
xhr.setRequestHeader('Content-Type', fileType)
xhr.send({ uri: filePath, type: fileType, name: fileName })

Presigned URL example (the bold parts vary):

https://s3.amazonaws.com/your-app-name/fileName?X-Amz-Expires=86400&X-Amz-Date=20160825T222851Z&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=your-credentials/date/your-server/s3/aws4_request&X-Amz-SignedHeaders=content-type;host;x-amz-acl&X-Amz-Signature=generated_signature

Note that the API is signing 3 things when generating the URL (the content-type;host;x-amz-acl part in the url above) which I must include as headers in the request (the xhr.setRequestHeader part). If I don’t, or if the signature doesn’t match the request headers, S3 will reject my file.

References:
http://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html

Leave a Reply

Your email address will not be published. Required fields are marked *