Merge pull request #1455 from MetaMask/networkController
Create a network controller to manage switching networks an updating t…feature/default_network_editable
commit
e8288ad4bf
@ -0,0 +1,128 @@ |
|||||||
|
const EventEmitter = require('events') |
||||||
|
const MetaMaskProvider = require('web3-provider-engine/zero.js') |
||||||
|
const ObservableStore = require('obs-store') |
||||||
|
const ComposedStore = require('obs-store/lib/composed') |
||||||
|
const extend = require('xtend') |
||||||
|
const EthQuery = require('eth-query') |
||||||
|
const RPC_ADDRESS_LIST = require('../config.js').network |
||||||
|
const DEFAULT_RPC = RPC_ADDRESS_LIST['rinkeby'] |
||||||
|
|
||||||
|
module.exports = class NetworkController extends EventEmitter { |
||||||
|
constructor (config) { |
||||||
|
super() |
||||||
|
this.networkStore = new ObservableStore('loading') |
||||||
|
config.provider.rpcTarget = this.getRpcAddressForType(config.provider.type, config.provider) |
||||||
|
this.providerStore = new ObservableStore(config.provider) |
||||||
|
this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore }) |
||||||
|
this._providerListeners = {} |
||||||
|
|
||||||
|
this.on('networkDidChange', this.lookupNetwork) |
||||||
|
this.providerStore.subscribe((state) => this.switchNetwork({rpcUrl: state.rpcTarget})) |
||||||
|
} |
||||||
|
|
||||||
|
get provider () { |
||||||
|
return this._proxy |
||||||
|
} |
||||||
|
|
||||||
|
set provider (provider) { |
||||||
|
this._provider = provider |
||||||
|
} |
||||||
|
|
||||||
|
initializeProvider (opts) { |
||||||
|
this.providerInit = opts |
||||||
|
this._provider = MetaMaskProvider(opts) |
||||||
|
this._proxy = new Proxy(this._provider, { |
||||||
|
get: (obj, name) => { |
||||||
|
if (name === 'on') return this._on.bind(this) |
||||||
|
return this._provider[name] |
||||||
|
}, |
||||||
|
set: (obj, name, value) => { |
||||||
|
this._provider[name] = value |
||||||
|
}, |
||||||
|
}) |
||||||
|
this.provider.on('block', this._logBlock.bind(this)) |
||||||
|
this.provider.on('error', this.verifyNetwork.bind(this)) |
||||||
|
this.ethQuery = new EthQuery(this.provider) |
||||||
|
this.lookupNetwork() |
||||||
|
return this.provider |
||||||
|
} |
||||||
|
|
||||||
|
switchNetwork (providerInit) { |
||||||
|
this.setNetworkState('loading') |
||||||
|
const newInit = extend(this.providerInit, providerInit) |
||||||
|
this.providerInit = newInit |
||||||
|
|
||||||
|
this._provider.removeAllListeners() |
||||||
|
this.provider = MetaMaskProvider(newInit) |
||||||
|
// apply the listners created by other controllers
|
||||||
|
Object.keys(this._providerListeners).forEach((key) => { |
||||||
|
this._providerListeners[key].forEach((handler) => this._provider.addListener(key, handler)) |
||||||
|
}) |
||||||
|
this.emit('networkDidChange') |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
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, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
getCurrentRpcAddress () { |
||||||
|
const provider = this.getProviderConfig() |
||||||
|
if (!provider) return null |
||||||
|
return this.getRpcAddressForType(provider.type) |
||||||
|
} |
||||||
|
|
||||||
|
setProviderType (type) { |
||||||
|
if (type === this.getProviderConfig().type) return |
||||||
|
const rpcTarget = this.getRpcAddressForType(type) |
||||||
|
this.providerStore.updateState({type, 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 |
||||||
|
} |
||||||
|
|
||||||
|
_logBlock (block) { |
||||||
|
log.info(`BLOCK CHANGED: #${block.number.toString('hex')} 0x${block.hash.toString('hex')}`) |
||||||
|
this.verifyNetwork() |
||||||
|
} |
||||||
|
|
||||||
|
_on (event, handler) { |
||||||
|
if (!this._providerListeners[event]) this._providerListeners[event] = [] |
||||||
|
this._providerListeners[event].push(handler) |
||||||
|
this._provider.on(event, handler) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
const version = 14 |
||||||
|
|
||||||
|
/* |
||||||
|
|
||||||
|
This migration removes provider from config and moves it too NetworkController. |
||||||
|
|
||||||
|
*/ |
||||||
|
|
||||||
|
const clone = require('clone') |
||||||
|
|
||||||
|
module.exports = { |
||||||
|
version, |
||||||
|
|
||||||
|
migrate: function (originalVersionedData) { |
||||||
|
const versionedData = clone(originalVersionedData) |
||||||
|
versionedData.meta.version = version |
||||||
|
try { |
||||||
|
const state = versionedData.data |
||||||
|
const newState = transformState(state) |
||||||
|
versionedData.data = newState |
||||||
|
} catch (err) { |
||||||
|
console.warn(`MetaMask Migration #${version}` + err.stack) |
||||||
|
} |
||||||
|
return Promise.resolve(versionedData) |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
function transformState (state) { |
||||||
|
const newState = state |
||||||
|
newState.NetworkController = {} |
||||||
|
newState.NetworkController.provider = newState.config.provider |
||||||
|
delete newState.config.provider |
||||||
|
return newState |
||||||
|
} |
@ -0,0 +1,74 @@ |
|||||||
|
const EventEmitter = require('events') |
||||||
|
const assert = require('assert') |
||||||
|
const NetworkController = require('../../app/scripts/controllers/network') |
||||||
|
|
||||||
|
describe('# Network Controller', function () { |
||||||
|
let networkController |
||||||
|
|
||||||
|
beforeEach(function () { |
||||||
|
networkController = new NetworkController({ |
||||||
|
provider: { |
||||||
|
type: 'rinkeby', |
||||||
|
}, |
||||||
|
}) |
||||||
|
// stub out provider
|
||||||
|
networkController._provider = new EventEmitter() |
||||||
|
networkController.providerInit = { |
||||||
|
getAccounts: () => {}, |
||||||
|
} |
||||||
|
|
||||||
|
networkController.ethQuery = new Proxy({}, { |
||||||
|
get: (obj, name) => { |
||||||
|
return () => {} |
||||||
|
}, |
||||||
|
}) |
||||||
|
}) |
||||||
|
describe('network', function () { |
||||||
|
describe('#provider', function() { |
||||||
|
it('provider should be updatable without reassignment', function () { |
||||||
|
networkController.initializeProvider(networkController.providerInit) |
||||||
|
const provider = networkController.provider |
||||||
|
networkController._provider = {test: true} |
||||||
|
assert.ok(provider.test) |
||||||
|
}) |
||||||
|
}) |
||||||
|
describe('#getNetworkState', function () { |
||||||
|
it('should return loading when new', function () { |
||||||
|
let networkState = networkController.getNetworkState() |
||||||
|
assert.equal(networkState, 'loading', 'network is loading') |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('#setNetworkState', function () { |
||||||
|
it('should update the network', function () { |
||||||
|
networkController.setNetworkState(1) |
||||||
|
let networkState = networkController.getNetworkState() |
||||||
|
assert.equal(networkState, 1, 'network is 1') |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('#getRpcAddressForType', function () { |
||||||
|
it('should return the right rpc address', function () { |
||||||
|
let rpcTarget = networkController.getRpcAddressForType('mainnet') |
||||||
|
assert.equal(rpcTarget, 'https://mainnet.infura.io/metamask', 'returns the right rpcAddress') |
||||||
|
}) |
||||||
|
}) |
||||||
|
describe('#setProviderType', function () { |
||||||
|
it('should update provider.type', function () { |
||||||
|
networkController.setProviderType('mainnet') |
||||||
|
const type = networkController.getProviderConfig().type |
||||||
|
assert.equal(type, 'mainnet', 'provider type is updated') |
||||||
|
}) |
||||||
|
it('should set the network to loading', function () { |
||||||
|
networkController.setProviderType('mainnet') |
||||||
|
const loading = networkController.isNetworkLoading() |
||||||
|
assert.ok(loading, 'network is loading') |
||||||
|
}) |
||||||
|
it('should set the right rpcTarget', function () { |
||||||
|
networkController.setProviderType('mainnet') |
||||||
|
const rpcTarget = networkController.getProviderConfig().rpcTarget |
||||||
|
assert.equal(rpcTarget, 'https://mainnet.infura.io/metamask', 'returns the right rpcAddress') |
||||||
|
}) |
||||||
|
}) |
||||||
|
}) |
||||||
|
}) |
Loading…
Reference in new issue