diff --git a/.eslintrc b/.eslintrc index db24f86ff..604b87260 100644 --- a/.eslintrc +++ b/.eslintrc @@ -5,7 +5,9 @@ ], "globals": { "angular": true, - "app": true + "app": true, + "ipc": true, + "PRODUCTION": true }, "env": { "browser": true, diff --git a/app/ipc.js b/app/ipc.js new file mode 100644 index 000000000..b31218954 --- /dev/null +++ b/app/ipc.js @@ -0,0 +1,5 @@ +/** + * Add ipcRenderer to the window object + */ +const ipcRenderer = window.require('electron').ipcRenderer; +window.ipc = ipcRenderer; diff --git a/app/main.js b/app/main.js index e3c5ad6f2..b1d4ef112 100644 --- a/app/main.js +++ b/app/main.js @@ -1,4 +1,5 @@ const electron = require('electron'); // eslint-disable-line import/no-extraneous-dependencies +const path = require('path'); const { app } = electron; const { BrowserWindow } = electron; @@ -13,7 +14,15 @@ function createWindow() { width: width > 2000 ? Math.floor(width * 0.5) : width - 250, height: height > 1000 ? Math.floor(height * 0.7) : height - 150, center: true, + webPreferences: { + // Avoid app throttling when Electron is in background + backgroundThrottling: false, + // Specifies a script that will be loaded before other scripts run in the page. + preload: path.resolve(__dirname, 'ipc.js'), + }, }); + win.on('blur', () => win.webContents.send('blur')); + win.on('focus', () => win.webContents.send('focus')); const template = [ { diff --git a/package.json b/package.json index 47b3721e2..7e899eb97 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "bugs": "https://github.com/LiskHQ/lisk-nano/issues", "main": "main.js", "scripts": { - "build": "webpack --profile --progress --display-modules --display-exclude --display-chunks --display-cached --display-cached-assets", + "build": "export NODE_ENV=prod && webpack --profile --progress --display-modules --display-exclude --display-chunks --display-cached --display-cached-assets", "dev": "webpack-dev-server --host 0.0.0.0 --profile --progress", "e2e-test": "protractor protractor.conf.js", "test": "grunt eslint && export NODE_ENV=test && karma start", diff --git a/src/components/main/main.js b/src/components/main/main.js index 5b80a2efc..38fa73a01 100644 --- a/src/components/main/main.js +++ b/src/components/main/main.js @@ -15,7 +15,7 @@ app.component('main', { */ controller: class main { constructor($scope, $rootScope, $timeout, $q, $state, Peers, - dialog, SendModal, Account, AccountApi) { + dialog, SendModal, Account, AccountApi, Notification) { this.$scope = $scope; this.$rootScope = $rootScope; this.$timeout = $timeout; @@ -26,6 +26,7 @@ app.component('main', { this.$state = $state; this.account = Account; this.accountApi = AccountApi; + this.notify = Notification.init(); this.activeTab = this.init(); } @@ -108,6 +109,11 @@ app.component('main', { delete res.publicKey; } + if (res.balance > this.account.get().balance) { + const amount = res.balance - this.account.get().balance; + this.notify.about('deposit', amount); + } + this.account.set(res); }) .catch((res) => { diff --git a/src/liskNano.js b/src/liskNano.js index 933f5f752..e8bd184b0 100644 --- a/src/liskNano.js +++ b/src/liskNano.js @@ -33,6 +33,7 @@ import './services/dialog'; import './services/lsk'; import './services/signVerify'; import './services/sync'; +import './services/notification'; import './filters/lsk'; diff --git a/src/services/notification.js b/src/services/notification.js new file mode 100644 index 000000000..0cd9e00ca --- /dev/null +++ b/src/services/notification.js @@ -0,0 +1,67 @@ +/** + * @description This factory provides methods to call Notification + * + * @module app + * @submodule Notify + */ +app.factory('Notification', ($window, lsk) => { + /** + * The Notify factory constructor class + * @class Notify + * @constructor + */ + class Notification { + constructor() { + this.isFocused = true; + } + + /** + * Initialize event listeners + * + * @returns {this} + * @method init + * @memberof Notify + */ + init() { + if (PRODUCTION) { + const { ipc } = $window; + ipc.on('blur', () => this.isFocused = false); + ipc.on('focus', () => this.isFocused = true); + } + return this; + } + + /** + * Routing to specific Notification creator based on type param + * @param {string} type + * @param {any} data + * + * @method about + * @public + * @memberof Notify + */ + about(type, data) { + if (this.isFocused) return; + switch (type) { + case 'deposit': + this._deposit(data); + break; + default: break; + } + } + + /** + * Creating notification about deposit + * + * @param {number} amount + * @private + * @memberof Notify + */ + _deposit(amount) { // eslint-disable-line + const body = `You've received ${lsk.normalize(amount)} LSK.`; + new $window.Notification('LSK received', { body }); // eslint-disable-line + } + } + + return new Notification(); +}); diff --git a/src/services/sync.js b/src/services/sync.js index 0797c5ae2..72cf5f07e 100644 --- a/src/services/sync.js +++ b/src/services/sync.js @@ -1,6 +1,11 @@ +const intervals = { + activeApp: 10000, + inactiveApp: 60000, +}; + app.factory('Sync', ($rootScope, $window) => { const config = { - updateInterval: 10000, + updateInterval: intervals.activeApp, freeze: false, }; let lastTick = new Date(); @@ -33,6 +38,19 @@ app.factory('Sync', ($rootScope, $window) => { $window.requestAnimationFrame(step); } }; + + const toggleSyncTimer = (inFocus) => { + config.updateInterval = (inFocus) ? + intervals.activeApp : + intervals.inactiveApp; + }; + + const initIntervalToggler = () => { + const { ipc } = $window; + ipc.on('blur', () => toggleSyncTimer(false)); + ipc.on('focus', () => toggleSyncTimer(true)); + }; + /** * Starts the first frame by calling requestAnimationFrame. * This will be @@ -41,6 +59,9 @@ app.factory('Sync', ($rootScope, $window) => { if (!running) { $window.requestAnimationFrame(step); } + if (PRODUCTION) { + initIntervalToggler(); + } }; /** @@ -50,7 +71,6 @@ app.factory('Sync', ($rootScope, $window) => { config.freeze = false; }; - init(); return { init, config, end, }; diff --git a/test/services/notification.spec.js b/test/services/notification.spec.js new file mode 100644 index 000000000..ef3ea624d --- /dev/null +++ b/test/services/notification.spec.js @@ -0,0 +1,50 @@ +const chai = require('chai'); +const sinon = require('sinon'); +const sinonChai = require('sinon-chai'); + +const expect = chai.expect; +chai.use(sinonChai); + +describe('Factory: Notification', () => { + let lsk; + let $window; + let notify; + + beforeEach(angular.mock.module('app')); + + beforeEach(inject((_Notification_, _lsk_, _$window_) => { + lsk = _lsk_; + $window = _$window_; + notify = _Notification_.init(); + })); + + describe('about(data)', () => { + const amount = 100000000; + const mockNotification = sinon.spy(); + + it('should call this._deposit', () => { + const spy = sinon.spy(notify, '_deposit'); + notify.isFocused = false; + notify.about('deposit', amount); + expect(spy).to.have.been.calledWith(amount); + }); + + it('should call $window.Notification', () => { + $window.Notification = mockNotification; + const msg = `You've received ${lsk.normalize(amount)} LSK.`; + + notify.isFocused = false; + notify.about('deposit', amount); + expect(mockNotification).to.have.been.calledWith( + 'LSK received', { body: msg }, + ); + mockNotification.reset(); + }); + + it('should not call $window.Notification if app is focused', () => { + notify.about('deposit', amount); + expect(mockNotification).to.have.been.not.calledWith(); + mockNotification.reset(); + }); + }); +}); diff --git a/test/test.js b/test/test.js index d098badbe..bf9434f7f 100644 --- a/test/test.js +++ b/test/test.js @@ -25,6 +25,7 @@ require('./services/api/peers.spec'); require('./services/lsk.spec'); require('./services/passphrase.spec'); require('./services/signVerify.spec'); +require('./services/notification.spec'); require('./run.spec'); diff --git a/webpack.config.babel.js b/webpack.config.babel.js index 4e6cc2240..a036a326e 100644 --- a/webpack.config.babel.js +++ b/webpack.config.babel.js @@ -169,6 +169,14 @@ const provide = () => ({ ], }); +const define = () => ({ + plugins: [ + new webpack.DefinePlugin({ + PRODUCTION: JSON.stringify(nodeEnvironment === 'prod'), + }), + ], +}); + const bundleAnalyzer = () => ({ plugins: [ new BundleAnalyzerPlugin({ @@ -182,10 +190,10 @@ let config; switch (process.env.npm_lifecycle_event) { case 'build': - config = merge(common, clean(path.join(PATHS.build, 'dist')), html(), provide(), eslint(), babel(), pug(), less(), css(), json(), png(), fonts(), bundleAnalyzer()); + config = merge(common, clean(path.join(PATHS.build, 'dist')), html(), provide(), define(), eslint(), babel(), pug(), less(), css(), json(), png(), fonts(), bundleAnalyzer()); break; default: - config = merge(common, devServer(), { devtool: 'eval-source-map' }, html(), provide(), eslint(), babel(), pug(), less(), css(), json(), png(), fonts()); + config = merge(common, devServer(), { devtool: 'eval-source-map' }, html(), provide(), define(), eslint(), babel(), pug(), less(), css(), json(), png(), fonts()); break; }