Skip to content

Instantly share code, notes, and snippets.

@vojtad
Last active April 26, 2020 06:32
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vojtad/28eb4c43806513d7fd2196f97e11a9e8 to your computer and use it in GitHub Desktop.
Save vojtad/28eb4c43806513d7fd2196f97e11a9e8 to your computer and use it in GitHub Desktop.
Helper to enable client-side signing for AWS S3 uploading for Uppy with AWS S3 Multipart. Set callbacks for AWS S3 Multipart plugin to methods from this heper (https://uppy.io/docs/aws-s3-multipart/#createMultipartUpload-file).
import AWS from 'aws-sdk';
export default class AwsS3MultipartHelper {
constructor(options) {
this.options = {
bucket: options.bucket,
acl: options.acl || 'public-read',
expires: options.expires || 5 * 60,
};
AWS.config.credentials = new AWS.Credentials({
accessKeyId: options.credentials.accessKeyId,
secretAccessKey: options.credentials.secretAccessKey,
sessionToken: options.credentials.sessionToken,
});
this.s3 = new AWS.S3({});
}
createMultipartUpload(file) {
return new Promise((resolve, reject) => {
const metadata = {};
Object.keys(file.meta).map(key => {
if (file.meta[key] != null) {
metadata[key] = file.meta[key].toString()
}
})
const key = file.name;
this.s3.createMultipartUpload({
Bucket: this.options.bucket,
Key: key,
ACL: this.options.acl,
ContentType: file.type,
Metadata: metadata,
Expires: this.options.expires,
}, (error, data) => {
if (error) {
reject(error);
} else {
resolve({
key: data.Key,
uploadId: data.UploadId,
});
}
});
});
}
listParts(file, { key, uploadId }) {
return new Promise((resolve, reject) => {
let parts = [];
const listPartsPart = (part) => {
this.s3.listParts({
Bucket: this.options.bucket,
Key: key,
UploadId: uploadId,
PartNumberMarker: part,
}, (error, data) => {
if (error) {
reject(error);
return;
}
parts = parts.concat(data.Parts);
if (data.IsTruncated) {
listPartsPart(data.NextPartNumberMarker);
} else {
resolve(parts);
}
})
}
listPartsPart(0);
});
}
prepareUploadPart(file, { key, uploadId, number }) {
return new Promise((resolve, reject) => {
this.s3.getSignedUrl('uploadPart', {
Bucket: this.options.bucket,
Key: key,
UploadId: uploadId,
PartNumber: number,
Body: '',
Expires: this.options.expires,
}, (error, url) => {
if (error) {
reject(error);
} else {
resolve({ url });
}
});
});
}
abortMultipartUpload(file, { key, uploadId }) {
return new Promise((resolve, reject) => {
this.s3.abortMultipartUpload({
Bucket: this.options.bucket,
Key: key,
UploadId: uploadId
}, (error, data) => {
if (error) {
reject(error);
} else {
resolve({});
}
});
});
}
completeMultipartUpload(file, { key, uploadId, parts }) {
return new Promise((resolve, reject) => {
this.s3.completeMultipartUpload({
Bucket: this.options.bucket,
Key: key,
UploadId: uploadId,
MultipartUpload: {
Parts: parts
}
}, (error, data) => {
if (error) {
reject(error);
} else {
resolve({ location: data.Location });
}
});
});
}
};
@vojtad
Copy link
Author

vojtad commented Mar 26, 2020

I setup this helper and Uppy like this:

/*
 * `options` is generated by our server. It contains information about upload and credentials to use. It is expected to look like this:
 * {
 *   bucket: 'bucket', // name of bucket you want to upload to
 *   acl: 'public-read, // ACL you want to set for uploaded objects
 *   credentails: { // actual AWS credentials you want to use, we generate them using AWS STS GetFederationToken (https://docs.aws.amazon.com/STS/latest/APIReference/API_GetFederationToken.html)
 *     accessKeyId: '',
 *     secretAccessKey: '',
 *     sessionToken: ''
 *   }
 * }
 */
const options = { ... };

const awsS3MultipartHelper = new AwsS3MultipartHelper({
  bucket: options.bucket,
  acl: options.acl,
  credentials: options.credentials,
});

const uppyOptions = {};

const awsS3MultipartOptions = {
  limit: options.limit || 5,
  
  createMultipartUpload: (file) => awsS3MultipartHelper.createMultipartUpload(file),
  listParts: (file, options) => awsS3MultipartHelper.listParts(file, options),
  prepareUploadPart: (file, options) => awsS3MultipartHelper.prepareUploadPart(file, options),
  abortMultipartUpload: (file, options) => awsS3MultipartHelper.abortMultipartUpload(file, options),
  completeMultipartUpload: (file, options) => awsS3MultipartHelper.completeMultipartUpload(file, options),
}

const uppy = Uppy(uppyOptions).use(AwsS3Multipart, awsS3MultipartOptions);

Now you can use Uppy as usual, it will use the helper to sign AWS requests on client side.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment