Skip to content
Greg MacWilliam edited this page Oct 16, 2018 · 2 revisions

Step 1: Configure CORS for your S3 bucket

First you’ll need to configure your S3 bucket to accept Evaporate uploads. Go into your AWS console and access Amazon S3 > your-bucket > Permissions > CORS Configuration, and enter the following:

<CORSConfiguration>
  <CORSRule>
    <AllowedOrigin>https://*.yourdomain.com</AllowedOrigin>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedMethod>DELETE</AllowedMethod>
    <AllowedMethod>GET</AllowedMethod>
    <ExposeHeader>ETag</ExposeHeader>
    <AllowedHeader>*</AllowedHeader>
  </CORSRule>
</CORSConfiguration>

The PUT and ETag options are essential, and DELETE is required for multipart uploads. See the S3 Bucket Configuration page for advanced configuration that support other Evaporate features.

Step 2: Setup a signing service for your application

All S3 uploads must be authorized using your AWS secret key. To keep your key private, Evaporate will generate an upload policy and send it to your application server for signing. A signature is just a SHA hash derived from the proposed upload policy combined with your AWS secret key. Once Evaporate has a policy signature, it can start uploading a file directly from the browser.

To establish a signing service, define a GET route that recieves datetime and to_sign params, and provides an AWS v4 signature as the response text. For example:

def signv4_upload
  date_stamp = Date.strptime(params[:datetime], "%Y%m%dT%H%M%SZ").strftime("%Y%m%d")
  secret_key = "AWS_SECRET_KEY"
  aws_region = "AWS_REGION"

  date_key = OpenSSL::HMAC.digest("sha256", "AWS4" + secret_key, date_stamp)
  region_key = OpenSSL::HMAC.digest("sha256", date_key, aws_region)
  service_key = OpenSSL::HMAC.digest("sha256", region_key, "s3")
  signing_key = OpenSSL::HMAC.digest("sha256", service_key, "aws4_request")

  render plain: OpenSSL::HMAC.hexdigest("sha256", signing_key, params[:to_sign]).gsub("\n", "")
end

See the Evaporate examples directory for v4 signature services implemented in Go, Node, PHP, Python, and Ruby.

Step 3: Configure Evaporate

Now you'll need a configured Evaporate instance to upload with. To upload from a browser, you’ll need to polyfill MD5 and SHA256 crypto functions, which can be obtained from the packages spark-md5 (supports ArrayBuffer) and js-sha256. If you’re uploading from a Node environment, you can use Node’s built-in crypto libraries.

Create an Evaporate instance with the path to your application’s signature service, your AWS public key (or, “Access Key”), the name of your bucket, and references to your crypto functions:

import Evaporate from 'evaporate';
import sparkMD5 from 'spark-md5';
import sha256 from 'js-sha256';

const uploader = Evaporate.create({
  signerUrl: '/auth/signv4_upload',
  aws_key: 'AWS_PUBLIC_KEY',
  bucket: 'your-bucket-name',
  cloudfront: true,
  computeContentMd5: true,
  cryptoMd5Method: (d) => btoa(sparkMD5.ArrayBuffer.hash(d, true)),
  cryptoHexEncodedHash256: sha256,
});

That’s it, Evaporate should be ready to upload.

Step 4: Upload files

To start uploading, call the Evaporate .add method with File objects. You may provide callback handlers to respond to any of Evaporate's state transitions:

function uploadFile(file) {
  uploader.then((evaporate) => {
    evaporate.add({
      file: file,
      name: file.name,
      progress: (percent, stats) => console.log('Progress', percent, stats),
      complete: (xhr, awsObjectKey) => console.log('Complete!', awsObjectKey),
      error: (mssg) => console.log('Error', mssg),
      paused: () => console.log('Paused'),
      pausing: () => console.log('Pausing'),
      resumed: () => console.log('Resumed'),
      cancelled: () => console.log('Cancelled'),
      started: (fileKey) => console.log('Started', fileKey),
      uploadInitiated: (s3Id) => console.log('Upload Initiated', s3Id),
      warn: (mssg) => console.log('Warning', mssg)
    }).then(
      (awsObjectKey) => console.log('File successfully uploaded to:', awsObjectKey),
      (reason) => console.log('File did not upload sucessfully:', reason)
    );
  });
}

// Respond to files selected using a file input field:
document.querySelector('#file-field').addEventListener('change', (evt) => {
  Array.from(evt.target.files).forEach(uploadFile);
  evt.target.value = '';
});

Happy uploading!