Ver código fonte

Mark network errors as NetworkError (#2110)

* Add NetworkError error type and isNetworkError utility check

* Detect network errors in tus and xhr-upload

Co-Authored-By: Ifedapo .A. Olarewaju <ifedapoolarewaju@gmail.com>

* add NetworkError check to RequestClient in companion-client

* return false if !xhr :see_no_evil:

* move catch so that only errors from _checkMinNumberOfFiles are caught — errors from uploads are logged in `upload-error` already

//cc @goto-bus-stop

* Add NetworkError to MiniXHRUpload in aws-s3

//cc @goto-bus-stop

* Add tests

//cc @ifedapoolarewaju

* pass xhr to NetworkError

* add logging in catch of uppy.upload()

* start error message with “This looks like a network error”

* originalRequest --> request, return NetworkError right away

* check for NetworkError in `delete` as well

do we need it in preflight too, @ifedapoolarewaju?

* remove redundant error message re-declaration

* _showOrLogErrorAndThrow, but don’t showInformer

Co-authored-by: Ifedapo .A. Olarewaju <ifedapoolarewaju@gmail.com>
Artur Paikin 5 anos atrás
pai
commit
ed8a494875

+ 0 - 1
packages/@uppy/aws-s3-multipart/src/index.js

@@ -151,7 +151,6 @@ module.exports = class AwsS3Multipart extends Plugin {
       const onError = (err) => {
         this.uppy.log(err)
         this.uppy.emit('upload-error', file, err)
-        err.message = `Failed because: ${err.message}`
 
         queuedRequest.done()
         this.resetUploaderReferences(file.id)

+ 7 - 0
packages/@uppy/aws-s3/src/MiniXHRUpload.js

@@ -4,6 +4,8 @@ const emitSocketProgress = require('@uppy/utils/lib/emitSocketProgress')
 const getSocketHost = require('@uppy/utils/lib/getSocketHost')
 const EventTracker = require('@uppy/utils/lib/EventTracker')
 const ProgressTimeout = require('@uppy/utils/lib/ProgressTimeout')
+const NetworkError = require('@uppy/utils/lib/NetworkError')
+const isNetworkError = require('@uppy/utils/lib/isNetworkError')
 
 // See XHRUpload
 function buildResponseError (xhr, error) {
@@ -16,6 +18,11 @@ function buildResponseError (xhr, error) {
     error = Object.assign(new Error('Upload error'), { data: error })
   }
 
+  if (isNetworkError(xhr)) {
+    error = new NetworkError(error, xhr)
+    return error
+  }
+
   error.request = xhr
   return error
 }

+ 22 - 0
packages/@uppy/companion-client/src/RequestClient.js

@@ -1,6 +1,7 @@
 'use strict'
 
 const AuthError = require('./AuthError')
+const NetworkError = require('@uppy/utils/lib/NetworkError')
 
 // Remove the trailing slash so we can always safely append /xyz.
 function stripSlash (url) {
@@ -138,6 +139,13 @@ module.exports = class RequestClient {
           headers: headers,
           credentials: 'same-origin'
         })
+          .catch((err) => {
+            if (err.name === 'AbortError') {
+              throw err
+            } else {
+              throw new NetworkError(err)
+            }
+          })
           .then(this._getPostResponseFunc(skipPostResponse))
           .then((res) => this._json(res).then(resolve))
           .catch((err) => {
@@ -157,6 +165,13 @@ module.exports = class RequestClient {
           credentials: 'same-origin',
           body: JSON.stringify(data)
         })
+          .catch((err) => {
+            if (err.name === 'AbortError') {
+              throw err
+            } else {
+              throw new NetworkError(err)
+            }
+          })
           .then(this._getPostResponseFunc(skipPostResponse))
           .then((res) => this._json(res).then(resolve))
           .catch((err) => {
@@ -176,6 +191,13 @@ module.exports = class RequestClient {
           credentials: 'same-origin',
           body: data ? JSON.stringify(data) : null
         })
+          .catch((err) => {
+            if (err.name === 'AbortError') {
+              throw err
+            } else {
+              throw new NetworkError(err)
+            }
+          })
           .then(this._getPostResponseFunc(skipPostResponse))
           .then((res) => this._json(res).then(resolve))
           .catch((err) => {

+ 6 - 1
packages/@uppy/core/src/index.js

@@ -1554,6 +1554,9 @@ class Uppy {
 
     return Promise.resolve()
       .then(() => this._checkMinNumberOfFiles(files))
+      .catch((err) => {
+        this._showOrLogErrorAndThrow(err)
+      })
       .then(() => {
         const { currentUploads } = this.getState()
         // get a list of files that are currently assigned to uploads
@@ -1572,7 +1575,9 @@ class Uppy {
         return this._runUpload(uploadID)
       })
       .catch((err) => {
-        this._showOrLogErrorAndThrow(err)
+        this._showOrLogErrorAndThrow(err, {
+          showInformer: false
+        })
       })
   }
 }

+ 7 - 1
packages/@uppy/tus/src/index.js

@@ -5,6 +5,8 @@ const emitSocketProgress = require('@uppy/utils/lib/emitSocketProgress')
 const getSocketHost = require('@uppy/utils/lib/getSocketHost')
 const settle = require('@uppy/utils/lib/settle')
 const EventTracker = require('@uppy/utils/lib/EventTracker')
+const NetworkError = require('@uppy/utils/lib/NetworkError')
+const isNetworkError = require('@uppy/utils/lib/isNetworkError')
 const RateLimitedQueue = require('@uppy/utils/lib/RateLimitedQueue')
 const getFingerprint = require('./getFingerprint')
 
@@ -175,8 +177,12 @@ module.exports = class Tus extends Plugin {
 
       optsTus.onError = (err) => {
         this.uppy.log(err)
+
+        if (isNetworkError(err.originalRequest)) {
+          err = new NetworkError(err, err.originalRequest)
+        }
+
         this.uppy.emit('upload-error', file, err)
-        err.message = `Failed because: ${err.message}`
 
         this.resetUploaderReferences(file.id)
         queuedRequest.done()

+ 10 - 0
packages/@uppy/utils/src/NetworkError.js

@@ -0,0 +1,10 @@
+class NetworkError extends Error {
+  constructor (error, xhr = null) {
+    super(`This looks like a network error, the endpoint might be blocked by an internet provider or a firewall.\n\nSource error: [${error}]`)
+
+    this.isNetworkError = true
+    this.request = xhr
+  }
+}
+
+module.exports = NetworkError

+ 8 - 0
packages/@uppy/utils/src/isNetworkError.js

@@ -0,0 +1,8 @@
+function isNetworkError (xhr) {
+  if (!xhr) {
+    return false
+  }
+  return (xhr.readyState !== 0 && xhr.readyState !== 4) || xhr.status === 0
+}
+
+module.exports = isNetworkError

+ 34 - 0
packages/@uppy/utils/src/isNetworkError.test.js

@@ -0,0 +1,34 @@
+const isNetworkError = require('./isNetworkError')
+
+describe('isNetworkError', () => {
+  it('should return true if the specified xhr object contains a network error', () => {
+    const xhrNetworkErrorMock = {
+      readyState: 4,
+      responseText: '',
+      status: 0
+    }
+
+    const xhrNetworkError2Mock = {
+      readyState: 2,
+      responseText: '',
+      status: 300
+    }
+
+    const xhrRegularErrorMock = {
+      readyState: 4,
+      responseText: 'Failed',
+      status: 400
+    }
+
+    const xhrNetworkSuccessMock = {
+      readyState: 4,
+      responseText: 'Success',
+      status: 200
+    }
+
+    expect(isNetworkError(xhrNetworkErrorMock)).toEqual(true)
+    expect(isNetworkError(xhrNetworkError2Mock)).toEqual(true)
+    expect(isNetworkError(xhrRegularErrorMock)).toEqual(false)
+    expect(isNetworkError(xhrNetworkSuccessMock)).toEqual(false)
+  })
+})

+ 14 - 1
packages/@uppy/xhr-upload/src/index.js

@@ -8,6 +8,8 @@ const settle = require('@uppy/utils/lib/settle')
 const EventTracker = require('@uppy/utils/lib/EventTracker')
 const ProgressTimeout = require('@uppy/utils/lib/ProgressTimeout')
 const RateLimitedQueue = require('@uppy/utils/lib/RateLimitedQueue')
+const NetworkError = require('@uppy/utils/lib/NetworkError')
+const isNetworkError = require('@uppy/utils/lib/isNetworkError')
 
 function buildResponseError (xhr, error) {
   // No error message
@@ -19,6 +21,11 @@ function buildResponseError (xhr, error) {
     error = Object.assign(new Error('Upload error'), { data: error })
   }
 
+  if (isNetworkError(xhr)) {
+    error = new NetworkError(error, xhr)
+    return error
+  }
+
   error.request = xhr
   return error
 }
@@ -90,7 +97,13 @@ module.exports = class XHRUpload extends Plugin {
        * @param {XMLHttpRequest | respObj} response the response object (XHR or similar)
        */
       getResponseError (responseText, response) {
-        return new Error('Upload error')
+        let error = new Error('Upload error')
+
+        if (isNetworkError(response)) {
+          error = new NetworkError(error, response)
+        }
+
+        return error
       },
       /**
        * Check if the response from the upload endpoint indicates that the upload was successful.