diff --git a/embark-ui/src/actions/index.js b/embark-ui/src/actions/index.js index a17d6ef07..2caf23e3b 100644 --- a/embark-ui/src/actions/index.js +++ b/embark-ui/src/actions/index.js @@ -426,6 +426,13 @@ export const removeEditorTabs = { failure: () => action(REMOVE_EDITOR_TABS[FAILURE]) }; +export const INIT_REGULAR_TXS = createRequestTypes('INIT_REGULAR_TXS'); +export const initRegularTxs = { + request: () => action(INIT_REGULAR_TXS[REQUEST], {mode: 'on'}), + success: () => action(INIT_REGULAR_TXS[SUCCESS]), + failure: () => action(INIT_REGULAR_TXS[FAILURE]) +}; + // Web Socket export const WATCH_NEW_PROCESS_LOGS = 'WATCH_NEW_PROCESS_LOGS'; export const STOP_NEW_PROCESS_LOGS = 'STOP_NEW_PROCESS_LOGS'; diff --git a/embark-ui/src/containers/AppContainer.js b/embark-ui/src/containers/AppContainer.js index 2e1d810ad..e38122234 100644 --- a/embark-ui/src/containers/AppContainer.js +++ b/embark-ui/src/containers/AppContainer.js @@ -6,7 +6,7 @@ import routes from '../routes'; import Login from '../components/Login'; import Layout from "../components/Layout"; import {DEFAULT_HOST} from '../constants'; -import {getQueryToken, stripQueryToken} from '../utils/utils'; +import {getQueryToken, stripQueryToken, getQueryParam, stripQueryParam} from '../utils/utils'; import {Helmet} from "react-helmet"; import { @@ -16,6 +16,7 @@ import { plugins as pluginsAction, listenToServices as listenToServicesAction, listenToContracts as listenToContractsAction, + initRegularTxs as initRegularTxsAction, changeTheme, fetchTheme } from '../actions'; @@ -25,6 +26,8 @@ import { getCredentials, getAuthenticationError, getProcesses, getTheme } from '../reducers/selectors'; +const ENABLE_REGULAR_TXS = 'enableRegularTxs'; + class AppContainer extends Component { componentDidMount() { this.props.fetchCredentials(); @@ -50,26 +53,28 @@ class AppContainer extends Component { const queryToken = getQueryToken(this.props.location); if (queryToken && !(queryToken === this.props.credentials.token && - this.props.credentials.host === DEFAULT_HOST)) { + this.props.credentials.host === DEFAULT_HOST)) { return true; } if (!this.props.credentials.authenticated && - this.props.credentials.host && - this.props.credentials.token) { + this.props.credentials.host && + this.props.credentials.token) { return true; } return false; } - componentDidUpdate(){ + componentDidUpdate() { if (this.requireAuthentication()) { this.doAuthenticate(); } - if (getQueryToken(this.props.location) && - (!this.props.credentials.authenticating || + const enableRegularTxs = !!getQueryParam(this.props.location, ENABLE_REGULAR_TXS); + + if (getQueryToken(this.props.location) && + (!this.props.credentials.authenticating || this.props.credentials.authenticated)) { this.props.history.replace(stripQueryToken(this.props.location)); } @@ -80,6 +85,10 @@ class AppContainer extends Component { this.props.listenToServices(); this.props.fetchPlugins(); this.props.listenToContracts(); + if (enableRegularTxs) { + this.props.initRegularTxs(); + this.props.history.replace(stripQueryParam(this.props.location, ENABLE_REGULAR_TXS)); + } } } @@ -99,18 +108,18 @@ class AppContainer extends Component { renderBody() { if (this.shouldRenderLogin()) { return ( - + ); } else if (this.props.credentials.authenticating) { - return ; + return ; } return ( this.toggleTheme()} - currentTheme={this.props.theme}> + logout={this.props.logout} + toggleTheme={() => this.toggleTheme()} + currentTheme={this.props.theme}> {routes} ); @@ -148,7 +157,8 @@ AppContainer.propTypes = { fetchTheme: PropTypes.func, history: PropTypes.object, listenToServices: PropTypes.func, - listenToContracts: PropTypes.func + listenToContracts: PropTypes.func, + initRegularTxs: PropTypes.func }; function mapStateToProps(state) { @@ -173,6 +183,7 @@ export default withRouter(connect( fetchPlugins: pluginsAction.request, changeTheme: changeTheme.request, fetchTheme: fetchTheme.request, - listenToContracts: listenToContractsAction + listenToContracts: listenToContractsAction, + initRegularTxs: initRegularTxsAction.request }, )(AppContainer)); diff --git a/embark-ui/src/sagas/index.js b/embark-ui/src/sagas/index.js index 8f1df6c05..3c21ca5e8 100644 --- a/embark-ui/src/sagas/index.js +++ b/embark-ui/src/sagas/index.js @@ -79,6 +79,7 @@ export const debugStepIntoForward = doRequest.bind(null, actions.debugStepIntoFo export const debugStepIntoBackward = doRequest.bind(null, actions.debugStepIntoBackward, api.debugStepIntoBackward); export const toggleBreakpoint = doRequest.bind(null, actions.toggleBreakpoint, api.toggleBreakpoint); export const authenticate = doRequest.bind(null, actions.authenticate, api.authenticate); +export const initRegularTxs = doRequest.bind(null, actions.initRegularTxs, api.initRegularTxs); export const fetchCredentials = doRequest.bind(null, actions.fetchCredentials, storage.fetchCredentials); export const saveCredentials = doRequest.bind(null, actions.saveCredentials, storage.saveCredentials); @@ -343,6 +344,10 @@ export function *watchRemoveEditorTabsSuccess() { yield takeEvery(actions.REMOVE_EDITOR_TABS[actions.SUCCESS], fetchEditorTabs); } +export function *watchInitRegularTxs() { + yield takeEvery(actions.INIT_REGULAR_TXS[actions.REQUEST], initRegularTxs); +} + function createChannel(socket) { return eventChannel(emit => { socket.onmessage = ((message) => { @@ -585,6 +590,7 @@ export default function *root() { fork(watchRemoveEditorTabsSuccess), fork(watchPostFileSuccess), fork(watchPostFolderSuccess), - fork(watchListenContracts) + fork(watchListenContracts), + fork(watchInitRegularTxs) ]); } diff --git a/embark-ui/src/services/api.js b/embark-ui/src/services/api.js index abaef78ab..9db453969 100644 --- a/embark-ui/src/services/api.js +++ b/embark-ui/src/services/api.js @@ -228,6 +228,10 @@ export function toggleBreakpoint(payload) { return post('/debugger/breakpoint', {params: payload, credentials: payload.credentials}); } +export function initRegularTxs(payload) { + return get('/regular-txs', {params: payload, credentials: payload.credentials}); +} + export function listenToDebugger(credentials) { return websocket(credentials, '/debugger'); } diff --git a/embark-ui/src/utils/utils.js b/embark-ui/src/utils/utils.js index 51ddd1402..fe1629fbd 100644 --- a/embark-ui/src/utils/utils.js +++ b/embark-ui/src/utils/utils.js @@ -23,8 +23,12 @@ export function ansiToHtml(text) { return convert.toHtml(text.replace(/\n/g,'
')); } +export function getQueryParam(location, param) { + return qs.parse(location.search, {ignoreQueryPrefix: true})[param]; +} + export function getQueryToken(location) { - return qs.parse(location.search, {ignoreQueryPrefix: true}).token; + return getQueryParam(location, 'token'); } export function getDebuggerTransactionHash(location) { @@ -32,9 +36,13 @@ export function getDebuggerTransactionHash(location) { } export function stripQueryToken(location) { + return stripQueryParam(location, 'token'); +} + +export function stripQueryParam(location, param) { const _location = Object.assign({}, location); _location.search = _location.search.replace( - /(\?|&?)(token=[\w-]*)(&?)/, + new RegExp(`(\\?|&?)(${param}=[\\w-]*)(&?)`), (_, p1, p2, p3) => (p2 ? (p3 === '&' ? p1 : '') : '') ); return _location; diff --git a/src/lib/constants.json b/src/lib/constants.json index 47bfa9a27..3599f9587 100644 --- a/src/lib/constants.json +++ b/src/lib/constants.json @@ -53,7 +53,9 @@ "eth_sendTransaction": "eth_sendTransaction", "eth_sendRawTransaction": "eth_sendRawTransaction", "eth_getTransactionReceipt": "eth_getTransactionReceipt" - } + }, + "startRegularTxs": "startRegularTxs", + "stopRegularTxs": "stopRegularTxs" }, "storage": { "init": "init", diff --git a/src/lib/modules/blockchain_listener/index.js b/src/lib/modules/blockchain_listener/index.js index 4a436db6d..527506313 100644 --- a/src/lib/modules/blockchain_listener/index.js +++ b/src/lib/modules/blockchain_listener/index.js @@ -26,6 +26,9 @@ class BlockchainListener { if (this.ipc.isServer()) { this._listenToBlockchainLogs(); + this._listenToCommands(); + this._registerConsoleCommands(); + this._registerApiEndpoint(); } } @@ -40,6 +43,43 @@ class BlockchainListener { this.processLogsApi.logHandler.handleLog({logLevel, message}); }); } + + _registerConsoleCommands() { + this.embark.registerConsoleCommand({ + description: 'Toggles regular transactions used to prevent transactions from getting stuck when using Geth and Metamask', + matches: ['regularTxs on', 'regularTxs off'], + usage: "regularTxs on/off", + process: (cmd, callback) => { + const eventCmd = `regularTxs:${cmd.trim().endsWith('on') ? 'start' : 'stop'}`; + this.events.request(eventCmd, callback); + } + }); + } + + _registerApiEndpoint() { + this.embark.registerAPICall( + 'get', + '/embark-api/regular-txs', + (req, _res) => { + this.events.request(`regularTxs:${req.query.mode === 'on' ? 'start' : 'stop'}`); + } + ); + } + + _listenToCommands() { + + this.events.setCommandHandler('regularTxs:start', (cb) => { + this.events.emit('regularTxs:start'); + this.ipc.broadcast('regularTxs', 'start'); + return cb(null, 'Enabling regular transactions'); + }); + + this.events.setCommandHandler('regularTxs:stop', (cb) => { + this.events.emit('regularTxs:stop'); + this.ipc.broadcast('regularTxs', 'stop'); + return cb(null, 'Disabling regular transactions'); + }); + } } module.exports = BlockchainListener; diff --git a/src/lib/modules/blockchain_process/blockchain.js b/src/lib/modules/blockchain_process/blockchain.js index 9758ecd47..9da45f8af 100644 --- a/src/lib/modules/blockchain_process/blockchain.js +++ b/src/lib/modules/blockchain_process/blockchain.js @@ -65,6 +65,8 @@ var Blockchain = function(userConfig, clientClass) { proxy: this.userConfig.proxy }; + this.devFunds = null; + if (this.userConfig.accounts) { const nodeAccounts = this.userConfig.accounts.find(account => account.nodeAccounts); if (nodeAccounts) { @@ -120,7 +122,7 @@ Blockchain.prototype.initStandaloneProcess = function () { if (this.isStandalone) { // on every log logged in logger (say that 3x fast), send the log // to the IPC serve listening (only if we're connected of course) - this.events.on('log', (logLevel, message) => { + this.logger.events.on('log', (logLevel, message) => { if (this.ipc.connected) { this.ipc.request('blockchain:log', {logLevel, message}); } @@ -133,7 +135,14 @@ Blockchain.prototype.initStandaloneProcess = function () { // `embark run` without restarting `embark blockchain`) setInterval(() => { if (!this.ipc.connected) { - this.ipc.connect(() => {}); + this.ipc.connect(() => { + if (this.ipc.connected) { + this.ipc.listenTo('regularTxs', (mode) => { + if(mode === 'start') this.startRegularTxs(() => {}); + else if (mode === 'stop') this.stopRegularTxs(() => {}); + }); + } + }); } }, IPC_CONNECT_INTERVAL); } @@ -244,11 +253,6 @@ Blockchain.prototype.run = function () { data = data.toString(); if (!self.readyCalled && self.client.isReady(data)) { self.readyCalled = true; - if (self.isDev) { - self.fundAccounts((err) => { - if (err) this.logger.error('Error funding accounts', err); - }); - } if (self.config.proxy) { await self.setupProxy(); } @@ -280,14 +284,50 @@ Blockchain.prototype.run = function () { }; Blockchain.prototype.fundAccounts = function(cb) { - DevFunds.new({blockchainConfig: this.config}).then(devFunds => { - devFunds.fundAccounts(this.client.needKeepAlive(), (err) => { + if(this.isDev && this.devFunds){ + this.devFunds.fundAccounts((err) => { cb(err); }); - }); + } +}; + +Blockchain.prototype.startRegularTxs = function(cb) { + if (this.client.needKeepAlive() && this.devFunds){ + return this.devFunds.startRegularTxs(() => { + this.logger.info('Regular transactions have been enabled.'); + cb(); + }); + } + cb(); +}; + +Blockchain.prototype.stopRegularTxs = function(cb) { + if (this.client.needKeepAlive() && this.devFunds){ + return this.devFunds.stopRegularTxs(() => { + this.logger.info('Regular transactions have been disabled.'); + cb(); + }); + } + cb(); }; Blockchain.prototype.readyCallback = function () { + if (this.isDev) { + if(!this.devFunds) { + DevFunds.new({blockchainConfig: this.config}).then(devFunds => { + this.devFunds = devFunds; + this.fundAccounts((err) => { + if (err) this.logger.error('Error funding accounts', err); + }); + }); + } + else { + this.fundAccounts((err) => { + if (err) this.logger.error('Error funding accounts', err); + }); + } + } + if (this.onReadyCallback) { this.onReadyCallback(); } @@ -450,7 +490,7 @@ Blockchain.prototype.initChainAndGetAddress = function (callback) { }); }; -var BlockchainClient = function(userConfig, clientName, env, certOptions, onReadyCallback, onExitCallback, logger, _events, _isStandalone) { +var BlockchainClient = function(userConfig, clientName, env, certOptions, onReadyCallback, onExitCallback, logger, _events, isStandalone) { if ((userConfig === {} || JSON.stringify(userConfig) === '{"enabled":true}') && env !== 'development') { logger.info("===> " + __("warning: running default config on a non-development environment")); } @@ -478,6 +518,7 @@ var BlockchainClient = function(userConfig, clientName, env, certOptions, onRead userConfig.onExitCallback = onExitCallback; userConfig.logger = logger; userConfig.certOptions = certOptions; + userConfig.isStandalone = isStandalone; return new Blockchain(userConfig, clientClass); }; diff --git a/src/lib/modules/blockchain_process/blockchainProcess.js b/src/lib/modules/blockchain_process/blockchainProcess.js index e44d72223..b68754976 100644 --- a/src/lib/modules/blockchain_process/blockchainProcess.js +++ b/src/lib/modules/blockchain_process/blockchainProcess.js @@ -52,4 +52,10 @@ process.on('message', (msg) => { blockchainProcess = new BlockchainProcess(msg.options); return blockchainProcess.send({result: constants.blockchain.initiated}); } + else if(msg.action === constants.blockchain.startRegularTxs){ + blockchainProcess.blockchain.startRegularTxs(() => {}); + } + else if(msg.action === constants.blockchain.stopRegularTxs){ + blockchainProcess.blockchain.stopRegularTxs(() => {}); + } }); diff --git a/src/lib/modules/blockchain_process/blockchainProcessLauncher.js b/src/lib/modules/blockchain_process/blockchainProcessLauncher.js index 6bf058b72..e8cdd50c5 100644 --- a/src/lib/modules/blockchain_process/blockchainProcessLauncher.js +++ b/src/lib/modules/blockchain_process/blockchainProcessLauncher.js @@ -38,7 +38,8 @@ class BlockchainProcessLauncher { env: this.env, isDev: this.isDev, locale: this.locale, - certOptions: this.embark.config.webServerConfig.certOptions + certOptions: this.embark.config.webServerConfig.certOptions, + events: this.events } }); @@ -62,6 +63,14 @@ class BlockchainProcessLauncher { this.events.on('logs:ethereum:disable', () => { this.blockchainProcess.silent = true; }); + + this.events.on('regularTxs:start', () => { + this.blockchainProcess.send({action: constants.blockchain.startRegularTxs}); + }); + + this.events.on('regularTxs:stop', () => { + this.blockchainProcess.send({action: constants.blockchain.stopRegularTxs}); + }); this.events.on('exit', () => { this.blockchainProcess.send('exit'); diff --git a/src/lib/modules/blockchain_process/dev_funds.js b/src/lib/modules/blockchain_process/dev_funds.js index bc012d0c7..a269b77a3 100644 --- a/src/lib/modules/blockchain_process/dev_funds.js +++ b/src/lib/modules/blockchain_process/dev_funds.js @@ -60,20 +60,28 @@ class DevFunds { this.web3.eth.sendTransaction({value: "1000000000000000", to: "0xA2817254cb8E7b6269D1689c3E0eBadbB78889d1", from: this.web3.eth.defaultAccount}); } - _regularTxs(cb) { + startRegularTxs(cb) { const self = this; self.web3.eth.net.getId().then((networkId) => { self.networkId = networkId; if (self.networkId !== 1337) { return; } - setInterval(function() { self._sendTx(); }, 1500); + this.regularTxsInt = setInterval(function() { self._sendTx(); }, 1500); if (cb) { cb(); } }); } + stopRegularTxs(cb) { + if(this.regularTxsInt) { + clearInterval(this.regularTxsInt); + return cb(); + } + cb('Regular txs not enabled.'); + } + _fundAccounts(balance, cb) { async.each(this.accounts, (account, next) => { this.web3.eth.getBalance(account).then(currBalance => { @@ -113,13 +121,12 @@ class DevFunds { }, cb); } - fundAccounts(pingForever = false, cb) { + fundAccounts(cb) { if (!this.web3) { return cb(); } async.waterfall([ (next) => { - if (pingForever) this._regularTxs(); this._fundAccounts(this.balance, next); } ], cb); diff --git a/src/lib/modules/code_generator/code_templates/web3-connector.js.ejs b/src/lib/modules/code_generator/code_templates/web3-connector.js.ejs index 69a2625ec..d88ccd659 100644 --- a/src/lib/modules/code_generator/code_templates/web3-connector.js.ejs +++ b/src/lib/modules/code_generator/code_templates/web3-connector.js.ejs @@ -1,4 +1,4 @@ EmbarkJS.Blockchain.autoEnable = <%= autoEnable %>; -EmbarkJS.Blockchain.connect(<%- connectionList %>, {warnAboutMetamask: <%= warnAboutMetamask %>}, function(err) { +EmbarkJS.Blockchain.connect(<%- connectionList %>, {warnAboutMetamask: <%= warnAboutMetamask %>, blockchainClient: "<%= blockchainClient %>"}, function(err) { <%- done %> }); diff --git a/src/lib/modules/code_generator/index.js b/src/lib/modules/code_generator/index.js index c25db16f2..23c2419c4 100644 --- a/src/lib/modules/code_generator/index.js +++ b/src/lib/modules/code_generator/index.js @@ -26,6 +26,7 @@ class CodeGenerator { this.storageConfig = embark.config.storageConfig || {}; this.communicationConfig = embark.config.communicationConfig || {}; this.namesystemConfig = embark.config.namesystemConfig || {}; + this.webServerConfig = embark.config.webServerConfig || {}; this.env = options.env || 'development'; this.plugins = options.plugins; this.events = embark.events; @@ -124,7 +125,8 @@ class CodeGenerator { autoEnable: this.contractsConfig.dappAutoEnable, connectionList: connectionList, done: 'done(err);', - warnAboutMetamask: isDev + warnAboutMetamask: isDev, + blockchainClient: this.blockchainConfig.ethereumClientName }); } diff --git a/src/test/devFunds.js b/src/test/devFunds.js index 730aad34f..db503a382 100644 --- a/src/test/devFunds.js +++ b/src/test/devFunds.js @@ -110,7 +110,7 @@ describe('embark.DevFunds', function() { // provider.injectResult('0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe'); // send tx response }); - devFunds.fundAccounts(devFunds.balance, (errFundAccounts) => { + devFunds.fundAccounts((errFundAccounts) => { assert.equal(errFundAccounts, null); // inject response for web3.eth.getAccounts