@uppy/tus
Advanced tools
Comparing version 1.4.0 to 1.4.1
375
lib/index.js
@@ -25,6 +25,23 @@ var _class, _temp; | ||
var limitPromises = require('@uppy/utils/lib/limitPromises'); // 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 EventTracker = require('@uppy/utils/lib/EventTracker'); | ||
var RateLimitedQueue = require('@uppy/utils/lib/RateLimitedQueue'); | ||
var getFingerprint = require('./getFingerprint'); | ||
/** @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} | ||
*/ | ||
var tusDefaultOptions = { | ||
@@ -45,30 +62,6 @@ endpoint: '', | ||
/** | ||
* Create a wrapper around an event emitter with a `remove` method to remove | ||
* all events that were added using the wrapped emitter. | ||
* Tus resumable file uploader | ||
*/ | ||
}; | ||
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 = (_temp = _class = | ||
@@ -79,2 +72,6 @@ /*#__PURE__*/ | ||
/** | ||
* @param {Uppy} uppy | ||
* @param {TusOptions} opts | ||
*/ | ||
function Tus(uppy, opts) { | ||
@@ -95,13 +92,13 @@ var _this; | ||
/** @type {import("..").TusOptions} */ | ||
}; | ||
_this.opts = _extends({}, defaultOptions, opts); // Simultaneous upload limiting is shared across all uploads with this plugin. | ||
_this.opts = _extends({}, defaultOptions, opts); | ||
/** | ||
* Simultaneous upload limiting is shared across all uploads with this plugin. | ||
* | ||
* @type {RateLimitedQueue} | ||
*/ | ||
if (typeof _this.opts.limit === 'number' && _this.opts.limit !== 0) { | ||
_this.limitUploads = limitPromises(_this.opts.limit); | ||
} else { | ||
_this.limitUploads = function (fn) { | ||
return fn; | ||
}; | ||
} | ||
_this.requests = new RateLimitedQueue(_this.opts.limit); | ||
_this.uploaders = Object.create(null); | ||
@@ -138,2 +135,4 @@ _this.uploaderEvents = Object.create(null); | ||
* any events related to the file, and the Companion WebSocket connection. | ||
* | ||
* @param {string} fileID | ||
*/ | ||
@@ -159,8 +158,31 @@ ; | ||
/** | ||
* 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>} | ||
*/ | ||
@@ -175,5 +197,13 @@ ; | ||
return new Promise(function (resolve, reject) { | ||
_this2.uppy.emit('upload-started', file); | ||
var optsTus = _extends({}, tusDefaultOptions, _this2.opts, // Install file-specific upload overrides. | ||
file.tus || {}); | ||
file.tus || {}); // We override tus fingerprint to uppy’s `file.id`, since the `file.id` | ||
// now also includes `relativePath` for files added from folders. | ||
// This means you can add 2 identical files, if one is in folder a, | ||
// the other in folder b. | ||
optsTus.fingerprint = getFingerprint(file); | ||
optsTus.onError = function (err) { | ||
@@ -188,2 +218,3 @@ _this2.uppy.log(err); | ||
queuedRequest.done(); | ||
reject(err); | ||
@@ -215,2 +246,3 @@ }; | ||
queuedRequest.done(); | ||
resolve(upload); | ||
@@ -237,5 +269,21 @@ }; | ||
_this2.uploaders[file.id] = upload; | ||
_this2.uploaderEvents[file.id] = createEventTracker(_this2.uppy); | ||
_this2.uploaderEvents[file.id] = new EventTracker(_this2.uppy); | ||
var queuedRequest = _this2.requests.run(function () { | ||
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 function () {}; | ||
}); | ||
_this2.onFileRemove(file.id, function (targetFileID) { | ||
queuedRequest.abort(); | ||
_this2.resetUploaderReferences(file.id); | ||
@@ -248,5 +296,12 @@ | ||
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 = _this2.requests.run(function () { | ||
upload.start(); | ||
return function () {}; | ||
}); | ||
} | ||
@@ -256,2 +311,3 @@ }); | ||
_this2.onPauseAll(file.id, function () { | ||
queuedRequest.abort(); | ||
upload.abort(); | ||
@@ -261,2 +317,4 @@ }); | ||
_this2.onCancelAll(file.id, function () { | ||
queuedRequest.abort(); | ||
_this2.resetUploaderReferences(file.id); | ||
@@ -268,2 +326,4 @@ | ||
_this2.onResumeAll(file.id, function () { | ||
queuedRequest.abort(); | ||
if (file.error) { | ||
@@ -273,10 +333,20 @@ upload.abort(); | ||
upload.start(); | ||
queuedRequest = _this2.requests.run(function () { | ||
upload.start(); | ||
return function () {}; | ||
}); | ||
}); | ||
}).catch(function (err) { | ||
_this2.uppy.emit('upload-error', file, err); | ||
if (!file.isPaused) { | ||
upload.start(); | ||
} | ||
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 | ||
* @returns {Promise<void>} | ||
*/ | ||
; | ||
@@ -288,18 +358,20 @@ _proto.uploadRemote = function uploadRemote(file, current, total) { | ||
var opts = _extends({}, this.opts, // Install file-specific upload overrides. | ||
file.tus || {}); | ||
var opts = _extends({}, this.opts); | ||
return new Promise(function (resolve, reject) { | ||
_this3.uppy.log(file.remote.url); | ||
if (file.tus) { | ||
// Install file-specific upload overrides. | ||
_extends(opts, file.tus); | ||
} | ||
if (file.serverToken) { | ||
return _this3.connectToServerSocket(file).then(function () { | ||
return resolve(); | ||
}).catch(reject); | ||
} | ||
this.uppy.emit('upload-started', file); | ||
this.uppy.log(file.remote.url); | ||
_this3.uppy.emit('upload-started', file); | ||
if (file.serverToken) { | ||
return this.connectToServerSocket(file); | ||
} | ||
return new Promise(function (resolve, reject) { | ||
var Client = file.remote.providerOptions.provider ? Provider : RequestClient; | ||
var client = new Client(_this3.uppy, file.remote.providerOptions); | ||
var client = new Client(_this3.uppy, file.remote.providerOptions); // !! cancellation is NOT supported at this stage yet | ||
client.post(file.remote.url, _extends({}, file.remote.body, { | ||
@@ -317,4 +389,2 @@ endpoint: opts.endpoint, | ||
file = _this3.uppy.getFile(file.id); | ||
return file; | ||
}).then(function (file) { | ||
return _this3.connectToServerSocket(file); | ||
@@ -327,3 +397,11 @@ }).then(function () { | ||
}); | ||
}; | ||
} | ||
/** | ||
* 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 | ||
*/ | ||
; | ||
@@ -337,9 +415,14 @@ _proto.connectToServerSocket = function connectToServerSocket(file) { | ||
var socket = new Socket({ | ||
target: host + "/api/" + token | ||
target: host + "/api/" + token, | ||
autoOpen: false | ||
}); | ||
_this4.uploaderSockets[file.id] = socket; | ||
_this4.uploaderEvents[file.id] = createEventTracker(_this4.uppy); | ||
_this4.uploaderEvents[file.id] = new EventTracker(_this4.uppy); | ||
_this4.onFileRemove(file.id, function () { | ||
queuedRequest.abort(); | ||
socket.send('pause', {}); | ||
_this4.resetUploaderReferences(file.id); | ||
resolve("upload " + file.id + " was removed"); | ||
@@ -349,14 +432,33 @@ }); | ||
_this4.onPause(file.id, function (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 = _this4.requests.run(function () { | ||
socket.send('resume', {}); | ||
return function () {}; | ||
}); | ||
} | ||
}); | ||
_this4.onPauseAll(file.id, function () { | ||
return socket.send('pause', {}); | ||
queuedRequest.abort(); | ||
socket.send('pause', {}); | ||
}); | ||
_this4.onCancelAll(file.id, function () { | ||
return socket.send('pause', {}); | ||
queuedRequest.abort(); | ||
socket.send('pause', {}); | ||
_this4.resetUploaderReferences(file.id); | ||
resolve("upload " + file.id + " was canceled"); | ||
}); | ||
_this4.onResumeAll(file.id, function () { | ||
queuedRequest.abort(); | ||
if (file.error) { | ||
@@ -366,19 +468,27 @@ socket.send('pause', {}); | ||
socket.send('resume', {}); | ||
queuedRequest = _this4.requests.run(function () { | ||
socket.send('resume', {}); | ||
return function () {}; | ||
}); | ||
}); | ||
_this4.onRetry(file.id, function () { | ||
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', {}); | ||
} | ||
}); | ||
_this4.onRetryAll(file.id, function () { | ||
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', function (progressData) { | ||
@@ -403,2 +513,4 @@ return emitSocketProgress(_this4, progressData, file); | ||
}); | ||
} else { | ||
socket.close(); | ||
} | ||
@@ -408,2 +520,3 @@ | ||
queuedRequest.done(); | ||
reject(error); | ||
@@ -420,4 +533,21 @@ }); | ||
queuedRequest.done(); | ||
resolve(); | ||
}); | ||
var queuedRequest = _this4.requests.run(function () { | ||
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 function () {}; | ||
}); | ||
}); | ||
@@ -428,2 +558,5 @@ } | ||
* restores state, we will continue uploading to the correct URL. | ||
* | ||
* @param {UppyFile} file | ||
* @param {string} uploadURL | ||
*/ | ||
@@ -444,3 +577,8 @@ ; | ||
} | ||
}; | ||
} | ||
/** | ||
* @param {string} fileID | ||
* @param {function(string): void} cb | ||
*/ | ||
; | ||
@@ -451,3 +589,8 @@ _proto.onFileRemove = function onFileRemove(fileID, cb) { | ||
}); | ||
}; | ||
} | ||
/** | ||
* @param {string} fileID | ||
* @param {function(boolean): void} cb | ||
*/ | ||
; | ||
@@ -461,3 +604,8 @@ _proto.onPause = function onPause(fileID, cb) { | ||
}); | ||
}; | ||
} | ||
/** | ||
* @param {string} fileID | ||
* @param {function(): void} cb | ||
*/ | ||
; | ||
@@ -470,3 +618,8 @@ _proto.onRetry = function onRetry(fileID, cb) { | ||
}); | ||
}; | ||
} | ||
/** | ||
* @param {string} fileID | ||
* @param {function(): void} cb | ||
*/ | ||
; | ||
@@ -480,3 +633,8 @@ _proto.onRetryAll = function onRetryAll(fileID, cb) { | ||
}); | ||
}; | ||
} | ||
/** | ||
* @param {string} fileID | ||
* @param {function(): void} cb | ||
*/ | ||
; | ||
@@ -490,3 +648,8 @@ _proto.onPauseAll = function onPauseAll(fileID, cb) { | ||
}); | ||
}; | ||
} | ||
/** | ||
* @param {string} fileID | ||
* @param {function(): void} cb | ||
*/ | ||
; | ||
@@ -500,3 +663,8 @@ _proto.onCancelAll = function onCancelAll(fileID, cb) { | ||
}); | ||
}; | ||
} | ||
/** | ||
* @param {string} fileID | ||
* @param {function(): void} cb | ||
*/ | ||
; | ||
@@ -510,3 +678,7 @@ _proto.onResumeAll = function onResumeAll(fileID, cb) { | ||
}); | ||
}; | ||
} | ||
/** | ||
* @param {(UppyFile | FailedUppyFile)[]} files | ||
*/ | ||
; | ||
@@ -516,29 +688,20 @@ _proto.uploadFiles = function uploadFiles(files) { | ||
var actions = files.map(function (file, i) { | ||
var current = parseInt(i, 10) + 1; | ||
var promises = files.map(function (file, i) { | ||
var current = i + 1; | ||
var total = files.length; | ||
if (file.error) { | ||
return function () { | ||
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. | ||
_this9.uppy.emit('upload-started', file); | ||
return _this9.uploadRemote.bind(_this9, file, current, total); | ||
return _this9.uploadRemote(file, current, total); | ||
} else { | ||
_this9.uppy.emit('upload-started', file); | ||
return _this9.upload.bind(_this9, file, current, total); | ||
return _this9.upload(file, current, total); | ||
} | ||
}); | ||
var promises = actions.map(function (action) { | ||
var limitedAction = _this9.limitUploads(action); | ||
return limitedAction(); | ||
}); | ||
return settle(promises); | ||
}; | ||
} | ||
/** | ||
* @param {string[]} fileIDs | ||
*/ | ||
; | ||
@@ -594,2 +757,2 @@ _proto.handleUpload = function handleUpload(fileIDs) { | ||
return Tus; | ||
}(Plugin), _class.VERSION = "1.4.0", _temp); | ||
}(Plugin), _class.VERSION = "1.4.1", _temp); |
{ | ||
"name": "@uppy/tus", | ||
"description": "Resumable uploads for Uppy using Tus.io", | ||
"version": "1.4.0", | ||
"version": "1.4.1", | ||
"license": "MIT", | ||
@@ -25,5 +25,5 @@ "main": "lib/index.js", | ||
"dependencies": { | ||
"@uppy/companion-client": "^1.3.0", | ||
"@uppy/utils": "^1.3.0", | ||
"tus-js-client": "^1.8.0-0" | ||
"@uppy/companion-client": "^1.4.0", | ||
"@uppy/utils": "^2.0.0", | ||
"tus-js-client": "^1.8.0-2" | ||
}, | ||
@@ -33,3 +33,3 @@ "peerDependencies": { | ||
}, | ||
"gitHead": "056a7114a15fc7480a4014342d7f2c19305dc96c" | ||
"gitHead": "4e32e61d7c7821ca5d8641a3df741487ea27f0bb" | ||
} |
333
src/index.js
@@ -7,6 +7,17 @@ const { Plugin } = require('@uppy/core') | ||
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 = { | ||
@@ -29,23 +40,3 @@ endpoint: '', | ||
/** | ||
* 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 | ||
* | ||
*/ | ||
@@ -55,2 +46,6 @@ module.exports = class Tus extends Plugin { | ||
/** | ||
* @param {Uppy} uppy | ||
* @param {TusOptions} opts | ||
*/ | ||
constructor (uppy, opts) { | ||
@@ -72,10 +67,11 @@ super(uppy, opts) | ||
// 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) | ||
@@ -107,2 +103,4 @@ this.uploaders = Object.create(null) | ||
* any events related to the file, and the Companion WebSocket connection. | ||
* | ||
* @param {string} fileID | ||
*/ | ||
@@ -125,8 +123,31 @@ resetUploaderReferences (fileID) { | ||
/** | ||
* 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>} | ||
*/ | ||
@@ -138,2 +159,4 @@ upload (file, current, total) { | ||
return new Promise((resolve, reject) => { | ||
this.uppy.emit('upload-started', file) | ||
const optsTus = Object.assign( | ||
@@ -147,2 +170,8 @@ {}, | ||
// We override tus fingerprint to uppy’s `file.id`, since the `file.id` | ||
// now also includes `relativePath` for files added from folders. | ||
// This means you can add 2 identical files, if one is in folder a, | ||
// the other in folder b. | ||
optsTus.fingerprint = getFingerprint(file) | ||
optsTus.onError = (err) => { | ||
@@ -154,2 +183,3 @@ this.uppy.log(err) | ||
this.resetUploaderReferences(file.id) | ||
queuedRequest.done() | ||
reject(err) | ||
@@ -179,2 +209,3 @@ } | ||
this.resetUploaderReferences(file.id) | ||
queuedRequest.done() | ||
resolve(upload) | ||
@@ -209,5 +240,19 @@ } | ||
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) | ||
@@ -219,5 +264,12 @@ resolve(`upload ${targetFileID} was removed`) | ||
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 () => {} | ||
}) | ||
} | ||
@@ -227,2 +279,3 @@ }) | ||
this.onPauseAll(file.id, () => { | ||
queuedRequest.abort() | ||
upload.abort() | ||
@@ -232,2 +285,3 @@ }) | ||
this.onCancelAll(file.id, () => { | ||
queuedRequest.abort() | ||
this.resetUploaderReferences(file.id) | ||
@@ -238,49 +292,54 @@ 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 | ||
* @returns {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) | ||
} | ||
this.uppy.emit('upload-started', file) | ||
this.uppy.log(file.remote.url) | ||
if (file.serverToken) { | ||
return this.connectToServerSocket(file) | ||
} | ||
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) | ||
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) | ||
@@ -295,2 +354,9 @@ }).then(() => { | ||
/** | ||
* 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) { | ||
@@ -300,8 +366,10 @@ return new Promise((resolve, reject) => { | ||
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`) | ||
@@ -311,30 +379,58 @@ }) | ||
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)) | ||
@@ -354,5 +450,8 @@ | ||
}) | ||
} else { | ||
socket.close() | ||
} | ||
this.uppy.emit('upload-error', file, error) | ||
queuedRequest.done() | ||
reject(error) | ||
@@ -368,4 +467,20 @@ }) | ||
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 () => {} | ||
}) | ||
}) | ||
@@ -377,2 +492,5 @@ } | ||
* restores state, we will continue uploading to the correct URL. | ||
* | ||
* @param {UppyFile} file | ||
* @param {string} uploadURL | ||
*/ | ||
@@ -393,2 +511,6 @@ onReceiveUploadUrl (file, uploadURL) { | ||
/** | ||
* @param {string} fileID | ||
* @param {function(string): void} cb | ||
*/ | ||
onFileRemove (fileID, cb) { | ||
@@ -400,2 +522,6 @@ this.uploaderEvents[fileID].on('file-removed', (file) => { | ||
/** | ||
* @param {string} fileID | ||
* @param {function(boolean): void} cb | ||
*/ | ||
onPause (fileID, cb) { | ||
@@ -410,2 +536,6 @@ this.uploaderEvents[fileID].on('upload-pause', (targetFileID, isPaused) => { | ||
/** | ||
* @param {string} fileID | ||
* @param {function(): void} cb | ||
*/ | ||
onRetry (fileID, cb) { | ||
@@ -419,2 +549,6 @@ this.uploaderEvents[fileID].on('upload-retry', (targetFileID) => { | ||
/** | ||
* @param {string} fileID | ||
* @param {function(): void} cb | ||
*/ | ||
onRetryAll (fileID, cb) { | ||
@@ -427,2 +561,6 @@ this.uploaderEvents[fileID].on('retry-all', (filesToRetry) => { | ||
/** | ||
* @param {string} fileID | ||
* @param {function(): void} cb | ||
*/ | ||
onPauseAll (fileID, cb) { | ||
@@ -435,2 +573,6 @@ this.uploaderEvents[fileID].on('pause-all', () => { | ||
/** | ||
* @param {string} fileID | ||
* @param {function(): void} cb | ||
*/ | ||
onCancelAll (fileID, cb) { | ||
@@ -443,2 +585,6 @@ this.uploaderEvents[fileID].on('cancel-all', () => { | ||
/** | ||
* @param {string} fileID | ||
* @param {function(): void} cb | ||
*/ | ||
onResumeAll (fileID, cb) { | ||
@@ -451,28 +597,25 @@ this.uploaderEvents[fileID].on('resume-all', () => { | ||
/** | ||
* @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) { | ||
@@ -479,0 +622,0 @@ if (fileIDs.length === 0) { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
49989
10
1230
2
19
+ Added@uppy/utils@2.4.4(transitive)
- Removed@uppy/utils@1.3.0(transitive)
Updated@uppy/utils@^2.0.0
Updatedtus-js-client@^1.8.0-2