|
@@ -4,11 +4,21 @@ const { Provider, RequestClient, Socket } = require('@uppy/companion-client')
|
|
|
const emitSocketProgress = require('@uppy/utils/lib/emitSocketProgress')
|
|
|
const getSocketHost = require('@uppy/utils/lib/getSocketHost')
|
|
|
const settle = require('@uppy/utils/lib/settle')
|
|
|
-const limitPromises = require('@uppy/utils/lib/limitPromises')
|
|
|
+const EventTracker = require('@uppy/utils/lib/EventTracker')
|
|
|
+const RateLimitedQueue = require('@uppy/utils/lib/RateLimitedQueue')
|
|
|
const getFingerprint = require('./getFingerprint')
|
|
|
|
|
|
-// 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
|
|
|
+/** @typedef {import('..').TusOptions} TusOptions */
|
|
|
+/** @typedef {import('@uppy/core').Uppy} Uppy */
|
|
|
+/** @typedef {import('@uppy/core').UppyFile} UppyFile */
|
|
|
+/** @typedef {import('@uppy/core').FailedUppyFile<{}>} FailedUppyFile */
|
|
|
+
|
|
|
+/**
|
|
|
+ * 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
|
|
|
+ *
|
|
|
+ * @type {TusOptions}
|
|
|
+ */
|
|
|
const tusDefaultOptions = {
|
|
|
endpoint: '',
|
|
|
resume: true,
|
|
@@ -25,32 +35,16 @@ const tusDefaultOptions = {
|
|
|
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) {
|
|
|
- const events = []
|
|
|
- return {
|
|
|
- on (event, fn) {
|
|
|
- events.push([event, fn])
|
|
|
- return emitter.on(event, fn)
|
|
|
- },
|
|
|
- remove () {
|
|
|
- events.forEach(([event, fn]) => {
|
|
|
- emitter.off(event, fn)
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
/**
|
|
|
* Tus resumable file uploader
|
|
|
- *
|
|
|
*/
|
|
|
module.exports = class Tus extends Plugin {
|
|
|
static VERSION = require('../package.json').version
|
|
|
|
|
|
+ /**
|
|
|
+ * @param {Uppy} uppy
|
|
|
+ * @param {TusOptions} opts
|
|
|
+ */
|
|
|
constructor (uppy, opts) {
|
|
|
super(uppy, opts)
|
|
|
this.type = 'uploader'
|
|
@@ -67,14 +61,14 @@ module.exports = class Tus extends Plugin {
|
|
|
}
|
|
|
|
|
|
// merge default options with the ones set by user
|
|
|
+ /** @type {import("..").TusOptions} */
|
|
|
this.opts = Object.assign({}, defaultOptions, opts)
|
|
|
|
|
|
- // Simultaneous upload limiting is shared across all uploads with this plugin.
|
|
|
- if (typeof this.opts.limit === 'number' && this.opts.limit !== 0) {
|
|
|
- this.limitUploads = limitPromises(this.opts.limit)
|
|
|
- } else {
|
|
|
- this.limitUploads = (fn) => fn
|
|
|
- }
|
|
|
+ /**
|
|
|
+ * Simultaneous upload limiting is shared across all uploads with this plugin.
|
|
|
+ * @type {RateLimitedQueue}
|
|
|
+ */
|
|
|
+ this.requests = new RateLimitedQueue(this.opts.limit)
|
|
|
|
|
|
this.uploaders = Object.create(null)
|
|
|
this.uploaderEvents = Object.create(null)
|
|
@@ -101,6 +95,8 @@ module.exports = class Tus extends Plugin {
|
|
|
/**
|
|
|
* Clean up all references for a file's upload: the tus.Upload instance,
|
|
|
* any events related to the file, and the Companion WebSocket connection.
|
|
|
+ *
|
|
|
+ * @param {string} fileID
|
|
|
*/
|
|
|
resetUploaderReferences (fileID) {
|
|
|
if (this.uploaders[fileID]) {
|
|
@@ -118,18 +114,43 @@ module.exports = class Tus extends Plugin {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Create a new Tus upload
|
|
|
+ * 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}
|
|
|
+ * A lot can happen during an upload, so this is quite hard to follow!
|
|
|
+ * - First, the upload is started. If the file was already paused by the time the upload starts, nothing should happen.
|
|
|
+ * If the `limit` option is used, the upload must be queued onto the `this.requests` queue.
|
|
|
+ * When an upload starts, we store the tus.Upload instance, and an EventTracker instance that manages the event listeners
|
|
|
+ * for pausing, cancellation, removal, etc.
|
|
|
+ * - While the upload is in progress, it may be paused or cancelled.
|
|
|
+ * Pausing aborts the underlying tus.Upload, and removes the upload from the `this.requests` queue. All other state is
|
|
|
+ * maintained.
|
|
|
+ * Cancelling removes the upload from the `this.requests` queue, and completely aborts the upload--the tus.Upload instance
|
|
|
+ * is aborted and discarded, the EventTracker instance is destroyed (removing all listeners).
|
|
|
+ * Resuming the upload uses the `this.requests` queue as well, to prevent selectively pausing and resuming uploads from
|
|
|
+ * bypassing the limit.
|
|
|
+ * - After completing an upload, the tus.Upload and EventTracker instances are cleaned up, and the upload is marked as done
|
|
|
+ * in the `this.requests` queue.
|
|
|
+ * - When an upload completed with an error, the same happens as on successful completion, but the `upload()` promise is rejected.
|
|
|
+ *
|
|
|
+ * When working on this function, keep in mind:
|
|
|
+ * - When an upload is completed or cancelled for any reason, the tus.Upload and EventTracker instances need to be cleaned up using this.resetUploaderReferences().
|
|
|
+ * - When an upload is cancelled or paused, for any reason, it needs to be removed from the `this.requests` queue using `queuedRequest.abort()`.
|
|
|
+ * - When an upload is completed for any reason, including errors, it needs to be marked as such using `queuedRequest.done()`.
|
|
|
+ * - When an upload is started or resumed, it needs to go through the `this.requests` queue. The `queuedRequest` variable must be updated so the other uses of it are valid.
|
|
|
+ * - Before replacing the `queuedRequest` variable, the previous `queuedRequest` must be aborted, else it will keep taking up a spot in the queue.
|
|
|
+ *
|
|
|
+ * @param {UppyFile} file for use with upload
|
|
|
+ * @param {number} current file in a queue
|
|
|
+ * @param {number} total number of files in a queue
|
|
|
+ * @returns {Promise<void>}
|
|
|
*/
|
|
|
upload (file, current, total) {
|
|
|
this.resetUploaderReferences(file.id)
|
|
|
|
|
|
// Create a new tus upload
|
|
|
return new Promise((resolve, reject) => {
|
|
|
+ this.uppy.emit('upload-started', file)
|
|
|
+
|
|
|
const optsTus = Object.assign(
|
|
|
{},
|
|
|
tusDefaultOptions,
|
|
@@ -150,6 +171,7 @@ module.exports = class Tus extends Plugin {
|
|
|
err.message = `Failed because: ${err.message}`
|
|
|
|
|
|
this.resetUploaderReferences(file.id)
|
|
|
+ queuedRequest.done()
|
|
|
reject(err)
|
|
|
}
|
|
|
|
|
@@ -174,6 +196,7 @@ module.exports = class Tus extends Plugin {
|
|
|
}
|
|
|
|
|
|
this.resetUploaderReferences(file.id)
|
|
|
+ queuedRequest.done()
|
|
|
resolve(upload)
|
|
|
}
|
|
|
|
|
@@ -203,78 +226,106 @@ module.exports = class Tus extends Plugin {
|
|
|
|
|
|
const upload = new tus.Upload(file.data, optsTus)
|
|
|
this.uploaders[file.id] = upload
|
|
|
- this.uploaderEvents[file.id] = createEventTracker(this.uppy)
|
|
|
+ this.uploaderEvents[file.id] = new EventTracker(this.uppy)
|
|
|
+
|
|
|
+ let queuedRequest = this.requests.run(() => {
|
|
|
+ if (!file.isPaused) {
|
|
|
+ upload.start()
|
|
|
+ }
|
|
|
+ // Don't do anything here, the caller will take care of cancelling the upload itself
|
|
|
+ // using resetUploaderReferences(). This is because resetUploaderReferences() has to be
|
|
|
+ // called when this request is still in the queue, and has not been started yet, too. At
|
|
|
+ // that point this cancellation function is not going to be called.
|
|
|
+ // Also, we need to remove the request from the queue _without_ destroying everything
|
|
|
+ // related to this upload to handle pauses.
|
|
|
+ return () => {}
|
|
|
+ })
|
|
|
|
|
|
this.onFileRemove(file.id, (targetFileID) => {
|
|
|
+ queuedRequest.abort()
|
|
|
this.resetUploaderReferences(file.id)
|
|
|
resolve(`upload ${targetFileID} was removed`)
|
|
|
})
|
|
|
|
|
|
this.onPause(file.id, (isPaused) => {
|
|
|
if (isPaused) {
|
|
|
+ // Remove this file from the queue so another file can start in its place.
|
|
|
+ queuedRequest.abort()
|
|
|
upload.abort()
|
|
|
} else {
|
|
|
- upload.start()
|
|
|
+ // Resuming an upload should be queued, else you could pause and then resume a queued upload to make it skip the queue.
|
|
|
+ queuedRequest.abort()
|
|
|
+ queuedRequest = this.requests.run(() => {
|
|
|
+ upload.start()
|
|
|
+ return () => {}
|
|
|
+ })
|
|
|
}
|
|
|
})
|
|
|
|
|
|
this.onPauseAll(file.id, () => {
|
|
|
+ queuedRequest.abort()
|
|
|
upload.abort()
|
|
|
})
|
|
|
|
|
|
this.onCancelAll(file.id, () => {
|
|
|
+ queuedRequest.abort()
|
|
|
this.resetUploaderReferences(file.id)
|
|
|
resolve(`upload ${file.id} was canceled`)
|
|
|
})
|
|
|
|
|
|
this.onResumeAll(file.id, () => {
|
|
|
+ queuedRequest.abort()
|
|
|
if (file.error) {
|
|
|
upload.abort()
|
|
|
}
|
|
|
- upload.start()
|
|
|
+ queuedRequest = this.requests.run(() => {
|
|
|
+ upload.start()
|
|
|
+ return () => {}
|
|
|
+ })
|
|
|
})
|
|
|
-
|
|
|
- if (!file.isPaused) {
|
|
|
- upload.start()
|
|
|
- }
|
|
|
+ }).catch((err) => {
|
|
|
+ this.uppy.emit('upload-error', file, err)
|
|
|
+ throw err
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * @param {UppyFile} file for use with upload
|
|
|
+ * @param {number} current file in a queue
|
|
|
+ * @param {number} total number of files in a queue
|
|
|
+ * @return {Promise<void>}
|
|
|
+ */
|
|
|
uploadRemote (file, current, total) {
|
|
|
this.resetUploaderReferences(file.id)
|
|
|
|
|
|
- const opts = Object.assign(
|
|
|
- {},
|
|
|
- this.opts,
|
|
|
+ const opts = { ...this.opts }
|
|
|
+ if (file.tus) {
|
|
|
// Install file-specific upload overrides.
|
|
|
- file.tus || {}
|
|
|
- )
|
|
|
+ Object.assign(opts, file.tus)
|
|
|
+ }
|
|
|
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- this.uppy.log(file.remote.url)
|
|
|
- if (file.serverToken) {
|
|
|
- return this.connectToServerSocket(file)
|
|
|
- .then(() => resolve())
|
|
|
- .catch(reject)
|
|
|
- }
|
|
|
+ this.uppy.emit('upload-started', file)
|
|
|
+ this.uppy.log(file.remote.url)
|
|
|
|
|
|
- this.uppy.emit('upload-started', file)
|
|
|
+ if (file.serverToken) {
|
|
|
+ return this.connectToServerSocket(file)
|
|
|
+ }
|
|
|
+
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
const Client = file.remote.providerOptions.provider ? Provider : RequestClient
|
|
|
const client = new Client(this.uppy, file.remote.providerOptions)
|
|
|
- client.post(
|
|
|
- file.remote.url,
|
|
|
- Object.assign({}, file.remote.body, {
|
|
|
- endpoint: opts.endpoint,
|
|
|
- uploadUrl: opts.uploadUrl,
|
|
|
- protocol: 'tus',
|
|
|
- size: file.data.size,
|
|
|
- metadata: file.meta
|
|
|
- })
|
|
|
- ).then((res) => {
|
|
|
+
|
|
|
+ // !! cancellation is NOT supported at this stage yet
|
|
|
+ client.post(file.remote.url, {
|
|
|
+ ...file.remote.body,
|
|
|
+ endpoint: opts.endpoint,
|
|
|
+ uploadUrl: opts.uploadUrl,
|
|
|
+ protocol: 'tus',
|
|
|
+ size: file.data.size,
|
|
|
+ metadata: file.meta
|
|
|
+ }).then((res) => {
|
|
|
this.uppy.setFileState(file.id, { serverToken: res.token })
|
|
|
file = this.uppy.getFile(file.id)
|
|
|
- return file
|
|
|
- }).then((file) => {
|
|
|
return this.connectToServerSocket(file)
|
|
|
}).then(() => {
|
|
|
resolve()
|
|
@@ -284,48 +335,85 @@ module.exports = class Tus extends Plugin {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * See the comment on the upload() method.
|
|
|
+ *
|
|
|
+ * Additionally, when an upload is removed, completed, or cancelled, we need to close the WebSocket connection. This is handled by the resetUploaderReferences() function, so the same guidelines apply as in upload().
|
|
|
+ *
|
|
|
+ * @param {UppyFile} file
|
|
|
+ */
|
|
|
connectToServerSocket (file) {
|
|
|
return new Promise((resolve, reject) => {
|
|
|
const token = file.serverToken
|
|
|
const host = getSocketHost(file.remote.companionUrl)
|
|
|
- const socket = new Socket({ target: `${host}/api/${token}` })
|
|
|
+ const socket = new Socket({ target: `${host}/api/${token}`, autoOpen: false })
|
|
|
this.uploaderSockets[file.id] = socket
|
|
|
- this.uploaderEvents[file.id] = createEventTracker(this.uppy)
|
|
|
+ this.uploaderEvents[file.id] = new EventTracker(this.uppy)
|
|
|
|
|
|
this.onFileRemove(file.id, () => {
|
|
|
+ queuedRequest.abort()
|
|
|
socket.send('pause', {})
|
|
|
+ this.resetUploaderReferences(file.id)
|
|
|
resolve(`upload ${file.id} was removed`)
|
|
|
})
|
|
|
|
|
|
this.onPause(file.id, (isPaused) => {
|
|
|
- isPaused ? socket.send('pause', {}) : socket.send('resume', {})
|
|
|
+ if (isPaused) {
|
|
|
+ // Remove this file from the queue so another file can start in its place.
|
|
|
+ queuedRequest.abort()
|
|
|
+ socket.send('pause', {})
|
|
|
+ } else {
|
|
|
+ // Resuming an upload should be queued, else you could pause and then resume a queued upload to make it skip the queue.
|
|
|
+ queuedRequest.abort()
|
|
|
+ queuedRequest = this.requests.run(() => {
|
|
|
+ socket.send('resume', {})
|
|
|
+ return () => {}
|
|
|
+ })
|
|
|
+ }
|
|
|
})
|
|
|
|
|
|
- this.onPauseAll(file.id, () => socket.send('pause', {}))
|
|
|
+ this.onPauseAll(file.id, () => {
|
|
|
+ queuedRequest.abort()
|
|
|
+ socket.send('pause', {})
|
|
|
+ })
|
|
|
|
|
|
- this.onCancelAll(file.id, () => socket.send('pause', {}))
|
|
|
+ this.onCancelAll(file.id, () => {
|
|
|
+ queuedRequest.abort()
|
|
|
+ socket.send('pause', {})
|
|
|
+ this.resetUploaderReferences(file.id)
|
|
|
+ resolve(`upload ${file.id} was canceled`)
|
|
|
+ })
|
|
|
|
|
|
this.onResumeAll(file.id, () => {
|
|
|
+ queuedRequest.abort()
|
|
|
if (file.error) {
|
|
|
socket.send('pause', {})
|
|
|
}
|
|
|
- socket.send('resume', {})
|
|
|
+ queuedRequest = this.requests.run(() => {
|
|
|
+ socket.send('resume', {})
|
|
|
+ return () => {}
|
|
|
+ })
|
|
|
})
|
|
|
|
|
|
this.onRetry(file.id, () => {
|
|
|
- socket.send('pause', {})
|
|
|
- socket.send('resume', {})
|
|
|
+ // Only do the retry if the upload is actually in progress;
|
|
|
+ // else we could try to send these messages when the upload is still queued.
|
|
|
+ // We may need a better check for this since the socket may also be closed
|
|
|
+ // for other reasons, like network failures.
|
|
|
+ if (socket.isOpen) {
|
|
|
+ socket.send('pause', {})
|
|
|
+ socket.send('resume', {})
|
|
|
+ }
|
|
|
})
|
|
|
|
|
|
this.onRetryAll(file.id, () => {
|
|
|
- socket.send('pause', {})
|
|
|
- socket.send('resume', {})
|
|
|
+ // See the comment in the onRetry() call
|
|
|
+ if (socket.isOpen) {
|
|
|
+ socket.send('pause', {})
|
|
|
+ socket.send('resume', {})
|
|
|
+ }
|
|
|
})
|
|
|
|
|
|
- if (file.isPaused) {
|
|
|
- socket.send('pause', {})
|
|
|
- }
|
|
|
-
|
|
|
socket.on('progress', (progressData) => emitSocketProgress(this, progressData, file))
|
|
|
|
|
|
socket.on('error', (errData) => {
|
|
@@ -340,9 +428,12 @@ module.exports = class Tus extends Plugin {
|
|
|
this.uppy.setFileState(file.id, {
|
|
|
serverToken: null
|
|
|
})
|
|
|
+ } else {
|
|
|
+ socket.close()
|
|
|
}
|
|
|
|
|
|
this.uppy.emit('upload-error', file, error)
|
|
|
+ queuedRequest.done()
|
|
|
reject(error)
|
|
|
})
|
|
|
|
|
@@ -353,14 +444,33 @@ module.exports = class Tus extends Plugin {
|
|
|
|
|
|
this.uppy.emit('upload-success', file, uploadResp)
|
|
|
this.resetUploaderReferences(file.id)
|
|
|
+ queuedRequest.done()
|
|
|
resolve()
|
|
|
})
|
|
|
+
|
|
|
+ let queuedRequest = this.requests.run(() => {
|
|
|
+ socket.open()
|
|
|
+ if (file.isPaused) {
|
|
|
+ socket.send('pause', {})
|
|
|
+ }
|
|
|
+
|
|
|
+ // Don't do anything here, the caller will take care of cancelling the upload itself
|
|
|
+ // using resetUploaderReferences(). This is because resetUploaderReferences() has to be
|
|
|
+ // called when this request is still in the queue, and has not been started yet, too. At
|
|
|
+ // that point this cancellation function is not going to be called.
|
|
|
+ // Also, we need to remove the request from the queue _without_ destroying everything
|
|
|
+ // related to this upload to handle pauses.
|
|
|
+ return () => {}
|
|
|
+ })
|
|
|
})
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Store the uploadUrl on the file options, so that when Golden Retriever
|
|
|
* restores state, we will continue uploading to the correct URL.
|
|
|
+ *
|
|
|
+ * @param {UppyFile} file
|
|
|
+ * @param {string} uploadURL
|
|
|
*/
|
|
|
onReceiveUploadUrl (file, uploadURL) {
|
|
|
const currentFile = this.uppy.getFile(file.id)
|
|
@@ -376,12 +486,20 @@ module.exports = class Tus extends Plugin {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * @param {string} fileID
|
|
|
+ * @param {function(string): void} cb
|
|
|
+ */
|
|
|
onFileRemove (fileID, cb) {
|
|
|
this.uploaderEvents[fileID].on('file-removed', (file) => {
|
|
|
if (fileID === file.id) cb(file.id)
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * @param {string} fileID
|
|
|
+ * @param {function(boolean): void} cb
|
|
|
+ */
|
|
|
onPause (fileID, cb) {
|
|
|
this.uploaderEvents[fileID].on('upload-pause', (targetFileID, isPaused) => {
|
|
|
if (fileID === targetFileID) {
|
|
@@ -391,6 +509,10 @@ module.exports = class Tus extends Plugin {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * @param {string} fileID
|
|
|
+ * @param {function(): void} cb
|
|
|
+ */
|
|
|
onRetry (fileID, cb) {
|
|
|
this.uploaderEvents[fileID].on('upload-retry', (targetFileID) => {
|
|
|
if (fileID === targetFileID) {
|
|
@@ -399,6 +521,10 @@ module.exports = class Tus extends Plugin {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * @param {string} fileID
|
|
|
+ * @param {function(): void} cb
|
|
|
+ */
|
|
|
onRetryAll (fileID, cb) {
|
|
|
this.uploaderEvents[fileID].on('retry-all', (filesToRetry) => {
|
|
|
if (!this.uppy.getFile(fileID)) return
|
|
@@ -406,6 +532,10 @@ module.exports = class Tus extends Plugin {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * @param {string} fileID
|
|
|
+ * @param {function(): void} cb
|
|
|
+ */
|
|
|
onPauseAll (fileID, cb) {
|
|
|
this.uploaderEvents[fileID].on('pause-all', () => {
|
|
|
if (!this.uppy.getFile(fileID)) return
|
|
@@ -413,6 +543,10 @@ module.exports = class Tus extends Plugin {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * @param {string} fileID
|
|
|
+ * @param {function(): void} cb
|
|
|
+ */
|
|
|
onCancelAll (fileID, cb) {
|
|
|
this.uploaderEvents[fileID].on('cancel-all', () => {
|
|
|
if (!this.uppy.getFile(fileID)) return
|
|
@@ -420,6 +554,10 @@ module.exports = class Tus extends Plugin {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * @param {string} fileID
|
|
|
+ * @param {function(): void} cb
|
|
|
+ */
|
|
|
onResumeAll (fileID, cb) {
|
|
|
this.uploaderEvents[fileID].on('resume-all', () => {
|
|
|
if (!this.uppy.getFile(fileID)) return
|
|
@@ -427,32 +565,29 @@ module.exports = class Tus extends Plugin {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * @param {(UppyFile | FailedUppyFile)[]} files
|
|
|
+ */
|
|
|
uploadFiles (files) {
|
|
|
- const actions = files.map((file, i) => {
|
|
|
- const current = parseInt(i, 10) + 1
|
|
|
+ const promises = files.map((file, i) => {
|
|
|
+ const current = i + 1
|
|
|
const total = files.length
|
|
|
|
|
|
- if (file.error) {
|
|
|
- return () => Promise.reject(new Error(file.error))
|
|
|
+ if ('error' in file && file.error) {
|
|
|
+ return Promise.reject(new Error(file.error))
|
|
|
} else if (file.isRemote) {
|
|
|
- // We emit upload-started here, so that it's also emitted for files
|
|
|
- // that have to wait due to the `limit` option.
|
|
|
- this.uppy.emit('upload-started', file)
|
|
|
- return this.uploadRemote.bind(this, file, current, total)
|
|
|
+ return this.uploadRemote(file, current, total)
|
|
|
} else {
|
|
|
- this.uppy.emit('upload-started', file)
|
|
|
- return this.upload.bind(this, file, current, total)
|
|
|
+ return this.upload(file, current, total)
|
|
|
}
|
|
|
})
|
|
|
|
|
|
- const promises = actions.map((action) => {
|
|
|
- const limitedAction = this.limitUploads(action)
|
|
|
- return limitedAction()
|
|
|
- })
|
|
|
-
|
|
|
return settle(promises)
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * @param {string[]} fileIDs
|
|
|
+ */
|
|
|
handleUpload (fileIDs) {
|
|
|
if (fileIDs.length === 0) {
|
|
|
this.uppy.log('[Tus] No files to upload')
|