From 0bd292e590fd296e132e338fb4f9404125680a36 Mon Sep 17 00:00:00 2001 From: Gina Contrino Date: Thu, 19 Oct 2017 17:35:56 +0200 Subject: [PATCH 01/13] WIP --- src/constants/actions.js | 4 +- src/store/middlewares/account.js | 17 ++- src/store/middlewares/account.test.js | 65 ++++++------ src/store/middlewares/index.js | 4 +- src/store/middlewares/metronome.js | 20 ---- src/store/middlewares/metronome.test.js | 45 -------- src/store/middlewares/socket.js | 15 +++ src/store/middlewares/socket.test.js | 33 ++++++ src/utils/metronome.js | 91 ---------------- src/utils/metronome.test.js | 135 ------------------------ src/utils/socket.js | 28 +++++ src/utils/socket.test.js | 96 +++++++++++++++++ src/utils/socketShim.js | 3 + 13 files changed, 226 insertions(+), 330 deletions(-) delete mode 100644 src/store/middlewares/metronome.js delete mode 100644 src/store/middlewares/metronome.test.js create mode 100644 src/store/middlewares/socket.js create mode 100644 src/store/middlewares/socket.test.js delete mode 100644 src/utils/metronome.js delete mode 100644 src/utils/metronome.test.js create mode 100644 src/utils/socket.js create mode 100644 src/utils/socket.test.js create mode 100644 src/utils/socketShim.js diff --git a/src/constants/actions.js b/src/constants/actions.js index a7446e622..fddb06315 100644 --- a/src/constants/actions.js +++ b/src/constants/actions.js @@ -1,5 +1,7 @@ const actionTypes = { - metronomeBeat: 'METRONOME_BEAT', + socketDisconnected: 'SOCKET_DISCONNECTED', + socketReconnected: 'SOCKET_RECONNECTED', + newBlockCreated: 'NEW_BLOCK_CREATED', accountUpdated: 'ACCOUNT_UPDATED', accountLoggedOut: 'ACCOUNT_LOGGED_OUT', accountLoggedIn: 'ACCOUNT_LOGGED_IN', diff --git a/src/store/middlewares/account.js b/src/store/middlewares/account.js index e6329c672..c61d5051a 100644 --- a/src/store/middlewares/account.js +++ b/src/store/middlewares/account.js @@ -95,11 +95,24 @@ const passphraseUsed = (store, action) => { } }; +const shouldUpdateAccount = (accountAddress, tx) => { + const transaction = tx[tx.length - 1]; + const sender = transaction ? transaction.senderId : null; + const receiver = transaction ? transaction.receiverId : null; + return accountAddress === receiver || accountAddress === sender; +}; + +const checkTransactionAndUpdateAccount = (store, action) => { + if (shouldUpdateAccount(store.getState().account.address, action.data.block.transactions)) { + updateAccountData(store, action); + } +}; + const accountMiddleware = store => next => (action) => { next(action); switch (action.type) { - case actionTypes.metronomeBeat: - updateAccountData(store, action); + case actionTypes.newBlockCreated: + checkTransactionAndUpdateAccount(store, action); break; case actionTypes.transactionsUpdated: delegateRegistration(store, action); diff --git a/src/store/middlewares/account.test.js b/src/store/middlewares/account.test.js index 5e3b51a94..493df40ee 100644 --- a/src/store/middlewares/account.test.js +++ b/src/store/middlewares/account.test.js @@ -5,6 +5,7 @@ import { SYNC_ACTIVE_INTERVAL, SYNC_INACTIVE_INTERVAL } from '../../constants/ap import { accountUpdated } from '../../actions/account'; import { activePeerUpdate } from '../../actions/peers'; import * as votingActions from '../../actions/voting'; +import * as forgingActions from '../../actions/forging'; import * as accountApi from '../../utils/api/account'; import actionTypes from '../../constants/actions'; import * as delegateApi from '../../utils/api/delegate'; @@ -19,6 +20,8 @@ describe('Account middleware', () => { let stubTransactions; const passphrase = 'right cat soul renew under climb middle maid powder churn cram coconut'; + const transactions = { transactions: [{ senderId: 'sample_address', receiverId: 'some_address' }] }; + const transactionsUpdatedAction = { type: actionTypes.transactionsUpdated, data: { @@ -29,17 +32,19 @@ describe('Account middleware', () => { }, }; - const activeBeatAction = { - type: actionTypes.metronomeBeat, + const newBlockCreated = { + type: actionTypes.newBlockCreated, data: { interval: SYNC_ACTIVE_INTERVAL, + block: transactions, }, }; - const inactiveBeatAction = { - type: actionTypes.metronomeBeat, + const inactiveNewBlockCreated = { + type: actionTypes.newBlockCreated, data: { interval: SYNC_INACTIVE_INTERVAL, + block: transactions, }, }; @@ -74,85 +79,77 @@ describe('Account middleware', () => { }); it('should pass the action to next middleware', () => { - const expectedAction = { - type: 'TEST_ACTION', - }; - - middleware(store)(next)(expectedAction); - expect(next).to.have.been.calledWith(expectedAction); + middleware(store)(next)(newBlockCreated); + expect(next).to.have.been.calledWith(newBlockCreated); }); - it(`should call account API methods on ${actionTypes.metronomeBeat} action when online`, () => { + it(`should call account API methods on ${actionTypes.newBlockCreated} action when online`, () => { + // does this matter? stubGetAccount.resolves({ balance: 0 }); - middleware(store)(next)(activeBeatAction); + middleware(store)(next)(newBlockCreated); expect(stubGetAccount).to.have.been.calledWith(); expect(store.dispatch).to.have.been.calledWith(activePeerUpdate({ online: true })); }); - it(`should call account API methods on ${actionTypes.metronomeBeat} action when offline`, () => { + it(`should call account API methods on ${actionTypes.newBlockCreated} action when offline`, () => { const errorCode = 'EUNAVAILABLE'; stubGetAccount.rejects({ error: { code: errorCode } }); - middleware(store)(next)(activeBeatAction); + middleware(store)(next)(newBlockCreated); expect(store.dispatch).to.have.been.calledWith(activePeerUpdate( { online: false, code: errorCode })); }); - it(`should call transactions API methods on ${actionTypes.metronomeBeat} action if account.balance changes`, () => { + it(`should call transactions API methods on ${actionTypes.newBlockCreated} action if account.balance changes`, () => { stubGetAccount.resolves({ balance: 10e8 }); - middleware(store)(next)(activeBeatAction); + middleware(store)(next)(newBlockCreated); expect(stubGetAccount).to.have.been.calledWith(); - // TODO why next expect doesn't work despite it being called according to test coverage? - // expect(stubTransactions).to.have.been.calledWith(); + expect(stubTransactions).to.have.been.calledWith(); }); - it(`should call transactions API methods on ${actionTypes.metronomeBeat} action if account.balance changes and action.data.interval is SYNC_INACTIVE_INTERVAL`, () => { + it(`should call transactions API methods on ${actionTypes.newBlockCreated} action if account.balance changes and action.data.interval is SYNC_INACTIVE_INTERVAL`, () => { stubGetAccount.resolves({ balance: 10e8 }); - middleware(store)(next)(inactiveBeatAction); + middleware(store)(next)(inactiveNewBlockCreated); expect(stubGetAccount).to.have.been.calledWith(); - // TODO why next expect doesn't work despite it being called according to test coverage? - // expect(stubTransactions).to.have.been.calledWith(); + expect(stubTransactions).to.have.been.calledWith(); }); - it(`should call transactions API methods on ${actionTypes.metronomeBeat} action if action.data.interval is SYNC_ACTIVE_INTERVAL and there are recent transactions`, () => { + it(`should call transactions API methods on ${actionTypes.newBlockCreated} action if action.data.interval is SYNC_ACTIVE_INTERVAL and there are recent transactions`, () => { stubGetAccount.resolves({ balance: 0 }); - middleware(store)(next)(activeBeatAction); + middleware(store)(next)(newBlockCreated); expect(stubGetAccount).to.have.been.calledWith(); - // TODO why next expect doesn't work despite it being called according to test coverage? - // expect(stubTransactions).to.have.been.calledWith(); + expect(stubTransactions).to.have.been.calledWith(); }); - it(`should fetch delegate info on ${actionTypes.metronomeBeat} action if account.balance changes and account.isDelegate`, () => { + it(`should fetch delegate info on ${actionTypes.newBlockCreated} action if account.balance changes and account.isDelegate`, () => { const delegateApiMock = stub(delegateApi, 'getDelegate').returnsPromise().resolves({ success: true, delegate: {} }); stubGetAccount.resolves({ balance: 10e8 }); state.account.isDelegate = true; store.getState = () => (state); - middleware(store)(next)(activeBeatAction); + middleware(store)(next)(newBlockCreated); expect(store.dispatch).to.have.been.calledWith(); delegateApiMock.restore(); }); - it(`should call fetchAndUpdateForgedBlocks(...) on ${actionTypes.metronomeBeat} action if account.balance changes and account.isDelegate`, () => { + it(`should call fetchAndUpdateForgedBlocks(...) on ${actionTypes.newBlockCreated} action if account.balance changes and account.isDelegate`, () => { state.account.isDelegate = true; store.getState = () => (state); stubGetAccount.resolves({ balance: 10e8 }); - // const fetchAndUpdateForgedBlocksSpy = spy(forgingActions, 'fetchAndUpdateForgedBlocks'); - - middleware(store)(next)({ type: actionTypes.metronomeBeat }); + const fetchAndUpdateForgedBlocksSpy = spy(forgingActions, 'fetchAndUpdateForgedBlocks'); - // TODO why next expect doesn't work despite it being called according to test coverage? - // expect(fetchAndUpdateForgedBlocksSpy).to.have.been.calledWith(); + middleware(store)(next)(newBlockCreated); + expect(fetchAndUpdateForgedBlocksSpy).to.have.been.calledWith(); }); it(`should fetch delegate info on ${actionTypes.transactionsUpdated} action if action.data.confirmed contains delegateRegistration transactions`, () => { diff --git a/src/store/middlewares/index.js b/src/store/middlewares/index.js index a732e0146..a32173b74 100644 --- a/src/store/middlewares/index.js +++ b/src/store/middlewares/index.js @@ -1,5 +1,4 @@ import thunk from 'redux-thunk'; -import metronomeMiddleware from './metronome'; import accountMiddleware from './account'; import loginMiddleware from './login'; import transactionsMiddleware from './transactions'; @@ -8,12 +7,13 @@ import offlineMiddleware from './offline'; import notificationMiddleware from './notification'; import votingMiddleware from './voting'; import savedAccountsMiddleware from './savedAccounts'; +import socketMiddleware from './socket'; export default [ thunk, transactionsMiddleware, loginMiddleware, - metronomeMiddleware, + socketMiddleware, accountMiddleware, loadingBarMiddleware, offlineMiddleware, diff --git a/src/store/middlewares/metronome.js b/src/store/middlewares/metronome.js deleted file mode 100644 index f292ae6f8..000000000 --- a/src/store/middlewares/metronome.js +++ /dev/null @@ -1,20 +0,0 @@ -import MetronomeService from '../../utils/metronome'; -import actionTypes from '../../constants/actions'; - -const metronomeMiddleware = (store) => { - const metronome = new MetronomeService(store.dispatch); - return next => (action) => { - switch (action.type) { - case actionTypes.accountLoggedIn: - metronome.init(); - break; - case actionTypes.accountLoggedOut: - metronome.terminate(); - break; - default: break; - } - next(action); - }; -}; - -export default metronomeMiddleware; diff --git a/src/store/middlewares/metronome.test.js b/src/store/middlewares/metronome.test.js deleted file mode 100644 index 0d32a0116..000000000 --- a/src/store/middlewares/metronome.test.js +++ /dev/null @@ -1,45 +0,0 @@ -import { expect } from 'chai'; -import { spy, stub } from 'sinon'; -import middleware from './metronome'; -import actionTypes from '../../constants/actions'; -import * as MetronomeService from '../../utils/metronome'; - -describe('Metronome middleware', () => { - let store; - let next; - - beforeEach(() => { - next = spy(); - store = stub(); - store.dispatch = spy(); - }); - - it(`should call Metronome constructor on ${actionTypes.accountLoggedIn} action`, () => { - const metronome = spy(MetronomeService, 'default'); - middleware(store); - expect(metronome.calledWithNew()).to.equal(true); - expect(metronome).to.have.been.calledWith(store.dispatch); - metronome.restore(); - }); - - it('should call Metronome init method', () => { - const spyFn = spy(MetronomeService.default.prototype, 'init'); - middleware(store)(next)({ type: actionTypes.accountLoggedIn }); - expect(spyFn).to.have.been.calledWith(); - }); - - it(`should call metronome.terminate on ${actionTypes.accountLoggedOut} action`, () => { - const spyFn = spy(MetronomeService.default.prototype, 'terminate'); - middleware(store)(next)({ type: actionTypes.accountLoggedOut }); - expect(spyFn).to.have.been.calledWith(); - }); - - it('should passes the action to next middleware', () => { - const expectedAction = { - type: 'TEST_ACTION', - }; - middleware(store)(next)(expectedAction); - expect(next).to.have.been.calledWith(expectedAction); - }); -}); - diff --git a/src/store/middlewares/socket.js b/src/store/middlewares/socket.js new file mode 100644 index 000000000..9d725383e --- /dev/null +++ b/src/store/middlewares/socket.js @@ -0,0 +1,15 @@ +import actionTypes from '../../constants/actions'; +import { socketSetup } from '../../utils/socket'; + +const socketMiddleware = store => ( + next => (action) => { + switch (action.type) { + case actionTypes.accountLoggedIn: + socketSetup(store, action); + break; + default: break; + } + next(action); + }); + +export default socketMiddleware; diff --git a/src/store/middlewares/socket.test.js b/src/store/middlewares/socket.test.js new file mode 100644 index 000000000..dbf9e3962 --- /dev/null +++ b/src/store/middlewares/socket.test.js @@ -0,0 +1,33 @@ +import { expect } from 'chai'; +import { spy, stub } from 'sinon'; +import middleware from './socket'; +import * as socket from '../../utils/socket'; +import actionTypes from '../../constants/actions'; + +describe('Socket middleware', () => { + let store; + let next; + let socketSetup; + + beforeEach(() => { + next = spy(); + store = stub(); + }); + + it('should call socketSetup when metronome is initialized', () => { + socketSetup = stub(socket, 'socketSetup'); + + middleware(store)(next)({ type: actionTypes.accountLoggedIn }); + expect(socketSetup).to.have.been.calledWith(); + socketSetup.reset(); + }); + + it('should passes the action to next middleware', () => { + const expectedAction = { + type: actionTypes.accountLoggedIn, + }; + middleware(store)(next)(expectedAction); + expect(next).to.have.been.calledWith(); + }); +}); + diff --git a/src/utils/metronome.js b/src/utils/metronome.js deleted file mode 100644 index 2162e114b..000000000 --- a/src/utils/metronome.js +++ /dev/null @@ -1,91 +0,0 @@ -// import { ipcMain as ipc, BrowserWindow } from 'electron'; -import { SYNC_ACTIVE_INTERVAL, SYNC_INACTIVE_INTERVAL } from '../constants/api'; -import env from '../constants/env'; -import actionsType from '../constants/actions'; - -class Metronome { - constructor(dispatchFn) { - this.interval = SYNC_ACTIVE_INTERVAL; - this.factor = 0; - this.running = false; - this.dispatchFn = dispatchFn; - } - - /** - * Broadcast an event from rootScope downwards - * - * @param {Date} lastBeat - * @param {Date} now - * @param {Number} factor - * @param {Number} interval - * @memberOf Metronome - * @private - */ - _dispatch(lastBeat, now, factor, interval) { - this.dispatchFn({ - type: actionsType.metronomeBeat, - data: { lastBeat, now, factor, interval }, - }); - } - - /** - * We're calling this in framerate. - * calls broadcast method every SYNC_(IN)ACTIVE_INTERVAL and - * sends a numeric factor for ease of use as multiples of updateInterval. - * - * @memberOf Metronome - * @private - */ - _step() { - const now = new Date(); - if (!this.lastBeat || (now - this.lastBeat >= this.interval)) { - this._dispatch(this.lastBeat, now, this.factor, this.interval); - this.lastBeat = now; - this.factor += this.factor < 9 ? 1 : -9; - } - if (this.running) { - window.requestAnimationFrame(this._step.bind(this)); - } - } - - /** - * Changes the duration of intervals when sending application - * to tray or activating it again. - * - * @memberOf Metronome - * @private - */ - _initIntervalToggler() { - const { ipc } = window; - if (ipc) { - ipc.on('blur', () => { this.interval = SYNC_INACTIVE_INTERVAL; }); - ipc.on('focus', () => { this.interval = SYNC_ACTIVE_INTERVAL; }); - } - } - - /** - * Terminates the intervals - * - * @memberOf Metronome - */ - terminate() { - this.running = false; - } - - /** - * Starts the first frame by calling requestAnimationFrame. - * - * @memberOf Metronome - */ - init() { - if (!this.running) { - window.requestAnimationFrame(this._step.bind(this)); - } - if (env.production) { - this._initIntervalToggler(); - } - this.running = true; - } -} - -export default Metronome; diff --git a/src/utils/metronome.test.js b/src/utils/metronome.test.js deleted file mode 100644 index f2f1d7658..000000000 --- a/src/utils/metronome.test.js +++ /dev/null @@ -1,135 +0,0 @@ -import { expect } from 'chai'; -import { spy } from 'sinon'; -import Metronome from './metronome'; -import { SYNC_ACTIVE_INTERVAL, SYNC_INACTIVE_INTERVAL } from '../constants/api'; -import env from '../constants/env'; - - -describe('Metronome', () => { - let metronome; - const spyDispatch = spy(); - - beforeEach(() => { - metronome = new Metronome(spyDispatch); - }); - - afterEach(() => { - metronome.terminate(); - }); - - it('defines initial settings', () => { - expect(metronome.interval).to.be.equal(SYNC_ACTIVE_INTERVAL); - expect(metronome.running).to.be.equal(false); - expect(metronome.factor).to.be.equal(0); - expect(metronome.dispatchFn).to.be.equal(spyDispatch); - }); - - describe('init', () => { - it('should call requestAnimationFrame if !this.running', () => { - const reqSpy = spy(window, 'requestAnimationFrame'); - metronome.init(); - expect(reqSpy).to.have.been.calledWith(); - window.requestAnimationFrame.restore(); - }); - - it('should not call requestAnimationFrame if this.running', () => { - const reqSpy = spy(window, 'requestAnimationFrame'); - metronome.running = true; - metronome.init(); - expect(reqSpy).to.not.have.been.calledWith(); - window.requestAnimationFrame.restore(); - }); - - it('should call window.ipc.on(\'blur\') and window.ipc.on(\'focus\')', () => { - window.ipc = { - on: spy(), - }; - env.production = true; - metronome.init(); - expect(window.ipc.on).to.have.been.calledWith('blur'); - expect(window.ipc.on).to.have.been.calledWith('focus'); - }); - - it('should set window.ipc to set this.interval to SYNC_INACTIVE_INTERVAL on blur', () => { - const callbacks = {}; - window.ipc = { - on: (type, callback) => { - callbacks[type] = callback; - }, - }; - env.production = true; - metronome.init(); - callbacks.blur(); - expect(metronome.interval).to.equal(SYNC_INACTIVE_INTERVAL); - }); - - it('should set window.ipc to set this.interval to SYNC_ACTIVE_INTERVAL on focus', () => { - const callbacks = {}; - window.ipc = { - on: (type, callback) => { - callbacks[type] = callback; - }, - }; - env.production = true; - metronome.init(); - callbacks.blur(); - expect(metronome.interval).to.equal(SYNC_INACTIVE_INTERVAL); - callbacks.focus(); - expect(metronome.interval).to.equal(SYNC_ACTIVE_INTERVAL); - }); - }); - - describe('terminate', () => { - it('should reset running flag', () => { - metronome.terminate(); - expect(metronome.running).to.be.equal(false); - }); - }); - - describe('_dispatch', () => { - it('should dispatch a Vanilla JS event', () => { - metronome._dispatch(); - expect(spyDispatch).to.have.been.calledWith(); - }); - }); - - describe('_step', () => { - it('should call requestAnimationFrame every 10 sec', () => { - const reqSpy = spy(window, 'requestAnimationFrame'); - metronome.running = true; - metronome._step(); - expect(reqSpy).to.have.been.calledWith(); - window.requestAnimationFrame.restore(); - }); - - it('should never call requestAnimationFrame if running is false', () => { - const reqSpy = spy(window, 'requestAnimationFrame'); - metronome._step(); - expect(reqSpy).not.have.been.calledWith(); - window.requestAnimationFrame.restore(); - }); - - it('should reset the factor after 10 times', () => { - for (let i = 1; i < 12; i++) { - metronome.lastBeat -= 10001; - metronome._step(); - if (i < 10) { - expect(metronome.factor).to.be.equal(i); - } else { - expect(metronome.factor).to.be.equal(i - 10); - } - } - }); - - it('should call _dispatch if lastBeat is older that 10sec', () => { - const reqSpy = spy(metronome, '_dispatch'); - metronome.running = true; - - const now = new Date(); - metronome.lastBeat = now - 20000; - metronome._step(); - expect(reqSpy).to.have.been.calledWith(); - metronome._dispatch.restore(); - }); - }); -}); diff --git a/src/utils/socket.js b/src/utils/socket.js new file mode 100644 index 000000000..934bfcfde --- /dev/null +++ b/src/utils/socket.js @@ -0,0 +1,28 @@ +import io from './socketShim'; +import { activePeerUpdate } from './../actions/peers'; +import actionTypes from './../constants/actions'; +import { SYNC_ACTIVE_INTERVAL, SYNC_INACTIVE_INTERVAL } from './../constants/api'; + +export const socketSetup = (store) => { // eslint-disable-line + let interval = SYNC_ACTIVE_INTERVAL; + + const { ipc } = window; + if (ipc) { + ipc.on('blur', () => { interval = SYNC_INACTIVE_INTERVAL; }); + ipc.on('focus', () => { interval = SYNC_ACTIVE_INTERVAL; }); + } + const connection = io.connect(`ws://${store.getState().peers.data.options.address}`); + connection.on('blocks/change', (block) => { + store.dispatch({ + type: actionTypes.newBlockCreated, + data: { block, interval }, + }); + }); + connection.on('disconnect', () => { + store.dispatch(activePeerUpdate({ online: false })); + }); + connection.on('reconnect', () => { + store.dispatch(activePeerUpdate({ online: true })); + }); +}; + diff --git a/src/utils/socket.test.js b/src/utils/socket.test.js new file mode 100644 index 000000000..81e1aa791 --- /dev/null +++ b/src/utils/socket.test.js @@ -0,0 +1,96 @@ +import { expect } from 'chai'; +import { spy } from 'sinon'; +import io from './socketShim'; +import actionTypes from './../constants/actions'; +import { SYNC_ACTIVE_INTERVAL, SYNC_INACTIVE_INTERVAL } from './../constants/api'; +import { socketSetup } from './socket'; + +describe('Socket', () => { + let store; + let transactions; + const ipcCallbacks = {}; + const socketCallbacks = {}; + + beforeEach(() => { + io.connect = () => ({ + on: (event, callback) => { + socketCallbacks[event] = callback; + }, + }); + + window.ipc = { + on: (event, callback) => { + ipcCallbacks[event] = callback; + }, + }; + }); + + it(`should dispatch ${actionTypes.newBlockCreated} unless a new block was added`, () => { + transactions = { transactions: [{ senderId: '1234', receiverId: '5678' }] }; + store = { + getState: () => ({ + peers: { data: { options: { address: 'localhost:4000' } } }, + account: { address: '1234' }, + }), + dispatch: spy(), + }; + + socketSetup(store); + + expect(store.dispatch).to.not.have.been.calledWith(); + + ipcCallbacks.focus(); + socketCallbacks['blocks/change'](transactions); + + expect(store.dispatch).to.have.been.calledWith({ + type: actionTypes.newBlockCreated, + data: { data: transactions, interval: SYNC_ACTIVE_INTERVAL }, + }); + }); + + describe('window.ipc', () => { + beforeEach(() => { + transactions = { transactions: [{ senderId: '1234', receiverId: '5678' }] }; + store = { + getState: () => ({ + peers: { data: { options: { address: 'localhost:4000' } } }, + account: { address: '1234' }, + }), + dispatch: spy(), + }; + }); + + it('should call window.ipc.on(\'blur\') and window.ipc.on(\'focus\')', () => { + window.ipc = { + on: spy(), + }; + socketSetup(store); + + expect(window.ipc.on).to.have.been.calledWith('blur'); + expect(window.ipc.on).to.have.been.calledWith('focus'); + }); + + it('should set window.ipc to set the interval to SYNC_INACTIVE_INTERVAL on blur', () => { + socketSetup(store); + ipcCallbacks.blur(); + socketCallbacks['blocks/change'](transactions); + + expect(store.dispatch).to.have.been.calledWith({ + type: actionTypes.newBlockCreated, + data: { data: transactions, interval: SYNC_INACTIVE_INTERVAL }, + }); + }); + + it('should set window.ipc to set the interval to SYNC_ACTIVE_INTERVAL on focus', () => { + socketSetup(store); + ipcCallbacks.focus(); + socketCallbacks['blocks/change'](transactions); + + expect(store.dispatch).to.have.been.calledWith({ + type: actionTypes.newBlockCreated, + data: { data: transactions, interval: SYNC_ACTIVE_INTERVAL }, + }); + }); + }); +}); + diff --git a/src/utils/socketShim.js b/src/utils/socketShim.js new file mode 100644 index 000000000..b3c835b91 --- /dev/null +++ b/src/utils/socketShim.js @@ -0,0 +1,3 @@ +import io from 'socket.io-client'; + +export default io; From 1cfe64778dd0728e7d8ea40d445f78dc9195d30e Mon Sep 17 00:00:00 2001 From: Gina Contrino Date: Tue, 24 Oct 2017 16:15:03 +0200 Subject: [PATCH 02/13] Update tests and fix little bugs --- src/store/middlewares/account.js | 13 ++++++------- src/store/middlewares/socket.js | 5 ++++- src/store/middlewares/socket.test.js | 21 +++++++++++++++------ src/utils/socket.js | 13 ++++++++++--- src/utils/socket.test.js | 14 +++++++------- 5 files changed, 42 insertions(+), 24 deletions(-) diff --git a/src/store/middlewares/account.js b/src/store/middlewares/account.js index c61d5051a..412502aad 100644 --- a/src/store/middlewares/account.js +++ b/src/store/middlewares/account.js @@ -23,7 +23,7 @@ const hasRecentTransactions = state => ( state.transactions.pending.length !== 0 ); -const updateAccountData = (store, action) => { // eslint-disable-line +const updateAccountData = (store, action) => { const state = store.getState(); const { peers, account } = state; @@ -95,15 +95,14 @@ const passphraseUsed = (store, action) => { } }; -const shouldUpdateAccount = (accountAddress, tx) => { +const checkTransactionAndUpdateAccount = (store, action) => { + const tx = action.data.block.transactions; + const accountAddress = store.getState().account.address; const transaction = tx[tx.length - 1]; const sender = transaction ? transaction.senderId : null; - const receiver = transaction ? transaction.receiverId : null; - return accountAddress === receiver || accountAddress === sender; -}; + const recipient = transaction ? transaction.recipientId : null; -const checkTransactionAndUpdateAccount = (store, action) => { - if (shouldUpdateAccount(store.getState().account.address, action.data.block.transactions)) { + if (accountAddress === recipient || accountAddress === sender) { updateAccountData(store, action); } }; diff --git a/src/store/middlewares/socket.js b/src/store/middlewares/socket.js index 9d725383e..0848600a3 100644 --- a/src/store/middlewares/socket.js +++ b/src/store/middlewares/socket.js @@ -1,5 +1,5 @@ import actionTypes from '../../constants/actions'; -import { socketSetup } from '../../utils/socket'; +import { socketSetup, closeConnection } from '../../utils/socket'; const socketMiddleware = store => ( next => (action) => { @@ -7,6 +7,9 @@ const socketMiddleware = store => ( case actionTypes.accountLoggedIn: socketSetup(store, action); break; + case actionTypes.accountLoggedOut: + closeConnection(); + break; default: break; } next(action); diff --git a/src/store/middlewares/socket.test.js b/src/store/middlewares/socket.test.js index dbf9e3962..6236cb8fb 100644 --- a/src/store/middlewares/socket.test.js +++ b/src/store/middlewares/socket.test.js @@ -8,26 +8,35 @@ describe('Socket middleware', () => { let store; let next; let socketSetup; + let closeConnection; beforeEach(() => { next = spy(); store = stub(); }); - it('should call socketSetup when metronome is initialized', () => { + it('should call socketSetup after login', () => { socketSetup = stub(socket, 'socketSetup'); middleware(store)(next)({ type: actionTypes.accountLoggedIn }); expect(socketSetup).to.have.been.calledWith(); - socketSetup.reset(); + socketSetup.restore(); + }); + + it('should close the connection after logout', () => { + closeConnection = stub(socket, 'closeConnection'); + + middleware(store)(next)({ type: actionTypes.accountLoggedOut }); + expect(closeConnection).to.have.been.calledWith(); + closeConnection.restore(); }); it('should passes the action to next middleware', () => { - const expectedAction = { - type: actionTypes.accountLoggedIn, - }; - middleware(store)(next)(expectedAction); + socketSetup = stub(socket, 'socketSetup'); + + middleware(store)(next)({ type: actionTypes.accountLoggedIn }); expect(next).to.have.been.calledWith(); + socketSetup.restore(); }); }); diff --git a/src/utils/socket.js b/src/utils/socket.js index 934bfcfde..5b3113049 100644 --- a/src/utils/socket.js +++ b/src/utils/socket.js @@ -3,15 +3,16 @@ import { activePeerUpdate } from './../actions/peers'; import actionTypes from './../constants/actions'; import { SYNC_ACTIVE_INTERVAL, SYNC_INACTIVE_INTERVAL } from './../constants/api'; -export const socketSetup = (store) => { // eslint-disable-line - let interval = SYNC_ACTIVE_INTERVAL; +let connection; +export const socketSetup = (store) => { + let interval = SYNC_ACTIVE_INTERVAL; const { ipc } = window; if (ipc) { ipc.on('blur', () => { interval = SYNC_INACTIVE_INTERVAL; }); ipc.on('focus', () => { interval = SYNC_ACTIVE_INTERVAL; }); } - const connection = io.connect(`ws://${store.getState().peers.data.options.address}`); + connection = io.connect(`ws://${store.getState().peers.data.options.address}`); connection.on('blocks/change', (block) => { store.dispatch({ type: actionTypes.newBlockCreated, @@ -26,3 +27,9 @@ export const socketSetup = (store) => { // eslint-disable-line }); }; +export const closeConnection = () => { + if (connection) { + connection.close(); + } +}; + diff --git a/src/utils/socket.test.js b/src/utils/socket.test.js index 81e1aa791..a455e7f5f 100644 --- a/src/utils/socket.test.js +++ b/src/utils/socket.test.js @@ -19,14 +19,14 @@ describe('Socket', () => { }); window.ipc = { - on: (event, callback) => { - ipcCallbacks[event] = callback; + on: (type, callback) => { + ipcCallbacks[type] = callback; }, }; }); it(`should dispatch ${actionTypes.newBlockCreated} unless a new block was added`, () => { - transactions = { transactions: [{ senderId: '1234', receiverId: '5678' }] }; + transactions = { transactions: [{ senderId: '1234', recipientId: '5678' }] }; store = { getState: () => ({ peers: { data: { options: { address: 'localhost:4000' } } }, @@ -44,13 +44,13 @@ describe('Socket', () => { expect(store.dispatch).to.have.been.calledWith({ type: actionTypes.newBlockCreated, - data: { data: transactions, interval: SYNC_ACTIVE_INTERVAL }, + data: { block: transactions, interval: SYNC_ACTIVE_INTERVAL }, }); }); describe('window.ipc', () => { beforeEach(() => { - transactions = { transactions: [{ senderId: '1234', receiverId: '5678' }] }; + transactions = { transactions: [{ senderId: '1234', recipientId: '5678' }] }; store = { getState: () => ({ peers: { data: { options: { address: 'localhost:4000' } } }, @@ -77,7 +77,7 @@ describe('Socket', () => { expect(store.dispatch).to.have.been.calledWith({ type: actionTypes.newBlockCreated, - data: { data: transactions, interval: SYNC_INACTIVE_INTERVAL }, + data: { block: transactions, interval: SYNC_INACTIVE_INTERVAL }, }); }); @@ -88,7 +88,7 @@ describe('Socket', () => { expect(store.dispatch).to.have.been.calledWith({ type: actionTypes.newBlockCreated, - data: { data: transactions, interval: SYNC_ACTIVE_INTERVAL }, + data: { block: transactions, interval: SYNC_ACTIVE_INTERVAL }, }); }); }); From f400eee3b580f6965e6de21f43341b3dc712ef7f Mon Sep 17 00:00:00 2001 From: Gina Contrino Date: Thu, 26 Oct 2017 17:48:22 +0200 Subject: [PATCH 03/13] Moving files and update account on login --- src/store/middlewares/account.js | 3 +++ src/store/middlewares/socket.js | 2 +- src/store/middlewares/socket.test.js | 2 +- src/utils/{ => api}/socket.js | 10 +++++----- src/utils/{ => api}/socket.test.js | 6 +++--- 5 files changed, 13 insertions(+), 10 deletions(-) rename src/utils/{ => api}/socket.js (68%) rename src/utils/{ => api}/socket.test.js (93%) diff --git a/src/store/middlewares/account.js b/src/store/middlewares/account.js index 412502aad..63e1a49c9 100644 --- a/src/store/middlewares/account.js +++ b/src/store/middlewares/account.js @@ -110,6 +110,9 @@ const checkTransactionAndUpdateAccount = (store, action) => { const accountMiddleware = store => next => (action) => { next(action); switch (action.type) { + case actionTypes.accountLoggedIn: + updateAccountData(store, action); + break; case actionTypes.newBlockCreated: checkTransactionAndUpdateAccount(store, action); break; diff --git a/src/store/middlewares/socket.js b/src/store/middlewares/socket.js index 0848600a3..bba9c4c77 100644 --- a/src/store/middlewares/socket.js +++ b/src/store/middlewares/socket.js @@ -1,5 +1,5 @@ import actionTypes from '../../constants/actions'; -import { socketSetup, closeConnection } from '../../utils/socket'; +import { socketSetup, closeConnection } from '../../utils/api/socket'; const socketMiddleware = store => ( next => (action) => { diff --git a/src/store/middlewares/socket.test.js b/src/store/middlewares/socket.test.js index 6236cb8fb..288ddcce8 100644 --- a/src/store/middlewares/socket.test.js +++ b/src/store/middlewares/socket.test.js @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { spy, stub } from 'sinon'; import middleware from './socket'; -import * as socket from '../../utils/socket'; +import * as socket from '../../utils/api/socket'; import actionTypes from '../../constants/actions'; describe('Socket middleware', () => { diff --git a/src/utils/socket.js b/src/utils/api/socket.js similarity index 68% rename from src/utils/socket.js rename to src/utils/api/socket.js index 5b3113049..3a3f882bc 100644 --- a/src/utils/socket.js +++ b/src/utils/api/socket.js @@ -1,7 +1,7 @@ -import io from './socketShim'; -import { activePeerUpdate } from './../actions/peers'; -import actionTypes from './../constants/actions'; -import { SYNC_ACTIVE_INTERVAL, SYNC_INACTIVE_INTERVAL } from './../constants/api'; +import io from '../socketShim'; +import { activePeerUpdate } from '../../actions/peers'; +import actionTypes from '../../constants/actions'; +import { SYNC_ACTIVE_INTERVAL, SYNC_INACTIVE_INTERVAL } from '../../constants/api'; let connection; @@ -12,7 +12,7 @@ export const socketSetup = (store) => { ipc.on('blur', () => { interval = SYNC_INACTIVE_INTERVAL; }); ipc.on('focus', () => { interval = SYNC_ACTIVE_INTERVAL; }); } - connection = io.connect(`ws://${store.getState().peers.data.options.address}`); + connection = io.connect(`ws://${store.getState().peers.data.currentPeer}:${store.getState().peers.data.port}`); connection.on('blocks/change', (block) => { store.dispatch({ type: actionTypes.newBlockCreated, diff --git a/src/utils/socket.test.js b/src/utils/api/socket.test.js similarity index 93% rename from src/utils/socket.test.js rename to src/utils/api/socket.test.js index a455e7f5f..e8da13ea5 100644 --- a/src/utils/socket.test.js +++ b/src/utils/api/socket.test.js @@ -1,8 +1,8 @@ import { expect } from 'chai'; import { spy } from 'sinon'; -import io from './socketShim'; -import actionTypes from './../constants/actions'; -import { SYNC_ACTIVE_INTERVAL, SYNC_INACTIVE_INTERVAL } from './../constants/api'; +import io from '../socketShim'; +import actionTypes from '../../constants/actions'; +import { SYNC_ACTIVE_INTERVAL, SYNC_INACTIVE_INTERVAL } from '../../constants/api'; import { socketSetup } from './socket'; describe('Socket', () => { From a534f02b822f2cc0aabeff1816d716963e0310f2 Mon Sep 17 00:00:00 2001 From: Gina Contrino Date: Fri, 27 Oct 2017 11:24:27 +0200 Subject: [PATCH 04/13] Refactor account middleware --- src/store/middlewares/account.js | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/store/middlewares/account.js b/src/store/middlewares/account.js index 63e1a49c9..5a4e32675 100644 --- a/src/store/middlewares/account.js +++ b/src/store/middlewares/account.js @@ -24,13 +24,9 @@ const hasRecentTransactions = state => ( ); const updateAccountData = (store, action) => { - const state = store.getState(); - const { peers, account } = state; + const { peers, account } = store.getState(); getAccount(peers.data, account.address).then((result) => { - if (action.data.interval === SYNC_ACTIVE_INTERVAL && hasRecentTransactions(state)) { - updateTransactions(store, peers, account); - } if (result.balance !== account.balance) { if (action.data.interval === SYNC_INACTIVE_INTERVAL) { updateTransactions(store, peers, account); @@ -95,14 +91,23 @@ const passphraseUsed = (store, action) => { } }; -const checkTransactionAndUpdateAccount = (store, action) => { +const checkTransactionsAndUpdateAccount = (store, action) => { + const state = store.getState(); + const { peers, account } = state; + + if (action.data.interval === SYNC_ACTIVE_INTERVAL && hasRecentTransactions(state)) { + updateTransactions(store, peers, account); + } + const tx = action.data.block.transactions; - const accountAddress = store.getState().account.address; - const transaction = tx[tx.length - 1]; - const sender = transaction ? transaction.senderId : null; - const recipient = transaction ? transaction.recipientId : null; + const accountAddress = state.account.address; + const blockContainsRelevantTransaction = tx.filter((transaction) => { + const sender = transaction ? transaction.senderId : null; + const recipient = transaction ? transaction.recipientId : null; + return accountAddress === recipient || accountAddress === sender; + }).length > 0; - if (accountAddress === recipient || accountAddress === sender) { + if (blockContainsRelevantTransaction) { updateAccountData(store, action); } }; @@ -110,11 +115,14 @@ const checkTransactionAndUpdateAccount = (store, action) => { const accountMiddleware = store => next => (action) => { next(action); switch (action.type) { + // update on login because the 'save account' button + // depends on a rerendering of the page + // TODO: fix the 'save account' path problem, so we can remove this case actionTypes.accountLoggedIn: updateAccountData(store, action); break; case actionTypes.newBlockCreated: - checkTransactionAndUpdateAccount(store, action); + checkTransactionsAndUpdateAccount(store, action); break; case actionTypes.transactionsUpdated: delegateRegistration(store, action); From daf3f9b6bbfa9bba34480e42eeec352d1612fca6 Mon Sep 17 00:00:00 2001 From: Gina Contrino Date: Fri, 27 Oct 2017 12:29:00 +0200 Subject: [PATCH 05/13] Refactor socket middleware --- src/store/middlewares/socket.js | 33 +++++++- src/store/middlewares/socket.test.js | 115 ++++++++++++++++++++++++--- src/utils/api/socket.js | 35 -------- src/utils/api/socket.test.js | 96 ---------------------- 4 files changed, 135 insertions(+), 144 deletions(-) delete mode 100644 src/utils/api/socket.js delete mode 100644 src/utils/api/socket.test.js diff --git a/src/store/middlewares/socket.js b/src/store/middlewares/socket.js index bba9c4c77..310a9df72 100644 --- a/src/store/middlewares/socket.js +++ b/src/store/middlewares/socket.js @@ -1,5 +1,36 @@ +import io from '../../utils/socketShim'; import actionTypes from '../../constants/actions'; -import { socketSetup, closeConnection } from '../../utils/api/socket'; +import { activePeerUpdate } from '../../actions/peers'; +import { SYNC_ACTIVE_INTERVAL, SYNC_INACTIVE_INTERVAL } from '../../constants/api'; + +let connection; + +const socketSetup = (store) => { + let interval = SYNC_ACTIVE_INTERVAL; + const { ipc } = window; + if (ipc) { + ipc.on('blur', () => { interval = SYNC_INACTIVE_INTERVAL; }); + ipc.on('focus', () => { interval = SYNC_ACTIVE_INTERVAL; }); + } + connection = io.connect(`ws://${store.getState().peers.data.currentPeer}:${store.getState().peers.data.port}`); + connection.on('blocks/change', (block) => { + store.dispatch({ + type: actionTypes.newBlockCreated, + data: { block, interval }, + }); + }); + connection.on('disconnect', () => { + store.dispatch(activePeerUpdate({ online: false })); + }); + connection.on('reconnect', () => { + store.dispatch(activePeerUpdate({ online: true })); + }); +}; +const closeConnection = () => { + if (connection) { + connection.close(); + } +}; const socketMiddleware = store => ( next => (action) => { diff --git a/src/store/middlewares/socket.test.js b/src/store/middlewares/socket.test.js index 288ddcce8..44e644f25 100644 --- a/src/store/middlewares/socket.test.js +++ b/src/store/middlewares/socket.test.js @@ -1,42 +1,133 @@ import { expect } from 'chai'; import { spy, stub } from 'sinon'; +import io from './../../utils/socketShim'; import middleware from './socket'; -import * as socket from '../../utils/api/socket'; import actionTypes from '../../constants/actions'; +import { SYNC_ACTIVE_INTERVAL, SYNC_INACTIVE_INTERVAL } from '../../constants/api'; + describe('Socket middleware', () => { let store; let next; - let socketSetup; - let closeConnection; + let transactions; + const ipcCallbacks = {}; + const socketCallbacks = {}; beforeEach(() => { next = spy(); store = stub(); + + io.connect = () => ({ + on: (event, callback) => { + socketCallbacks[event] = callback; + }, + close: spy(), + }); + + window.ipc = { + on: (type, callback) => { + ipcCallbacks[type] = callback; + }, + }; }); - it('should call socketSetup after login', () => { - socketSetup = stub(socket, 'socketSetup'); + it(`should dispatch ${actionTypes.newBlockCreated}, on login action, unless a new block was added`, () => { + transactions = { transactions: [{ senderId: '1234', recipientId: '5678' }] }; + store = { + getState: () => ({ + peers: { data: { options: { address: 'localhost:4000' } } }, + account: { address: '1234' }, + }), + dispatch: stub(), + }; + + expect(store.dispatch).to.not.have.been.calledWith(); middleware(store)(next)({ type: actionTypes.accountLoggedIn }); - expect(socketSetup).to.have.been.calledWith(); - socketSetup.restore(); + ipcCallbacks.focus(); + socketCallbacks['blocks/change'](transactions); + + expect(store.dispatch).to.have.been.calledWith({ + type: actionTypes.newBlockCreated, + data: { block: transactions, interval: SYNC_ACTIVE_INTERVAL }, + }); }); + it('should close the connection after logout', () => { - closeConnection = stub(socket, 'closeConnection'); + store = { + getState: () => ({ + peers: { data: { options: { address: 'localhost:4000' } } }, + account: { address: '1234' }, + }), + dispatch: spy(), + }; + middleware(store)(next)({ type: actionTypes.accountLoggedIn }); + expect(io.connect().close).to.not.have.been.calledWith(); middleware(store)(next)({ type: actionTypes.accountLoggedOut }); - expect(closeConnection).to.have.been.calledWith(); - closeConnection.restore(); + // TODO: figure out why spy wasn't called + // expect(io.connect().close).to.have.been.calledWith(); }); + it('should passes the action to next middleware', () => { - socketSetup = stub(socket, 'socketSetup'); + store = { + getState: () => ({ + peers: { data: { options: { address: 'localhost:4000' } } }, + account: { address: '1234' }, + }), + dispatch: spy(), + }; middleware(store)(next)({ type: actionTypes.accountLoggedIn }); expect(next).to.have.been.calledWith(); - socketSetup.restore(); + }); + + describe('window.ipc', () => { + beforeEach(() => { + transactions = { transactions: [{ senderId: '1234', recipientId: '5678' }] }; + store = { + getState: () => ({ + peers: { data: { options: { address: 'localhost:4000' } } }, + account: { address: '1234' }, + }), + dispatch: spy(), + }; + }); + + it('should call window.ipc.on(\'blur\') and window.ipc.on(\'focus\')', () => { + window.ipc = { + on: spy(), + }; + + middleware(store)(next)({ type: actionTypes.accountLoggedIn }); + + expect(window.ipc.on).to.have.been.calledWith('blur'); + expect(window.ipc.on).to.have.been.calledWith('focus'); + }); + + it('should set window.ipc to set the interval to SYNC_INACTIVE_INTERVAL on blur', () => { + middleware(store)(next)({ type: actionTypes.accountLoggedIn }); + ipcCallbacks.blur(); + socketCallbacks['blocks/change'](transactions); + + expect(store.dispatch).to.have.been.calledWith({ + type: actionTypes.newBlockCreated, + data: { block: transactions, interval: SYNC_INACTIVE_INTERVAL }, + }); + }); + + it('should set window.ipc to set the interval to SYNC_ACTIVE_INTERVAL on focus', () => { + middleware(store)(next)({ type: actionTypes.accountLoggedIn }); + ipcCallbacks.focus(); + socketCallbacks['blocks/change'](transactions); + + expect(store.dispatch).to.have.been.calledWith({ + type: actionTypes.newBlockCreated, + data: { block: transactions, interval: SYNC_ACTIVE_INTERVAL }, + }); + }); }); }); diff --git a/src/utils/api/socket.js b/src/utils/api/socket.js deleted file mode 100644 index 3a3f882bc..000000000 --- a/src/utils/api/socket.js +++ /dev/null @@ -1,35 +0,0 @@ -import io from '../socketShim'; -import { activePeerUpdate } from '../../actions/peers'; -import actionTypes from '../../constants/actions'; -import { SYNC_ACTIVE_INTERVAL, SYNC_INACTIVE_INTERVAL } from '../../constants/api'; - -let connection; - -export const socketSetup = (store) => { - let interval = SYNC_ACTIVE_INTERVAL; - const { ipc } = window; - if (ipc) { - ipc.on('blur', () => { interval = SYNC_INACTIVE_INTERVAL; }); - ipc.on('focus', () => { interval = SYNC_ACTIVE_INTERVAL; }); - } - connection = io.connect(`ws://${store.getState().peers.data.currentPeer}:${store.getState().peers.data.port}`); - connection.on('blocks/change', (block) => { - store.dispatch({ - type: actionTypes.newBlockCreated, - data: { block, interval }, - }); - }); - connection.on('disconnect', () => { - store.dispatch(activePeerUpdate({ online: false })); - }); - connection.on('reconnect', () => { - store.dispatch(activePeerUpdate({ online: true })); - }); -}; - -export const closeConnection = () => { - if (connection) { - connection.close(); - } -}; - diff --git a/src/utils/api/socket.test.js b/src/utils/api/socket.test.js deleted file mode 100644 index e8da13ea5..000000000 --- a/src/utils/api/socket.test.js +++ /dev/null @@ -1,96 +0,0 @@ -import { expect } from 'chai'; -import { spy } from 'sinon'; -import io from '../socketShim'; -import actionTypes from '../../constants/actions'; -import { SYNC_ACTIVE_INTERVAL, SYNC_INACTIVE_INTERVAL } from '../../constants/api'; -import { socketSetup } from './socket'; - -describe('Socket', () => { - let store; - let transactions; - const ipcCallbacks = {}; - const socketCallbacks = {}; - - beforeEach(() => { - io.connect = () => ({ - on: (event, callback) => { - socketCallbacks[event] = callback; - }, - }); - - window.ipc = { - on: (type, callback) => { - ipcCallbacks[type] = callback; - }, - }; - }); - - it(`should dispatch ${actionTypes.newBlockCreated} unless a new block was added`, () => { - transactions = { transactions: [{ senderId: '1234', recipientId: '5678' }] }; - store = { - getState: () => ({ - peers: { data: { options: { address: 'localhost:4000' } } }, - account: { address: '1234' }, - }), - dispatch: spy(), - }; - - socketSetup(store); - - expect(store.dispatch).to.not.have.been.calledWith(); - - ipcCallbacks.focus(); - socketCallbacks['blocks/change'](transactions); - - expect(store.dispatch).to.have.been.calledWith({ - type: actionTypes.newBlockCreated, - data: { block: transactions, interval: SYNC_ACTIVE_INTERVAL }, - }); - }); - - describe('window.ipc', () => { - beforeEach(() => { - transactions = { transactions: [{ senderId: '1234', recipientId: '5678' }] }; - store = { - getState: () => ({ - peers: { data: { options: { address: 'localhost:4000' } } }, - account: { address: '1234' }, - }), - dispatch: spy(), - }; - }); - - it('should call window.ipc.on(\'blur\') and window.ipc.on(\'focus\')', () => { - window.ipc = { - on: spy(), - }; - socketSetup(store); - - expect(window.ipc.on).to.have.been.calledWith('blur'); - expect(window.ipc.on).to.have.been.calledWith('focus'); - }); - - it('should set window.ipc to set the interval to SYNC_INACTIVE_INTERVAL on blur', () => { - socketSetup(store); - ipcCallbacks.blur(); - socketCallbacks['blocks/change'](transactions); - - expect(store.dispatch).to.have.been.calledWith({ - type: actionTypes.newBlockCreated, - data: { block: transactions, interval: SYNC_INACTIVE_INTERVAL }, - }); - }); - - it('should set window.ipc to set the interval to SYNC_ACTIVE_INTERVAL on focus', () => { - socketSetup(store); - ipcCallbacks.focus(); - socketCallbacks['blocks/change'](transactions); - - expect(store.dispatch).to.have.been.calledWith({ - type: actionTypes.newBlockCreated, - data: { block: transactions, interval: SYNC_ACTIVE_INTERVAL }, - }); - }); - }); -}); - From 99dcd70e64e084259197fbaf2fcec37dc850381f Mon Sep 17 00:00:00 2001 From: Gina Contrino Date: Fri, 27 Oct 2017 13:30:54 +0200 Subject: [PATCH 06/13] Remove sending offline status on forced closing of the connection --- src/store/middlewares/socket.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/store/middlewares/socket.js b/src/store/middlewares/socket.js index 310a9df72..e85dd099a 100644 --- a/src/store/middlewares/socket.js +++ b/src/store/middlewares/socket.js @@ -4,6 +4,7 @@ import { activePeerUpdate } from '../../actions/peers'; import { SYNC_ACTIVE_INTERVAL, SYNC_INACTIVE_INTERVAL } from '../../constants/api'; let connection; +let forcedClosing = false; const socketSetup = (store) => { let interval = SYNC_ACTIVE_INTERVAL; @@ -20,7 +21,9 @@ const socketSetup = (store) => { }); }); connection.on('disconnect', () => { - store.dispatch(activePeerUpdate({ online: false })); + if (!forcedClosing) { + store.dispatch(activePeerUpdate({ online: false })); + } }); connection.on('reconnect', () => { store.dispatch(activePeerUpdate({ online: true })); @@ -28,7 +31,9 @@ const socketSetup = (store) => { }; const closeConnection = () => { if (connection) { + forcedClosing = true; connection.close(); + forcedClosing = false; } }; From 33d68f62c73559ba8d6899c73472ca12f03bfc96 Mon Sep 17 00:00:00 2001 From: Gina Contrino Date: Wed, 1 Nov 2017 14:37:46 +0100 Subject: [PATCH 07/13] Update package.json --- package.json | 1 + src/constants/actions.js | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 6115f93ab..6e16a9dca 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "redux": "3.7.2", "redux-logger": "=3.0.6", "redux-thunk": "=2.2.0", + "socket.io-client": "=2.0.3", "webpack-merge": "=4.1.0" }, "devDependencies": { diff --git a/src/constants/actions.js b/src/constants/actions.js index fddb06315..23f2dc9a9 100644 --- a/src/constants/actions.js +++ b/src/constants/actions.js @@ -1,6 +1,4 @@ const actionTypes = { - socketDisconnected: 'SOCKET_DISCONNECTED', - socketReconnected: 'SOCKET_RECONNECTED', newBlockCreated: 'NEW_BLOCK_CREATED', accountUpdated: 'ACCOUNT_UPDATED', accountLoggedOut: 'ACCOUNT_LOGGED_OUT', From 4de33f0ab11ce15d7eb74a46a570bb1a52d1d95e Mon Sep 17 00:00:00 2001 From: Gina Contrino Date: Wed, 1 Nov 2017 16:37:01 +0100 Subject: [PATCH 08/13] Remove constants --- src/constants/api.js | 5 ----- src/store/middlewares/account.js | 5 ++--- src/store/middlewares/account.test.js | 10 ++++------ src/store/middlewares/socket.js | 9 ++++----- src/store/middlewares/socket.test.js | 12 +++++------- 5 files changed, 15 insertions(+), 26 deletions(-) delete mode 100644 src/constants/api.js diff --git a/src/constants/api.js b/src/constants/api.js deleted file mode 100644 index f11dc7ef4..000000000 --- a/src/constants/api.js +++ /dev/null @@ -1,5 +0,0 @@ -/** - * The interval of syncTick event - */ -export const SYNC_ACTIVE_INTERVAL = 10000; -export const SYNC_INACTIVE_INTERVAL = 120000; diff --git a/src/store/middlewares/account.js b/src/store/middlewares/account.js index 5a4e32675..5bf0c8a04 100644 --- a/src/store/middlewares/account.js +++ b/src/store/middlewares/account.js @@ -7,7 +7,6 @@ import actionTypes from '../../constants/actions'; import { fetchAndUpdateForgedBlocks } from '../../actions/forging'; import { getDelegate } from '../../utils/api/delegate'; import transactionTypes from '../../constants/transactionTypes'; -import { SYNC_ACTIVE_INTERVAL, SYNC_INACTIVE_INTERVAL } from '../../constants/api'; const updateTransactions = (store, peers, account) => { const maxBlockSize = 25; @@ -28,7 +27,7 @@ const updateAccountData = (store, action) => { getAccount(peers.data, account.address).then((result) => { if (result.balance !== account.balance) { - if (action.data.interval === SYNC_INACTIVE_INTERVAL) { + if (!action.data.windowIsFocused) { updateTransactions(store, peers, account); } if (account.isDelegate) { @@ -95,7 +94,7 @@ const checkTransactionsAndUpdateAccount = (store, action) => { const state = store.getState(); const { peers, account } = state; - if (action.data.interval === SYNC_ACTIVE_INTERVAL && hasRecentTransactions(state)) { + if (action.data.windowIsFocused && hasRecentTransactions(state)) { updateTransactions(store, peers, account); } diff --git a/src/store/middlewares/account.test.js b/src/store/middlewares/account.test.js index 493df40ee..7cb85dc4d 100644 --- a/src/store/middlewares/account.test.js +++ b/src/store/middlewares/account.test.js @@ -1,7 +1,5 @@ import { expect } from 'chai'; import { spy, stub } from 'sinon'; - -import { SYNC_ACTIVE_INTERVAL, SYNC_INACTIVE_INTERVAL } from '../../constants/api'; import { accountUpdated } from '../../actions/account'; import { activePeerUpdate } from '../../actions/peers'; import * as votingActions from '../../actions/voting'; @@ -35,7 +33,7 @@ describe('Account middleware', () => { const newBlockCreated = { type: actionTypes.newBlockCreated, data: { - interval: SYNC_ACTIVE_INTERVAL, + windowIsFocused: true, block: transactions, }, }; @@ -43,7 +41,7 @@ describe('Account middleware', () => { const inactiveNewBlockCreated = { type: actionTypes.newBlockCreated, data: { - interval: SYNC_INACTIVE_INTERVAL, + windowIsFocused: false, block: transactions, }, }; @@ -112,7 +110,7 @@ describe('Account middleware', () => { expect(stubTransactions).to.have.been.calledWith(); }); - it(`should call transactions API methods on ${actionTypes.newBlockCreated} action if account.balance changes and action.data.interval is SYNC_INACTIVE_INTERVAL`, () => { + it(`should call transactions API methods on ${actionTypes.newBlockCreated} action if account.balance changes and the window is in blur`, () => { stubGetAccount.resolves({ balance: 10e8 }); middleware(store)(next)(inactiveNewBlockCreated); @@ -121,7 +119,7 @@ describe('Account middleware', () => { expect(stubTransactions).to.have.been.calledWith(); }); - it(`should call transactions API methods on ${actionTypes.newBlockCreated} action if action.data.interval is SYNC_ACTIVE_INTERVAL and there are recent transactions`, () => { + it(`should call transactions API methods on ${actionTypes.newBlockCreated} action if the window is in focus and there are recent transactions`, () => { stubGetAccount.resolves({ balance: 0 }); middleware(store)(next)(newBlockCreated); diff --git a/src/store/middlewares/socket.js b/src/store/middlewares/socket.js index e85dd099a..73641ee6b 100644 --- a/src/store/middlewares/socket.js +++ b/src/store/middlewares/socket.js @@ -1,23 +1,22 @@ import io from '../../utils/socketShim'; import actionTypes from '../../constants/actions'; import { activePeerUpdate } from '../../actions/peers'; -import { SYNC_ACTIVE_INTERVAL, SYNC_INACTIVE_INTERVAL } from '../../constants/api'; let connection; let forcedClosing = false; const socketSetup = (store) => { - let interval = SYNC_ACTIVE_INTERVAL; + let windowIsFocused = true; const { ipc } = window; if (ipc) { - ipc.on('blur', () => { interval = SYNC_INACTIVE_INTERVAL; }); - ipc.on('focus', () => { interval = SYNC_ACTIVE_INTERVAL; }); + ipc.on('blur', () => { windowIsFocused = false; }); + ipc.on('focus', () => { windowIsFocused = true; }); } connection = io.connect(`ws://${store.getState().peers.data.currentPeer}:${store.getState().peers.data.port}`); connection.on('blocks/change', (block) => { store.dispatch({ type: actionTypes.newBlockCreated, - data: { block, interval }, + data: { block, windowIsFocused }, }); }); connection.on('disconnect', () => { diff --git a/src/store/middlewares/socket.test.js b/src/store/middlewares/socket.test.js index 44e644f25..56ea3634a 100644 --- a/src/store/middlewares/socket.test.js +++ b/src/store/middlewares/socket.test.js @@ -3,8 +3,6 @@ import { spy, stub } from 'sinon'; import io from './../../utils/socketShim'; import middleware from './socket'; import actionTypes from '../../constants/actions'; -import { SYNC_ACTIVE_INTERVAL, SYNC_INACTIVE_INTERVAL } from '../../constants/api'; - describe('Socket middleware', () => { let store; @@ -49,7 +47,7 @@ describe('Socket middleware', () => { expect(store.dispatch).to.have.been.calledWith({ type: actionTypes.newBlockCreated, - data: { block: transactions, interval: SYNC_ACTIVE_INTERVAL }, + data: { block: transactions, windowIsFocused: true }, }); }); @@ -107,25 +105,25 @@ describe('Socket middleware', () => { expect(window.ipc.on).to.have.been.calledWith('focus'); }); - it('should set window.ipc to set the interval to SYNC_INACTIVE_INTERVAL on blur', () => { + it('should register window focus changes', () => { middleware(store)(next)({ type: actionTypes.accountLoggedIn }); ipcCallbacks.blur(); socketCallbacks['blocks/change'](transactions); expect(store.dispatch).to.have.been.calledWith({ type: actionTypes.newBlockCreated, - data: { block: transactions, interval: SYNC_INACTIVE_INTERVAL }, + data: { block: transactions, windowIsFocused: false }, }); }); - it('should set window.ipc to set the interval to SYNC_ACTIVE_INTERVAL on focus', () => { + it('should register window focus changes', () => { middleware(store)(next)({ type: actionTypes.accountLoggedIn }); ipcCallbacks.focus(); socketCallbacks['blocks/change'](transactions); expect(store.dispatch).to.have.been.calledWith({ type: actionTypes.newBlockCreated, - data: { block: transactions, interval: SYNC_ACTIVE_INTERVAL }, + data: { block: transactions, windowIsFocused: true }, }); }); }); From 0eecaf230e6cf45056cdc88f4ecbd363521e4c7e Mon Sep 17 00:00:00 2001 From: Gina Contrino Date: Thu, 2 Nov 2017 10:48:43 +0100 Subject: [PATCH 09/13] Adjust protocol --- src/store/middlewares/socket.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/store/middlewares/socket.js b/src/store/middlewares/socket.js index 73641ee6b..3c66e9f45 100644 --- a/src/store/middlewares/socket.js +++ b/src/store/middlewares/socket.js @@ -12,7 +12,11 @@ const socketSetup = (store) => { ipc.on('blur', () => { windowIsFocused = false; }); ipc.on('focus', () => { windowIsFocused = true; }); } - connection = io.connect(`ws://${store.getState().peers.data.currentPeer}:${store.getState().peers.data.port}`); + + const ssl = store.getState().peers.data.options.ssl; + const protocol = ssl ? 'https' : 'http'; + + connection = io.connect(`${protocol}://${store.getState().peers.data.currentPeer}:${store.getState().peers.data.port}`); connection.on('blocks/change', (block) => { store.dispatch({ type: actionTypes.newBlockCreated, From 2a1e995eba27cf962ee65386f8065076641b3a37 Mon Sep 17 00:00:00 2001 From: Gina Contrino Date: Thu, 2 Nov 2017 17:41:54 +0100 Subject: [PATCH 10/13] Refactor --- src/store/middlewares/account.js | 14 +++++++------- src/store/middlewares/socket.js | 27 ++++++++++++++++----------- src/store/middlewares/socket.test.js | 2 +- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/store/middlewares/account.js b/src/store/middlewares/account.js index 5bf0c8a04..6f749dcbe 100644 --- a/src/store/middlewares/account.js +++ b/src/store/middlewares/account.js @@ -1,4 +1,4 @@ -import { getAccount, transactions } from '../../utils/api/account'; +import { getAccount, transactions as getTransactions } from '../../utils/api/account'; import { accountUpdated, accountLoggedIn } from '../../actions/account'; import { transactionsUpdated } from '../../actions/transactions'; import { activePeerUpdate } from '../../actions/peers'; @@ -10,16 +10,16 @@ import transactionTypes from '../../constants/transactionTypes'; const updateTransactions = (store, peers, account) => { const maxBlockSize = 25; - transactions(peers.data, account.address, maxBlockSize) + getTransactions(peers.data, account.address, maxBlockSize) .then(response => store.dispatch(transactionsUpdated({ confirmed: response.transactions, count: parseInt(response.count, 10), }))); }; -const hasRecentTransactions = state => ( - state.transactions.confirmed.filter(tx => tx.confirmations < 1000).length !== 0 || - state.transactions.pending.length !== 0 +const hasRecentTransactions = txs => ( + txs.confirmed.filter(tx => tx.confirmations < 1000).length !== 0 || + txs.pending.length !== 0 ); const updateAccountData = (store, action) => { @@ -92,9 +92,9 @@ const passphraseUsed = (store, action) => { const checkTransactionsAndUpdateAccount = (store, action) => { const state = store.getState(); - const { peers, account } = state; + const { peers, account, transactions } = state; - if (action.data.windowIsFocused && hasRecentTransactions(state)) { + if (action.data.windowIsFocused && hasRecentTransactions(transactions)) { updateTransactions(store, peers, account); } diff --git a/src/store/middlewares/socket.js b/src/store/middlewares/socket.js index 3c66e9f45..8e5705aac 100644 --- a/src/store/middlewares/socket.js +++ b/src/store/middlewares/socket.js @@ -5,6 +5,21 @@ import { activePeerUpdate } from '../../actions/peers'; let connection; let forcedClosing = false; +const openConnection = (state) => { + const ssl = state.peers.data.options.ssl; + const protocol = ssl ? 'https' : 'http'; + + return io.connect(`${protocol}://${state.peers.data.currentPeer}:${state.peers.data.port}`); +}; + +const closeConnection = () => { + if (connection) { + forcedClosing = true; + connection.close(); + forcedClosing = false; + } +}; + const socketSetup = (store) => { let windowIsFocused = true; const { ipc } = window; @@ -13,10 +28,7 @@ const socketSetup = (store) => { ipc.on('focus', () => { windowIsFocused = true; }); } - const ssl = store.getState().peers.data.options.ssl; - const protocol = ssl ? 'https' : 'http'; - - connection = io.connect(`${protocol}://${store.getState().peers.data.currentPeer}:${store.getState().peers.data.port}`); + connection = openConnection(store.getState()); connection.on('blocks/change', (block) => { store.dispatch({ type: actionTypes.newBlockCreated, @@ -32,13 +44,6 @@ const socketSetup = (store) => { store.dispatch(activePeerUpdate({ online: true })); }); }; -const closeConnection = () => { - if (connection) { - forcedClosing = true; - connection.close(); - forcedClosing = false; - } -}; const socketMiddleware = store => ( next => (action) => { diff --git a/src/store/middlewares/socket.test.js b/src/store/middlewares/socket.test.js index 56ea3634a..9ee29fbeb 100644 --- a/src/store/middlewares/socket.test.js +++ b/src/store/middlewares/socket.test.js @@ -52,7 +52,7 @@ describe('Socket middleware', () => { }); - it('should close the connection after logout', () => { + it.skip('should close the connection after logout', () => { store = { getState: () => ({ peers: { data: { options: { address: 'localhost:4000' } } }, From 9ee10950ad08592c90b743575235fe6b8671d10d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADt=20Stanislav?= Date: Fri, 3 Nov 2017 06:59:43 +0100 Subject: [PATCH 11/13] Fix close socket middleware unit test --- src/store/middlewares/socket.test.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/store/middlewares/socket.test.js b/src/store/middlewares/socket.test.js index 9ee29fbeb..b7277e46d 100644 --- a/src/store/middlewares/socket.test.js +++ b/src/store/middlewares/socket.test.js @@ -8,18 +8,20 @@ describe('Socket middleware', () => { let store; let next; let transactions; + let closeSpy; const ipcCallbacks = {}; const socketCallbacks = {}; beforeEach(() => { next = spy(); store = stub(); + closeSpy = spy(); io.connect = () => ({ on: (event, callback) => { socketCallbacks[event] = callback; }, - close: spy(), + close: closeSpy, }); window.ipc = { @@ -52,7 +54,7 @@ describe('Socket middleware', () => { }); - it.skip('should close the connection after logout', () => { + it('should close the connection after logout', () => { store = { getState: () => ({ peers: { data: { options: { address: 'localhost:4000' } } }, @@ -64,8 +66,7 @@ describe('Socket middleware', () => { middleware(store)(next)({ type: actionTypes.accountLoggedIn }); expect(io.connect().close).to.not.have.been.calledWith(); middleware(store)(next)({ type: actionTypes.accountLoggedOut }); - // TODO: figure out why spy wasn't called - // expect(io.connect().close).to.have.been.calledWith(); + expect(io.connect().close).to.have.been.calledWith(); }); From 955c55be9377d2acfa9d0589405a450809568500 Mon Sep 17 00:00:00 2001 From: Gina Contrino Date: Fri, 3 Nov 2017 08:51:10 +0100 Subject: [PATCH 12/13] Add and refactor tests --- src/store/middlewares/socket.test.js | 46 +++++++++++----------------- 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/src/store/middlewares/socket.test.js b/src/store/middlewares/socket.test.js index b7277e46d..1f0ffc24b 100644 --- a/src/store/middlewares/socket.test.js +++ b/src/store/middlewares/socket.test.js @@ -3,6 +3,7 @@ import { spy, stub } from 'sinon'; import io from './../../utils/socketShim'; import middleware from './socket'; import actionTypes from '../../constants/actions'; +import { activePeerUpdate } from '../../actions/peers'; describe('Socket middleware', () => { let store; @@ -14,7 +15,6 @@ describe('Socket middleware', () => { beforeEach(() => { next = spy(); - store = stub(); closeSpy = spy(); io.connect = () => ({ @@ -29,17 +29,18 @@ describe('Socket middleware', () => { ipcCallbacks[type] = callback; }, }; - }); - it(`should dispatch ${actionTypes.newBlockCreated}, on login action, unless a new block was added`, () => { - transactions = { transactions: [{ senderId: '1234', recipientId: '5678' }] }; store = { getState: () => ({ peers: { data: { options: { address: 'localhost:4000' } } }, account: { address: '1234' }, }), - dispatch: stub(), + dispatch: spy(), }; + }); + + it(`should dispatch ${actionTypes.newBlockCreated}, on login action, unless a new block was added`, () => { + transactions = { transactions: [{ senderId: '1234', recipientId: '5678' }] }; expect(store.dispatch).to.not.have.been.calledWith(); @@ -55,30 +56,26 @@ describe('Socket middleware', () => { it('should close the connection after logout', () => { - store = { - getState: () => ({ - peers: { data: { options: { address: 'localhost:4000' } } }, - account: { address: '1234' }, - }), - dispatch: spy(), - }; - middleware(store)(next)({ type: actionTypes.accountLoggedIn }); expect(io.connect().close).to.not.have.been.calledWith(); middleware(store)(next)({ type: actionTypes.accountLoggedOut }); expect(io.connect().close).to.have.been.calledWith(); + expect(store.dispatch).to.not.have.been.calledWith(activePeerUpdate({ online: false })); }); + it('should dispatch online event on reconnect', () => { + middleware(store)(next)({ type: actionTypes.accountLoggedIn }); + socketCallbacks.reconnect(); + expect(store.dispatch).to.have.been.calledWith(activePeerUpdate({ online: true })); + }); - it('should passes the action to next middleware', () => { - store = { - getState: () => ({ - peers: { data: { options: { address: 'localhost:4000' } } }, - account: { address: '1234' }, - }), - dispatch: spy(), - }; + it('should dispatch offline event on disconnect', () => { + middleware(store)(next)({ type: actionTypes.accountLoggedIn }); + socketCallbacks.disconnect(); + expect(store.dispatch).to.have.been.calledWith(activePeerUpdate({ online: false })); + }); + it('should passes the action to next middleware', () => { middleware(store)(next)({ type: actionTypes.accountLoggedIn }); expect(next).to.have.been.calledWith(); }); @@ -86,13 +83,6 @@ describe('Socket middleware', () => { describe('window.ipc', () => { beforeEach(() => { transactions = { transactions: [{ senderId: '1234', recipientId: '5678' }] }; - store = { - getState: () => ({ - peers: { data: { options: { address: 'localhost:4000' } } }, - account: { address: '1234' }, - }), - dispatch: spy(), - }; }); it('should call window.ipc.on(\'blur\') and window.ipc.on(\'focus\')', () => { From 0bdcfb3b2bf63917e4d6045bf96da7ddde06bc6f Mon Sep 17 00:00:00 2001 From: Gina Contrino Date: Fri, 3 Nov 2017 08:56:11 +0100 Subject: [PATCH 13/13] Fix eslint error --- src/store/middlewares/socket.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/store/middlewares/socket.test.js b/src/store/middlewares/socket.test.js index 1f0ffc24b..710e9b6b7 100644 --- a/src/store/middlewares/socket.test.js +++ b/src/store/middlewares/socket.test.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spy, stub } from 'sinon'; +import { spy } from 'sinon'; import io from './../../utils/socketShim'; import middleware from './socket'; import actionTypes from '../../constants/actions';