@uppy/transloadit
Version:
The Transloadit plugin can be used to upload files to Transloadit for all kinds of processing, such as transcoding video, resizing images, zipping/unzipping, and more
819 lines (801 loc) • 32.7 kB
JavaScript
function _classPrivateFieldLooseBase(e, t) { if (!{}.hasOwnProperty.call(e, t)) throw new TypeError("attempted to use private field on non-instance"); return e; }
var id = 0;
function _classPrivateFieldLooseKey(e) { return "__private_" + id++ + "_" + e; }
import hasProperty from '@uppy/utils/lib/hasProperty';
import ErrorWithCause from '@uppy/utils/lib/ErrorWithCause';
import { RateLimitedQueue } from '@uppy/utils/lib/RateLimitedQueue';
import Tus from '@uppy/tus';
import { BasePlugin } from '@uppy/core';
import Assembly from './Assembly.js';
import Client from './Client.js';
import AssemblyWatcher from './AssemblyWatcher.js';
import locale from './locale.js';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore We don't want TS to generate types for the package.json
const packageJson = {
"version": "4.2.1"
}; // eslint-disable-next-line @typescript-eslint/no-unused-vars
const defaultOptions = {
service: 'https://api2.transloadit.com',
errorReporting: true,
waitForEncoding: false,
waitForMetadata: false,
alwaysRunAssembly: false,
importFromUploadURLs: false,
limit: 20,
retryDelays: [7000, 10000, 15000, 20000],
clientName: null
};
/**
* State we want to store in Golden Retriever to be able to recover uploads.
*/
const sendErrorToConsole = originalErr => err => {
const error = new ErrorWithCause('Failed to send error to the client', {
cause: err
});
// eslint-disable-next-line no-console
console.error(error, originalErr);
};
function validateParams(params) {
if (params == null) {
throw new Error('Transloadit: The `params` option is required.');
}
if (typeof params === 'string') {
try {
// eslint-disable-next-line no-param-reassign
params = JSON.parse(params);
} catch (err) {
// Tell the user that this is not an Uppy bug!
throw new ErrorWithCause('Transloadit: The `params` option is a malformed JSON string.', {
cause: 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/c/template-credentials');
}
}
const COMPANION_URL = 'https://api2.transloadit.com/companion';
// Regex matching acceptable postMessage() origins for authentication feedback from companion.
const COMPANION_ALLOWED_HOSTS = /\.transloadit\.com$/;
// Regex used to check if a Companion address is run by Transloadit.
const TL_COMPANION = /https?:\/\/api2(?:-\w+)?\.transloadit\.com\/companion/;
/**
* Upload files to Transloadit using Tus.
*/
var _rateLimitedQueue = /*#__PURE__*/_classPrivateFieldLooseKey("rateLimitedQueue");
var _watcher = /*#__PURE__*/_classPrivateFieldLooseKey("watcher");
var _getClientVersion = /*#__PURE__*/_classPrivateFieldLooseKey("getClientVersion");
var _attachAssemblyMetadata = /*#__PURE__*/_classPrivateFieldLooseKey("attachAssemblyMetadata");
var _createAssembly = /*#__PURE__*/_classPrivateFieldLooseKey("createAssembly");
var _createAssemblyWatcher = /*#__PURE__*/_classPrivateFieldLooseKey("createAssemblyWatcher");
var _shouldWaitAfterUpload = /*#__PURE__*/_classPrivateFieldLooseKey("shouldWaitAfterUpload");
var _reserveFiles = /*#__PURE__*/_classPrivateFieldLooseKey("reserveFiles");
var _onFileUploadURLAvailable = /*#__PURE__*/_classPrivateFieldLooseKey("onFileUploadURLAvailable");
var _findFile = /*#__PURE__*/_classPrivateFieldLooseKey("findFile");
var _onFileUploadComplete = /*#__PURE__*/_classPrivateFieldLooseKey("onFileUploadComplete");
var _onResult = /*#__PURE__*/_classPrivateFieldLooseKey("onResult");
var _onAssemblyFinished = /*#__PURE__*/_classPrivateFieldLooseKey("onAssemblyFinished");
var _cancelAssembly = /*#__PURE__*/_classPrivateFieldLooseKey("cancelAssembly");
var _onCancelAll = /*#__PURE__*/_classPrivateFieldLooseKey("onCancelAll");
var _getPersistentData = /*#__PURE__*/_classPrivateFieldLooseKey("getPersistentData");
var _onRestored = /*#__PURE__*/_classPrivateFieldLooseKey("onRestored");
var _connectAssembly = /*#__PURE__*/_classPrivateFieldLooseKey("connectAssembly");
var _prepareUpload = /*#__PURE__*/_classPrivateFieldLooseKey("prepareUpload");
var _afterUpload = /*#__PURE__*/_classPrivateFieldLooseKey("afterUpload");
var _closeAssemblyIfExists = /*#__PURE__*/_classPrivateFieldLooseKey("closeAssemblyIfExists");
var _onError = /*#__PURE__*/_classPrivateFieldLooseKey("onError");
var _onTusError = /*#__PURE__*/_classPrivateFieldLooseKey("onTusError");
export default class Transloadit extends BasePlugin {
constructor(uppy, opts) {
super(uppy, {
...defaultOptions,
...opts
});
Object.defineProperty(this, _connectAssembly, {
value: _connectAssembly2
});
Object.defineProperty(this, _cancelAssembly, {
value: _cancelAssembly2
});
/**
* When an Assembly has finished processing, get the final state
* and emit it.
*/
Object.defineProperty(this, _onAssemblyFinished, {
value: _onAssemblyFinished2
});
Object.defineProperty(this, _onResult, {
value: _onResult2
});
Object.defineProperty(this, _onFileUploadComplete, {
value: _onFileUploadComplete2
});
Object.defineProperty(this, _findFile, {
value: _findFile2
});
/**
* Used when `importFromUploadURLs` is enabled: reserves all files in
* the Assembly.
*/
Object.defineProperty(this, _reserveFiles, {
value: _reserveFiles2
});
Object.defineProperty(this, _shouldWaitAfterUpload, {
value: _shouldWaitAfterUpload2
});
Object.defineProperty(this, _createAssemblyWatcher, {
value: _createAssemblyWatcher2
});
Object.defineProperty(this, _createAssembly, {
value: _createAssembly2
});
/**
* Attach metadata to files to configure the Tus plugin to upload to Transloadit.
* Also use Transloadit's Companion
*
* See: https://github.com/tus/tusd/wiki/Uploading-to-Transloadit-using-tus#uploading-using-tus
*/
Object.defineProperty(this, _attachAssemblyMetadata, {
value: _attachAssemblyMetadata2
});
Object.defineProperty(this, _getClientVersion, {
value: _getClientVersion2
});
Object.defineProperty(this, _rateLimitedQueue, {
writable: true,
value: void 0
});
Object.defineProperty(this, _watcher, {
writable: true,
value: void 0
});
this.restored = null;
/**
* Used when `importFromUploadURLs` is enabled: adds files to the Assembly
* once they have been fully uploaded.
*/
Object.defineProperty(this, _onFileUploadURLAvailable, {
writable: true,
value: rawFile => {
var _file$transloadit;
const file = this.uppy.getFile(rawFile.id);
if (!(file != null && (_file$transloadit = file.transloadit) != null && _file$transloadit.assembly)) {
return;
}
const {
status
} = this.assembly;
this.client.addFile(status, file).catch(err => {
this.uppy.log(err);
this.uppy.emit('transloadit:import-error', status, file.id, err);
});
}
});
/**
* When all files are removed, cancel in-progress Assemblies.
*/
Object.defineProperty(this, _onCancelAll, {
writable: true,
value: async () => {
if (!this.assembly) return;
try {
await _classPrivateFieldLooseBase(this, _cancelAssembly)[_cancelAssembly](this.assembly.status);
} catch (err) {
this.uppy.log(err);
}
}
});
/**
* Custom state serialization for the Golden Retriever plugin.
* It will pass this back to the `_onRestored` function.
*/
Object.defineProperty(this, _getPersistentData, {
writable: true,
value: setData => {
if (this.assembly) {
setData({
[this.id]: {
assemblyResponse: this.assembly.status
}
});
}
}
});
Object.defineProperty(this, _onRestored, {
writable: true,
value: pluginData => {
const savedState = pluginData && pluginData[this.id] ? pluginData[this.id] : {};
const previousAssembly = savedState.assemblyResponse;
if (!previousAssembly) {
// Nothing to restore.
return;
}
// Convert loaded Assembly statuses to a Transloadit plugin state object.
const restoreState = () => {
const files = {};
const results = [];
const {
assembly_id: id
} = previousAssembly;
previousAssembly.uploads.forEach(uploadedFile => {
const file = _classPrivateFieldLooseBase(this, _findFile)[_findFile](uploadedFile);
files[uploadedFile.id] = {
id: file.id,
assembly: id,
uploadedFile
};
});
const state = this.getPluginState();
Object.keys(previousAssembly.results).forEach(stepName => {
for (const result of previousAssembly.results[stepName]) {
const file = state.files[result.original_id];
result.localId = file ? file.id : null;
results.push({
id: result.id,
result,
stepName,
assembly: id
});
}
});
this.assembly = new Assembly(previousAssembly, _classPrivateFieldLooseBase(this, _rateLimitedQueue)[_rateLimitedQueue]);
this.assembly.status = previousAssembly;
this.setPluginState({
files,
results
});
return files;
};
// Set up the Assembly instances and AssemblyWatchers for existing Assemblies.
const restoreAssemblies = ids => {
_classPrivateFieldLooseBase(this, _createAssemblyWatcher)[_createAssemblyWatcher](previousAssembly.assembly_id);
_classPrivateFieldLooseBase(this, _connectAssembly)[_connectAssembly](this.assembly, ids);
};
// Force-update Assembly to check for missed events.
const updateAssembly = () => {
var _this$assembly;
return (_this$assembly = this.assembly) == null ? void 0 : _this$assembly.update();
};
// Restore all Assembly state.
this.restored = (async () => {
const files = restoreState();
restoreAssemblies(Object.keys(files));
await updateAssembly();
this.restored = null;
})();
this.restored.catch(err => {
this.uppy.log('Failed to restore', err);
});
}
});
Object.defineProperty(this, _prepareUpload, {
writable: true,
value: async fileIDs => {
var _assemblyOptions$fiel;
const assemblyOptions = typeof this.opts.assemblyOptions === 'function' ? await this.opts.assemblyOptions() : this.opts.assemblyOptions;
(_assemblyOptions$fiel = assemblyOptions.fields) != null ? _assemblyOptions$fiel : assemblyOptions.fields = {};
validateParams(assemblyOptions.params);
try {
var _this$assembly2;
const assembly = // this.assembly can already be defined if we recovered files with Golden Retriever (this.#onRestored)
(_this$assembly2 = this.assembly) != null ? _this$assembly2 : await _classPrivateFieldLooseBase(this, _createAssembly)[_createAssembly](fileIDs, assemblyOptions);
if (assembly == null) throw new Error('All files were canceled after assembly was created');
if (this.opts.importFromUploadURLs) {
await _classPrivateFieldLooseBase(this, _reserveFiles)[_reserveFiles](assembly, fileIDs);
}
fileIDs.forEach(fileID => {
const file = this.uppy.getFile(fileID);
this.uppy.emit('preprocess-complete', file);
});
_classPrivateFieldLooseBase(this, _createAssemblyWatcher)[_createAssemblyWatcher](assembly.status.assembly_id);
_classPrivateFieldLooseBase(this, _connectAssembly)[_connectAssembly](assembly, fileIDs);
} catch (err) {
fileIDs.forEach(fileID => {
const file = this.uppy.getFile(fileID);
// Clear preprocessing state when the Assembly could not be created,
// otherwise the UI gets confused about the lingering progress keys
this.uppy.emit('preprocess-complete', file);
this.uppy.emit('upload-error', file, err);
});
throw err;
}
}
});
Object.defineProperty(this, _afterUpload, {
writable: true,
value: async (fileIDs, uploadID) => {
try {
var _this$assembly3, _this$assembly6;
// If we're still restoring state, wait for that to be done.
await this.restored;
const files = fileIDs.map(fileID => this.uppy.getFile(fileID))
// Only use files without errors
.filter(file => !file.error);
const assemblyID = (_this$assembly3 = this.assembly) == null ? void 0 : _this$assembly3.status.assembly_id;
const closeSocketConnections = () => {
var _this$assembly4;
(_this$assembly4 = this.assembly) == null || _this$assembly4.close();
};
// If we don't have to wait for encoding metadata or results, we can close
// the socket immediately and finish the upload.
if (!_classPrivateFieldLooseBase(this, _shouldWaitAfterUpload)[_shouldWaitAfterUpload]()) {
var _this$assembly5;
closeSocketConnections();
const status = (_this$assembly5 = this.assembly) == null ? void 0 : _this$assembly5.status;
if (status != null) {
this.uppy.addResultData(uploadID, {
transloadit: [status]
});
}
return;
}
// 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 (!assemblyID) {
this.uppy.addResultData(uploadID, {
transloadit: []
});
return;
}
const incompleteFiles = files.filter(file => !hasProperty(this.completedFiles, file.id));
incompleteFiles.forEach(file => {
this.uppy.emit('postprocess-progress', file, {
mode: 'indeterminate',
message: this.i18n('encoding')
});
});
await _classPrivateFieldLooseBase(this, _watcher)[_watcher].promise;
// assembly is now done processing!
closeSocketConnections();
const status = (_this$assembly6 = this.assembly) == null ? void 0 : _this$assembly6.status;
if (status != null) {
this.uppy.addResultData(uploadID, {
transloadit: [status]
});
}
} finally {
// in case allowMultipleUploadBatches is true and the user wants to upload again,
// we need to allow a new assembly to be created.
// see https://github.com/transloadit/uppy/issues/5397
this.assembly = undefined;
}
}
});
Object.defineProperty(this, _closeAssemblyIfExists, {
writable: true,
value: () => {
var _this$assembly7;
(_this$assembly7 = this.assembly) == null || _this$assembly7.close();
}
});
Object.defineProperty(this, _onError, {
writable: true,
value: err => {
_classPrivateFieldLooseBase(this, _closeAssemblyIfExists)[_closeAssemblyIfExists]();
this.assembly = undefined;
this.client.submitError(err)
// if we can't report the error that sucks
.catch(sendErrorToConsole(err));
}
});
Object.defineProperty(this, _onTusError, {
writable: true,
value: (_, err) => {
var _err$message;
_classPrivateFieldLooseBase(this, _closeAssemblyIfExists)[_closeAssemblyIfExists]();
if (err != null && (_err$message = err.message) != null && _err$message.startsWith('tus: ')) {
var _originalRequest;
const endpoint = (_originalRequest = err.originalRequest) == null || (_originalRequest = _originalRequest.getUnderlyingObject()) == null ? void 0 : _originalRequest.responseURL;
this.client.submitError(err, {
endpoint
})
// if we can't report the error that sucks
.catch(sendErrorToConsole(err));
}
}
});
this.type = 'uploader';
this.id = this.opts.id || 'Transloadit';
this.defaultLocale = locale;
_classPrivateFieldLooseBase(this, _rateLimitedQueue)[_rateLimitedQueue] = new RateLimitedQueue(this.opts.limit);
this.i18nInit();
this.client = new Client({
service: this.opts.service,
client: _classPrivateFieldLooseBase(this, _getClientVersion)[_getClientVersion](),
errorReporting: this.opts.errorReporting,
rateLimitedQueue: _classPrivateFieldLooseBase(this, _rateLimitedQueue)[_rateLimitedQueue]
});
// Contains a file IDs that have completed postprocessing before the upload
// they belong to has entered the postprocess stage.
this.completedFiles = Object.create(null);
}
install() {
this.uppy.addPreProcessor(_classPrivateFieldLooseBase(this, _prepareUpload)[_prepareUpload]);
this.uppy.addPostProcessor(_classPrivateFieldLooseBase(this, _afterUpload)[_afterUpload]);
// We may need to close socket.io connections on error.
this.uppy.on('error', _classPrivateFieldLooseBase(this, _onError)[_onError]);
// Handle cancellation.
this.uppy.on('cancel-all', _classPrivateFieldLooseBase(this, _onCancelAll)[_onCancelAll]);
this.uppy.on('upload-error', _classPrivateFieldLooseBase(this, _onTusError)[_onTusError]);
if (this.opts.importFromUploadURLs) {
// No uploader needed when importing; instead we take the upload URL from an existing uploader.
this.uppy.on('upload-success', _classPrivateFieldLooseBase(this, _onFileUploadURLAvailable)[_onFileUploadURLAvailable]);
} else {
// we don't need it here.
// the regional endpoint from the Transloadit API before we can set it.
this.uppy.use(Tus, {
// Disable tus-js-client fingerprinting, otherwise uploading the same file at different times
// will upload to an outdated Assembly, and we won't get socket events for it.
//
// To resume a Transloadit upload, we need to reconnect to the websocket, and the state that's
// required to do that is not saved by tus-js-client's fingerprinting. We need the tus URL,
// the Assembly URL, and the WebSocket URL, at least. We also need to know _all_ the files that
// were added to the Assembly, so we can properly complete it. All that state is handled by
// Golden Retriever. So, Golden Retriever is required to do resumability with the Transloadit plugin,
// and we disable Tus's default resume implementation to prevent bad behaviours.
storeFingerprintForResuming: false,
// Send all metadata to Transloadit. Metadata set by the user
// ends up as in the template as `file.user_meta`
allowedMetaFields: true,
// Pass the limit option to @uppy/tus
limit: this.opts.limit,
rateLimitedQueue: _classPrivateFieldLooseBase(this, _rateLimitedQueue)[_rateLimitedQueue],
retryDelays: this.opts.retryDelays
});
}
this.uppy.on('restore:get-data', _classPrivateFieldLooseBase(this, _getPersistentData)[_getPersistentData]);
this.uppy.on('restored', _classPrivateFieldLooseBase(this, _onRestored)[_onRestored]);
this.setPluginState({
// Contains file data from Transloadit, indexed by their Transloadit-assigned ID.
files: {},
// Contains result data from Transloadit.
results: []
});
// We cannot cancel individual files because Assemblies tend to contain many files.
const {
capabilities
} = this.uppy.getState();
this.uppy.setState({
capabilities: {
...capabilities,
individualCancellation: false
}
});
}
uninstall() {
this.uppy.removePreProcessor(_classPrivateFieldLooseBase(this, _prepareUpload)[_prepareUpload]);
this.uppy.removePostProcessor(_classPrivateFieldLooseBase(this, _afterUpload)[_afterUpload]);
this.uppy.off('error', _classPrivateFieldLooseBase(this, _onError)[_onError]);
if (this.opts.importFromUploadURLs) {
this.uppy.off('upload-success', _classPrivateFieldLooseBase(this, _onFileUploadURLAvailable)[_onFileUploadURLAvailable]);
}
const {
capabilities
} = this.uppy.getState();
this.uppy.setState({
capabilities: {
...capabilities,
individualCancellation: true
}
});
}
getAssembly() {
var _this$assembly8;
return (_this$assembly8 = this.assembly) == null ? void 0 : _this$assembly8.status;
}
getAssemblyFiles(assemblyID) {
return this.uppy.getFiles().filter(file => {
var _file$transloadit2;
return (file == null || (_file$transloadit2 = file.transloadit) == null ? void 0 : _file$transloadit2.assembly) === assemblyID;
});
}
}
function _getClientVersion2() {
const list = [
// @ts-expect-error VERSION comes from babel, TS does not understand
`uppy-core:${this.uppy.constructor.VERSION}`,
// @ts-expect-error VERSION comes from babel, TS does not understand
`uppy-transloadit:${this.constructor.VERSION}`, `uppy-tus:${Tus.VERSION}`];
const addPluginVersion = (pluginName, versionName) => {
const plugin = this.uppy.getPlugin(pluginName);
if (plugin) {
// @ts-expect-error VERSION comes from babel, TS does not understand
list.push(`${versionName}:${plugin.constructor.VERSION}`);
}
};
if (this.opts.importFromUploadURLs) {
addPluginVersion('XHRUpload', 'uppy-xhr-upload');
addPluginVersion('AwsS3', 'uppy-aws-s3');
addPluginVersion('AwsS3Multipart', 'uppy-aws-s3-multipart');
}
addPluginVersion('Dropbox', 'uppy-dropbox');
addPluginVersion('Box', 'uppy-box');
addPluginVersion('Facebook', 'uppy-facebook');
addPluginVersion('GoogleDrive', 'uppy-google-drive');
addPluginVersion('GooglePhotos', 'uppy-google-photos');
addPluginVersion('GoogleDrivePicker', 'uppy-google-drive-picker');
addPluginVersion('GooglePhotosPicker', 'uppy-google-photos-picker');
addPluginVersion('Instagram', 'uppy-instagram');
addPluginVersion('OneDrive', 'uppy-onedrive');
addPluginVersion('Zoom', 'uppy-zoom');
addPluginVersion('Url', 'uppy-url');
if (this.opts.clientName != null) {
list.push(this.opts.clientName);
}
return list.join(',');
}
function _attachAssemblyMetadata2(file, status) {
// Add the metadata parameters Transloadit needs.
const meta = {
...file.meta,
assembly_url: status.assembly_url,
filename: file.name,
fieldname: 'file'
};
// Add Assembly-specific Tus endpoint.
const tus = {
...file.tus,
endpoint: status.tus_url,
// Include X-Request-ID headers for better debugging.
addRequestId: true
};
// Set Companion location. We only add this, if 'file' has the attribute
// remote, because this is the criteria to identify remote files.
// We only replace the hostname for Transloadit's companions, so that
// people can also self-host them while still using Transloadit for encoding.
let {
remote
} = file;
if (file.remote && TL_COMPANION.test(file.remote.companionUrl)) {
const newHost = status.companion_url.replace(/\/$/, '');
const path = file.remote.url.replace(file.remote.companionUrl, '').replace(/^\//, '');
remote = {
...file.remote,
companionUrl: newHost,
url: `${newHost}/${path}`
};
}
// Store the Assembly ID this file is in on the file under the `transloadit` key.
const newFile = {
...file,
transloadit: {
assembly: status.assembly_id
}
};
// Only configure the Tus plugin if we are uploading straight to Transloadit (the default).
if (!this.opts.importFromUploadURLs) {
Object.assign(newFile, {
meta,
tus,
remote
});
}
return newFile;
}
async function _createAssembly2(fileIDs, assemblyOptions) {
this.uppy.log('[Transloadit] Create Assembly');
try {
const newAssembly = await this.client.createAssembly({
...assemblyOptions,
expectedFiles: fileIDs.length
});
const files = this.uppy.getFiles().filter(_ref => {
let {
id
} = _ref;
return fileIDs.includes(id);
});
if (files.length === 0 && fileIDs.length !== 0) {
// All files have been removed, cancelling.
await this.client.cancelAssembly(newAssembly);
return null;
}
const assembly = new Assembly(newAssembly, _classPrivateFieldLooseBase(this, _rateLimitedQueue)[_rateLimitedQueue]);
const {
status
} = assembly;
const assemblyID = status.assembly_id;
const updatedFiles = {};
files.forEach(file => {
updatedFiles[file.id] = _classPrivateFieldLooseBase(this, _attachAssemblyMetadata)[_attachAssemblyMetadata](file, status);
});
this.uppy.setState({
files: {
...this.uppy.getState().files,
...updatedFiles
}
});
this.uppy.emit('transloadit:assembly-created', status, fileIDs);
this.uppy.log(`[Transloadit] Created Assembly ${assemblyID}`);
return assembly;
} catch (err) {
// TODO: use AssemblyError?
const wrapped = new ErrorWithCause(`${this.i18n('creatingAssemblyFailed')}: ${err.message}`, {
cause: err
});
if ('details' in err) {
// @ts-expect-error details is not in the Error type
wrapped.details = err.details;
}
if ('assembly' in err) {
// @ts-expect-error assembly is not in the Error type
wrapped.assembly = err.assembly;
}
throw wrapped;
}
}
function _createAssemblyWatcher2(idOrArrayOfIds) {
// AssemblyWatcher tracks completion states of all Assemblies in this upload.
const ids = Array.isArray(idOrArrayOfIds) ? idOrArrayOfIds : [idOrArrayOfIds];
const watcher = new AssemblyWatcher(this.uppy, ids);
watcher.on('assembly-complete', id => {
const files = this.getAssemblyFiles(id);
files.forEach(file => {
this.completedFiles[file.id] = true;
this.uppy.emit('postprocess-complete', file);
});
});
watcher.on('assembly-error', (id, error) => {
// Clear postprocessing state for all our files.
const filesFromAssembly = this.getAssemblyFiles(id);
filesFromAssembly.forEach(file => {
// TODO Maybe make a postprocess-error event here?
this.uppy.emit('upload-error', file, error);
this.uppy.emit('postprocess-complete', file);
});
// Reset `tus` key in the file state, so when the upload is retried,
// old tus upload is not re-used — Assebmly expects a new upload, can't currently
// re-use the old one. See: https://github.com/transloadit/uppy/issues/4412
// and `onReceiveUploadUrl` in @uppy/tus
const files = {
...this.uppy.getState().files
};
filesFromAssembly.forEach(file => delete files[file.id].tus);
this.uppy.setState({
files
});
this.uppy.emit('error', error);
});
_classPrivateFieldLooseBase(this, _watcher)[_watcher] = watcher;
}
function _shouldWaitAfterUpload2() {
return this.opts.waitForEncoding || this.opts.waitForMetadata;
}
function _reserveFiles2(assembly, fileIDs) {
return Promise.all(fileIDs.map(fileID => {
const file = this.uppy.getFile(fileID);
return this.client.reserveFile(assembly.status, file);
}));
}
function _findFile2(uploadedFile) {
const files = this.uppy.getFiles();
for (let i = 0; i < files.length; i++) {
const file = files[i];
// Completed file upload.
if (file.uploadURL === uploadedFile.tus_upload_url) {
return file;
}
// In-progress file upload.
if (file.tus && file.tus.uploadUrl === uploadedFile.tus_upload_url) {
return file;
}
if (!uploadedFile.is_tus_file) {
// Fingers-crossed check for non-tus uploads, eg imported from S3.
if (file.name === uploadedFile.name && file.size === uploadedFile.size) {
return file;
}
}
}
return undefined;
}
function _onFileUploadComplete2(assemblyId, uploadedFile) {
const state = this.getPluginState();
const file = _classPrivateFieldLooseBase(this, _findFile)[_findFile](uploadedFile);
if (!file) {
this.uppy.log('[Transloadit] Couldn’t find the file, it was likely removed in the process');
return;
}
this.setPluginState({
files: {
...state.files,
[uploadedFile.id]: {
assembly: assemblyId,
id: file.id,
uploadedFile
}
}
});
this.uppy.emit('transloadit:upload', uploadedFile, this.getAssembly());
}
function _onResult2(assemblyId, stepName, result) {
const state = this.getPluginState();
const 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; // eslint-disable-line no-param-reassign
const entry = {
result,
stepName,
id: result.id,
assembly: assemblyId
};
this.setPluginState({
results: [...state.results, entry]
});
this.uppy.emit('transloadit:result', stepName, result, this.getAssembly());
}
function _onAssemblyFinished2(assembly) {
const url = assembly.status.assembly_ssl_url;
this.client.getAssemblyStatus(url).then(finalStatus => {
// eslint-disable-next-line no-param-reassign
assembly.status = finalStatus;
this.uppy.emit('transloadit:complete', finalStatus);
});
}
async function _cancelAssembly2(assembly) {
await this.client.cancelAssembly(assembly);
// TODO bubble this through AssemblyWatcher so its event handlers can clean up correctly
this.uppy.emit('transloadit:assembly-cancelled', assembly);
this.assembly = undefined;
}
function _connectAssembly2(assembly, ids) {
const {
status
} = assembly;
const id = status.assembly_id;
this.assembly = assembly;
assembly.on('upload', file => {
_classPrivateFieldLooseBase(this, _onFileUploadComplete)[_onFileUploadComplete](id, file);
});
assembly.on('error', error => {
error.assembly = assembly.status; // eslint-disable-line no-param-reassign
this.uppy.emit('transloadit:assembly-error', assembly.status, error);
});
assembly.on('executing', () => {
this.uppy.emit('transloadit:assembly-executing', assembly.status);
});
assembly.on('execution-progress', details => {
this.uppy.emit('transloadit:execution-progress', details);
if (details.progress_combined != null) {
// TODO: Transloadit emits progress information for the entire Assembly combined
// (progress_combined) and for each imported/uploaded file (progress_per_original_file).
// Uppy's current design requires progress to be set for each file, which is then
// averaged to get the total progress (see calculateProcessingProgress.js).
// Therefore, we currently set the combined progres for every file, so that this is
// the same value that is displayed to the end user, although we have more accurate
// per-file progress as well. We cannot use this here or otherwise progress from
// imported files would not be counted towards the total progress because imported
// files are not registered with Uppy.
for (const file of this.uppy.getFilesByIds(ids)) {
this.uppy.emit('postprocess-progress', file, {
mode: 'determinate',
value: details.progress_combined / 100,
message: this.i18n('encoding')
});
}
}
});
if (this.opts.waitForEncoding) {
assembly.on('result', (stepName, result) => {
_classPrivateFieldLooseBase(this, _onResult)[_onResult](id, stepName, result);
});
}
if (this.opts.waitForEncoding) {
assembly.on('finished', () => {
_classPrivateFieldLooseBase(this, _onAssemblyFinished)[_onAssemblyFinished](assembly);
});
} else if (this.opts.waitForMetadata) {
assembly.on('metadata', () => {
_classPrivateFieldLooseBase(this, _onAssemblyFinished)[_onAssemblyFinished](assembly);
});
}
// No need to connect to the socket if the Assembly has completed by now.
// @ts-expect-error ok does not exist on Assembly?
if (assembly.ok === 'ASSEMBLY_COMPLETE') {
return assembly;
}
assembly.connect();
return assembly;
}
Transloadit.VERSION = packageJson.version;
export { COMPANION_URL, COMPANION_ALLOWED_HOSTS };