New block Header ws API + Client

This commit is contained in:
Anthony Laibe 2018-08-03 12:08:42 +01:00 committed by Pascal Precht
parent 6243d7c453
commit bfd123b133
No known key found for this signature in database
GPG Key ID: 0EE28D8D6FD85D7D
11 changed files with 109 additions and 24 deletions

View File

@ -18,6 +18,8 @@ export const RECEIVE_BLOCKS_ERROR = 'RECEIVE_BLOCKS_ERROR';
export const FETCH_TRANSACTIONS = 'FETCH_TRANSACTIONS'; export const FETCH_TRANSACTIONS = 'FETCH_TRANSACTIONS';
export const RECEIVE_TRANSACTIONS = 'RECEIVE_TRANSACTIONS'; export const RECEIVE_TRANSACTIONS = 'RECEIVE_TRANSACTIONS';
export const RECEIVE_TRANSACTIONS_ERROR = 'RECEIVE_TRANSACTIONS_ERROR'; export const RECEIVE_TRANSACTIONS_ERROR = 'RECEIVE_TRANSACTIONS_ERROR';
// BlockHeader
export const INIT_BLOCK_HEADER = 'INIT_BLOCK_HEADER';
export function fetchAccounts() { export function fetchAccounts() {
return { return {
@ -119,3 +121,9 @@ export function receiveTransactionsError() {
type: RECEIVE_TRANSACTIONS_ERROR type: RECEIVE_TRANSACTIONS_ERROR
}; };
} }
export function initBlockHeader(){
return {
type: INIT_BLOCK_HEADER
};
}

View File

@ -1,24 +1,26 @@
import axios from "axios"; import axios from "axios";
import constants from '../constants'; import constants from '../constants';
const BASE_URL = 'http://localhost:8000/embark-api';
export function fetchAccounts() { export function fetchAccounts() {
return axios.get(constants.httpEndpoint + '/blockchain/accounts'); return axios.get(`${constants.httpEndpoint}/blockchain/accounts`);
} }
export function fetchBlocks(from) { 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) { 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() { export function fetchProcesses() {
return axios.get(constants.httpEndpoint + '/processes'); return axios.get(`${constants.httpEndpoint}/processes`);
} }
export function fetchProcessLogs(processName) { export function fetchProcessLogs(processName) {
return axios.get(`${constants.httpEndpoint}/process-logs/${processName}`); return axios.get(`${constants.httpEndpoint}/process-logs/${processName}`);
} }
export function webSocketBlockHeader() {
return new WebSocket(`${constants.wsEndpoint}/blockchain/blockHeader`);
}

View File

@ -1,11 +1,18 @@
import {ConnectedRouter} from "connected-react-router"; import {ConnectedRouter} from "connected-react-router";
import PropTypes from "prop-types";
import {connect} from 'react-redux';
import React, {Component} from 'react'; import React, {Component} from 'react';
import history from '../history'; import history from '../history';
import Layout from '../components/Layout'; import Layout from '../components/Layout';
import routes from '../routes'; import routes from '../routes';
import {initBlockHeader} from '../actions';
class AppContainer extends Component { class AppContainer extends Component {
componentDidMount() {
this.props.initBlockHeader();
}
render() { render() {
return ( return (
<ConnectedRouter history={history}> <ConnectedRouter history={history}>
@ -17,4 +24,13 @@ class AppContainer extends Component {
} }
} }
export default AppContainer; AppContainer.propTypes = {
initBlockHeader: PropTypes.func
};
export default connect(
null,
{
initBlockHeader
},
)(AppContainer);

View File

@ -9,9 +9,7 @@ import LoadMore from '../components/LoadMore';
class BlocksContainer extends Component { class BlocksContainer extends Component {
componentDidMount() { componentDidMount() {
if (!this.props.blocks.data) { this.props.fetchBlocks();
this.props.fetchBlocks();
}
} }
loadMore() { loadMore() {

View File

@ -9,9 +9,7 @@ import LoadMore from '../components/LoadMore';
class TransactionsContainer extends Component { class TransactionsContainer extends Component {
componentDidMount() { componentDidMount() {
if (!this.props.transactions.data) { this.props.fetchTransactions();
this.props.fetchTransactions();
}
} }
loadMore() { loadMore() {

View File

@ -3,7 +3,7 @@ import {RECEIVE_ACCOUNTS, RECEIVE_ACCOUNTS_ERROR} from "../actions";
export default function accounts(state = {}, action) { export default function accounts(state = {}, action) {
switch (action.type) { switch (action.type) {
case RECEIVE_ACCOUNTS: case RECEIVE_ACCOUNTS:
return {...state, data: [...state.data || [], ...action.accounts.data]}; return Object.assign({}, state, {data: action.accounts.data});
case RECEIVE_ACCOUNTS_ERROR: case RECEIVE_ACCOUNTS_ERROR:
return Object.assign({}, state, {error: true}); return Object.assign({}, state, {error: true});
default: default:

View File

@ -3,7 +3,11 @@ import {RECEIVE_BLOCKS, RECEIVE_BLOCKS_ERROR} from "../actions";
export default function blocks(state = {}, action) { export default function blocks(state = {}, action) {
switch (action.type) { switch (action.type) {
case RECEIVE_BLOCKS: 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: case RECEIVE_BLOCKS_ERROR:
return Object.assign({}, state, {error: true}); return Object.assign({}, state, {error: true});
default: default:

View File

@ -1,9 +1,19 @@
import {RECEIVE_TRANSACTIONS, RECEIVE_TRANSACTIONS_ERROR} from "../actions"; import {RECEIVE_TRANSACTIONS, RECEIVE_TRANSACTIONS_ERROR} from "../actions";
const BN_FACTOR = 10000;
export default function transactions(state = {}, action) { export default function transactions(state = {}, action) {
switch (action.type) { switch (action.type) {
case RECEIVE_TRANSACTIONS: 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: case RECEIVE_TRANSACTIONS_ERROR:
return Object.assign({}, state, {error: true}); return Object.assign({}, state, {error: true});
default: default:

View File

@ -1,6 +1,7 @@
import * as actions from '../actions'; import * as actions from '../actions';
import * as api from '../api'; 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) { export function *fetchTransactions(payload) {
try { try {
@ -67,8 +68,34 @@ export function *watchFetchProcessLogs() {
yield takeEvery(actions.FETCH_PROCESS_LOGS, fetchProcessLogs); 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() { export default function *root() {
yield all([ yield all([
fork(watchInitBlockHeader),
fork(watchFetchAccounts), fork(watchFetchAccounts),
fork(watchFetchProcesses), fork(watchFetchProcesses),
fork(watchFetchProcessLogs), fork(watchFetchProcessLogs),

View File

@ -138,8 +138,10 @@ class BlockchainConnector {
}) })
.catch(console.error); .catch(console.error);
self.provider.fundAccounts(() => { self.provider.fundAccounts(() => {
self._emitWeb3Ready(); self.isWeb3Ready = true;
cb(); self.events.emit(WEB3_READY);
self.registerWeb3Object();
self.subscribeToNewBlockHeaders();
}); });
}); });
}); });
@ -320,9 +322,7 @@ class BlockchainConnector {
callback(); callback();
}); });
} }
], function () { ], eachCb);
eachCb();
});
}, function () { }, function () {
res.send(results); res.send(results);
}); });
@ -349,6 +349,17 @@ class BlockchainConnector {
self.getTransactions(blockFrom, blockLimit, res.send.bind(res)); 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) { getTransactions(blockFrom, blockLimit, callback) {
@ -393,6 +404,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() { defaultAccount() {
return this.web3.eth.defaultAccount; return this.web3.eth.defaultAccount;

View File

@ -4,8 +4,8 @@ module.exports = {
// Blockchain node to deploy the contracts // Blockchain node to deploy the contracts
deployment: { deployment: {
host: "localhost", // Host of the blockchain node host: "localhost", // Host of the blockchain node
port: 8545, // Port of the blockchain node port: 8546, // Port of the blockchain node
type: "rpc" // Type of connection (ws or rpc), type: "ws" // Type of connection (ws or rpc),
// Accounts to use instead of the default account to populate your wallet // Accounts to use instead of the default account to populate your wallet
/*,accounts: [ /*,accounts: [
{ {