const assert = require('assert') const EventEmitter = require('events') const ObservableStore = require('obs-store') const ComposedStore = require('obs-store/lib/composed') const extend = require('xtend') const EthQuery = require('eth-query') const createEthRpcClient = require('eth-rpc-client') const createEventEmitterProxy = require('../lib/events-proxy.js') const createObjectProxy = require('../lib/obj-proxy.js') const RPC_ADDRESS_LIST = require('../config.js').network const DEFAULT_RPC = RPC_ADDRESS_LIST['rinkeby'] module.exports = class NetworkController extends EventEmitter { constructor (config) { super() config.provider.rpcTarget = this.getRpcAddressForType(config.provider.type, config.provider) this.networkStore = new ObservableStore('loading') this.providerStore = new ObservableStore(config.provider) this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore }) this.providerProxy = createObjectProxy() this.blockTrackerProxy = createEventEmitterProxy() this.on('networkDidChange', this.lookupNetwork) } initializeProvider (_providerParams) { this._baseProviderParams = _providerParams const rpcUrl = this.getCurrentRpcAddress() this._configureStandardClient({ rpcUrl }) this.blockTrackerProxy.on('block', this._logBlock.bind(this)) this.blockTrackerProxy.on('error', this.verifyNetwork.bind(this)) this.ethQuery = new EthQuery(this.providerProxy) this.lookupNetwork() } verifyNetwork () { // Check network when restoring connectivity: if (this.isNetworkLoading()) this.lookupNetwork() } getNetworkState () { return this.networkStore.getState() } setNetworkState (network) { return this.networkStore.putState(network) } isNetworkLoading () { return this.getNetworkState() === 'loading' } lookupNetwork () { this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => { if (err) return this.setNetworkState('loading') log.info('web3.getNetwork returned ' + network) this.setNetworkState(network) }) } setRpcTarget (rpcUrl) { this.providerStore.updateState({ type: 'rpc', rpcTarget: rpcUrl, }) this._switchNetwork({ rpcUrl }) } getCurrentRpcAddress () { const provider = this.getProviderConfig() if (!provider) return null return this.getRpcAddressForType(provider.type) } async setProviderType (type) { assert(type !== 'rpc', `NetworkController.setProviderType - cannot connect by type "rpc"`) // skip if type already matches if (type === this.getProviderConfig().type) return // lookup rpcTarget for typecreateMetamaskProvider const rpcTarget = this.getRpcAddressForType(type) assert(rpcTarget, `NetworkController - unknown rpc address for type "${type}"`) // update connectioncreateMetamaskProvider this.providerStore.updateState({ type, rpcTarget }) this._switchNetwork({ rpcUrl: rpcTarget }) } getProviderConfig () { return this.providerStore.getState() } getRpcAddressForType (type, provider = this.getProviderConfig()) { if (RPC_ADDRESS_LIST[type]) return RPC_ADDRESS_LIST[type] return provider && provider.rpcTarget ? provider.rpcTarget : DEFAULT_RPC } // // Private // _switchNetwork (providerParams) { this.setNetworkState('loading') this._configureStandardClient(providerParams) this.emit('networkDidChange') } _configureStandardClient(_providerParams) { const providerParams = extend(this._baseProviderParams, _providerParams) const client = createEthRpcClient(providerParams) this._setClient(client) } _setClient (newClient) { // teardown old client const oldClient = this._currentClient if (oldClient) { oldClient.blockTracker.stop() // asyncEventEmitter lacks a "removeAllListeners" method // oldClient.blockTracker.removeAllListeners oldClient.blockTracker._events = {} } // set as new provider this._currentClient = newClient this.providerProxy.setTarget(newClient.provider) this.blockTrackerProxy.setTarget(newClient.blockTracker) } _logBlock (block) { log.info(`BLOCK CHANGED: #${block.number.toString('hex')} 0x${block.hash.toString('hex')}`) this.verifyNetwork() } }