uppy
Version:
Extensible JavaScript file upload widget with support for drag&drop, resumable uploads, previews, restrictions, file processing/encoding, remote providers like Instagram, Dropbox, Google Drive, S3 and more :dog:
498 lines (404 loc) • 14.9 kB
JavaScript
;
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var _Promise = typeof Promise === 'undefined' ? require('es6-promise').Promise : Promise;
var Plugin = require('../core/Plugin');
var tus = require('tus-js-client');
var UppySocket = require('../core/UppySocket');
var _require = require('../core/Utils'),
emitSocketProgress = _require.emitSocketProgress,
getSocketHost = _require.getSocketHost,
settle = _require.settle;
require('whatwg-fetch');
// Extracted from https://github.com/tus/tus-js-client/blob/master/lib/upload.js#L13
// excepted we removed 'fingerprint' key to avoid adding more dependencies
var tusDefaultOptions = {
endpoint: '',
resume: true,
onProgress: null,
onChunkComplete: null,
onSuccess: null,
onError: null,
headers: {},
chunkSize: Infinity,
withCredentials: false,
uploadUrl: null,
uploadSize: null,
overridePatchMethod: false,
retryDelays: null
/**
* Create a wrapper around an event emitter with a `remove` method to remove
* all events that were added using the wrapped emitter.
*/
};function createEventTracker(emitter) {
var events = [];
return {
on: function on(event, fn) {
events.push([event, fn]);
return emitter.on(event, fn);
},
remove: function remove() {
events.forEach(function (_ref) {
var event = _ref[0],
fn = _ref[1];
emitter.off(event, fn);
});
}
};
}
/**
* Tus resumable file uploader
*
*/
module.exports = function (_Plugin) {
_inherits(Tus, _Plugin);
function Tus(uppy, opts) {
_classCallCheck(this, Tus);
var _this = _possibleConstructorReturn(this, _Plugin.call(this, uppy, opts));
_this.type = 'uploader';
_this.id = 'Tus';
_this.title = 'Tus';
// set default options
var defaultOptions = {
resume: true,
autoRetry: true,
retryDelays: [0, 1000, 3000, 5000]
// merge default options with the ones set by user
};_this.opts = _extends({}, defaultOptions, opts);
_this.uploaders = Object.create(null);
_this.uploaderEvents = Object.create(null);
_this.uploaderSockets = Object.create(null);
_this.handleResetProgress = _this.handleResetProgress.bind(_this);
_this.handleUpload = _this.handleUpload.bind(_this);
return _this;
}
Tus.prototype.handleResetProgress = function handleResetProgress() {
var files = _extends({}, this.uppy.state.files);
Object.keys(files).forEach(function (fileID) {
// Only clone the file object if it has a Tus `uploadUrl` attached.
if (files[fileID].tus && files[fileID].tus.uploadUrl) {
var tusState = _extends({}, files[fileID].tus);
delete tusState.uploadUrl;
files[fileID] = _extends({}, files[fileID], { tus: tusState });
}
});
this.uppy.setState({ files: files });
};
/**
* Clean up all references for a file's upload: the tus.Upload instance,
* any events related to the file, and the uppy-server WebSocket connection.
*/
Tus.prototype.resetUploaderReferences = function resetUploaderReferences(fileID) {
if (this.uploaders[fileID]) {
this.uploaders[fileID].abort();
this.uploaders[fileID] = null;
}
if (this.uploaderEvents[fileID]) {
this.uploaderEvents[fileID].remove();
this.uploaderEvents[fileID] = null;
}
if (this.uploaderSockets[fileID]) {
this.uploaderSockets[fileID].close();
this.uploaderSockets[fileID] = null;
}
};
/**
* Create a new Tus upload
*
* @param {object} file for use with upload
* @param {integer} current file in a queue
* @param {integer} total number of files in a queue
* @returns {Promise}
*/
Tus.prototype.upload = function upload(file, current, total) {
var _this2 = this;
this.resetUploaderReferences(file.id);
// Create a new tus upload
return new _Promise(function (resolve, reject) {
var optsTus = _extends({}, tusDefaultOptions, _this2.opts,
// Install file-specific upload overrides.
file.tus || {});
optsTus.onError = function (err) {
_this2.uppy.log(err);
_this2.uppy.emit('upload-error', file, err);
err.message = 'Failed because: ' + err.message;
_this2.resetUploaderReferences(file.id);
reject(err);
};
optsTus.onProgress = function (bytesUploaded, bytesTotal) {
_this2.onReceiveUploadUrl(file, upload.url);
_this2.uppy.emit('upload-progress', file, {
uploader: _this2,
bytesUploaded: bytesUploaded,
bytesTotal: bytesTotal
});
};
optsTus.onSuccess = function () {
_this2.uppy.emit('upload-success', file, upload, upload.url);
if (upload.url) {
_this2.uppy.log('Download ' + upload.file.name + ' from ' + upload.url);
}
_this2.resetUploaderReferences(file.id);
resolve(upload);
};
optsTus.metadata = file.meta;
var upload = new tus.Upload(file.data, optsTus);
_this2.uploaders[file.id] = upload;
_this2.uploaderEvents[file.id] = createEventTracker(_this2.uppy);
_this2.onFileRemove(file.id, function (targetFileID) {
_this2.resetUploaderReferences(file.id);
resolve('upload ' + targetFileID + ' was removed');
});
_this2.onPause(file.id, function (isPaused) {
if (isPaused) {
upload.abort();
} else {
upload.start();
}
});
_this2.onPauseAll(file.id, function () {
upload.abort();
});
_this2.onCancelAll(file.id, function () {
_this2.resetUploaderReferences(file.id);
});
_this2.onResumeAll(file.id, function () {
if (file.error) {
upload.abort();
}
upload.start();
});
if (!file.isPaused) {
upload.start();
}
if (!file.isRestored) {
_this2.uppy.emit('upload-started', file, upload);
}
});
};
Tus.prototype.uploadRemote = function uploadRemote(file, current, total) {
var _this3 = this;
this.resetUploaderReferences(file.id);
var opts = _extends({}, this.opts,
// Install file-specific upload overrides.
file.tus || {});
return new _Promise(function (resolve, reject) {
_this3.uppy.log(file.remote.url);
if (file.serverToken) {
return _this3.connectToServerSocket(file).then(function () {
return resolve();
}).catch(reject);
}
_this3.uppy.emit('upload-started', file);
fetch(file.remote.url, {
method: 'post',
credentials: 'include',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(_extends({}, file.remote.body, {
endpoint: opts.endpoint,
uploadUrl: opts.uploadUrl,
protocol: 'tus',
size: file.data.size,
metadata: file.meta
}))
}).then(function (res) {
if (res.status < 200 || res.status > 300) {
return reject(res.statusText);
}
return res.json().then(function (data) {
_this3.uppy.setFileState(file.id, { serverToken: data.token });
file = _this3.getFile(file.id);
return file;
});
}).then(function (file) {
return _this3.connectToServerSocket(file);
}).then(function () {
resolve();
}).catch(function (err) {
reject(new Error(err));
});
});
};
Tus.prototype.connectToServerSocket = function connectToServerSocket(file) {
var _this4 = this;
return new _Promise(function (resolve, reject) {
var token = file.serverToken;
var host = getSocketHost(file.remote.host);
var socket = new UppySocket({ target: host + '/api/' + token });
_this4.uploaderSockets[file.id] = socket;
_this4.uploaderEvents[file.id] = createEventTracker(_this4.uppy);
_this4.onFileRemove(file.id, function () {
socket.send('pause', {});
resolve('upload ' + file.id + ' was removed');
});
_this4.onPause(file.id, function (isPaused) {
isPaused ? socket.send('pause', {}) : socket.send('resume', {});
});
_this4.onPauseAll(file.id, function () {
return socket.send('pause', {});
});
_this4.onCancelAll(file.id, function () {
return socket.send('pause', {});
});
_this4.onResumeAll(file.id, function () {
if (file.error) {
socket.send('pause', {});
}
socket.send('resume', {});
});
_this4.onRetry(file.id, function () {
socket.send('pause', {});
socket.send('resume', {});
});
_this4.onRetryAll(file.id, function () {
socket.send('pause', {});
socket.send('resume', {});
});
if (file.isPaused) {
socket.send('pause', {});
}
socket.on('progress', function (progressData) {
return emitSocketProgress(_this4, progressData, file);
});
socket.on('error', function (errData) {
_this4.uppy.emit('upload-error', file, new Error(errData.error));
reject(new Error(errData.error));
});
socket.on('success', function (data) {
_this4.uppy.emit('upload-success', file, data, data.url);
_this4.resetUploaderReferences(file.id);
resolve();
});
});
};
Tus.prototype.getFile = function getFile(fileID) {
return this.uppy.state.files[fileID];
};
Tus.prototype.updateFile = function updateFile(file) {
var _extends2;
var files = _extends({}, this.uppy.state.files, (_extends2 = {}, _extends2[file.id] = file, _extends2));
this.uppy.setState({ files: files });
};
Tus.prototype.onReceiveUploadUrl = function onReceiveUploadUrl(file, uploadURL) {
var currentFile = this.getFile(file.id);
if (!currentFile) return;
// Only do the update if we didn't have an upload URL yet,
// or resume: false in options
if ((!currentFile.tus || currentFile.tus.uploadUrl !== uploadURL) && this.opts.resume) {
this.uppy.log('[Tus] Storing upload url');
var newFile = _extends({}, currentFile, {
tus: _extends({}, currentFile.tus, {
uploadUrl: uploadURL
})
});
this.updateFile(newFile);
}
};
Tus.prototype.onFileRemove = function onFileRemove(fileID, cb) {
this.uploaderEvents[fileID].on('file-removed', function (file) {
if (fileID === file.id) cb(file.id);
});
};
Tus.prototype.onPause = function onPause(fileID, cb) {
this.uploaderEvents[fileID].on('upload-pause', function (targetFileID, isPaused) {
if (fileID === targetFileID) {
// const isPaused = this.uppy.pauseResume(fileID)
cb(isPaused);
}
});
};
Tus.prototype.onRetry = function onRetry(fileID, cb) {
this.uploaderEvents[fileID].on('upload-retry', function (targetFileID) {
if (fileID === targetFileID) {
cb();
}
});
};
Tus.prototype.onRetryAll = function onRetryAll(fileID, cb) {
var _this5 = this;
this.uploaderEvents[fileID].on('retry-all', function (filesToRetry) {
if (!_this5.uppy.getFile(fileID)) return;
cb();
});
};
Tus.prototype.onPauseAll = function onPauseAll(fileID, cb) {
var _this6 = this;
this.uploaderEvents[fileID].on('pause-all', function () {
if (!_this6.uppy.getFile(fileID)) return;
cb();
});
};
Tus.prototype.onCancelAll = function onCancelAll(fileID, cb) {
var _this7 = this;
this.uploaderEvents[fileID].on('cancel-all', function () {
if (!_this7.uppy.getFile(fileID)) return;
cb();
});
};
Tus.prototype.onResumeAll = function onResumeAll(fileID, cb) {
var _this8 = this;
this.uploaderEvents[fileID].on('resume-all', function () {
if (!_this8.uppy.getFile(fileID)) return;
cb();
});
};
Tus.prototype.uploadFiles = function uploadFiles(files) {
var _this9 = this;
var promises = files.map(function (file, index) {
var current = parseInt(index, 10) + 1;
var total = files.length;
if (file.error) {
return _Promise.reject(new Error(file.error));
}
_this9.uppy.log('uploading ' + current + ' of ' + total);
if (file.isRemote) {
return _this9.uploadRemote(file, current, total);
} else {
return _this9.upload(file, current, total);
}
});
return settle(promises);
};
Tus.prototype.handleUpload = function handleUpload(fileIDs) {
var _this10 = this;
if (fileIDs.length === 0) {
this.uppy.log('Tus: no files to upload!');
return _Promise.resolve();
}
this.uppy.log('Tus is uploading...');
var filesToUpload = fileIDs.map(function (fileID) {
return _this10.uppy.getFile(fileID);
});
return this.uploadFiles(filesToUpload).then(function () {
return null;
});
};
Tus.prototype.addResumableUploadsCapabilityFlag = function addResumableUploadsCapabilityFlag() {
var newCapabilities = _extends({}, this.uppy.getState().capabilities);
newCapabilities.resumableUploads = true;
this.uppy.setState({
capabilities: newCapabilities
});
};
Tus.prototype.install = function install() {
this.addResumableUploadsCapabilityFlag();
this.uppy.addUploader(this.handleUpload);
this.uppy.on('reset-progress', this.handleResetProgress);
if (this.opts.autoRetry) {
this.uppy.on('back-online', this.uppy.retryAll);
}
};
Tus.prototype.uninstall = function uninstall() {
this.uppy.removeUploader(this.handleUpload);
if (this.opts.autoRetry) {
this.uppy.off('back-online', this.uppy.retryAll);
}
};
return Tus;
}(Plugin);
//# sourceMappingURL=Tus.js.map