refactor(GUI): make use of etcher-latest-version (#544)

The code that performs an HTTP request to the S3 bucket where released
are stored and determines which is the latest available version was
extracted to a separate module called `etcher-latest-version`, mainly
for the website to be able to re-use this functionality.

See: https://github.com/resin-io-modules/etcher-latest-version
Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
This commit is contained in:
Juan Cruz Viotti 2016-06-30 13:17:03 -04:00 committed by GitHub
parent f277724749
commit c434746a49
6 changed files with 74 additions and 221 deletions

View File

@ -1,70 +0,0 @@
/*
* Copyright 2016 Resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
const _ = require('lodash');
const semver = require('semver');
const xml = require('xml2js');
module.exports = function($q, $http, UPDATE_NOTIFIER_URL) {
/**
* @summary Get the latest published Etcher version
* @function
* @public
*
* @description
* This function performs its job by querying the publicily accessible
* S3 bucket where we store the builds and uses the `node-semver` module
* to determine which is the latest one.
*
* @fulfil {String} - latest version
* @returns {Promise}
*
* @example
* UpdateNotifierS3Service.getLatestVersion().then((latestVersion) => {
* console.log('The latest version is: ' + latestVersion);
* });
*/
this.getLatestVersion = () => {
return $http.get(UPDATE_NOTIFIER_URL).then((response) => {
return $q((resolve, reject) => {
xml.parseString(response.data, (error, result) => {
if (error) {
return reject(error);
}
const bucketEntries = result.ListBucketResult.Contents;
return resolve(_.reduce(bucketEntries, (latest, entry) => {
const version = _.chain(entry.Key)
.first()
.split('/')
.nth(1)
.value();
return semver.gt(version, latest) ? version : latest;
// This is a good accumulator default value since
// every version is semantically greater than this.
}, '0.0.0'));
});
});
});
};
};

View File

@ -17,8 +17,40 @@
'use strict'; 'use strict';
const semver = require('semver'); const semver = require('semver');
const etcherLatestVersion = require('etcher-latest-version');
module.exports = function($uibModal, UPDATE_NOTIFIER_SLEEP_TIME, ManifestBindService, UpdateNotifierS3Service, SettingsModel) { module.exports = function($uibModal, $http, $q, UPDATE_NOTIFIER_SLEEP_TIME, ManifestBindService, SettingsModel) {
/**
* @summary Get the latest available Etcher version
* @function
* @private
*
* @fulfil {String} - latest version
* @returns {Promise}
*
* @example
* UpdateNotifierService.getLatestVersion().then((latestVersion) => {
* console.log(`The latest version is: ${latestVersion}`);
* });
*/
this.getLatestVersion = () => {
return $q((resolve, reject) => {
return etcherLatestVersion((url, callback) => {
return $http.get(url).then((response) => {
return callback(null, response.data);
}).catch((error) => {
return callback(error);
});
}, (error, latestVersion) => {
if (error) {
return reject(error);
}
return resolve(latestVersion);
});
});
};
/** /**
* @summary Check if the current version is the latest version * @summary Check if the current version is the latest version
@ -36,7 +68,7 @@ module.exports = function($uibModal, UPDATE_NOTIFIER_SLEEP_TIME, ManifestBindSer
* }); * });
*/ */
this.isLatestVersion = () => { this.isLatestVersion = () => {
return UpdateNotifierS3Service.getLatestVersion().then((version) => { return this.getLatestVersion().then((version) => {
return semver.gte(ManifestBindService.get('version'), version); return semver.gte(ManifestBindService.get('version'), version);
}); });
}; };

View File

@ -29,10 +29,8 @@ const UpdateNotifier = angular.module(MODULE_NAME, [
require('../../os/open-external/open-external') require('../../os/open-external/open-external')
]); ]);
UpdateNotifier.constant('UPDATE_NOTIFIER_URL', 'https://resin-production-downloads.s3.amazonaws.com');
UpdateNotifier.constant('UPDATE_NOTIFIER_SLEEP_TIME', 7 * 24 * 60 * 60 * 100); UpdateNotifier.constant('UPDATE_NOTIFIER_SLEEP_TIME', 7 * 24 * 60 * 60 * 100);
UpdateNotifier.controller('UpdateNotifierController', require('./controllers/update-notifier')); UpdateNotifier.controller('UpdateNotifierController', require('./controllers/update-notifier'));
UpdateNotifier.service('UpdateNotifierService', require('./services/update-notifier')); UpdateNotifier.service('UpdateNotifierService', require('./services/update-notifier'));
UpdateNotifier.service('UpdateNotifierS3Service', require('./services/update-notifier-s3'));
module.exports = MODULE_NAME; module.exports = MODULE_NAME;

12
npm-shrinkwrap.json generated
View File

