diff --git a/embark-ui/src/actions/index.js b/embark-ui/src/actions/index.js index 068316da..b540a998 100644 --- a/embark-ui/src/actions/index.js +++ b/embark-ui/src/actions/index.js @@ -18,6 +18,8 @@ export const RECEIVE_BLOCKS_ERROR = 'RECEIVE_BLOCKS_ERROR'; export const FETCH_TRANSACTIONS = 'FETCH_TRANSACTIONS'; export const RECEIVE_TRANSACTIONS = 'RECEIVE_TRANSACTIONS'; export const RECEIVE_TRANSACTIONS_ERROR = 'RECEIVE_TRANSACTIONS_ERROR'; +// BlockHeader +export const INIT_BLOCK_HEADER = 'INIT_BLOCK_HEADER'; export function fetchAccounts() { return { @@ -119,3 +121,9 @@ export function receiveTransactionsError() { type: RECEIVE_TRANSACTIONS_ERROR }; } + +export function initBlockHeader(){ + return { + type: INIT_BLOCK_HEADER + }; +} diff --git a/embark-ui/src/api/index.js b/embark-ui/src/api/index.js index 4ab0c14d..ab46e38f 100644 --- a/embark-ui/src/api/index.js +++ b/embark-ui/src/api/index.js @@ -1,24 +1,26 @@ import axios from "axios"; import constants from '../constants'; -const BASE_URL = 'http://localhost:8000/embark-api'; - export function fetchAccounts() { - return axios.get(constants.httpEndpoint + '/blockchain/accounts'); + return axios.get(`${constants.httpEndpoint}/blockchain/accounts`); } export function fetchBlocks(from) { - return axios.get(`${BASE_URL}/blockchain/blocks`, {params: {from}}); + return axios.get(`${constants.httpEndpoint}/blockchain/blocks`, {params: {from}}); } export function fetchTransactions(blockFrom) { - return axios.get(`${BASE_URL}/blockchain/transactions`, {params: {blockFrom}}); + return axios.get(`${constants.httpEndpoint}/blockchain/transactions`, {params: {blockFrom}}); } export function fetchProcesses() { - return axios.get(constants.httpEndpoint + '/processes'); + return axios.get(`${constants.httpEndpoint}/processes`); } export function fetchProcessLogs(processName) { return axios.get(`${constants.httpEndpoint}/process-logs/${processName}`); } + +export function webSocketBlockHeader() { + return new WebSocket(`${constants.wsEndpoint}/blockchain/blockHeader`); +} diff --git a/embark-ui/src/containers/AppContainer.js b/embark-ui/src/containers/AppContainer.js index 55f1c1e2..94c83767 100644 --- a/embark-ui/src/containers/AppContainer.js +++ b/embark-ui/src/containers/AppContainer.js @@ -1,11 +1,18 @@ import {ConnectedRouter} from "connected-react-router"; +import PropTypes from "prop-types"; +import {connect} from 'react-redux'; import React, {Component} from 'react'; import history from '../history'; import Layout from '../components/Layout'; import routes from '../routes'; +import {initBlockHeader} from '../actions'; class AppContainer extends Component { + componentDidMount() { + this.props.initBlockHeader(); + } + render() { return ( @@ -17,4 +24,13 @@ class AppContainer extends Component { } } -export default AppContainer; +AppContainer.propTypes = { + initBlockHeader: PropTypes.func +}; + +export default connect( + null, + { + initBlockHeader + }, +)(AppContainer); diff --git a/embark-ui/src/containers/BlocksContainer.js b/embark-ui/src/containers/BlocksContainer.js index c939dbd7..985990a8 100644 --- a/embark-ui/src/containers/BlocksContainer.js +++ b/embark-ui/src/containers/BlocksContainer.js @@ -9,9 +9,7 @@ import LoadMore from '../components/LoadMore'; class BlocksContainer extends Component { componentDidMount() { - if (!this.props.blocks.data) { - this.props.fetchBlocks(); - } + this.props.fetchBlocks(); } loadMore() { diff --git a/embark-ui/src/containers/TransactionsContainer.js b/embark-ui/src/containers/TransactionsContainer.js index 9298fc9f..7e7edc81 100644 --- a/embark-ui/src/containers/TransactionsContainer.js +++ b/embark-ui/src/containers/TransactionsContainer.js @@ -9,9 +9,7 @@ import LoadMore from '../components/LoadMore'; class TransactionsContainer extends Component { componentDidMount() { - if (!this.props.transactions.data) { - this.props.fetchTransactions(); - } + this.props.fetchTransactions(); } loadMore() { diff --git a/embark-ui/src/reducers/accountsReducer.js b/embark-ui/src/reducers/accountsReducer.js index c1f76a73..a89cde48 100644 --- a/embark-ui/src/reducers/accountsReducer.js +++ b/embark-ui/src/reducers/accountsReducer.js @@ -3,7 +3,7 @@ import {RECEIVE_ACCOUNTS, RECEIVE_ACCOUNTS_ERROR} from "../actions"; export default function accounts(state = {}, action) { switch (action.type) { case RECEIVE_ACCOUNTS: - return {...state, data: [...state.data || [], ...action.accounts.data]}; + return Object.assign({}, state, {data: action.accounts.data}); case RECEIVE_ACCOUNTS_ERROR: return Object.assign({}, state, {error: true}); default: diff --git a/embark-ui/src/reducers/blocksReducer.js b/embark-ui/src/reducers/blocksReducer.js index 5ffac690..9b4c8de2 100644 --- a/embark-ui/src/reducers/blocksReducer.js +++ b/embark-ui/src/reducers/blocksReducer.js @@ -3,7 +3,11 @@ import {RECEIVE_BLOCKS, RECEIVE_BLOCKS_ERROR} from "../actions"; export default function blocks(state = {}, action) { switch (action.type) { case RECEIVE_BLOCKS: - return {...state, data: [...state.data || [], ...action.blocks.data]}; + return { + ...state, data: [...state.data || [], ...action.blocks.data] + .filter((block, index, self) => index === self.findIndex((t) => t.number === block.number)) + .sort((a, b) => b.number - a.number) + }; case RECEIVE_BLOCKS_ERROR: return Object.assign({}, state, {error: true}); default: diff --git a/embark-ui/src/reducers/transactionsReducer.js b/embark-ui/src/reducers/transactionsReducer.js index 36b6a4b7..e2f3b290 100644 --- a/embark-ui/src/reducers/transactionsReducer.js +++ b/embark-ui/src/reducers/transactionsReducer.js @@ -1,9 +1,19 @@ import {RECEIVE_TRANSACTIONS, RECEIVE_TRANSACTIONS_ERROR} from "../actions"; +const BN_FACTOR = 10000; + export default function transactions(state = {}, action) { switch (action.type) { case RECEIVE_TRANSACTIONS: - return {...state, data: [...state.data || [], ...action.transactions.data]}; + return { + ...state, data: [...state.data || [], ...action.transactions.data] + .filter((tx, index, self) => index === self.findIndex((t) => ( + t.blockNumber === tx.blockNumber && t.transactionIndex === tx.transactionIndex + ))) + .sort((a, b) => ( + ((BN_FACTOR * b.blockNumber) + b.transactionIndex) - ((BN_FACTOR * a.blockNumber) + a.transactionIndex)) + ) + }; case RECEIVE_TRANSACTIONS_ERROR: return Object.assign({}, state, {error: true}); default: diff --git a/embark-ui/src/sagas/index.js b/embark-ui/src/sagas/index.js index e242bf3a..b1868d72 100644 --- a/embark-ui/src/sagas/index.js +++ b/embark-ui/src/sagas/index.js @@ -1,6 +1,7 @@ import * as actions from '../actions'; import * as api from '../api'; -import {all, call, fork, put, takeEvery} from 'redux-saga/effects'; +import {eventChannel} from 'redux-saga'; +import {all, call, fork, put, takeEvery, take} from 'redux-saga/effects'; export function *fetchTransactions(payload) { try { @@ -67,8 +68,34 @@ export function *watchFetchProcessLogs() { yield takeEvery(actions.FETCH_PROCESS_LOGS, fetchProcessLogs); } +function createChannel(socket) { + return eventChannel(emit => { + socket.onmessage = ((message) => { + emit(JSON.parse(message.data)); + }); + return () => { + socket.close(); + }; + }); +} + +export function *initBlockHeader() { + const socket = api.webSocketBlockHeader(); + const channel = yield call(createChannel, socket); + while (true) { + yield take(channel); + yield put({type: actions.FETCH_BLOCKS}); + yield put({type: actions.FETCH_TRANSACTIONS}); + } +} + +export function *watchInitBlockHeader() { + yield takeEvery(actions.INIT_BLOCK_HEADER, initBlockHeader); +} + export default function *root() { yield all([ + fork(watchInitBlockHeader), fork(watchFetchAccounts), fork(watchFetchProcesses), fork(watchFetchProcessLogs), diff --git a/lib/modules/blockchain_connector/index.js b/lib/modules/blockchain_connector/index.js index e44da745..0d6eff59 100644 --- a/lib/modules/blockchain_connector/index.js +++ b/lib/modules/blockchain_connector/index.js @@ -70,6 +70,7 @@ class BlockchainConnector { self.isWeb3Ready = true; self.events.emit(WEB3_READY); self.registerWeb3Object(); + self.subscribeToNewBlockHeaders(); }); }); }); @@ -204,9 +205,7 @@ class BlockchainConnector { callback(); }); } - ], function () { - eachCb(); - }); + ], eachCb); }, function () { res.send(results); }); @@ -233,6 +232,17 @@ class BlockchainConnector { self.getTransactions(blockFrom, blockLimit, res.send.bind(res)); } ); + + + plugin.registerAPICall( + 'ws', + '/embark-api/blockchain/blockHeader', + (ws) => { + self.events.on('blockchain:newBlockHeaders', (block) => { + ws.send(JSON.stringify({block: block}), () => {}); + }); + } + ); } getTransactions(blockFrom, blockLimit, callback) { @@ -277,6 +287,17 @@ class BlockchainConnector { }); } + subscribeToNewBlockHeaders() { + let self = this; + self.web3.eth.subscribe("newBlockHeaders", (err) => { + if (err) { + self.logger.error(err.message); + } + }).on("data", (block) => { + self.events.emit("blockchain:newBlockHeaders", block); + }); + } + defaultAccount() { return this.web3.eth.defaultAccount; diff --git a/templates/demo/config/contracts.js b/templates/demo/config/contracts.js index bf2164b3..00844146 100644 --- a/templates/demo/config/contracts.js +++ b/templates/demo/config/contracts.js @@ -4,8 +4,8 @@ module.exports = { // Blockchain node to deploy the contracts deployment: { host: "localhost", // Host of the blockchain node - port: 8545, // Port of the blockchain node - type: "rpc" // Type of connection (ws or rpc), + port: 8546, // Port of the blockchain node + type: "ws" // Type of connection (ws or rpc), // Accounts to use instead of the default account to populate your wallet /*,accounts: [ {