Browse Source

Merge pull request #378 from goto-bus-stop/feature/xhr-timeout

 xhrupload: stop trying to upload if it stalled
Artur Paikin 7 years ago
parent
commit
6061535d90
3 changed files with 61 additions and 3 deletions
  1. 2 2
      CHANGELOG.md
  2. 50 1
      src/plugins/XHRUpload.js
  3. 9 0
      website/src/docs/xhrupload.md

+ 2 - 2
CHANGELOG.md

@@ -80,7 +80,7 @@ What we need to do to release Uppy 1.0
 - [ ] refactoring: clean up code everywhere
 - [ ] docs: on using plugins, all options, list of plugins, i18n
 - [ ] uppy-server: better error handling, general cleanup (remove unused code. etc)
-- [ ] uppy-server: security audit 
+- [ ] uppy-server: security audit
 - [x] uppy-server: storing tokens in user’s browser only (d040281cc9a63060e2f2685c16de0091aee5c7b4)
 - [ ] consider iframe / more security for Transloadit/Uppy integration widget and Uppy itself. Page can’t get files from Google Drive if its an iframe; possibility for folder restriction for provider plugins
 - [ ] automatically host releases on edgly and use that as our main CDN
@@ -112,7 +112,7 @@ To be released: 2017-11-10
 - [x] core: refactor `uppy-base` (#382 / @goto-bus-stop)
 - [x] uppy-server: look into storing tokens in user’s browser only (@ifedapoolarewaju)
 - [ ] accessibility: add tabindex="0" to buttons and tabs, aria-, focus; add https://github.com/pa11y/pa11y for automated accessibility testing  (@arturi)
-- [ ] xhrupload: set a timeout in the onprogress event handler to detect stale network (#378 / @goto-bus-stop)
+- [x] xhrupload: set a timeout in the onprogress event handler to detect stale network (#378 / @goto-bus-stop)
 - [ ] tus: Review b3cc48130e292f08c2a09f2f0adf6b6332bf7692
 - [x] tus: Rename Tus10 → Tus
 - [ ] docs: quick start guide: https://community.transloadit.com/t/quick-start-guide-would-be-really-helpful/14605 (@arturi)

+ 50 - 1
src/plugins/XHRUpload.js

@@ -1,4 +1,6 @@
 const Plugin = require('./Plugin')
+const cuid = require('cuid')
+const Translator = require('../core/Translator')
 const UppySocket = require('../core/UppySocket')
 const {
   emitSocketProgress,
@@ -13,6 +15,12 @@ module.exports = class XHRUpload extends Plugin {
     this.id = 'XHRUpload'
     this.title = 'XHRUpload'
 
+    const defaultLocale = {
+      strings: {
+        timedOut: 'Upload stalled for %{seconds} seconds, aborting.'
+      }
+    }
+
     // Default options
     const defaultOptions = {
       formData: true,
@@ -22,6 +30,8 @@ module.exports = class XHRUpload extends Plugin {
       responseUrlFieldName: 'url',
       bundle: true,
       headers: {},
+      locale: defaultLocale,
+      timeout: 30 * 1000,
       getResponseData (xhr) {
         return JSON.parse(xhr.response)
       },
@@ -32,6 +42,12 @@ module.exports = class XHRUpload extends Plugin {
 
     // Merge default options with the ones set by user
     this.opts = Object.assign({}, defaultOptions, opts)
+    this.locale = Object.assign({}, defaultLocale, this.opts.locale)
+    this.locale.strings = Object.assign({}, defaultLocale.strings, this.opts.locale.strings)
+
+    // i18n
+    this.translator = new Translator({ locale: this.locale })
+    this.i18n = this.translator.translate.bind(this.translator)
 
     this.handleUpload = this.handleUpload.bind(this)
   }
@@ -83,9 +99,36 @@ module.exports = class XHRUpload extends Plugin {
         ? this.createFormDataUpload(file, opts)
         : this.createBareUpload(file, opts)
 
+      const onTimedOut = () => {
+        xhr.abort()
+        this.core.log(`[XHRUpload] ${id} timed out`)
+        const error = new Error(this.i18n('timedOut', { seconds: Math.ceil(opts.timeout / 1000) }))
+        this.core.emit('core:upload-error', file.id, error)
+        reject(error)
+      }
+      let aliveTimer
+      const isAlive = () => {
+        clearTimeout(aliveTimer)
+        aliveTimer = setTimeout(onTimedOut, opts.timeout)
+      }
+
       const xhr = new XMLHttpRequest()
+      const id = cuid()
+
+      xhr.upload.addEventListener('loadstart', (ev) => {
+        this.core.log(`[XHRUpload] ${id} started`)
+        if (opts.timeout > 0) {
+          // Begin checking for timeouts when loading starts.
+          isAlive()
+        }
+      })
 
       xhr.upload.addEventListener('progress', (ev) => {
+        this.core.log(`[XHRUpload] ${id} progress: ${ev.loaded} / ${ev.total}`)
+        if (opts.timeout > 0) {
+          isAlive()
+        }
+
         if (ev.lengthComputable) {
           this.core.emit('core:upload-progress', {
             uploader: this,
@@ -97,6 +140,9 @@ module.exports = class XHRUpload extends Plugin {
       })
 
       xhr.addEventListener('load', (ev) => {
+        this.core.log(`[XHRUpload] ${id} finished`)
+        clearTimeout(aliveTimer)
+
         if (ev.target.status >= 200 && ev.target.status < 300) {
           const resp = opts.getResponseData(xhr)
           const uploadURL = resp[opts.responseUrlFieldName]
@@ -117,9 +163,12 @@ module.exports = class XHRUpload extends Plugin {
       })
 
       xhr.addEventListener('error', (ev) => {
+        this.core.log(`[XHRUpload] ${id} errored`)
+        clearTimeout(aliveTimer)
+
         const error = opts.getResponseError(xhr) || new Error('Upload error')
         this.core.emit('core:upload-error', file.id, error)
-        return reject(new Error('Upload error'))
+        return reject(error)
       })
 
       xhr.open(opts.method.toUpperCase(), opts.endpoint, true)

+ 9 - 0
website/src/docs/xhrupload.md

@@ -100,4 +100,13 @@ getResponseError (xhr) {
 
 The field name containing a publically accessible location of the uploaded file in the response data returned by `getResponseData(xhr)`.
 
+### `timeout: 30 * 1000`
+
+When no upload progress events have been received for this amount of milliseconds, assume the connection has an issue and abort the upload.
+Note that unlike the [`XMLHttpRequest.timeout`][XHR.timeout] property, this is a timer between progress events: the total upload can take longer than this value.
+Set to `0` to disable this check.
+
+The default is 30 seconds.
+
 [FormData]: https://developer.mozilla.org/en-US/docs/Web/API/FormData
+[XHR.timeout]: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/timeout