Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add onShouldRetry as option to @uppy/tus #3720

Merged
merged 18 commits into from
May 17, 2022
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion e2e/clients/dashboard-tus/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,16 @@ import '@uppy/dashboard/dist/style.css'
const companionUrl = 'http://localhost:3020'
const uppy = new Uppy()
.use(Dashboard, { target: '#app', inline: true })
.use(Tus, { endpoint: 'https://tusd.tusdemo.net/files' })
.use(Tus, { endpoint: 'https://tusd.tusdemo.net/files', onShouldRetry })
.use(Url, { target: Dashboard, companionUrl })
.use(Unsplash, { target: Dashboard, companionUrl })

function onShouldRetry (err, next) {
if (err?.originalResponse?.getStatus() === 418) {
return true
}
return next(err)
}

// Keep this here to access uppy in tests
window.uppy = uppy
23 changes: 0 additions & 23 deletions e2e/cypress/integration/dashboard-tus.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,6 @@ describe('Dashboard with Tus', () => {
cy.intercept('http://localhost:3020/search/unsplash/*').as('unsplash')
})

it('should emit `error` and `upload-error` events on failed POST request', () => {
cy.get('@file-input').attachFile(['images/traffic.jpg'])

const error = cy.spy()
const uploadError = cy.spy()
cy.window().then(({ uppy }) => {
uppy.on('upload-error', uploadError)
uppy.on('error', error)
})

cy.get('.uppy-StatusBar-actionBtn--upload').click()

cy.intercept(
{ method: 'POST', url: 'https://tusd.tusdemo.net/*', times: 1 },
{ statusCode: 401, body: { code: 401, message: 'Expired JWT Token' } },
).as('post')

cy.wait('@post').then(() => {
expect(error).to.be.called
expect(uploadError).to.be.called
})
})

it('should upload cat image successfully', () => {
cy.get('@file-input').attachFile('images/cat.jpg')
cy.get('.uppy-StatusBar-actionBtn--upload').click()
Expand Down
9 changes: 8 additions & 1 deletion packages/@uppy/tus/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -277,8 +277,15 @@ export default class Tus extends BasePlugin {
resolve(upload)
}

uploadOptions.onShouldRetry = (err) => {
if (opts.onShouldRetry !== null) {
uploadOptions.onShouldRetry = async (err) => opts.onShouldRetry(err, defaultOnShouldRetry.bind(this))
} else {
uploadOptions.onShouldRetry = defaultOnShouldRetry
}

function defaultOnShouldRetry (err) {
Murderlon marked this conversation as resolved.
Show resolved Hide resolved
Murderlon marked this conversation as resolved.
Show resolved Hide resolved
const status = err?.originalResponse?.getStatus()

if (status === 429) {
// HTTP 429 Too Many Requests => to avoid the whole download to fail, pause all requests.
if (!this.requests.isPaused) {
Expand Down
24 changes: 14 additions & 10 deletions packages/@uppy/tus/types/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
import type { PluginOptions, BasePlugin } from '@uppy/core'
import type { UploadOptions } from 'tus-js-client'

type TusUploadOptions = Pick<UploadOptions, Exclude<keyof UploadOptions,
| 'fingerprint'
| 'metadata'
| 'onProgress'
| 'onChunkComplete'
| 'onSuccess'
| 'onError'
| 'uploadUrl'
| 'uploadSize'
>>
type TusUploadOptions = Pick<UploadOptions, Exclude<keyof UploadOptions,
| 'fingerprint'
| 'metadata'
| 'onProgress'
| 'onChunkComplete'
| 'onShouldRetry'
| 'onSuccess'
| 'onError'
| 'uploadUrl'
| 'uploadSize'
>>

type Next = (err: Error) => boolean
aduh95 marked this conversation as resolved.
Show resolved Hide resolved
Murderlon marked this conversation as resolved.
Show resolved Hide resolved

export interface TusOptions extends PluginOptions, TusUploadOptions {
metaFields?: string[] | null
limit?: number
useFastRemoteRetry?: boolean
withCredentials?: boolean
onShouldRetry: (err: Error, next: Next) => boolean
Murderlon marked this conversation as resolved.
Show resolved Hide resolved
}

declare class Tus extends BasePlugin<TusOptions> {}
Expand Down
24 changes: 24 additions & 0 deletions website/src/docs/tus.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,30 @@ When uploading a chunk fails, automatically try again after the millisecond inte

Set to `null` to disable automatic retries, and fail instantly if any chunk fails to upload.

### `onShouldRetry: (err, next) => next(err)`

When an upload fails `onShouldRetry` is called with the error and the default retry logic as the second argument. The default retry logic is an [exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff) algorithm triggered on HTTP 429 (Too Many Requests) errors. Meaning if your server (or proxy) returns HTTP 429 because it’s being overloaded, @uppy/tus will find the ideal sweet spot to keep uploading without overloading.

If you want to extend this functionality, for instance to retry on unauthorized requests (to retrieve a new authentication token):

```js
import Uppy from '@uppy/core'
import Tus from '@uppy/tus'

const uppy = new Uppy().use(Tus, { endpoint: '', onShouldRetry })

async function onShouldRetry (err, next) {
if (err?.originalResponse?.getStatus() === 401) {
return true
}
Murderlon marked this conversation as resolved.
Show resolved Hide resolved
return next(err)
}
```

### `onBeforeRequest: (req) => void`

See [`onBeforeRequest`](https://github.com/tus/tus-js-client/blob/master/docs/api.md#onbeforerequest) from `tus-js-client`.

### `metaFields: null`

Pass an array of field names to limit the metadata fields that will be added to uploads as [Tus Metadata](https://tus.io/protocols/resumable-upload.html#upload-metadata).
Expand Down