Allow Transloadit-hosted Companion with other uploaders
Closes #5515
We want to allow people to only use Transloadit for Companion, even if they use another uploader. This makes using Transloadit less all-or-nothing.
Unfortunately there is no pretty way of doing this. Instead of separating Companion logic from uploaders, every uploader implements remote uploads themselves. It works like this:
- Install a remote plugin (such as
@uppy/google-drive) - The remote plugin creates a companion request client (
@uppy/companion-client) and@uppy/provider-views - Inside
@uppy/provider-viewswe calluppy.registerRequestClient()to put the request client on core so it can be shared.- We need to store request clients by a unique ID, so we can share RequestClient instances across files this allows us to do rate limiting and synchronous operations like refreshing provider tokens example: refreshing tokens: if each file has their own requestclient, we don't have any way to synchronize all requests in order to block all requests, refresh the token, and unblock all requests and allow them to run with a the new access token.
- When the upload starts, uploader plugins (such as
@uppy/aws-s3) filters the remote files and gets the instructions to tell Companion what uploader to use:
https://github.com/transloadit/uppy/blob/d6156bb92f0f0ff400a7dbb46bbe375f10421ac1/packages/%40uppy/aws-s3/src/index.ts#L925-L972
With this PR you can do this:
uppy.use(AwsS3, { /* ... */ })
uppy.use(Transloadit, { onlyRemoteFiles: true /* ... */ })
This is the least ugly way I could come up with.
This is possible due to a new value on Uppy's state, remoteUploader, which is set by the Transloadit plugin when onlyRemoteFiles is true.
You can also (ab)use this to allow users to change the remote uploader in other cases: uppy.setState({ remoteUploader: 'multipart' }).
Diff output files
diff --git a/packages/@uppy/aws-s3/lib/index.js b/packages/@uppy/aws-s3/lib/index.js
index b2bf3c6..118e3f2 100644
--- a/packages/@uppy/aws-s3/lib/index.js
+++ b/packages/@uppy/aws-s3/lib/index.js
@@ -693,10 +693,19 @@ function _uploadLocalFile2(file) {
});
}
function _getCompanionClientArgs2(file) {
- var _file$remote;
+ var _file$remote, _tusOpts$endpoint;
+ const opts = {
+ ...this.opts,
+ };
+ const tusOpts = file.tus;
return {
...((_file$remote = file.remote) == null ? void 0 : _file$remote.body),
- protocol: "s3-multipart",
+ protocol: this.uppy.getState().remoteUploader || "s3-multipart",
+ endpoint: (_tusOpts$endpoint = tusOpts.endpoint) != null ? _tusOpts$endpoint : opts.endpoint,
+ headers: {
+ ...opts.headers,
+ ...tusOpts.headers,
+ },
size: file.data.size,
metadata: file.meta,
};
diff --git a/packages/@uppy/transloadit/lib/index.js b/packages/@uppy/transloadit/lib/index.js
index 864333d..912ee5d 100644
--- a/packages/@uppy/transloadit/lib/index.js
+++ b/packages/@uppy/transloadit/lib/index.js
@@ -25,6 +25,7 @@ const defaultOptions = {
waitForMetadata: false,
alwaysRunAssembly: false,
importFromUploadURLs: false,
+ onlyRemoteFiles: false,
limit: 20,
retryDelays: [7000, 10000, 15000, 20000],
clientName: null,
@@ -248,25 +249,26 @@ export default class Transloadit extends BasePlugin {
: this.opts.assemblyOptions;
(_assemblyOptions$fiel = assemblyOptions.fields) != null ? _assemblyOptions$fiel : assemblyOptions.fields = {};
validateParams(assemblyOptions.params);
+ const ids = this.opts.onlyRemoteFiles ? fileIDs.filter(id => this.uppy.getFile(id).isRemote) : fileIDs;
try {
var _this$assembly2;
const assembly = (_this$assembly2 = this.assembly) != null
? _this$assembly2
- : await _classPrivateFieldLooseBase(this, _createAssembly)[_createAssembly](fileIDs, assemblyOptions);
+ : await _classPrivateFieldLooseBase(this, _createAssembly)[_createAssembly](ids, 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);
+ await _classPrivateFieldLooseBase(this, _reserveFiles)[_reserveFiles](assembly, ids);
}
- fileIDs.forEach(fileID => {
+ ids.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);
+ _classPrivateFieldLooseBase(this, _connectAssembly)[_connectAssembly](assembly, ids);
} catch (err) {
- fileIDs.forEach(fileID => {
+ ids.forEach(fileID => {
const file = this.uppy.getFile(fileID);
this.uppy.emit("preprocess-complete", file);
this.uppy.emit("upload-error", file, err);
@@ -361,6 +363,11 @@ export default class Transloadit extends BasePlugin {
this.defaultLocale = locale;
_classPrivateFieldLooseBase(this, _rateLimitedQueue)[_rateLimitedQueue] = new RateLimitedQueue(this.opts.limit);
this.i18nInit();
+ if (this.opts.onlyRemoteFiles) {
+ this.uppy.setState({
+ remoteUploader: "tus",
+ });
+ }
this.client = new Client({
service: this.opts.service,
client: _classPrivateFieldLooseBase(this, _getClientVersion)[_getClientVersion](),
@@ -380,7 +387,7 @@ export default class Transloadit extends BasePlugin {
"upload-success",
_classPrivateFieldLooseBase(this, _onFileUploadURLAvailable)[_onFileUploadURLAvailable],
);
- } else {
+ } else if (!this.opts.onlyRemoteFiles) {
this.uppy.use(Tus, {
storeFingerprintForResuming: false,
allowedMetaFields: true,
diff --git a/packages/@uppy/xhr-upload/lib/index.js b/packages/@uppy/xhr-upload/lib/index.js
index 6245c94..f3585ac 100644
--- a/packages/@uppy/xhr-upload/lib/index.js
+++ b/packages/@uppy/xhr-upload/lib/index.js
@@ -361,19 +361,23 @@ async function _uploadBundle2(files) {
}
}
function _getCompanionClientArgs2(file) {
- var _file$remote;
+ var _file$remote, _tusOpts$endpoint;
const opts = this.getOptions(file);
const allowedMetaFields = getAllowedMetaFields(opts.allowedMetaFields, file.meta);
+ const tusOpts = file.tus;
return {
...((_file$remote = file.remote) == null ? void 0 : _file$remote.body),
- protocol: "multipart",
- endpoint: opts.endpoint,
+ protocol: this.uppy.getState().remoteUploader || "multipart",
+ endpoint: (_tusOpts$endpoint = tusOpts.endpoint) != null ? _tusOpts$endpoint : opts.endpoint,
+ headers: {
+ ...opts.headers,
+ ...tusOpts.headers,
+ },
size: file.data.size,
fieldname: opts.fieldName,
metadata: Object.fromEntries(allowedMetaFields.map(name => [name, file.meta[name]])),
httpMethod: opts.method,
useFormData: opts.formData,
- headers: opts.headers,
};
}
async function _uploadFiles2(files) {