import AWS from "aws-sdk";
import bytes from "bytes";
const debug = require("debug")("app:Folder:AWSUploader");
const REGION = "us-west-2";
const POOL_ID = "us-west-2:banabnbanabnbanbfnabnabnabnabna";
const BUCKET_NAME = "sigma-direct";
export default class AWSUploader {
constructor(filename, type) {
this.bucketName = BUCKET_NAME; //audio file store
this.etag = []; // etag is used to save the parts of the single upload file
this.partNumber = 0; // multipart requires incremetal so that they can merge all parts by ascending order
this.filename = filename; //unique filename
this.type = type;
this.uploadId = ""; // upload id is required in multipart
this.uploadPromises = [];
this.curBlob = null;
AWS.config.region = REGION;
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: POOL_ID
});
this.s3 = new AWS.S3();
//make start request now, but don't block
this.initalizedP = this.startMultiUpload();
}
upload(blob) {
const prevUploads = [...this.uploadPromises]; //needs to be copy of array in prev state
const f = async () => {
await this.initalizedP; //make sure start request happened, I assume multiple blobs should never be waiting here
await Promise.all(prevUploads); //ensure all prevUploads are done
if (this.curBlob === null) {
this.curBlob = blob;
} else {
this.curBlob = new Blob([this.curBlob, blob], { type: this.type });
}
debug("Currently", bytes(this.curBlob.size), "sends at 5mb");
if (this.curBlob.size > bytes("5mb")) {
const cb = this.curBlob;
this.curBlob = null;
await this.continueMultiUpload(cb);
}
};
const uploadP = f();
this.uploadPromises.push(uploadP);
return uploadP;
}
/*
Initiates a multipart upload and returns an upload ID.
Upload id is used to upload the other parts of the stream
*/
startMultiUpload() {
debug("STARTING MULTIUPLOAD");
const startParams = {
Bucket: this.bucketName,
Key: this.filename,
ContentType: this.type,
ACL: "private"
};
return new Promise(async (resolve, reject) => {
this.s3.createMultipartUpload(startParams, (err, data) => {
if (err) {
reject(err);
} else {
debug("Created", data);
this.uploadId = data.UploadId;
resolve(data);
}
});
});
}
/*
Uploads a part in a multipart upload.
The following code uploads part of a multipart upload.
it specifies a file name for the part data. The Upload ID is same that is returned by the initiate multipart upload.
*/
continueMultiUpload(blob) {
this.partNumber += 1;
const curPartNumber = this.partNumber;
const params = {
Body: blob,
Bucket: this.bucketName,
Key: this.filename,
PartNumber: curPartNumber,
UploadId: this.uploadId
};
debug("Continuing upload with", params);
return new Promise((resolve, reject) => {
this.s3.uploadPart(params, (err, data) => {
if (err) {
reject(err);
} // an error occurred
else {
/*
Once the part of data is uploaded we get an Entity tag for the uploaded object(ETag).
which is used later when we complete our multipart upload.
*/
debug("Uploaded part", curPartNumber);
this.etag.push({
ETag: data.ETag,
PartNumber: curPartNumber
});
resolve(data);
}
});
});
}
// Completes a multipart upload by assembling previously uploaded parts.
async completeMultiUpload() {
//wait for all current uploads, then check if any blobs left over
await Promise.all(this.uploadPromises);
debug("Leftover blobs", this.curBlob);
if (this.curBlob) {
await this.continueMultiUpload(this.curBlob);
}
debug("Finalizing parts", this.etag);
this.etag = this.etag.sort((a, b) => a.PartNumber - b.PartNumber);
const params = {
Bucket: this.bucketName, // required
Key: this.filename, // required
UploadId: this.uploadId, // required
MultipartUpload: {
Parts: this.etag
}
};
return new Promise((resolve, reject) => {
this.s3.completeMultipartUpload(params, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
async abortMultiUpload() {
//wait for current uploads just to be sure
await Promise.all(this.uploadPromises);
//s3 error if you try to abort an invalid uploadID or an uploadID with no parts uploaded yet
if (!this.uploadId || this.partNumber === 0) {
debug("Nothing to abort");
return;
}
debug("Aborting");
const params = {
Bucket: this.bucketName, // required
Key: this.filename, // required
UploadId: this.uploadId // required
};
return new Promise((resolve, reject) => {
this.s3.completeMultipartUpload(params, (err, data) => {
if (err) {
debug("Err had", err);
reject(err);
} else {
debug("Aborted");
resolve(data);
}
});
});
}
}