diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index a256a3f5b..1b7b98ec9 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -1,5 +1,7 @@ const LocalMessageDuplexStream = require('./lib/local-message-stream.js') const PortStream = require('./lib/port-stream.js') +const ObjectMultiplex = require('./lib/obj-multiplex') + // inject in-page script @@ -15,13 +17,22 @@ var pageStream = new LocalMessageDuplexStream({ name: 'contentscript', target: 'inpage', }) +pageStream.on('error', console.error.bind(console)) var pluginPort = chrome.runtime.connect({name: 'contentscript'}) var pluginStream = new PortStream(pluginPort) +pluginStream.on('error', console.error.bind(console)) -// forward communication across -pageStream.pipe(pluginStream) -pluginStream.pipe(pageStream) +// forward communication plugin->inpage +pageStream.pipe(pluginStream).pipe(pageStream) -// log errors -pageStream.on('error', console.error.bind(console)) -pluginStream.on('error', console.error.bind(console)) \ No newline at end of file +// connect contentscript->inpage control stream +var mx = ObjectMultiplex() +mx.on('error', console.error.bind(console)) +mx.pipe(pageStream) +var controlStream = mx.createStream('control') +controlStream.on('error', console.error.bind(console)) + +// if we lose connection with the plugin, trigger tab refresh +pluginStream.on('close', function(){ + controlStream.write({ method: 'reset' }) +}) \ No newline at end of file diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 54470220f..33e2c9358 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -5,6 +5,7 @@ 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 once = require('once') restoreContextAfterImports() // rename on window @@ -24,32 +25,61 @@ var pluginStream = new LocalMessageDuplexStream({ target: 'contentscript', }) var mx = setupMultiplex(pluginStream) -// connect features + +// connect to provider var remoteProvider = new StreamProvider() remoteProvider.pipe(mx.createStream('provider')).pipe(remoteProvider) remoteProvider.on('error', console.error.bind(console)) +// subscribe to metamask public config 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 +// setup web3 // var web3 = new Web3(remoteProvider) -window.web3 = web3 web3.setProvider = function(){ console.log('MetaMask - overrode web3.setProvider') } console.log('MetaMask - injected web3') +// +// automatic dapp reset +// + +// export web3 as a global, checking for usage +var pageIsUsingWeb3 = false +var resetWasRequested = false +window.web3 = ensnare(web3, once(function(){ + // if web3 usage happened after a reset request, trigger reset late + if (resetWasRequested) return triggerReset() + // mark web3 as used + pageIsUsingWeb3 = true + // reset web3 reference + window.web3 = web3 +})) + +// listen for reset requests +mx.createStream('control').once('data', function(){ + resetWasRequested = true + // ignore if web3 was not used + if (!pageIsUsingWeb3) return + // reload after short timeout + triggerReset() +}) + +function triggerReset(){ + setTimeout(function(){ + window.location.reload() + }, 500) +} // // handle synchronous requests @@ -104,6 +134,34 @@ remoteProvider.send = function(payload){ } } + +// +// util +// + +// creates a proxy object that calls cb everytime the obj's properties/fns are accessed +function ensnare(obj, cb){ + var proxy = {} + Object.keys(obj).forEach(function(key){ + var val = obj[key] + switch (typeof val) { + case 'function': + proxy[key] = function(){ + cb() + val.apply(obj, arguments) + } + return + default: + Object.defineProperty(proxy, key, { + get: function(){ cb(); return obj[key] }, + set: function(val){ cb(); return obj[key] = val }, + }) + return + } + }) + return proxy +} + // need to make sure we aren't affected by overlapping namespaces // and that we dont affect the app with our namespace // mostly a fix for web3's BigNumber if AMD's "define" is defined... @@ -116,4 +174,4 @@ function cleanContextForImports(){ function restoreContextAfterImports(){ global.define = __define -} \ No newline at end of file +} diff --git a/app/scripts/lib/local-message-stream.js b/app/scripts/lib/local-message-stream.js index 42d193e04..76fedd9df 100644 --- a/app/scripts/lib/local-message-stream.js +++ b/app/scripts/lib/local-message-stream.js @@ -23,7 +23,7 @@ function LocalMessageDuplexStream(opts){ LocalMessageDuplexStream.prototype._onMessage = function(event){ var msg = event.data - // console.log('LocalMessageDuplexStream ('+this._name+') - heard message...') + // console.log('LocalMessageDuplexStream ('+this._name+') - heard message...', event) // validate message if (event.origin !== location.origin) return //console.log('LocalMessageDuplexStream ('+this._name+') - rejected - (event.origin !== location.origin) ') if (typeof msg !== 'object') return //console.log('LocalMessageDuplexStream ('+this._name+') - rejected - (typeof msg !== "object") ') @@ -31,7 +31,11 @@ LocalMessageDuplexStream.prototype._onMessage = function(event){ if (!msg.data) return //console.log('LocalMessageDuplexStream ('+this._name+') - rejected - (!msg.data) ') // console.log('LocalMessageDuplexStream ('+this._name+') - accepted', msg.data) // forward message - this.push(msg.data) + try { + this.push(msg.data) + } catch(err) { + this.emit('error', err) + } } // stream plumbing diff --git a/app/scripts/lib/port-stream.js b/app/scripts/lib/port-stream.js index a6c974d6d..2644741fc 100644 --- a/app/scripts/lib/port-stream.js +++ b/app/scripts/lib/port-stream.js @@ -31,7 +31,8 @@ PortDuplexStream.prototype._onMessage = function(msg){ PortDuplexStream.prototype._onDisconnect = function(){ try { - this.end() + // this.end() + this.emit('close') } catch(err){ this.emit('error', err) } @@ -54,6 +55,7 @@ PortDuplexStream.prototype._write = function(msg, encoding, cb){ } cb() } catch(err){ + console.error(err) // this.emit('error', err) cb(new Error('PortDuplexStream - disconnected')) } diff --git a/app/scripts/lib/stream-utils.js b/app/scripts/lib/stream-utils.js index fd4417d94..ca245ca9a 100644 --- a/app/scripts/lib/stream-utils.js +++ b/app/scripts/lib/stream-utils.js @@ -27,7 +27,6 @@ function setupMultiplex(connectionStream){ connectionStream.pipe(mx).pipe(connectionStream) mx.on('error', function(err) { console.error(err) - // connectionStream.destroy() }) connectionStream.on('error', function(err) { console.error(err) diff --git a/package.json b/package.json index 8d99f1cae..7bc403494 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "inject-css": "^0.1.1", "metamask-logo": "^1.1.5", "multiplex": "^6.7.0", + "once": "^1.3.3", "pojo-migrator": "^2.1.0", "polyfill-crypto.getrandomvalues": "^1.0.0", "pumpify": "^1.3.4", @@ -46,7 +47,7 @@ "react-dom": "^0.14.3", "react-hyperscript": "^2.2.2", "react-redux": "^4.0.3", - "readable-stream": "^2.0.5", + "readable-stream": "^2.1.2", "redux": "^3.0.5", "redux-logger": "^2.3.1", "redux-thunk": "^1.0.2",