@ -1551,6 +1551,18 @@
"from": "etcher-image-write@>=5.0.2 <6.0.0", "from": "etcher-image-write@>=5.0.2 <6.0.0",
"resolved": "https://registry.npmjs.org/etcher-image-write/-/etcher-image-write-5.0.2.tgz" "resolved": "https://registry.npmjs.org/etcher-image-write/-/etcher-image-write-5.0.2.tgz"
}, },
"etcher-latest-version": {
"version": "1.0.0",
"from": "etcher-latest-version@latest",
"resolved": "https://registry.npmjs.org/etcher-latest-version/-/etcher-latest-version-1.0.0.tgz",
"dependencies": {
"semver": {
"version": "5.2.0",
"from": "semver@>=5.2.0 <6.0.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.2.0.tgz"
}
}
},
"event-emitter": { "event-emitter": {
"version": "0.3.4", "version": "0.3.4",
"from": "event-emitter@>=0.3.4 <0.4.0", "from": "event-emitter@>=0.3.4 <0.4.0",

View File

@ -69,6 +69,7 @@
"electron-is-running-in-asar": "^1.0.0", "electron-is-running-in-asar": "^1.0.0",
"etcher-image-stream": "^2.2.0", "etcher-image-stream": "^2.2.0",
"etcher-image-write": "^5.0.2", "etcher-image-write": "^5.0.2",
"etcher-latest-version": "^1.0.0",
"file-tail": "^0.3.0", "file-tail": "^0.3.0",
"flexboxgrid": "^6.3.0", "flexboxgrid": "^6.3.0",
"immutable": "^3.8.1", "immutable": "^3.8.1",
@ -87,7 +88,6 @@
"trackjs": "^2.1.16", "trackjs": "^2.1.16",
"umount": "^1.1.3", "umount": "^1.1.3",
"username": "^2.1.0", "username": "^2.1.0",
"xml2js": "^0.4.16",
"yargs": "^4.6.0" "yargs": "^4.6.0"
}, },
"devDependencies": { "devDependencies": {

View File

@ -101,16 +101,6 @@ describe('Browser: UpdateNotifier', function() {
let UpdateNotifierService; let UpdateNotifierService;
let ManifestBindService; let ManifestBindService;
beforeEach(function() {
angular.mock.module(function($provide) {
$provide.value('UpdateNotifierS3Service', {
getLatestVersion: function() {
return $q.resolve(ManifestBindService.get('version'));
}
});
});
});
beforeEach(angular.mock.inject(function(_$q_, _$rootScope_, _UpdateNotifierService_, _ManifestBindService_) { beforeEach(angular.mock.inject(function(_$q_, _$rootScope_, _UpdateNotifierService_, _ManifestBindService_) {
$q = _$q_; $q = _$q_;
$rootScope = _$rootScope_; $rootScope = _$rootScope_;
@ -118,6 +108,15 @@ describe('Browser: UpdateNotifier', function() {
ManifestBindService = _ManifestBindService_; ManifestBindService = _ManifestBindService_;
})); }));
beforeEach(function() {
this.getLatestVersionStub = m.sinon.stub(UpdateNotifierService, 'getLatestVersion');
this.getLatestVersionStub.returns($q.resolve(ManifestBindService.get('version')));
});
afterEach(function() {
this.getLatestVersionStub.restore();
});
it('should resolve true', function() { it('should resolve true', function() {
let result = null; let result = null;
@ -137,22 +136,21 @@ describe('Browser: UpdateNotifier', function() {
let $rootScope; let $rootScope;
let UpdateNotifierService; let UpdateNotifierService;
beforeEach(function() {
angular.mock.module(function($provide) {
$provide.value('UpdateNotifierS3Service', {
getLatestVersion: function() {
return $q.resolve('99999.9.9');
}
});
});
});
beforeEach(angular.mock.inject(function(_$q_, _$rootScope_, _UpdateNotifierService_) { beforeEach(angular.mock.inject(function(_$q_, _$rootScope_, _UpdateNotifierService_) {
$q = _$q_; $q = _$q_;
$rootScope = _$rootScope_; $rootScope = _$rootScope_;
UpdateNotifierService = _UpdateNotifierService_; UpdateNotifierService = _UpdateNotifierService_;
})); }));
beforeEach(function() {
this.getLatestVersionStub = m.sinon.stub(UpdateNotifierService, 'getLatestVersion');
this.getLatestVersionStub.returns($q.resolve('99999.9.9'));
});
afterEach(function() {
this.getLatestVersionStub.restore();
});
it('should resolve false', function() { it('should resolve false', function() {
let result = null; let result = null;
@ -172,22 +170,21 @@ describe('Browser: UpdateNotifier', function() {
let $rootScope; let $rootScope;
let UpdateNotifierService; let UpdateNotifierService;
beforeEach(function() {
angular.mock.module(function($provide) {
$provide.value('UpdateNotifierS3Service', {
getLatestVersion: function() {
return $q.resolve('0.0.0');
}
});
});
});
beforeEach(angular.mock.inject(function(_$q_, _$rootScope_, _UpdateNotifierService_) { beforeEach(angular.mock.inject(function(_$q_, _$rootScope_, _UpdateNotifierService_) {
$q = _$q_; $q = _$q_;
$rootScope = _$rootScope_; $rootScope = _$rootScope_;
UpdateNotifierService = _UpdateNotifierService_; UpdateNotifierService = _UpdateNotifierService_;
})); }));
beforeEach(function() {
this.getLatestVersionStub = m.sinon.stub(UpdateNotifierService, 'getLatestVersion');
this.getLatestVersionStub.returns($q.resolve('0.0.0'));
});
afterEach(function() {
this.getLatestVersionStub.restore();
});
it('should resolve true', function() { it('should resolve true', function() {
let result = null; let result = null;
@ -205,120 +202,4 @@ describe('Browser: UpdateNotifier', function() {
}); });
describe('UpdateNotifierS3Service', function() {
let UpdateNotifierS3Service;
let $rootScope;
beforeEach(angular.mock.inject(function(_$rootScope_, _UpdateNotifierS3Service_) {
$rootScope = _$rootScope_;
UpdateNotifierS3Service = _UpdateNotifierS3Service_;
}));
describe('given a mocked S3 XML response', function() {
let $httpBackend;
let UPDATE_NOTIFIER_URL;
beforeEach(angular.mock.inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
UPDATE_NOTIFIER_URL = $injector.get('UPDATE_NOTIFIER_URL');
$httpBackend.whenGET(UPDATE_NOTIFIER_URL).respond(`
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Name>resin-production-downloads</Name>
<Prefix/>
<Marker/>
<MaxKeys>1000</MaxKeys>
<IsTruncated>false</IsTruncated>
<Contents>
<Key>etcher/1.0.0-beta.0/Etcher-darwin-x64.dmg</Key>
<LastModified>2016-03-10T17:34:21.000Z</LastModified>
<ETag>"5a715255aa25686688bf1e23bc1d3fc6"</ETag>
<Size>46109720</Size>
<StorageClass>STANDARD</StorageClass>
</Contents>
<Contents>
<Key>etcher/1.0.0-beta.1/Etcher-darwin-x64.dmg</Key>
<LastModified>2016-04-08T20:12:03.000Z</LastModified>
<ETag>"cc1d6d9d53385e3edd099416fcd894c1"</ETag>
<Size>47071474</Size>
<StorageClass>STANDARD</StorageClass>
</Contents>
<Contents>
<Key>etcher/1.0.0-beta.2/Etcher-darwin-x64.dmg</Key>
<LastModified>2016-04-08T19:03:18.000Z</LastModified>
<ETag>"5f1849f7781197ce2ee6129c16bcd498"</ETag>
<Size>48650090</Size>
<StorageClass>STANDARD</StorageClass>
</Contents>
<Contents>
<Key>etcher/1.0.0-beta.3/Etcher-darwin-x64.dmg</Key>
<LastModified>2016-04-18T01:32:09.000Z</LastModified>
<ETag>"c173895886f44d115c66e7206ce3dff8"</ETag>
<Size>50585335</Size>
<StorageClass>STANDARD</StorageClass>
</Contents>
<Contents>
<Key>etcher/1.0.0-beta.3/Etcher-darwin-x64.zip</Key>
<LastModified>2016-04-18T01:42:37.000Z</LastModified>
<ETag>"e9f6e957e65373b232530215d98df141"</ETag>
<Size>129327442</Size>
<StorageClass>STANDARD</StorageClass>
</Contents>
<Contents>
<Key>etcher/1.0.0-beta.4/Etcher-darwin-x64.dmg</Key>
<LastModified>2016-04-22T17:29:49.000Z</LastModified>
<ETag>"bccb0024c58747a9b7516cbdfc5a7ecb"</ETag>
<Size>55240852</Size>
<StorageClass>STANDARD</StorageClass>
</Contents>
<Contents>
<Key>etcher/1.0.0-beta.4/Etcher-darwin-x64.zip</Key>
<LastModified>2016-04-22T17:43:27.000Z</LastModified>
<ETag>"c93e26e68b3c4f2b7e8e88e6befc8e64"</ETag>
<Size>135443284</Size>
<StorageClass>STANDARD</StorageClass>
</Contents>
<Contents>
<Key>etcher/1.0.0-beta.5/Etcher-darwin-x64.dmg</Key>
<LastModified>2016-05-04T08:27:11.000Z</LastModified>
<ETag>"fb596bfdb8bbaf09807b5fc4a940ce14"</ETag>
<Size>77757305</Size>
<StorageClass>STANDARD</StorageClass>
</Contents>
<Contents>
<Key>etcher/1.0.0-beta.5/Etcher-darwin-x64.zip</Key>
<LastModified>2016-05-04T08:39:56.000Z</LastModified>
<ETag>"3f11c1b6f06644f9ceb2aea4b1947fdf"</ETag>
<Size>157933876</Size>
<StorageClass>STANDARD</StorageClass>
</Contents>
</ListBucketResult>
`);
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should resolve the latest version', function() {
$httpBackend.expectGET(UPDATE_NOTIFIER_URL);
let latestVersion = null;
UpdateNotifierS3Service.getLatestVersion().then(function(result) {
latestVersion = result;
});
$rootScope.$apply();
$httpBackend.flush();
m.chai.expect(latestVersion).to.equal('1.0.0-beta.5');
});
});
});
}); });