@@ -40,7 +40,7 @@ const tusDefaultOptions = {
4040 addRequestId : false ,
4141
4242 chunkSize : Infinity ,
43- retryDelays : [ 0 , 1000 , 3000 , 5000 ] ,
43+ retryDelays : [ 100 , 1000 , 3000 , 5000 ] ,
4444 parallelUploads : 1 ,
4545 removeFingerprintOnSuccess : false ,
4646 uploadLengthDeferred : false ,
@@ -51,8 +51,11 @@ const tusDefaultOptions = {
5151 * Tus resumable file uploader
5252 */
5353module . exports = class Tus extends BasePlugin {
54+ // eslint-disable-next-line global-require
5455 static VERSION = require ( '../package.json' ) . version
5556
57+ #retryDelayIterator
58+
5659 /**
5760 * @param {Uppy } uppy
5861 * @param {TusOptions } opts
@@ -66,8 +69,8 @@ module.exports = class Tus extends BasePlugin {
6669 // set default options
6770 const defaultOptions = {
6871 useFastRemoteRetry : true ,
69- limit : 5 ,
70- retryDelays : [ 0 , 1000 , 3000 , 5000 ] ,
72+ limit : 20 ,
73+ retryDelays : tusDefaultOptions . retryDelays ,
7174 withCredentials : false ,
7275 }
7376
@@ -85,6 +88,7 @@ module.exports = class Tus extends BasePlugin {
8588 * @type {RateLimitedQueue }
8689 */
8790 this . requests = new RateLimitedQueue ( this . opts . limit )
91+ this . #retryDelayIterator = this . opts . retryDelays ?. values ( )
8892
8993 this . uploaders = Object . create ( null )
9094 this . uploaderEvents = Object . create ( null )
@@ -178,6 +182,9 @@ module.exports = class Tus extends BasePlugin {
178182
179183 // Create a new tus upload
180184 return new Promise ( ( resolve , reject ) => {
185+ let queuedRequest
186+ let qRequest
187+
181188 this . uppy . emit ( 'upload-started' , file )
182189
183190 const opts = {
@@ -219,7 +226,7 @@ module.exports = class Tus extends BasePlugin {
219226 }
220227
221228 this . resetUploaderReferences ( file . id )
222- queuedRequest . done ( )
229+ queuedRequest . abort ( )
223230
224231 this . uppy . emit ( 'upload-error' , file , err )
225232
@@ -252,6 +259,46 @@ module.exports = class Tus extends BasePlugin {
252259 resolve ( upload )
253260 }
254261
262+ uploadOptions . onShouldRetry = ( err , retryAttempt , options ) => {
263+ const status = err ?. originalResponse ?. getStatus ( )
264+ if ( status === 429 ) {
265+ // HTTP 429 Too Many Requests => to avoid the whole download to fail, pause all requests.
266+ if ( ! this . requests . isPaused ) {
267+ const next = this . #retryDelayIterator?. next ( )
268+ if ( next == null || next . done ) {
269+ return false
270+ }
271+ this . requests . rateLimit ( next . value )
272+ }
273+ queuedRequest . abort ( )
274+ queuedRequest = this . requests . run ( qRequest )
275+ } else if ( status > 400 && status < 500 && status !== 409 ) {
276+ // HTTP 4xx, the server won't send anything, it's doesn't make sense to retry
277+ return false
278+ } else if ( typeof navigator !== 'undefined' && navigator . onLine === false ) {
279+ // The navigator is offline, let's wait for it to come back online.
280+ if ( ! this . requests . isPaused ) {
281+ this . requests . pause ( )
282+ window . addEventListener ( 'online' , ( ) => {
283+ this . requests . resume ( )
284+ } , { once : true } )
285+ }
286+ queuedRequest . abort ( )
287+ queuedRequest = this . requests . run ( qRequest )
288+ } else {
289+ // For a non-4xx error, we can re-queue the request.
290+ setTimeout ( ( ) => {
291+ queuedRequest . abort ( )
292+ queuedRequest = this . requests . run ( qRequest )
293+ } , options . retryDelays [ retryAttempt ] )
294+ }
295+ // Aborting the timeout set by tus-js-client to not short-circuit the rate limiting.
296+ // eslint-disable-next-line no-underscore-dangle
297+ queueMicrotask ( ( ) => clearTimeout ( queuedRequest . _retryTimeout ) )
298+ // We need to return true here so tus-js-client increments the retryAttempt and do not emit an error event.
299+ return true
300+ }
301+
255302 const copyProp = ( obj , srcProp , destProp ) => {
256303 if ( hasProperty ( obj , srcProp ) && ! hasProperty ( obj , destProp ) ) {
257304 obj [ destProp ] = obj [ srcProp ]
@@ -278,15 +325,7 @@ module.exports = class Tus extends BasePlugin {
278325 this . uploaders [ file . id ] = upload
279326 this . uploaderEvents [ file . id ] = new EventTracker ( this . uppy )
280327
281- upload . findPreviousUploads ( ) . then ( ( previousUploads ) => {
282- const previousUpload = previousUploads [ 0 ]
283- if ( previousUpload ) {
284- this . uppy . log ( `[Tus] Resuming upload of ${ file . id } started at ${ previousUpload . creationTime } ` )
285- upload . resumeFromPreviousUpload ( previousUpload )
286- }
287- } )
288-
289- let queuedRequest = this . requests . run ( ( ) => {
328+ qRequest = ( ) => {
290329 if ( ! file . isPaused ) {
291330 upload . start ( )
292331 }
@@ -297,8 +336,18 @@ module.exports = class Tus extends BasePlugin {
297336 // Also, we need to remove the request from the queue _without_ destroying everything
298337 // related to this upload to handle pauses.
299338 return ( ) => { }
339+ }
340+
341+ upload . findPreviousUploads ( ) . then ( ( previousUploads ) => {
342+ const previousUpload = previousUploads [ 0 ]
343+ if ( previousUpload ) {
344+ this . uppy . log ( `[Tus] Resuming upload of ${ file . id } started at ${ previousUpload . creationTime } ` )
345+ upload . resumeFromPreviousUpload ( previousUpload )
346+ }
300347 } )
301348
349+ queuedRequest = this . requests . run ( qRequest )
350+
302351 this . onFileRemove ( file . id , ( targetFileID ) => {
303352 queuedRequest . abort ( )
304353 this . resetUploaderReferences ( file . id , { abort : ! ! upload . url } )
@@ -314,10 +363,7 @@ module.exports = class Tus extends BasePlugin {
314363 // Resuming an upload should be queued, else you could pause and then
315364 // resume a queued upload to make it skip the queue.
316365 queuedRequest . abort ( )
317- queuedRequest = this . requests . run ( ( ) => {
318- upload . start ( )
319- return ( ) => { }
320- } )
366+ queuedRequest = this . requests . run ( qRequest )
321367 }
322368 } )
323369
@@ -337,10 +383,7 @@ module.exports = class Tus extends BasePlugin {
337383 if ( file . error ) {
338384 upload . abort ( )
339385 }
340- queuedRequest = this . requests . run ( ( ) => {
341- upload . start ( )
342- return ( ) => { }
343- } )
386+ queuedRequest = this . requests . run ( qRequest )
344387 } )
345388 } ) . catch ( ( err ) => {
346389 this . uppy . emit ( 'upload-error' , file , err )
@@ -412,6 +455,8 @@ module.exports = class Tus extends BasePlugin {
412455 this . uploaderSockets [ file . id ] = socket
413456 this . uploaderEvents [ file . id ] = new EventTracker ( this . uppy )
414457
458+ let queuedRequest
459+
415460 this . onFileRemove ( file . id , ( ) => {
416461 queuedRequest . abort ( )
417462 socket . send ( 'cancel' , { } )
@@ -512,7 +557,7 @@ module.exports = class Tus extends BasePlugin {
512557 resolve ( )
513558 } )
514559
515- let queuedRequest = this . requests . run ( ( ) => {
560+ queuedRequest = this . requests . run ( ( ) => {
516561 socket . open ( )
517562 if ( file . isPaused ) {
518563 socket . send ( 'pause' , { } )
0 commit comments