Skip to content
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,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": [
Expand Down
46 changes: 26 additions & 20 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -18,9 +19,9 @@ if (fs.existsSync(IDMapPath)) {
}
}

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) {
Expand All @@ -40,24 +41,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;
Expand Down
23 changes: 23 additions & 0 deletions src/needUpdate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { parseString } from 'xml2js';
import { fetchData } from './utils';

export default (chromeStoreID, currentVersion) => new Promise(resolve =>
fetchData(
`https://clients2.google.com/service/update2/crx?x=id%3D${chromeStoreID}%26uc&prodversion=32`,
).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].$;
if (status !== 'ok') return resolve(false);

if (newestVersion !== currentVersion) return resolve(true);
return resolve(false);
});
}),
);
28 changes: 21 additions & 7 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,38 @@ 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),
);

export const changePermissions = (dir, mode) => {
const files = fs.readdirSync(dir);
files.forEach((file) => {
Expand Down
2 changes: 1 addition & 1 deletion test/install_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
19 changes: 19 additions & 0 deletions test/update_spec.js
Original file line number Diff line number Diff line change
@@ -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),
);
});
});