Skip to content
This repository has been archived by the owner on Apr 15, 2019. It is now read-only.

Setup auto update - Closes #196 #765

Merged
merged 19 commits into from
Nov 20, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions app/src/main.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import electron from 'electron'; // eslint-disable-line import/no-extraneous-dependencies
import electronLocalshortcut from 'electron-localshortcut'; // eslint-disable-line import/no-extraneous-dependencies
import { autoUpdater } from 'electron-updater'; // eslint-disable-line import/no-extraneous-dependencies
import path from 'path';
import storage from 'electron-json-storage'; // eslint-disable-line import/no-extraneous-dependencies
import win from './modules/win';
import localeHandler from './modules/localeHandler';
import updateChecker from './modules/autoUpdater';

const checkForUpdates = updateChecker({ autoUpdater, dialog: electron.dialog, win, process });

const { app, ipcMain } = electron;

app.on('ready', () => { win.create({ electron, path, electronLocalshortcut, storage }); });
app.on('ready', () => {
win.create({ electron, path, electronLocalshortcut, storage, checkForUpdates });
});

app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
Expand All @@ -23,7 +29,7 @@ if (process.platform === 'darwin') {

app.on('activate', () => {
if (win.browser === null) {
win.create({ electron, path, electronLocalshortcut, storage });
win.create({ electron, path, electronLocalshortcut, storage, checkForUpdates });
}
});

Expand Down Expand Up @@ -66,7 +72,7 @@ ipcMain.on('proxyCredentialsEntered', (event, username, password) => {
ipcMain.on('set-locale', (event, locale) => {
const langCode = locale.substr(0, 2);
if (langCode) {
localeHandler.update({ langCode, electron, storage, event });
localeHandler.update({ langCode, electron, storage, event, checkForUpdates });
}
});

Expand Down
12 changes: 11 additions & 1 deletion app/src/menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,19 @@ const addAboutMenuForNonMac = ({ template, electron }) => {
});
};

const addCheckForUpdates = ({ template, checkForUpdates }) => {
template[template.length - 1].submenu.push({
label: i18n.t('Check for updates...'),
click: checkForUpdates,
});
};

const menu = {
build: (electron) => {
build: (electron, checkForUpdates) => {
const template = menu.buildTemplate(electron);
if (process.platform !== 'linux') {
addCheckForUpdates({ template, checkForUpdates });
}
if (process.platform === 'darwin') {
addAboutMenuForMac({ template, name: electron.app.getName() });
} else {
Expand Down
91 changes: 91 additions & 0 deletions app/src/modules/autoUpdater.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import i18n from './../i18n';

export default ({ autoUpdater, dialog, win, process }) => {
const updater = {
menuItem: { enabled: true },
};
autoUpdater.autoDownload = false;

if (process.platform !== 'linux') {
autoUpdater.checkForUpdates();
setInterval(() => {
autoUpdater.checkForUpdates();
}, 24 * 60 * 60 * 1000);
}

autoUpdater.on('error', (error) => {
// eslint-disable-next-line no-console
console.error('There was a problem updating the application');
// eslint-disable-next-line no-console
console.error(error);
// eslint-disable-next-line max-len
if (updater.error !== error) {
updater.error = error;
dialog.showErrorBox('Error: ', error == null ? 'unknown' : error.toString());
}
});

autoUpdater.on('download-progress', (progressObj) => {
let logMessage = `Download speed: ${progressObj.bytesPerSecond}`;
logMessage = `${logMessage} - Downloaded ${progressObj.percent}%`;
logMessage = `${logMessage} (${progressObj.transferred}/${progressObj.total})`;
// eslint-disable-next-line no-console
console.log(logMessage);
win.browser.setProgressBar(progressObj.transferred / progressObj.total);
});

autoUpdater.on('update-available', ({ version }) => {
updater.error = undefined;
dialog.showMessageBox({
type: 'info',
title: i18n.t('New version available'),
message: i18n.t('There is a new version ({{version}}) available, do you want to update now?', { version }),
buttons: [i18n.t('Update now'), i18n.t('Later')],
}, (buttonIndex) => {
if (buttonIndex === 0) {
autoUpdater.downloadUpdate();
setTimeout(() => {
if (!updater.error) {
dialog.showMessageBox({
title: i18n.t('Dowload started'),
message: i18n.t('The download was started. Depending on your internet speed it can take up to several minutes. You will be informed then it is finished and prompted to restart the app.'),
});
}
}, 500);
} else {
updater.menuItem.enabled = true;
}
});
});

autoUpdater.on('update-not-available', () => {
if (!updater.menuItem.enabled) {
dialog.showMessageBox({
title: i18n.t('No Updates'),
message: i18n.t('Current version is up-to-date.'),
});
}
updater.menuItem.enabled = true;
});

autoUpdater.on('update-downloaded', () => {
dialog.showMessageBox({
title: i18n.t('Update download finished'),
buttons: [i18n.t('Restart now'), i18n.t('Later')],
message: i18n.t('Updates downloaded, application has to be restarted to apply the updates.'),
}, (buttonIndex) => {
if (buttonIndex === 0) {
autoUpdater.quitAndInstall();
}
});
});

// export this to MenuItem click callback
function checkForUpdates(menuItem) {
autoUpdater.checkForUpdates();
updater.menuItem = menuItem;
updater.menuItem.enabled = false;
}

return checkForUpdates;
};
136 changes: 136 additions & 0 deletions app/src/modules/autoUpdater.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { expect } from 'chai'; // eslint-disable-line import/no-extraneous-dependencies
import sinon, { spy } from 'sinon'; // eslint-disable-line import/no-extraneous-dependencies
import autoUpdater from './autoUpdater';

describe.only('autoUpdater', () => {
const version = '1.2.3';
let params;
let callbacks;
let clock;

beforeEach(() => {
callbacks = {};
params = {
process: {
platform: 'darwin',
},
autoUpdater: {
checkForUpdates: spy(),
on: (name, callback) => {
callbacks[name] = callback;
},
quitAndInstall: spy(),
downloadUpdate: spy(),
},
dialog: {
showMessageBox: (options, callback) => {
callbacks.dialog = callback;
},
showErrorBox: spy(),
},
win: {
browser: {
setProgressBar: spy(),
},
},
};

clock = sinon.useFakeTimers({
toFake: ['setTimeout', 'clearTimeout', 'Date', 'setInterval'],
});
});

afterEach(() => {
clock.restore();
});

it('should call params.autoUpdater.checkForUpdates', () => {
autoUpdater(params);
expect(params.autoUpdater.checkForUpdates).to.have.been.calledWithExactly();
});

it('should call params.autoUpdater.checkForUpdates every 24 hours', () => {
autoUpdater(params);
expect(params.autoUpdater.checkForUpdates).to.have.callCount(1);
clock.tick(24 * 60 * 60 * 1000);
expect(params.autoUpdater.checkForUpdates).to.have.callCount(2);
clock.tick(24 * 60 * 60 * 1000);
expect(params.autoUpdater.checkForUpdates).to.have.callCount(3);
});

it('should call params.autoUpdater.on with "update-downloaded" and "error"', () => {
autoUpdater(params);
expect(callbacks['update-downloaded']).to.be.a('function');
expect(callbacks.error).to.be.a('function');
});

it('should call params.dialog.showMessageBox on params.autoUpdater.on("update-downloaded", ...) ', () => {
const dialogSpy = spy(params.dialog, 'showMessageBox');

autoUpdater(params);
callbacks['update-downloaded']({ version });

expect(dialogSpy).to.have.been.calledWith();
});

it('should call params.dialog.showMessageBox on params.autoUpdater.on("update-not-available", ...) if checkForUpdates was called', () => {
const dialogSpy = spy(params.dialog, 'showMessageBox');

const checkForUpdates = autoUpdater(params);
checkForUpdates({});
callbacks['update-not-available']({ version });

expect(dialogSpy).to.have.been.calledWith();
});

it('should params.autoUpdater.quitAndInstall() on "update-downloaded" in params.dialog.showMessageBox callback if the first button was pressed', () => {
autoUpdater(params);
callbacks['update-downloaded']({ version });
callbacks.dialog(0);

expect(params.autoUpdater.quitAndInstall).to.have.been.calledWithExactly();
});

it('should not params.autoUpdater.quitAndInstall() on "update-downloaded" in params.dialog.showMessageBox callback if the second button was pressed', () => {
autoUpdater(params);
callbacks['update-downloaded']({ version });
callbacks.dialog(1);

expect(params.autoUpdater.quitAndInstall).to.not.have.been.calledWith();
});

it('should params.autoUpdater.downloadUpdate() on "update-available" in params.dialog.showMessageBox callback if the first button was pressed', () => {
autoUpdater(params);
callbacks['update-available']({ version });
callbacks.dialog(0);

expect(params.autoUpdater.downloadUpdate).to.have.been.calledWithExactly();
});

it('should not params.autoUpdater.downloadUpdate() on "update-available" in params.dialog.showMessageBox callback if the second button was pressed', () => {
autoUpdater(params);
callbacks['update-available']({ version });
callbacks.dialog(1);

expect(params.autoUpdater.downloadUpdate).to.not.have.been.calledWith();
});

it('should call win.browser.setProgressBar() on "download-progress"', () => {
autoUpdater(params);
callbacks['download-progress']({ transferred: 50, total: 100 });
expect(params.win.browser.setProgressBar).to.have.been.calledWith(50 / 100);
});

it('should console.error any error from params.autoUpdater.on("error", ...) ', () => {
const error = new Error('Error: Can not find Squirrel');
const consoleSpy = spy(console, 'error');

autoUpdater(params);
callbacks.error(error);

expect(consoleSpy).to.have.been.calledWith('There was a problem updating the application');
expect(consoleSpy).to.have.been.calledWith(error);
consoleSpy.restore();
});
});

4 changes: 2 additions & 2 deletions app/src/modules/localeHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import menu from './../menu';
import win from './win';

const handler = {
update: ({ electron, event, langCode, storage }) => {
update: ({ electron, event, langCode, storage, checkForUpdates }) => {
// change locale
i18n.changeLanguage(langCode);
// write selected lang on JSON file
Expand All @@ -13,7 +13,7 @@ const handler = {

// rebuild menu
const { Menu } = electron;
Menu.setApplicationMenu(menu.build(electron));
Menu.setApplicationMenu(menu.build(electron, checkForUpdates));
event.returnValue = 'Rebuilt electron menu.';
},

Expand Down
4 changes: 2 additions & 2 deletions app/src/modules/win.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const win = {
},


create: ({ electron, path, electronLocalshortcut, storage }) => {
create: ({ electron, path, electronLocalshortcut, storage, checkForUpdates }) => {
const { Menu } = electron;

win.init({ electron, path, electronLocalshortcut });
Expand All @@ -42,7 +42,7 @@ const win = {
win.send({ event: 'openUrl', value: process.argv[1] || '/' });
}

Menu.setApplicationMenu(menu.build(electron));
Menu.setApplicationMenu(menu.build(electron, checkForUpdates));

const selectionMenu = Menu.buildFromTemplate([
{ role: 'copy' },
Expand Down
12 changes: 12 additions & 0 deletions i18n/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,22 @@
"Block height": "Block height",
"Blockchain Application Registration": "Blockchain Application Registration",
"Cancel": "Cancel",
"Check for updates...": "Check for updates...",
"Click to send all funds": "Click to send all funds",
"Close": "Close",
"Confirm": "Confirm",
"Connection re-established": "Connection re-established",
"Copy": "Copy",
"Copy address to clipboard": "Copy address to clipboard",
"Current version is up-to-date.": "Current version is up-to-date.",
"Custom Node": "Custom Node",
"Cut": "Cut",
"Decrypt message": "Decrypt message",
"Delegate": "Delegate",
"Delegate Registration": "Delegate Registration",
"Delegate name": "Delegate name",
"Delegate registration was successfully submitted with username: \"{{username}}\". It can take several seconds before it is processed.": "Delegate registration was successfully submitted with username: \"{{username}}\". It can take several seconds before it is processed.",
"Dowload started": "Dowload started",
"Downvotes:": "Downvotes:",
"Edit": "Edit",
"Encrypt message": "Encrypt message",
Expand All @@ -58,6 +61,7 @@
"Last 24 hours": "Last 24 hours",
"Last {{count}} days": "Last {{count}} day",
"Last {{count}} days_plural": "Last {{count}} days",
"Later": "Later",
"Lisk Address": "Lisk Address",
"Lisk Chat": "Lisk Chat",
"Lisk Explorer": "Lisk Explorer",
Expand All @@ -77,7 +81,9 @@
"Name": "Name",
"Network": "Network",
"New Account": "New Account",
"New version available": "New version available",
"Next": "Next",
"No Updates": "No Updates",
"No delegates found": "No delegates found",
"No matches": "No matches",
"Node address": "Node address",
Expand Down Expand Up @@ -119,6 +125,7 @@
"Repeat the transaction": "Repeat the transaction",
"Report Issue...": "Report Issue...",
"Required": "Required",
"Restart now": "Restart now",
"Result": "Result",
"Result copied to clipboard": "Result copied to clipboard",
"Reward": "Reward",
Expand Down Expand Up @@ -148,7 +155,9 @@
"Switch": "Switch",
"Testnet": "Testnet",
"The URL was invalid": "The URL was invalid",
"The download was started. Depending on your internet speed it can take up to several minutes. You will be informed then it is finished and prompted to restart the app.": "The download was started. Depending on your internet speed it can take up to several minutes. You will be informed then it is finished and prompted to restart the app.",
"There are no transactions, yet.": "There are no transactions, yet.",
"There is a new version ({{version}}) available, do you want to update now?": "There is a new version ({{version}}) available, do you want to update now?",
"This account is protected by a second passphrase": "This account is protected by a second passphrase",
"This passphrase is not recoverable and if you lose it, you will lose access to your account forever.": "This passphrase is not recoverable and if you lose it, you will lose access to your account forever.",
"This will save public key of your account on this device, so next time it will launch without the need to log in. However, you will be prompted to enter the passphrase once you want to do any transaction.": "This will save public key of your account on this device, so next time it will launch without the need to log in. However, you will be prompted to enter the passphrase once you want to do any transaction.",
Expand All @@ -164,6 +173,9 @@
"URL is invalid": "URL is invalid",
"Unable to connect to the node": "Unable to connect to the node",
"Undo": "Undo",
"Update download finished": "Update download finished",
"Update now": "Update now",
"Updates downloaded, application has to be restarted to apply the updates.": "Updates downloaded, application has to be restarted to apply the updates.",
"Uptime": "Uptime",
"Upvotes:": "Upvotes:",
"Username": "Username",
Expand Down
Loading