diff --git a/lib/gui/app.js b/lib/gui/app.js index 1c6b8a9d..87da1568 100644 --- a/lib/gui/app.js +++ b/lib/gui/app.js @@ -34,6 +34,7 @@ const messages = require('../shared/messages') const s3Packages = require('../shared/s3-packages') const release = require('../shared/release') const store = require('../shared/store') +const errors = require('../shared/errors') const packageJSON = require('../../package.json') const flashState = require('../shared/models/flash-state') const settings = require('./models/settings') @@ -153,6 +154,17 @@ app.run(() => { return updateNotifier.notify(latestVersion, { allowSleepUpdateCheck: currentReleaseType === release.RELEASE_TYPE.PRODUCTION }) + + // If the error is an update user error, then we don't want + // to bother users each time they open the app. + // See: https://github.com/resin-io/etcher/issues/1525 + }).catch((error) => { + return errors.isUserError(error) && error.code === 'UPDATE_USER_ERROR' + }, (error) => { + analytics.logEvent('Update check user error', { + title: errors.getTitle(error), + description: errors.getDescription(error) + }) }) }).catch(exceptionReporter.report) }) diff --git a/lib/shared/errors.js b/lib/shared/errors.js index c9992f0d..7cd61580 100644 --- a/lib/shared/errors.js +++ b/lib/shared/errors.js @@ -257,6 +257,10 @@ exports.createError = (options) => { error.report = false } + if (!_.isNil(options.code)) { + error.code = options.code + } + return error } @@ -288,7 +292,8 @@ exports.createUserError = (options) => { return exports.createError({ title: options.title, description: options.description, - report: false + report: false, + code: options.code }) } diff --git a/lib/shared/s3-packages.js b/lib/shared/s3-packages.js index 3cc3ef5b..54b1d602 100644 --- a/lib/shared/s3-packages.js +++ b/lib/shared/s3-packages.js @@ -22,6 +22,7 @@ const Bluebird = require('bluebird') const request = Bluebird.promisifyAll(require('request')) const xml = Bluebird.promisifyAll(require('xml2js')) const release = require('./release') +const errors = require('./errors') /** * @summary Etcher S3 bucket URLs @@ -158,13 +159,22 @@ exports.getRemoteVersions = _.memoize((bucketUrl) => { code: 'ETIMEDOUT' }, { code: 'EHOSTDOWN' + }, { + + // May happen when behind a proxy + code: 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY' + }, { // May happen when behind a proxy code: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE' - }, () => { - return [] + }, (error) => { + throw errors.createUserError({ + title: 'Unable to check for updates', + description: `We got an ${error.code} error in response`, + code: 'UPDATE_USER_ERROR' + }) }) }) diff --git a/tests/shared/errors.spec.js b/tests/shared/errors.spec.js index 81b69566..36afd5dd 100644 --- a/tests/shared/errors.spec.js +++ b/tests/shared/errors.spec.js @@ -445,6 +445,16 @@ describe('Shared: Errors', function () { m.chai.expect(errors.getDescription(error)).to.equal('Something happened') }) + it('should correctly add a code', function () { + const error = errors.createError({ + title: 'Foo', + description: 'Something happened', + code: 'HELLO' + }) + + m.chai.expect(error.code).to.equal('HELLO') + }) + it('should correctly add only a title', function () { const error = errors.createError({ title: 'Foo' @@ -541,6 +551,15 @@ describe('Shared: Errors', function () { m.chai.expect(errors.getDescription(error)).to.equal(error.stack) }) + it('should correctly add a code', function () { + const error = errors.createUserError({ + title: 'Foo', + code: 'HELLO' + }) + + m.chai.expect(error.code).to.equal('HELLO') + }) + it('should ignore an empty description', function () { const error = errors.createUserError({ title: 'Foo', diff --git a/tests/shared/s3-packages.spec.js b/tests/shared/s3-packages.spec.js index 59e719ab..5bfc18ea 100644 --- a/tests/shared/s3-packages.spec.js +++ b/tests/shared/s3-packages.spec.js @@ -22,6 +22,7 @@ const request = Bluebird.promisifyAll(require('request')) const nock = require('nock') const s3Packages = require('../../lib/shared/s3-packages') const release = require('../../lib/shared/release') +const errors = require('../../lib/shared/errors') describe('Shared: s3Packages', function () { describe('.getBucketUrlFromReleaseType()', function () { @@ -573,9 +574,10 @@ describe('Shared: s3Packages', function () { nock.cleanAll() }) - it('should be rejected with an error', function (done) { + it('should be rejected with a non-user error', function (done) { s3Packages.getRemoteVersions(s3Packages.BUCKET_URL.PRODUCTION).catch((error) => { m.chai.expect(error).to.be.an.instanceof(Error) + m.chai.expect(errors.isUserError(error)).to.be.false done() }) }) @@ -594,11 +596,12 @@ describe('Shared: s3Packages', function () { this.requestGetAsyncStub.restore() }) - it('should resolve an empty array', function (done) { - s3Packages.getRemoteVersions(s3Packages.BUCKET_URL.PRODUCTION).then((versions) => { - m.chai.expect(versions).to.deep.equal([]) + it('should be rejected with a user error with code UPDATE_USER_ERROR', function (done) { + s3Packages.getRemoteVersions(s3Packages.BUCKET_URL.PRODUCTION).catch((error) => { + m.chai.expect(errors.isUserError(error)).to.be.true + m.chai.expect(error.code).to.equal('UPDATE_USER_ERROR') done() - }).catch(done) + }) }) }) @@ -615,11 +618,12 @@ describe('Shared: s3Packages', function () { this.requestGetAsyncStub.restore() }) - it('should resolve an empty array', function (done) { - s3Packages.getRemoteVersions(s3Packages.BUCKET_URL.PRODUCTION).then((versions) => { - m.chai.expect(versions).to.deep.equal([]) + it('should be rejected with a user error with code UPDATE_USER_ERROR', function (done) { + s3Packages.getRemoteVersions(s3Packages.BUCKET_URL.PRODUCTION).catch((error) => { + m.chai.expect(errors.isUserError(error)).to.be.true + m.chai.expect(error.code).to.equal('UPDATE_USER_ERROR') done() - }).catch(done) + }) }) }) @@ -636,11 +640,12 @@ describe('Shared: s3Packages', function () { this.requestGetAsyncStub.restore() }) - it('should resolve an empty array', function (done) { - s3Packages.getRemoteVersions(s3Packages.BUCKET_URL.PRODUCTION).then((versions) => { - m.chai.expect(versions).to.deep.equal([]) + it('should be rejected with a user error with code UPDATE_USER_ERROR', function (done) { + s3Packages.getRemoteVersions(s3Packages.BUCKET_URL.PRODUCTION).catch((error) => { + m.chai.expect(errors.isUserError(error)).to.be.true + m.chai.expect(error.code).to.equal('UPDATE_USER_ERROR') done() - }).catch(done) + }) }) }) @@ -657,11 +662,12 @@ describe('Shared: s3Packages', function () { this.requestGetAsyncStub.restore() }) - it('should resolve an empty array', function (done) { - s3Packages.getRemoteVersions(s3Packages.BUCKET_URL.PRODUCTION).then((versions) => { - m.chai.expect(versions).to.deep.equal([]) + it('should be rejected with a user error with code UPDATE_USER_ERROR', function (done) { + s3Packages.getRemoteVersions(s3Packages.BUCKET_URL.PRODUCTION).catch((error) => { + m.chai.expect(errors.isUserError(error)).to.be.true + m.chai.expect(error.code).to.equal('UPDATE_USER_ERROR') done() - }).catch(done) + }) }) }) @@ -678,11 +684,12 @@ describe('Shared: s3Packages', function () { this.requestGetAsyncStub.restore() }) - it('should resolve an empty array', function (done) { - s3Packages.getRemoteVersions(s3Packages.BUCKET_URL.PRODUCTION).then((versions) => { - m.chai.expect(versions).to.deep.equal([]) + it('should be rejected with a user error with code UPDATE_USER_ERROR', function (done) { + s3Packages.getRemoteVersions(s3Packages.BUCKET_URL.PRODUCTION).catch((error) => { + m.chai.expect(errors.isUserError(error)).to.be.true + m.chai.expect(error.code).to.equal('UPDATE_USER_ERROR') done() - }).catch(done) + }) }) }) @@ -699,11 +706,34 @@ describe('Shared: s3Packages', function () { this.requestGetAsyncStub.restore() }) - it('should resolve an empty array', function (done) { - s3Packages.getRemoteVersions(s3Packages.BUCKET_URL.PRODUCTION).then((versions) => { - m.chai.expect(versions).to.deep.equal([]) + it('should be rejected with a user error with code UPDATE_USER_ERROR', function (done) { + s3Packages.getRemoteVersions(s3Packages.BUCKET_URL.PRODUCTION).catch((error) => { + m.chai.expect(errors.isUserError(error)).to.be.true + m.chai.expect(error.code).to.equal('UPDATE_USER_ERROR') done() - }).catch(done) + }) + }) + }) + + describe('given UNABLE_TO_GET_ISSUER_CERT_LOCALLY', function () { + beforeEach(function () { + const error = new Error('UNABLE_TO_GET_ISSUER_CERT_LOCALLY') + error.code = 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY' + + this.requestGetAsyncStub = m.sinon.stub(request, 'getAsync') + this.requestGetAsyncStub.returns(Bluebird.reject(error)) + }) + + afterEach(function () { + this.requestGetAsyncStub.restore() + }) + + it('should be rejected with a user error with code UPDATE_USER_ERROR', function (done) { + s3Packages.getRemoteVersions(s3Packages.BUCKET_URL.PRODUCTION).catch((error) => { + m.chai.expect(errors.isUserError(error)).to.be.true + m.chai.expect(error.code).to.equal('UPDATE_USER_ERROR') + done() + }) }) }) @@ -720,11 +750,12 @@ describe('Shared: s3Packages', function () { this.requestGetAsyncStub.restore() }) - it('should resolve an empty array', function (done) { - s3Packages.getRemoteVersions(s3Packages.BUCKET_URL.PRODUCTION).then((versions) => { - m.chai.expect(versions).to.deep.equal([]) + it('should be rejected with a user error with code UPDATE_USER_ERROR', function (done) { + s3Packages.getRemoteVersions(s3Packages.BUCKET_URL.PRODUCTION).catch((error) => { + m.chai.expect(errors.isUserError(error)).to.be.true + m.chai.expect(error.code).to.equal('UPDATE_USER_ERROR') done() - }).catch(done) + }) }) }) })