react-s3-uploader
Version:
React component that renders a file input and automatically uploads to an S3 bucket
224 lines (199 loc) • 7.43 kB
JavaScript
/**
* Taken, CommonJS-ified, and heavily modified from:
* https://github.com/flyingsparx/NodeDirectUploader
*/
var mime = require('mime-types');
S3Upload.prototype.server = '';
S3Upload.prototype.signingUrl = '/sign-s3';
S3Upload.prototype.signingUrlMethod = 'GET';
S3Upload.prototype.successResponses = [200, 201];
S3Upload.prototype.fileElement = null;
S3Upload.prototype.files = null;
S3Upload.prototype.onFinishS3Put = function(signResult, file) {
return console.log('base.onFinishS3Put()', signResult.publicUrl);
};
S3Upload.prototype.preprocess = function(file, next) {
console.log('base.preprocess()', file);
return next(file);
};
S3Upload.prototype.onProgress = function(percent, status, file) {
return console.log('base.onProgress()', percent, status);
};
S3Upload.prototype.onError = function(status, file) {
return console.log('base.onError()', status);
};
S3Upload.prototype.onSignedUrl = function(result) {};
S3Upload.prototype.scrubFilename = function(filename) {
return filename.replace(/[^\w\d_\-\.]+/ig, '');
};
function S3Upload(options) {
if (options == null) {
options = {};
}
for (var option in options) {
if (options.hasOwnProperty(option)) {
this[option] = options[option];
}
}
var files = this.fileElement ? this.fileElement.files : this.files || [];
this.handleFileSelect(files);
}
function getFileMimeType(file) {
return file.type || mime.lookup(file.name);
}
S3Upload.prototype.handleFileSelect = function(files) {
var result = [];
for (var i=0; i < files.length; i++) {
var file = files[i];
this.preprocess(file, function(processedFile){
this.onProgress(0, 'Waiting', processedFile);
result.push(this.uploadFile(processedFile));
return result;
}.bind(this));
}
};
S3Upload.prototype.createCORSRequest = function(method, url, opts) {
var opts = opts || {};
var xhr = new XMLHttpRequest();
if (xhr.withCredentials != null) {
xhr.open(method, url, true);
if (opts.withCredentials != null) {
xhr.withCredentials = opts.withCredentials;
}
}
else if (typeof XDomainRequest !== "undefined") {
xhr = new XDomainRequest();
xhr.open(method, url);
}
else {
xhr = null;
}
return xhr;
};
S3Upload.prototype._getErrorRequestContext = function (xhr) {
return {
response: xhr.responseText,
status: xhr.status,
statusText: xhr.statusText,
readyState: xhr.readyState
};
}
S3Upload.prototype.executeOnSignedUrl = function(file, callback) {
var fileName = this.scrubFilename(file.name);
var queryString = '?objectName=' + fileName + '&contentType=' + encodeURIComponent(getFileMimeType(file));
if (this.s3path) {
queryString += '&path=' + encodeURIComponent(this.s3path);
}
if (this.signingUrlQueryParams) {
var signingUrlQueryParams = typeof this.signingUrlQueryParams === 'function' ? this.signingUrlQueryParams() : this.signingUrlQueryParams;
Object.keys(signingUrlQueryParams).forEach(function(key) {
var val = signingUrlQueryParams[key];
queryString += '&' + key + '=' + val;
});
}
var xhr = this.createCORSRequest(this.signingUrlMethod,
this.server + this.signingUrl + queryString, { withCredentials: this.signingUrlWithCredentials });
if (this.signingUrlHeaders) {
var signingUrlHeaders = typeof this.signingUrlHeaders === 'function' ? this.signingUrlHeaders() : this.signingUrlHeaders;
Object.keys(signingUrlHeaders).forEach(function(key) {
var val = signingUrlHeaders[key];
xhr.setRequestHeader(key, val);
});
}
xhr.overrideMimeType && xhr.overrideMimeType('text/plain; charset=x-user-defined');
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && this.successResponses.indexOf(xhr.status) >= 0) {
var result;
try {
result = JSON.parse(xhr.responseText);
this.onSignedUrl( result );
} catch (error) {
this.onError(
'Invalid response from server',
file,
this._getErrorRequestContext(xhr)
);
return false;
}
return callback(result);
} else if (xhr.readyState === 4 && this.successResponses.indexOf(xhr.status) < 0) {
return this.onError(
'Could not contact request signing server. Status = ' + xhr.status,
file,
this._getErrorRequestContext(xhr)
);
}
}.bind(this);
return xhr.send();
};
S3Upload.prototype.uploadToS3 = function(file, signResult) {
var xhr = this.createCORSRequest('PUT', signResult.signedUrl);
if (!xhr) {
this.onError('CORS not supported', file, {});
} else {
xhr.onload = function() {
if (this.successResponses.indexOf(xhr.status) >= 0) {
this.onProgress(100, 'Upload completed', file);
return this.onFinishS3Put(signResult, file);
} else {
return this.onError(
'Upload error: ' + xhr.status,
file,
this._getErrorRequestContext(xhr)
);
}
}.bind(this);
xhr.onerror = function() {
return this.onError(
'XHR error',
file,
this._getErrorRequestContext(xhr)
);
}.bind(this);
xhr.upload.onprogress = function(e) {
var percentLoaded;
if (e.lengthComputable) {
percentLoaded = Math.round((e.loaded / e.total) * 100);
return this.onProgress(percentLoaded, percentLoaded === 100 ? 'Finalizing' : 'Uploading', file);
}
}.bind(this);
}
var fileType = getFileMimeType(file);
var headers = {
'content-type': fileType
};
if (this.contentDisposition) {
var disposition = this.contentDisposition;
if (disposition === 'auto') {
if (fileType.substr(0, 6) === 'image/') {
disposition = 'inline';
} else {
disposition = 'attachment';
}
}
var fileName = this.scrubFilename(file.name)
headers['content-disposition'] = disposition + '; filename="' + fileName + '"';
}
if (!this.uploadRequestHeaders) {
xhr.setRequestHeader('x-amz-acl', 'public-read');
}
[signResult.headers, this.uploadRequestHeaders].filter(Boolean).forEach(function (hdrs) {
Object.entries(hdrs).forEach(function(pair) {
headers[pair[0].toLowerCase()] = pair[1];
})
});
Object.entries(headers).forEach(function (pair) {
xhr.setRequestHeader(pair[0], pair[1]);
})
this.httprequest = xhr;
return xhr.send(file);
};
S3Upload.prototype.uploadFile = function(file) {
var uploadToS3Callback = this.uploadToS3.bind(this, file);
if(this.getSignedUrl) return this.getSignedUrl(file, uploadToS3Callback);
return this.executeOnSignedUrl(file, uploadToS3Callback);
};
S3Upload.prototype.abortUpload = function() {
this.httprequest && this.httprequest.abort();
};
module.exports = S3Upload;