diff --git a/packages/@uppy/companion-client/src/RequestClient.js b/packages/@uppy/companion-client/src/RequestClient.js index be6b19219..4337ddf04 100644 --- a/packages/@uppy/companion-client/src/RequestClient.js +++ b/packages/@uppy/companion-client/src/RequestClient.js @@ -1,7 +1,7 @@ 'use strict' const AuthError = require('./AuthError') -const NetworkError = require('@uppy/utils/lib/NetworkError') +const fetchWithNetworkError = require('@uppy/utils/lib/fetchWithNetworkError') // Remove the trailing slash so we can always safely append /xyz. function stripSlash (url) { @@ -134,18 +134,11 @@ module.exports = class RequestClient { get (path, skipPostResponse) { return new Promise((resolve, reject) => { this.preflightAndHeaders(path).then((headers) => { - fetch(this._getUrl(path), { + fetchWithNetworkError(this._getUrl(path), { method: 'get', 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) => { @@ -159,19 +152,12 @@ module.exports = class RequestClient { post (path, data, skipPostResponse) { return new Promise((resolve, reject) => { this.preflightAndHeaders(path).then((headers) => { - fetch(this._getUrl(path), { + fetchWithNetworkError(this._getUrl(path), { method: 'post', headers: headers, 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) => { @@ -185,19 +171,12 @@ module.exports = class RequestClient { delete (path, data, skipPostResponse) { return new Promise((resolve, reject) => { this.preflightAndHeaders(path).then((headers) => { - fetch(`${this.hostname}/${path}`, { + fetchWithNetworkError(`${this.hostname}/${path}`, { method: 'delete', headers: headers, 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) => { diff --git a/packages/@uppy/transloadit/src/Assembly.js b/packages/@uppy/transloadit/src/Assembly.js index c97c22222..809564047 100644 --- a/packages/@uppy/transloadit/src/Assembly.js +++ b/packages/@uppy/transloadit/src/Assembly.js @@ -2,6 +2,8 @@ const io = requireSocketIo const Emitter = require('component-emitter') const has = require('@uppy/utils/lib/hasProperty') const parseUrl = require('./parseUrl') +const NetworkError = require('@uppy/utils/lib/NetworkError') +const fetchWithNetworkError = require('@uppy/utils/lib/fetchWithNetworkError') // Lazy load socket.io to avoid a console error // in IE 10 when the Transloadit plugin is not used. @@ -78,6 +80,12 @@ class TransloaditAssembly extends Emitter { this.emit('connect') }) + + socket.on('connect_failed', () => { + this._onError(new NetworkError('Transloadit Socket.io connection error')) + this.socket = null + }) + socket.on('error', () => { socket.disconnect() this.socket = null @@ -143,7 +151,7 @@ class TransloaditAssembly extends Emitter { * 'status'. */ _fetchStatus ({ diff = true } = {}) { - return fetch(this.status.assembly_ssl_url) + return fetchWithNetworkError(this.status.assembly_ssl_url) .then((response) => response.json()) .then((status) => { // Avoid updating if we closed during this request's lifetime. @@ -156,6 +164,7 @@ class TransloaditAssembly extends Emitter { this.status = status } }) + .catch((err) => this._onError(err)) } update () { diff --git a/packages/@uppy/transloadit/src/Client.js b/packages/@uppy/transloadit/src/Client.js index 506934d17..8b2124f49 100644 --- a/packages/@uppy/transloadit/src/Client.js +++ b/packages/@uppy/transloadit/src/Client.js @@ -1,3 +1,5 @@ +const fetchWithNetworkError = require('@uppy/utils/lib/fetchWithNetworkError') + /** * A Barebones HTTP API client for Transloadit. */ @@ -38,23 +40,25 @@ module.exports = class Client { data.append('num_expected_upload_files', expectedFiles) const url = `${this.opts.service}/assemblies` - return fetch(url, { + return fetchWithNetworkError(url, { method: 'post', headers: this._headers, body: data - }).then((response) => response.json()).then((assembly) => { - if (assembly.error) { - const error = new Error(assembly.error) - error.details = assembly.message - error.assembly = assembly - if (assembly.assembly_id) { - error.details += ' ' + `Assembly ID: ${assembly.assembly_id}` + }) + .then((response) => response.json()).then((assembly) => { + if (assembly.error) { + const error = new Error(assembly.error) + error.details = assembly.message + error.assembly = assembly + if (assembly.assembly_id) { + error.details += ' ' + `Assembly ID: ${assembly.assembly_id}` + } + throw error } - throw error - } - return assembly - }).catch((err) => this._reportError(err, { url, type: 'API_ERROR' })) + return assembly + }) + .catch((err) => this._reportError(err, { url, type: 'API_ERROR' })) } /** @@ -66,7 +70,7 @@ module.exports = class Client { reserveFile (assembly, file) { const size = encodeURIComponent(file.size) const url = `${assembly.assembly_ssl_url}/reserve_file?size=${size}` - return fetch(url, { method: 'post', headers: this._headers }) + return fetchWithNetworkError(url, { method: 'post', headers: this._headers }) .then((response) => response.json()) .catch((err) => this._reportError(err, { assembly, file, url, type: 'API_ERROR' })) } @@ -88,7 +92,7 @@ module.exports = class Client { const qs = `size=${size}&filename=${filename}&fieldname=${fieldname}&s3Url=${uploadUrl}` const url = `${assembly.assembly_ssl_url}/add_file?${qs}` - return fetch(url, { method: 'post', headers: this._headers }) + return fetchWithNetworkError(url, { method: 'post', headers: this._headers }) .then((response) => response.json()) .catch((err) => this._reportError(err, { assembly, file, url, type: 'API_ERROR' })) } @@ -100,7 +104,7 @@ module.exports = class Client { */ cancelAssembly (assembly) { const url = assembly.assembly_ssl_url - return fetch(url, { method: 'delete', headers: this._headers }) + return fetchWithNetworkError(url, { method: 'delete', headers: this._headers }) .then((response) => response.json()) .catch((err) => this._reportError(err, { url, type: 'API_ERROR' })) } @@ -111,7 +115,7 @@ module.exports = class Client { * @param {string} url The status endpoint of the assembly. */ getAssemblyStatus (url) { - return fetch(url, { headers: this._headers }) + return fetchWithNetworkError(url, { headers: this._headers }) .then((response) => response.json()) .catch((err) => this._reportError(err, { url, type: 'STATUS_ERROR' })) } @@ -121,7 +125,7 @@ module.exports = class Client { ? `${err.message} (${err.details})` : err.message - return fetch('https://status.transloadit.com/client_error', { + return fetchWithNetworkError('https://status.transloadit.com/client_error', { method: 'post', body: JSON.stringify({ endpoint, @@ -131,7 +135,8 @@ module.exports = class Client { client: this.opts.client, error: message }) - }).then((response) => response.json()) + }) + .then((response) => response.json()) } _reportError (err, params) { diff --git a/packages/@uppy/utils/src/fetchWithNetworkError.js b/packages/@uppy/utils/src/fetchWithNetworkError.js new file mode 100644 index 000000000..0c204c9d6 --- /dev/null +++ b/packages/@uppy/utils/src/fetchWithNetworkError.js @@ -0,0 +1,15 @@ +const NetworkError = require('@uppy/utils/lib/NetworkError') + +/** + * Wrapper around window.fetch that throws a NetworkError when appropriate + */ +module.exports = function fetchWithNetworkError (...options) { + return fetch(...options) + .catch((err) => { + if (err.name === 'AbortError') { + throw err + } else { + throw new NetworkError(err) + } + }) +} diff --git a/website/src/docs/uppy.md b/website/src/docs/uppy.md index c105a79e1..89a176b75 100644 --- a/website/src/docs/uppy.md +++ b/website/src/docs/uppy.md @@ -753,6 +753,18 @@ uppy.on('upload-error', (file, error, response) => { }) ``` +If the error is related to network conditions — endpoint unreachable due to firewall or ISP blockage, for instance — the error will have `error.isNetworkError` property set to `true`. Here’s how you can check for network errors: + +``` javascript +uppy.on('upload-error', (file, error, response) => { + if (error.isNetworkError) { + // Let your users know that file upload could have failed + // due to firewall or ISP issues + alertUserAboutPossibleFirewallOrISPIssues(error) + } +}) +``` + ### `upload-retry` Fired when an upload has been retried (after an error, for example):