uppy
Version:
Almost as cute as a Puppy :dog:
557 lines (455 loc) • 19.7 kB
JavaScript
'use strict';
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('../Plugin');
var Client = require('./Client');
var StatusSocket = require('./Socket'
/**
* Upload files to Transloadit using Tus.
*/
);module.exports = function (_Plugin) {
_inherits(Transloadit, _Plugin);
function Transloadit(core, opts) {
_classCallCheck(this, Transloadit);
var _this = _possibleConstructorReturn(this, _Plugin.call(this, core, opts));
_this.type = 'uploader';
_this.id = 'Transloadit';
_this.title = 'Transloadit';
var defaultLocale = {
strings: {
creatingAssembly: 'Preparing upload...',
creatingAssemblyFailed: 'Transloadit: Could not create assembly',
encoding: 'Encoding...'
}
};
var defaultOptions = {
waitForEncoding: false,
waitForMetadata: false,
alwaysRunAssembly: false, // TODO name
importFromUploadURLs: false,
signature: null,
params: null,
fields: {},
getAssemblyOptions: function getAssemblyOptions(file, options) {
return {
params: options.params,
signature: options.signature,
fields: options.fields
};
},
locale: defaultLocale
};
_this.opts = _extends({}, defaultOptions, opts);
_this.locale = _extends({}, defaultLocale, _this.opts.locale);
_this.locale.strings = _extends({}, defaultLocale.strings, _this.opts.locale.strings);
_this.prepareUpload = _this.prepareUpload.bind(_this);
_this.afterUpload = _this.afterUpload.bind(_this);
_this.onFileUploadURLAvailable = _this.onFileUploadURLAvailable.bind(_this);
if (_this.opts.params) {
_this.validateParams(_this.opts.params);
}
_this.client = new Client();
_this.sockets = {};
return _this;
}
Transloadit.prototype.validateParams = function validateParams(params) {
if (!params) {
throw new Error('Transloadit: The `params` option is required.');
}
if (typeof params === 'string') {
try {
params = JSON.parse(params);
} catch (err) {
// Tell the user that this is not an Uppy bug!
err.message = 'Transloadit: The `params` option is a malformed JSON string: ' + err.message;
throw err;
}
}
if (!params.auth || !params.auth.key) {
throw new Error('Transloadit: The `params.auth.key` option is required. ' + 'You can find your Transloadit API key at https://transloadit.com/accounts/credentials.');
}
};
Transloadit.prototype.getAssemblyOptions = function getAssemblyOptions(fileIDs) {
var _this2 = this;
var options = this.opts;
return Promise.all(fileIDs.map(function (fileID) {
var file = _this2.core.getFile(fileID);
var promise = Promise.resolve().then(function () {
return options.getAssemblyOptions(file, options);
});
return promise.then(function (assemblyOptions) {
_this2.validateParams(assemblyOptions.params);
return {
fileIDs: [fileID],
options: assemblyOptions
};
});
}));
};
Transloadit.prototype.dedupeAssemblyOptions = function dedupeAssemblyOptions(list) {
var dedupeMap = Object.create(null);
list.forEach(function (_ref) {
var fileIDs = _ref.fileIDs,
options = _ref.options;
var id = JSON.stringify(options);
if (dedupeMap[id]) {
var _dedupeMap$id$fileIDs;
(_dedupeMap$id$fileIDs = dedupeMap[id].fileIDs).push.apply(_dedupeMap$id$fileIDs, fileIDs);
} else {
dedupeMap[id] = {
options: options,
fileIDs: [].concat(fileIDs)
};
}
});
return Object.keys(dedupeMap).map(function (id) {
return dedupeMap[id];
});
};
Transloadit.prototype.createAssembly = function createAssembly(fileIDs, uploadID, options) {
var _this3 = this;
var pluginOptions = this.opts;
this.core.log('Transloadit: create assembly');
return this.client.createAssembly({
params: options.params,
fields: options.fields,
expectedFiles: fileIDs.length,
signature: options.signature
}).then(function (assembly) {
var _extends2, _extends3;
// Store the list of assemblies related to this upload.
var state = _this3.core.state.transloadit;
var assemblyList = state.uploadsAssemblies[uploadID];
var uploadsAssemblies = _extends({}, state.uploadsAssemblies, (_extends2 = {}, _extends2[uploadID] = assemblyList.concat([assembly.assembly_id]), _extends2));
_this3.setPluginState({
assemblies: _extends(state.assemblies, (_extends3 = {}, _extends3[assembly.assembly_id] = assembly, _extends3)),
uploadsAssemblies: uploadsAssemblies
});
function attachAssemblyMetadata(file, assembly) {
// Attach meta parameters for the Tus plugin. See:
// https://github.com/tus/tusd/wiki/Uploading-to-Transloadit-using-tus#uploading-using-tus
// TODO Should this `meta` be moved to a `tus.meta` property instead?
var tlMeta = {
assembly_url: assembly.assembly_url,
filename: file.name,
fieldname: 'file'
};
var meta = _extends({}, file.meta, tlMeta
// Add assembly-specific Tus endpoint.
);var tus = _extends({}, file.tus, {
endpoint: assembly.tus_url,
// Only send assembly metadata to the tus endpoint.
metaFields: Object.keys(tlMeta)
});
var transloadit = {
assembly: assembly.assembly_id
};
var newFile = _extends({}, file, { transloadit: transloadit }
// Only configure the Tus plugin if we are uploading straight to Transloadit (the default).
);if (!pluginOptions.importFromUploadURLs) {
_extends(newFile, { meta: meta, tus: tus });
}
return newFile;
}
var files = _extends({}, _this3.core.state.files);
fileIDs.forEach(function (id) {
files[id] = attachAssemblyMetadata(files[id], assembly);
});
_this3.core.setState({ files: files });
_this3.core.emit('transloadit:assembly-created', assembly, fileIDs);
return _this3.connectSocket(assembly).then(function () {
return assembly;
});
}).then(function (assembly) {
_this3.core.log('Transloadit: Created assembly');
return assembly;
}).catch(function (err) {
_this3.core.info(pluginOptions.locale.strings.creatingAssemblyFailed, 'error', 0
// Reject the promise.
);throw err;
});
};
Transloadit.prototype.shouldWait = function shouldWait() {
return this.opts.waitForEncoding || this.opts.waitForMetadata;
};
/**
* Used when `importFromUploadURLs` is enabled: reserves all files in
* the assembly.
*/
Transloadit.prototype.reserveFiles = function reserveFiles(assembly, fileIDs) {
var _this4 = this;
return Promise.all(fileIDs.map(function (fileID) {
var file = _this4.core.getFile(fileID);
return _this4.client.reserveFile(assembly, file);
}));
};
/**
* Used when `importFromUploadURLs` is enabled: adds files to the assembly
* once they have been fully uploaded.
*/
Transloadit.prototype.onFileUploadURLAvailable = function onFileUploadURLAvailable(fileID) {
var _this5 = this;
var file = this.core.getFile(fileID);
if (!file || !file.transloadit || !file.transloadit.assembly) {
return;
}
var state = this.core.state.transloadit;
var assembly = state.assemblies[file.transloadit.assembly];
this.client.addFile(assembly, file).catch(function (err) {
_this5.core.log(err);
_this5.core.emit('transloadit:import-error', assembly, file.id, err);
});
};
Transloadit.prototype.findFile = function findFile(uploadedFile) {
var files = this.core.state.files;
for (var id in files) {
if (!files.hasOwnProperty(id)) {
continue;
}
if (files[id].uploadURL === uploadedFile.tus_upload_url) {
return files[id];
}
}
};
Transloadit.prototype.onFileUploadComplete = function onFileUploadComplete(assemblyId, uploadedFile) {
var _extends4;
var state = this.core.state.transloadit;
var file = this.findFile(uploadedFile);
this.setPluginState({
files: _extends({}, state.files, (_extends4 = {}, _extends4[uploadedFile.id] = {
id: file.id,
uploadedFile: uploadedFile
}, _extends4))
});
this.core.emit('transloadit:upload', uploadedFile, this.getAssembly(assemblyId));
};
Transloadit.prototype.onResult = function onResult(assemblyId, stepName, result) {
var state = this.core.state.transloadit;
var file = state.files[result.original_id];
// The `file` may not exist if an import robot was used instead of a file upload.
result.localId = file ? file.id : null;
this.setPluginState({
results: state.results.concat(result)
});
this.core.emit('transloadit:result', stepName, result, this.getAssembly(assemblyId));
};
Transloadit.prototype.onAssemblyFinished = function onAssemblyFinished(url) {
var _this6 = this;
this.client.getAssemblyStatus(url).then(function (assembly) {
var _extends5;
var state = _this6.core.state.transloadit;
_this6.setPluginState({
assemblies: _extends({}, state.assemblies, (_extends5 = {}, _extends5[assembly.assembly_id] = assembly, _extends5))
});
_this6.core.emit('transloadit:complete', assembly);
});
};
Transloadit.prototype.connectSocket = function connectSocket(assembly) {
var _this7 = this;
var socket = new StatusSocket(assembly.websocket_url, assembly);
this.sockets[assembly.assembly_id] = socket;
socket.on('upload', this.onFileUploadComplete.bind(this, assembly.assembly_id));
socket.on('error', function (error) {
_this7.core.emit('transloadit:assembly-error', assembly, error);
});
if (this.opts.waitForEncoding) {
socket.on('result', this.onResult.bind(this, assembly.assembly_id));
}
if (this.opts.waitForEncoding) {
socket.on('finished', function () {
_this7.onAssemblyFinished(assembly.assembly_ssl_url);
});
} else if (this.opts.waitForMetadata) {
socket.on('metadata', function () {
_this7.onAssemblyFinished(assembly.assembly_ssl_url);
_this7.core.emit('transloadit:complete', assembly);
});
}
return new _Promise(function (resolve, reject) {
socket.on('connect', resolve);
socket.on('error', reject);
}).then(function () {
_this7.core.log('Transloadit: Socket is ready');
});
};
Transloadit.prototype.prepareUpload = function prepareUpload(fileIDs, uploadID) {
var _this8 = this,
_extends6;
fileIDs.forEach(function (fileID) {
_this8.core.emit('core:preprocess-progress', fileID, {
mode: 'indeterminate',
message: _this8.opts.locale.strings.creatingAssembly
});
});
var createAssembly = function createAssembly(_ref2) {
var fileIDs = _ref2.fileIDs,
options = _ref2.options;
return _this8.createAssembly(fileIDs, uploadID, options).then(function (assembly) {
if (_this8.opts.importFromUploadURLs) {
return _this8.reserveFiles(assembly, fileIDs);
}
}).then(function () {
fileIDs.forEach(function (fileID) {
_this8.core.emit('core:preprocess-complete', fileID);
});
});
};
var state = this.core.state.transloadit;
var uploadsAssemblies = _extends({}, state.uploadsAssemblies, (_extends6 = {}, _extends6[uploadID] = [], _extends6));
this.setPluginState({ uploadsAssemblies: uploadsAssemblies });
var optionsPromise = void 0;
if (fileIDs.length > 0) {
optionsPromise = this.getAssemblyOptions(fileIDs).then(function (allOptions) {
return _this8.dedupeAssemblyOptions(allOptions);
});
} else if (this.opts.alwaysRunAssembly) {
optionsPromise = Promise.resolve(this.opts.getAssemblyOptions(null, this.opts)).then(function (options) {
_this8.validateParams(options.params);
return [{ fileIDs: fileIDs, options: options }];
});
} else {
// If there are no files and we do not `alwaysRunAssembly`,
// don't do anything.
return Promise.resolve();
}
return optionsPromise.then(function (assemblies) {
return Promise.all(assemblies.map(createAssembly));
});
};
Transloadit.prototype.afterUpload = function afterUpload(fileIDs, uploadID) {
var _this9 = this;
var state = this.core.state.transloadit;
var assemblyIDs = state.uploadsAssemblies[uploadID];
// If we don't have to wait for encoding metadata or results, we can close
// the socket immediately and finish the upload.
if (!this.shouldWait()) {
assemblyIDs.forEach(function (assemblyID) {
var socket = _this9.sockets[assemblyID];
socket.close();
});
return Promise.resolve();
}
// If no assemblies were created for this upload, we also do not have to wait.
// There's also no sockets or anything to close, so just return immediately.
if (assemblyIDs.length === 0) {
return Promise.resolve();
}
var finishedAssemblies = 0;
return new _Promise(function (resolve, reject) {
fileIDs.forEach(function (fileID) {
_this9.core.emit('core:postprocess-progress', fileID, {
mode: 'indeterminate',
message: _this9.opts.locale.strings.encoding
});
});
var onAssemblyFinished = function onAssemblyFinished(assembly) {
// An assembly for a different upload just finished. We can ignore it.
if (assemblyIDs.indexOf(assembly.assembly_id) === -1) {
return;
}
// TODO set the `file.uploadURL` to a result?
// We will probably need an option here so the plugin user can tell us
// which result to pick…?
var files = _this9.getAssemblyFiles(assembly.assembly_id);
files.forEach(function (file) {
_this9.core.emit('core:postprocess-complete', file.id);
});
finishedAssemblies += 1;
if (finishedAssemblies === assemblyIDs.length) {
// We're done, these listeners can be removed
removeListeners();
resolve();
}
};
var onAssemblyError = function onAssemblyError(assembly, error) {
// An assembly for a different upload just finished. We can ignore it.
if (assemblyIDs.indexOf(assembly.assembly_id) === -1) {
return;
}
// Clear postprocessing state for all our files.
var files = _this9.getAssemblyFiles(assembly.assembly_id);
files.forEach(function (file) {
_this9.core.emit('core:postprocess-complete', file.id);
}
// Should we remove the listeners here or should we keep handling finished
// assemblies?
// Doing this for now so that it's not possible to receive more postprocessing
// events once the upload has failed.
);removeListeners
// Reject the `afterUpload()` promise.
();reject(error);
};
var onImportError = function onImportError(assembly, fileID, error) {
if (assemblyIDs.indexOf(assembly.assembly_id) === -1) {
return;
}
// Not sure if we should be doing something when it's just one file failing.
// ATM, the only options are 1) ignoring or 2) failing the entire upload.
// I think failing the upload is better than silently ignoring.
// In the future we should maybe have a way to resolve uploads with some failures,
// like returning an object with `{ successful, failed }` uploads.
onAssemblyError(assembly, error);
};
var removeListeners = function removeListeners() {
_this9.core.off('transloadit:complete', onAssemblyFinished);
_this9.core.off('transloadit:assembly-error', onAssemblyError);
_this9.core.off('transloadit:import-error', onImportError);
};
_this9.core.on('transloadit:complete', onAssemblyFinished);
_this9.core.on('transloadit:assembly-error', onAssemblyError);
_this9.core.on('transloadit:import-error', onImportError);
}).then(function () {
// Clean up uploadID → assemblyIDs, they're no longer going to be used anywhere.
var state = _this9.core.state.transloadit;
var uploadsAssemblies = _extends({}, state.uploadsAssemblies);
delete uploadsAssemblies[uploadID];
_this9.setPluginState({ uploadsAssemblies: uploadsAssemblies });
});
};
Transloadit.prototype.install = function install() {
this.core.addPreProcessor(this.prepareUpload);
this.core.addPostProcessor(this.afterUpload);
if (this.opts.importFromUploadURLs) {
this.core.on('core:upload-success', this.onFileUploadURLAvailable);
}
this.setPluginState({
// Contains assembly status objects, indexed by their ID.
assemblies: {},
// Contains arrays of assembly IDs, indexed by the upload ID that they belong to.
uploadsAssemblies: {},
// Contains file data from Transloadit, indexed by their Transloadit-assigned ID.
files: {},
// Contains result data from Transloadit.
results: []
});
};
Transloadit.prototype.uninstall = function uninstall() {
this.core.removePreProcessor(this.prepareUpload);
this.core.removePostProcessor(this.afterUpload);
if (this.opts.importFromUploadURLs) {
this.core.off('core:upload-success', this.onFileUploadURLAvailable);
}
};
Transloadit.prototype.getAssembly = function getAssembly(id) {
var state = this.core.state.transloadit;
return state.assemblies[id];
};
Transloadit.prototype.getAssemblyFiles = function getAssemblyFiles(assemblyID) {
var _this10 = this;
var fileIDs = Object.keys(this.core.state.files);
return fileIDs.map(function (fileID) {
return _this10.core.getFile(fileID);
}).filter(function (file) {
return file && file.transloadit && file.transloadit.assembly === assemblyID;
});
};
Transloadit.prototype.setPluginState = function setPluginState(newState) {
var transloadit = _extends({}, this.core.state.transloadit, newState);
this.core.setState({ transloadit: transloadit });
};
return Transloadit;
}(Plugin);
//# sourceMappingURL=index.js.map