From d840e81a101351bd661668cf0b1f9e5b73683890 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 15 Apr 2016 12:12:04 -0700 Subject: wiring - trusted-untrusted features + remote-store --- app/scripts/background.js | 62 ++++++++++++++----------- app/scripts/inpage.js | 30 ++++++++----- app/scripts/lib/obj-multiplex.js | 2 +- app/scripts/lib/remote-store.js | 97 ++++++++++++++++++++++++++++++++++++++++ app/scripts/lib/stream-utils.js | 16 +++++++ app/scripts/popup.js | 22 +++------ 6 files changed, 178 insertions(+), 51 deletions(-) create mode 100644 app/scripts/lib/remote-store.js diff --git a/app/scripts/background.js b/app/scripts/background.js index f3dd8cbb6..0bd7031d8 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -9,8 +9,8 @@ const MetaMaskProvider = require('./lib/zero.js') const IdentityStore = require('./lib/idStore') const createTxNotification = require('./lib/tx-notification.js') const configManager = require('./lib/config-manager-singleton') -const jsonParseStream = require('./lib/stream-utils.js').jsonParseStream -const jsonStringifyStream = require('./lib/stream-utils.js').jsonStringifyStream +const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex +const HostStore = require('./lib/remote-store.js').HostStore // // connect to other contexts @@ -22,15 +22,27 @@ function connectRemote(remotePort){ var portStream = new PortStream(remotePort) if (isMetaMaskInternalProcess) { // communication with popup - handleInternalCommunication(portStream) + setupTrustedCommunication(portStream) } else { // communication with page - handleEthRpcRequestStream(portStream) + setupUntrustedCommunication(portStream) } } -function handleEthRpcRequestStream(stream){ - stream.on('data', onRpcRequest.bind(null, stream)) +function setupUntrustedCommunication(connectionStream){ + // 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')) } // @@ -59,6 +71,13 @@ provider.on('block', function(block){ var ethStore = new EthStore(provider) idStore.setStore(ethStore) +// copy idStore substate into public store +var publicConfigStore = new HostStore() +idStore.on('update', function(state){ + publicConfigStore.set('selectedAddress', state.selectedAddress) +}) + + function getState(){ var state = extend( ethStore.getState(), @@ -84,27 +103,20 @@ function onRpcRequest(remoteStream, payload){ // -// popup integration +// remote features // -function handleInternalCommunication(portStream){ - // setup multiplexing - var mx = ObjectMultiplex() - portStream.pipe(mx).pipe(portStream) - mx.on('error', function(err) { - console.error(err) - // portStream.destroy() - }) - portStream.on('error', function(err) { - console.error(err) - mx.destroy() - }) - linkDnode(mx.createStream('dnode')) - handleEthRpcRequestStream(mx.createStream('provider')) +function setupPublicConfig(stream){ + var storeStream = publicConfigStore.createStream() + stream.pipe(storeStream).pipe(stream) +} + +function setupProviderConnection(stream){ + stream.on('data', onRpcRequest.bind(null, stream)) } -function linkDnode(stream){ - var connection = Dnode({ +function setupControllerConnection(stream){ + var dnode = Dnode({ getState: function(cb){ cb(null, getState()) }, setRpcTarget: setRpcTarget, useEtherscanProvider: useEtherscanProvider, @@ -119,8 +131,8 @@ function linkDnode(stream){ clearSeedWordCache: idStore.clearSeedWordCache.bind(idStore), exportAccount: idStore.exportAccount.bind(idStore), }) - stream.pipe(connection).pipe(stream) - connection.on('remote', function(remote){ + stream.pipe(dnode).pipe(stream) + dnode.on('remote', function(remote){ // push updates to popup ethStore.on('update', sendUpdate) diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 01f35d0fe..b59cf1b0c 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -1,13 +1,13 @@ -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 StreamProvider = require('./lib/stream-provider.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') + +// rename on window +delete window.Web3 +window.MetamaskWeb3 = Web3 const RPC_URL = 'https://testrpc.metamask.io/' @@ -16,16 +16,26 @@ const RPC_URL = 'https://testrpc.metamask.io/' // setup plugin communication // +// setup background connection var pluginStream = new LocalMessageDuplexStream({ name: 'inpage', target: 'contentscript', }) +var mx = setupMultiplex(pluginStream) +// connect features var remoteProvider = new StreamProvider() -remoteProvider.pipe(pluginStream).pipe(remoteProvider) - -pluginStream.on('error', console.error.bind(console)) +remoteProvider.pipe(mx.createStream('provider')).pipe(remoteProvider) remoteProvider.on('error', console.error.bind(console)) +var publicConfigStore = new RemoteStore() +var storeStream = publicConfigStore.createStream() +storeStream.pipe(mx.createStream('publicConfig')).pipe(storeStream) + +publicConfigStore.subscribe(function(state){ + console.log('store updated:', state) +}) + + // // global web3 // diff --git a/app/scripts/lib/obj-multiplex.js b/app/scripts/lib/obj-multiplex.js index 333b6061f..ad1d914f8 100644 --- a/app/scripts/lib/obj-multiplex.js +++ b/app/scripts/lib/obj-multiplex.js @@ -11,7 +11,7 @@ function ObjectMultiplex(opts){ var data = chunk.data var substream = mx.streams[name] if (!substream) { - console.warn("orphaned data for stream " + name) + console.warn('orphaned data for stream ' + name) } else { substream.push(data) } diff --git a/app/scripts/lib/remote-store.js b/app/scripts/lib/remote-store.js new file mode 100644 index 000000000..2dbdde811 --- /dev/null +++ b/app/scripts/lib/remote-store.js @@ -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) +} diff --git a/app/scripts/lib/stream-utils.js b/app/scripts/lib/stream-utils.js index 12560ffd8..fd4417d94 100644 --- a/app/scripts/lib/stream-utils.js +++ b/app/scripts/lib/stream-utils.js @@ -1,9 +1,11 @@ const Through = require('through2') +const ObjectMultiplex = require('./obj-multiplex') module.exports = { jsonParseStream: jsonParseStream, jsonStringifyStream: jsonStringifyStream, + setupMultiplex: setupMultiplex, } function jsonParseStream(){ @@ -19,3 +21,17 @@ function jsonStringifyStream(){ 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 +} \ No newline at end of file diff --git a/app/scripts/popup.js b/app/scripts/popup.js index 3049ff2c3..85b3e30f9 100644 --- a/app/scripts/popup.js +++ b/app/scripts/popup.js @@ -1,7 +1,6 @@ const url = require('url') const EventEmitter = require('events').EventEmitter const async = require('async') -const ObjectMultiplex = require('./lib/obj-multiplex') const Dnode = require('dnode') const Web3 = require('web3') const MetaMaskUi = require('../../ui') @@ -9,6 +8,7 @@ const MetaMaskUiCss = require('../../ui/css') const injectCss = require('inject-css') const PortStream = require('./lib/port-stream.js') const StreamProvider = require('./lib/stream-provider.js') +const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex // setup app var css = MetaMaskUiCss() @@ -24,21 +24,13 @@ function connectToAccountManager(cb){ var pluginPort = chrome.runtime.connect({name: 'popup'}) var portStream = new PortStream(pluginPort) // setup multiplexing - var mx = ObjectMultiplex() - portStream.pipe(mx).pipe(portStream) - mx.on('error', function(err) { - console.error(err) - portStream.destroy() - }) - portStream.on('error', function(err) { - console.error(err) - mx.destroy() - }) - linkDnode(mx.createStream('dnode'), cb) - linkWeb3(mx.createStream('provider')) + var mx = setupMultiplex(portStream) + // connect features + setupControllerConnection(mx.createStream('controller'), cb) + setupWeb3Connection(mx.createStream('provider')) } -function linkWeb3(stream){ +function setupWeb3Connection(stream){ var remoteProvider = new StreamProvider() remoteProvider.pipe(stream).pipe(remoteProvider) stream.on('error', console.error.bind(console)) @@ -46,7 +38,7 @@ function linkWeb3(stream){ global.web3 = new Web3(remoteProvider) } -function linkDnode(stream, cb){ +function setupControllerConnection(stream, cb){ var eventEmitter = new EventEmitter() var background = Dnode({ sendUpdate: function(state){ -- cgit