Merge pull request #113 from MetaMask/i53

publicConfigStore for sync provider and selected address
feature/default_network_editable
kumavis 9 years ago
commit 83b8741bbb
  1. 96
      app/scripts/background.js
  2. 80
      app/scripts/inpage.js
  3. 27
      app/scripts/lib/config-manager.js
  4. 2
      app/scripts/lib/obj-multiplex.js
  5. 97
      app/scripts/lib/remote-store.js
  6. 16
      app/scripts/lib/stream-utils.js
  7. 22
      app/scripts/popup.js

@ -9,8 +9,8 @@ const MetaMaskProvider = require('./lib/zero.js')
const IdentityStore = require('./lib/idStore') const IdentityStore = require('./lib/idStore')
const createTxNotification = require('./lib/tx-notification.js') const createTxNotification = require('./lib/tx-notification.js')
const configManager = require('./lib/config-manager-singleton') const configManager = require('./lib/config-manager-singleton')
const jsonParseStream = require('./lib/stream-utils.js').jsonParseStream const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
const jsonStringifyStream = require('./lib/stream-utils.js').jsonStringifyStream const HostStore = require('./lib/remote-store.js').HostStore
// //
// connect to other contexts // connect to other contexts
@ -22,15 +22,27 @@ function connectRemote(remotePort){
var portStream = new PortStream(remotePort) var portStream = new PortStream(remotePort)
if (isMetaMaskInternalProcess) { if (isMetaMaskInternalProcess) {
// communication with popup // communication with popup
handleInternalCommunication(portStream) setupTrustedCommunication(portStream)
} else { } else {
// communication with page // communication with page
handleEthRpcRequestStream(portStream) setupUntrustedCommunication(portStream)
} }
} }
function handleEthRpcRequestStream(stream){ function setupUntrustedCommunication(connectionStream){
stream.on('data', onRpcRequest.bind(null, stream)) // setup multiplexing
var mx = setupMultiplex(connectionStream)
// connect features
setupProviderConnection(mx.createStream('provider'))
setupPublicConfig(mx.createStream('publicConfig'))
}
function setupTrustedCommunication(connectionStream){
// setup multiplexing
var mx = setupMultiplex(connectionStream)
// connect features
setupControllerConnection(mx.createStream('controller'))
setupProviderConnection(mx.createStream('provider'))
} }
// //
@ -68,6 +80,47 @@ function getState(){
return state return state
} }
//
// public store
//
// get init state
var initPublicState = extend(
idStoreToPublic(idStore.getState()),
configToPublic(configManager.getConfig())
)
var publicConfigStore = new HostStore(initPublicState)
// subscribe to changes
configManager.subscribe(function(state){
storeSetFromObj(publicConfigStore, configToPublic(state))
})
idStore.on('update', function(state){
storeSetFromObj(publicConfigStore, idStoreToPublic(state))
})
// idStore substate
function idStoreToPublic(state){
return {
selectedAddress: state.selectedAddress,
}
}
// config substate
function configToPublic(state){
return {
provider: state.provider,
}
}
// dump obj into store
function storeSetFromObj(store, obj){
Object.keys(obj).forEach(function(key){
store.set(key, obj[key])
})
}
// handle rpc requests // handle rpc requests
function onRpcRequest(remoteStream, payload){ function onRpcRequest(remoteStream, payload){
// console.log('MetaMaskPlugin - incoming payload:', payload) // console.log('MetaMaskPlugin - incoming payload:', payload)
@ -84,27 +137,20 @@ function onRpcRequest(remoteStream, payload){
// //
// popup integration // remote features
// //
function handleInternalCommunication(portStream){ function setupPublicConfig(stream){
// setup multiplexing var storeStream = publicConfigStore.createStream()
var mx = ObjectMultiplex() stream.pipe(storeStream).pipe(stream)
portStream.pipe(mx).pipe(portStream) }
mx.on('error', function(err) {
console.error(err) function setupProviderConnection(stream){
// portStream.destroy() stream.on('data', onRpcRequest.bind(null, stream))
})
portStream.on('error', function(err) {
console.error(err)
mx.destroy()
})
linkDnode(mx.createStream('dnode'))
handleEthRpcRequestStream(mx.createStream('provider'))
} }
function linkDnode(stream){ function setupControllerConnection(stream){
var connection = Dnode({ var dnode = Dnode({
getState: function(cb){ cb(null, getState()) }, getState: function(cb){ cb(null, getState()) },
setRpcTarget: setRpcTarget, setRpcTarget: setRpcTarget,
useEtherscanProvider: useEtherscanProvider, useEtherscanProvider: useEtherscanProvider,
@ -119,8 +165,8 @@ function linkDnode(stream){
clearSeedWordCache: idStore.clearSeedWordCache.bind(idStore), clearSeedWordCache: idStore.clearSeedWordCache.bind(idStore),
exportAccount: idStore.exportAccount.bind(idStore), exportAccount: idStore.exportAccount.bind(idStore),
}) })
stream.pipe(connection).pipe(stream) stream.pipe(dnode).pipe(stream)
connection.on('remote', function(remote){ dnode.on('remote', function(remote){
// push updates to popup // push updates to popup
ethStore.on('update', sendUpdate) ethStore.on('update', sendUpdate)

@ -1,31 +1,42 @@
const XHR = window.XMLHttpRequest
// bring in web3 but rename on window
const Web3 = require('web3')
delete window.Web3
window.MetamaskWeb3 = Web3
const createPayload = require('web3-provider-engine/util/create-payload') const createPayload = require('web3-provider-engine/util/create-payload')
const StreamProvider = require('./lib/stream-provider.js') const StreamProvider = require('./lib/stream-provider.js')
const LocalMessageDuplexStream = require('./lib/local-message-stream.js') const LocalMessageDuplexStream = require('./lib/local-message-stream.js')
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
const RemoteStore = require('./lib/remote-store.js').RemoteStore
const Web3 = require('web3')
const RPC_URL = 'https://testrpc.metamask.io/' // rename on window
delete window.Web3
window.MetamaskWeb3 = Web3
const DEFAULT_RPC_URL = 'https://rpc.metamask.io/'
// //
// setup plugin communication // setup plugin communication
// //
// setup background connection
var pluginStream = new LocalMessageDuplexStream({ var pluginStream = new LocalMessageDuplexStream({
name: 'inpage', name: 'inpage',
target: 'contentscript', target: 'contentscript',
}) })
var mx = setupMultiplex(pluginStream)
// connect features
var remoteProvider = new StreamProvider() var remoteProvider = new StreamProvider()
remoteProvider.pipe(pluginStream).pipe(remoteProvider) remoteProvider.pipe(mx.createStream('provider')).pipe(remoteProvider)
pluginStream.on('error', console.error.bind(console))
remoteProvider.on('error', console.error.bind(console)) remoteProvider.on('error', console.error.bind(console))
var initState = JSON.parse(localStorage['MetaMask-Config'] || '{}')
var publicConfigStore = new RemoteStore(initState)
var storeStream = publicConfigStore.createStream()
storeStream.pipe(mx.createStream('publicConfig')).pipe(storeStream)
publicConfigStore.subscribe(function(state){
localStorage['MetaMask-Config'] = JSON.stringify(state)
})
// //
// global web3 // global web3
// //
@ -42,42 +53,39 @@ console.log('MetaMask - injected web3')
// handle synchronous requests // handle synchronous requests
// //
// handle accounts cache global.publicConfigStore = publicConfigStore
var accountsCache = JSON.parse(localStorage['MetaMask-Accounts'] || '[]')
web3.eth.defaultAccount = accountsCache[0] // set web3 defaultAcount
publicConfigStore.subscribe(function(state){
setInterval(populateAccountsCache, 4000) web3.eth.defaultAccount = state.selectedAddress
function populateAccountsCache(){ })
remoteProvider.sendAsync(createPayload({
method: 'eth_accounts', // setup sync http provider
params: [], var providerConfig = publicConfigStore.get('provider') || {}
isMetamaskInternal: true, var providerUrl = providerConfig.rpcTarget ? providerConfig.rpcTarget : DEFAULT_RPC_URL
}), function(err, response){ var syncProvider = new Web3.providers.HttpProvider(providerUrl)
if (err) return console.error('MetaMask - Error polling accounts') publicConfigStore.subscribe(function(state){
// update localStorage if (!state.provider) return
var accounts = response.result if (!state.provider.rpcTarget || state.provider.rpcTarget === providerUrl) return
if (accounts.toString() !== accountsCache.toString()) { providerUrl = state.provider.rpcTarget
accountsCache = accounts syncProvider = new Web3.providers.HttpProvider(providerUrl)
web3.eth.defaultAccount = accountsCache[0] })
localStorage['MetaMask-Accounts'] = JSON.stringify(accounts)
}
})
}
// handle synchronous methods via standard http provider // handle sync methods
var syncProvider = new Web3.providers.HttpProvider(RPC_URL)
remoteProvider.send = function(payload){ remoteProvider.send = function(payload){
var result = null var result = null
switch (payload.method) { switch (payload.method) {
case 'eth_accounts': case 'eth_accounts':
// read from localStorage // read from localStorage
result = accountsCache var selectedAddress = publicConfigStore.get('selectedAddress')
result = selectedAddress ? [selectedAddress] : []
break break
case 'eth_coinbase': case 'eth_coinbase':
// read from localStorage // read from localStorage
result = accountsCache[0] || '0x0000000000000000000000000000000000000000' var selectedAddress = publicConfigStore.get('selectedAddress')
result = selectedAddress || '0x0000000000000000000000000000000000000000'
break break
// fallback to normal rpc // fallback to normal rpc

@ -15,6 +15,8 @@ const migrations = require('./migrations')
*/ */
module.exports = ConfigManager module.exports = ConfigManager
function ConfigManager() { function ConfigManager() {
// ConfigManager is observable and will emit updates
this._subs = []
/* The migrator exported on the config-manager /* The migrator exported on the config-manager
* has two methods the user should be concerned with: * has two methods the user should be concerned with:
@ -47,6 +49,7 @@ ConfigManager.prototype.setConfig = function(config) {
var data = this.migrator.getData() var data = this.migrator.getData()
data.config = config data.config = config
this.setData(data) this.setData(data)
this._emitUpdates(config)
} }
ConfigManager.prototype.getConfig = function() { ConfigManager.prototype.getConfig = function() {
@ -127,6 +130,30 @@ ConfigManager.prototype.clearWallet = function() {
this.setData(data) this.setData(data)
} }
ConfigManager.prototype.setData = function(data) {
this.migrator.saveData(data)
}
// observable
ConfigManager.prototype.subscribe = function(fn){
this._subs.push(fn)
var unsubscribe = this.unsubscribe.bind(this, fn)
return unsubscribe
}
ConfigManager.prototype.unsubscribe = function(fn){
var index = this._subs.indexOf(fn)
if (index !== -1) this._subs.splice(index, 1)
}
ConfigManager.prototype._emitUpdates = function(state){
this._subs.forEach(function(handler){
handler(state)
})
}
function loadData() { function loadData() {
var oldData = getOldStyleData() var oldData = getOldStyleData()

@ -11,7 +11,7 @@ function ObjectMultiplex(opts){
var data = chunk.data var data = chunk.data
var substream = mx.streams[name] var substream = mx.streams[name]
if (!substream) { if (!substream) {
console.warn("orphaned data for stream " + name) console.warn('orphaned data for stream ' + name)
} else { } else {
substream.push(data) substream.push(data)
} }

@ -0,0 +1,97 @@
const Dnode = require('dnode')
const inherits = require('util').inherits
module.exports = {
HostStore: HostStore,
RemoteStore: RemoteStore,
}
function BaseStore(initState){
this._state = initState || {}
this._subs = []
}
BaseStore.prototype.set = function(key, value){
throw Error('Not implemented.')
}
BaseStore.prototype.get = function(key){
return this._state[key]
}
BaseStore.prototype.subscribe = function(fn){
this._subs.push(fn)
var unsubscribe = this.unsubscribe.bind(this, fn)
return unsubscribe
}
BaseStore.prototype.unsubscribe = function(fn){
var index = this._subs.indexOf(fn)
if (index !== -1) this._subs.splice(index, 1)
}
BaseStore.prototype._emitUpdates = function(state){
this._subs.forEach(function(handler){
handler(state)
})
}
//
// host
//
inherits(HostStore, BaseStore)
function HostStore(initState, opts){
BaseStore.call(this, initState)
}
HostStore.prototype.set = function(key, value){
this._state[key] = value
process.nextTick(this._emitUpdates.bind(this, this._state))
}
HostStore.prototype.createStream = function(){
var dnode = Dnode({
// update: this._didUpdate.bind(this),
})
dnode.on('remote', this._didConnect.bind(this))
return dnode
}
HostStore.prototype._didConnect = function(remote){
this.subscribe(function(state){
remote.update(state)
})
remote.update(this._state)
}
//
// remote
//
inherits(RemoteStore, BaseStore)
function RemoteStore(initState, opts){
BaseStore.call(this, initState)
this._remote = null
}
RemoteStore.prototype.set = function(key, value){
this._remote.set(key, value)
}
RemoteStore.prototype.createStream = function(){
var dnode = Dnode({
update: this._didUpdate.bind(this),
})
dnode.once('remote', this._didConnect.bind(this))
return dnode
}
RemoteStore.prototype._didConnect = function(remote){
this._remote = remote
}
RemoteStore.prototype._didUpdate = function(state){
this._state = state
this._emitUpdates(state)
}

@ -1,9 +1,11 @@
const Through = require('through2') const Through = require('through2')
const ObjectMultiplex = require('./obj-multiplex')
module.exports = { module.exports = {
jsonParseStream: jsonParseStream, jsonParseStream: jsonParseStream,
jsonStringifyStream: jsonStringifyStream, jsonStringifyStream: jsonStringifyStream,
setupMultiplex: setupMultiplex,
} }
function jsonParseStream(){ function jsonParseStream(){
@ -19,3 +21,17 @@ function jsonStringifyStream(){
cb() cb()
}) })
} }
function setupMultiplex(connectionStream){
var mx = ObjectMultiplex()
connectionStream.pipe(mx).pipe(connectionStream)
mx.on('error', function(err) {
console.error(err)
// connectionStream.destroy()
})
connectionStream.on('error', function(err) {
console.error(err)
mx.destroy()
})
return mx
}

@ -1,7 +1,6 @@
const url = require('url') const url = require('url')
const EventEmitter = require('events').EventEmitter const EventEmitter = require('events').EventEmitter
const async = require('async') const async = require('async')
const ObjectMultiplex = require('./lib/obj-multiplex')
const Dnode = require('dnode') const Dnode = require('dnode')
const Web3 = require('web3') const Web3 = require('web3')
const MetaMaskUi = require('../../ui') const MetaMaskUi = require('../../ui')
@ -9,6 +8,7 @@ const MetaMaskUiCss = require('../../ui/css')
const injectCss = require('inject-css') const injectCss = require('inject-css')
const PortStream = require('./lib/port-stream.js') const PortStream = require('./lib/port-stream.js')
const StreamProvider = require('./lib/stream-provider.js') const StreamProvider = require('./lib/stream-provider.js')
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
// setup app // setup app
var css = MetaMaskUiCss() var css = MetaMaskUiCss()
@ -24,21 +24,13 @@ function connectToAccountManager(cb){
var pluginPort = chrome.runtime.connect({name: 'popup'}) var pluginPort = chrome.runtime.connect({name: 'popup'})
var portStream = new PortStream(pluginPort) var portStream = new PortStream(pluginPort)
// setup multiplexing // setup multiplexing
var mx = ObjectMultiplex() var mx = setupMultiplex(portStream)
portStream.pipe(mx).pipe(portStream) // connect features
mx.on('error', function(err) { setupControllerConnection(mx.createStream('controller'), cb)
console.error(err) setupWeb3Connection(mx.createStream('provider'))
portStream.destroy()
})
portStream.on('error', function(err) {
console.error(err)
mx.destroy()
})
linkDnode(mx.createStream('dnode'), cb)
linkWeb3(mx.createStream('provider'))
} }
function linkWeb3(stream){ function setupWeb3Connection(stream){
var remoteProvider = new StreamProvider() var remoteProvider = new StreamProvider()
remoteProvider.pipe(stream).pipe(remoteProvider) remoteProvider.pipe(stream).pipe(remoteProvider)
stream.on('error', console.error.bind(console)) stream.on('error', console.error.bind(console))
@ -46,7 +38,7 @@ function linkWeb3(stream){
global.web3 = new Web3(remoteProvider) global.web3 = new Web3(remoteProvider)
} }
function linkDnode(stream, cb){ function setupControllerConnection(stream, cb){
var eventEmitter = new EventEmitter() var eventEmitter = new EventEmitter()
var background = Dnode({ var background = Dnode({
sendUpdate: function(state){ sendUpdate: function(state){

Loading…
Cancel
Save