From 8a9a73255a71b4fcabc178617e23bf5de0deb0c8 Mon Sep 17 00:00:00 2001 From: Jhen Date: Sun, 30 Apr 2017 00:03:16 +0800 Subject: [PATCH 1/5] Check update instead of force download --- package.json | 3 ++- src/index.js | 46 ++++++++++++++++++++++++++-------------------- src/needUpdate.js | 23 +++++++++++++++++++++++ src/utils.js | 28 +++++++++++++++++++++------- 4 files changed, 72 insertions(+), 28 deletions(-) create mode 100644 src/needUpdate.js diff --git a/package.json b/package.json index f1f8b96..9c8152a 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,8 @@ "7zip": "0.0.6", "cross-unzip": "0.0.2", "rimraf": "^2.5.2", - "semver": "^5.3.0" + "semver": "^5.3.0", + "xml2js": "^0.4.17" }, "babel": { "presets": [ diff --git a/src/index.js b/src/index.js index 0ad2fb6..5566959 100644 --- a/src/index.js +++ b/src/index.js @@ -4,6 +4,7 @@ import path from 'path'; import semver from 'semver'; import downloadChromeExtension from './downloadChromeExtension'; +import needUpdate from './needUpdate'; import { getPath } from './utils'; const { BrowserWindow } = remote || electron; @@ -14,9 +15,9 @@ if (fs.existsSync(IDMapPath)) { IDMap = JSON.parse(fs.readFileSync(IDMapPath, 'utf8')); } -const install = (extensionReference, forceDownload = false) => { +const install = (extensionReference, checkUpdate = false) => { if (Array.isArray(extensionReference)) { - return Promise.all(extensionReference.map(extension => install(extension, forceDownload))); + return Promise.all(extensionReference.map(extension => install(extension, checkUpdate))); } let chromeStoreID; if (typeof extensionReference === 'object' && extensionReference.id) { @@ -35,24 +36,29 @@ const install = (extensionReference, forceDownload = false) => { const extensionInstalled = extensionName && BrowserWindow.getDevToolsExtensions && BrowserWindow.getDevToolsExtensions()[extensionName]; - if (!forceDownload && extensionInstalled) { - return Promise.resolve(IDMap[chromeStoreID]); - } - return downloadChromeExtension(chromeStoreID, forceDownload) - .then((extensionFolder) => { - // Use forceDownload, but already installed - if (extensionInstalled) { - BrowserWindow.removeDevToolsExtension(extensionName); - } - const name = BrowserWindow.addDevToolsExtension(extensionFolder); // eslint-disable-line - fs.writeFileSync( - IDMapPath, - JSON.stringify(Object.assign(IDMap, { - [chromeStoreID]: name, - })), - ); - return Promise.resolve(name); - }); + const promise = checkUpdate && extensionInstalled ? + needUpdate(chromeStoreID, extensionInstalled.version) : + Promise.resolve(false); + return promise.then((toUpdate) => { + if (!toUpdate && extensionInstalled) { + return Promise.resolve(IDMap[chromeStoreID]); + } + return downloadChromeExtension(chromeStoreID, toUpdate) + .then((extensionFolder) => { + // Use forceDownload, but already installed + if (extensionInstalled) { + BrowserWindow.removeDevToolsExtension(extensionName); + } + const name = BrowserWindow.addDevToolsExtension(extensionFolder); // eslint-disable-line + fs.writeFileSync( + IDMapPath, + JSON.stringify(Object.assign(IDMap, { + [chromeStoreID]: name, + })), + ); + return Promise.resolve(name); + }); + }); }; export default install; diff --git a/src/needUpdate.js b/src/needUpdate.js new file mode 100644 index 0000000..70e8bfa --- /dev/null +++ b/src/needUpdate.js @@ -0,0 +1,23 @@ +import { parseString } from 'xml2js'; +import { fetchData } from './utils'; + +export default (chromeStoreID, currentVersion) => new Promise((resolve, reject) => + fetchData( + `https://clients2.google.com/service/update2/crx?x=id%3D${chromeStoreID}%26uc&prodversion=32`, + ).then((res) => { + if (res.statusCode === 200) { + parseString(res.body, (err, result) => { + const app = result.gupdate.app[0].$; + if (app.status !== 'ok') return resolve(false); + + const { status, version: newestVersion } = result.gupdate.app[0].updatecheck[0].$; + if (status !== 'ok') return resolve(false); + + if (newestVersion !== currentVersion) return resolve(true); + return resolve(false); + }); + } else { + reject(`Failed to check current version of ${chromeStoreID}.`); + } + }), +); diff --git a/src/utils.js b/src/utils.js index 11bf229..d0c6e2d 100644 --- a/src/utils.js +++ b/src/utils.js @@ -9,20 +9,34 @@ export const getPath = () => { }; // Use https.get fallback for Electron < 1.4.5 -const { net } = (remote || electron); +const { net } = remote || electron; const request = net ? net.request : https.get; -export const downloadFile = (from, to) => new Promise((resolve, reject) => { - const req = request(from); +const sendRequest = url => new Promise((resolve, reject) => { + const req = request(url); req.on('response', (res) => { // Shouldn't handle redirect with `electron.net`, this is for https.get fallback if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { - return downloadFile(res.headers.location, to) - .then(resolve) - .catch(reject); + return sendRequest(res.headers.location); } - res.pipe(fs.createWriteStream(to)).on('close', resolve); + resolve(res); }); req.on('error', reject); req.end(); }); + +export const downloadFile = (from, to) => new Promise((resolve, reject) => + sendRequest(from) + .then(res => res.pipe(fs.createWriteStream(to)).on('close', resolve)) + .catch(reject), +); + +export const fetchData = url => new Promise((resolve, reject) => + sendRequest(url) + .then((res) => { + let body = ''; + res.on('data', (chunk) => { body += chunk; }); + res.on('end', () => resolve(Object.assign(res, { body }))); + }) + .catch(reject), +); From 948290a79de1c0b3a58cc1c003bc3318f302719b Mon Sep 17 00:00:00 2001 From: Jhen Date: Sun, 30 Apr 2017 00:03:27 +0800 Subject: [PATCH 2/5] Add tests for checkUpdate option --- test/install_spec.js | 2 +- test/update_spec.js | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 test/update_spec.js diff --git a/test/install_spec.js b/test/install_spec.js index efaaeb9..001d7e5 100644 --- a/test/install_spec.js +++ b/test/install_spec.js @@ -28,7 +28,7 @@ describe('Extension Installer', () => { .catch(() => done('Failed to resolve')); }); - it('should upgraded the extension with forceDownload', (done) => { + it('should upgraded the extension with checkUpdate', (done) => { const extensionName = 'React Developer Tools'; const oldVersion = '0.14.0'; BrowserWindow.removeDevToolsExtension(extensionName); diff --git a/test/update_spec.js b/test/update_spec.js new file mode 100644 index 0000000..beaf07a --- /dev/null +++ b/test/update_spec.js @@ -0,0 +1,19 @@ +// Pre-run +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { given } from 'mocha-testdata'; + +// Actual Test Imports +import needUpdate from '../src/needUpdate'; +import knownExtensions from './testdata/knownExtensions'; + +chai.use(chaiAsPromised); +chai.should(); + +describe('Extension Update Checker', () => { + describe('when given a valid extension ID', () => { + given(...knownExtensions).it('should need to update (with different version)', item => + needUpdate(item.id, '0.0.0').should.become(true), + ); + }); +}); From a2f7cb3bb93537e5176e26715226fce5b7319c47 Mon Sep 17 00:00:00 2001 From: Jhen Date: Sat, 27 May 2017 00:59:21 +0800 Subject: [PATCH 3/5] fetchData: reassign to new object for result --- src/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.js b/src/utils.js index d0c6e2d..7ba7560 100644 --- a/src/utils.js +++ b/src/utils.js @@ -36,7 +36,7 @@ export const fetchData = url => new Promise((resolve, reject) => .then((res) => { let body = ''; res.on('data', (chunk) => { body += chunk; }); - res.on('end', () => resolve(Object.assign(res, { body }))); + res.on('end', () => resolve(Object.assign({}, res, { body }))); }) .catch(reject), ); From 6354a4016cb2653f056658504aa9260dc88b0036 Mon Sep 17 00:00:00 2001 From: Jhen Date: Sat, 27 May 2017 01:01:57 +0800 Subject: [PATCH 4/5] needUpdate: Remove reject statement --- src/needUpdate.js | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/needUpdate.js b/src/needUpdate.js index 70e8bfa..f341b99 100644 --- a/src/needUpdate.js +++ b/src/needUpdate.js @@ -1,23 +1,19 @@ import { parseString } from 'xml2js'; import { fetchData } from './utils'; -export default (chromeStoreID, currentVersion) => new Promise((resolve, reject) => +export default (chromeStoreID, currentVersion) => new Promise(resolve => fetchData( `https://clients2.google.com/service/update2/crx?x=id%3D${chromeStoreID}%26uc&prodversion=32`, ).then((res) => { - if (res.statusCode === 200) { - parseString(res.body, (err, result) => { - const app = result.gupdate.app[0].$; - if (app.status !== 'ok') return resolve(false); + parseString(res.body, (err, result) => { + const app = result.gupdate.app[0].$; + if (app.status !== 'ok') return resolve(false); - const { status, version: newestVersion } = result.gupdate.app[0].updatecheck[0].$; - if (status !== 'ok') return resolve(false); + const { status, version: newestVersion } = result.gupdate.app[0].updatecheck[0].$; + if (status !== 'ok') return resolve(false); - if (newestVersion !== currentVersion) return resolve(true); - return resolve(false); - }); - } else { - reject(`Failed to check current version of ${chromeStoreID}.`); - } + if (newestVersion !== currentVersion) return resolve(true); + return resolve(false); + }); }), ); From 64e5ea30beebbcc59f7a92eb34c4456119dcf172 Mon Sep 17 00:00:00 2001 From: Jhen Date: Sat, 27 May 2017 01:15:57 +0800 Subject: [PATCH 5/5] needUpdate: log message if status is error-invalidAppId --- src/needUpdate.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/needUpdate.js b/src/needUpdate.js index f341b99..55b601c 100644 --- a/src/needUpdate.js +++ b/src/needUpdate.js @@ -7,6 +7,10 @@ export default (chromeStoreID, currentVersion) => new Promise(resolve => ).then((res) => { parseString(res.body, (err, result) => { const app = result.gupdate.app[0].$; + if (app.status === 'error-invalidAppId') { + console.log('Check update with invalid chrome extension id', chromeStoreID); // eslint-disable-line + return resolve(false); + } if (app.status !== 'ok') return resolve(false); const { status, version: newestVersion } = result.gupdate.app[0].updatecheck[0].$;