From cc56d0d2f61da576acc72b1e7f63df4015469267 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 24 Aug 2017 15:44:40 -0700 Subject: [PATCH 01/51] inpage - use json-rpc-engine for inpage-provider --- app/scripts/lib/inpage-provider.js | 46 +++++++----------------------- app/scripts/metamask-controller.js | 42 ++++++++++++++++++++------- package.json | 1 + 3 files changed, 43 insertions(+), 46 deletions(-) diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js index fd032a673..de6e8b811 100644 --- a/app/scripts/lib/inpage-provider.js +++ b/app/scripts/lib/inpage-provider.js @@ -1,8 +1,8 @@ const pipe = require('pump') -const StreamProvider = require('web3-stream-provider') +const RpcEngine = require('json-rpc-engine') +const createStreamMiddleware = require('json-rpc-middleware-stream') const LocalStorageStore = require('obs-store') const ObjectMultiplex = require('./obj-multiplex') -const createRandomId = require('./random-id') module.exports = MetamaskInpageProvider @@ -30,38 +30,20 @@ function MetamaskInpageProvider (connectionStream) { multiStream.ignoreStream('phishing') // connect to async provider - const asyncProvider = self.asyncProvider = new StreamProvider() + const streamMiddleware = createStreamMiddleware() pipe( - asyncProvider, + streamMiddleware.stream, multiStream.createStream('provider'), - asyncProvider, + streamMiddleware.stream, (err) => logStreamDisconnectWarning('MetaMask RpcProvider', err) ) // start and stop polling to unblock first block lock - self.idMap = {} - // handle sendAsync requests via asyncProvider - self.sendAsync = function (payload, cb) { - // rewrite request ids - var request = eachJsonMessage(payload, (message) => { - var newId = createRandomId() - self.idMap[newId] = message.id - message.id = newId - return message - }) - // forward to asyncProvider - asyncProvider.sendAsync(request, function (err, res) { - if (err) return cb(err) - // transform messages to original ids - eachJsonMessage(res, (message) => { - var oldId = self.idMap[message.id] - delete self.idMap[message.id] - message.id = oldId - return message - }) - cb(null, res) - }) - } + // handle sendAsync requests via dapp-side rpc engine + const engine = new RpcEngine() + engine.push(streamMiddleware) + + self.sendAsync = engine.handle.bind(engine) } MetamaskInpageProvider.prototype.send = function (payload) { @@ -121,14 +103,6 @@ MetamaskInpageProvider.prototype.isMetaMask = true // util -function eachJsonMessage (payload, transformFn) { - if (Array.isArray(payload)) { - return payload.map(transformFn) - } else { - return transformFn(payload) - } -} - function logStreamDisconnectWarning (remoteLabel, err) { let warningMsg = `MetamaskInpageProvider - lost connection to ${remoteLabel}` if (err) warningMsg += '\n' + err.stack diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index a007d6fc5..e4b1b5975 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -6,7 +6,8 @@ const Dnode = require('dnode') const ObservableStore = require('obs-store') const EthStore = require('./lib/eth-store') const EthQuery = require('eth-query') -const streamIntoProvider = require('web3-stream-provider/handler') +const RpcEngine = require('json-rpc-engine') +const createEngineStream = require('json-rpc-middleware-stream/engineStream') const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex const KeyringController = require('./keyring-controller') const NetworkController = require('./controllers/network') @@ -375,19 +376,40 @@ module.exports = class MetamaskController extends EventEmitter { } setupProviderConnection (outStream, originDomain) { - streamIntoProvider(outStream, this.provider, onRequest, onResponse) + const engine = new RpcEngine() + engine.push(originMiddleware) + engine.push(loggerMiddleware) + engine.push(createProviderMiddleware({ provider: this.provider })) + + // setup connection + const providerStream = createEngineStream({ engine }) + outStream.pipe(providerStream).pipe(outStream) + // append dapp origin domain to request - function onRequest (request) { - request.origin = originDomain + function originMiddleware (req, res, next, end) { + req.origin = originDomain + next() } // log rpc activity - function onResponse (err, request, response) { - if (err) return console.error(err) - if (response.error) { - console.error('Error in RPC response:\n', response) + function loggerMiddleware (req, res, next, end) { + next((cb) => { + if (res.error) { + console.error('Error in RPC response:\n', res) + } + if (req.isMetamaskInternal) return + log.info(`RPC (${originDomain}):`, req, '->', res) + cb() + }) + } + // forward requests to provider + function createProviderMiddleware({ provider }) { + return (req, res, next, end) => { + provider.sendAsync(req, (err, _res) => { + if (err) return end(err) + res.result = _res.result + end() + }) } - if (request.isMetamaskInternal) return - log.info(`RPC (${originDomain}):`, request, '->', response) } } diff --git a/package.json b/package.json index 9a09e6305..a0d241341 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ "iframe-stream": "^3.0.0", "inject-css": "^0.1.1", "jazzicon": "^1.2.0", + "json-rpc-middleware-stream": "^1.0.0", "loglevel": "^1.4.1", "metamask-logo": "^2.1.2", "mississippi": "^1.2.0", From 440a42bbc38ed53b64dc017fd56bd3281355df33 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 7 Sep 2017 10:08:07 -0700 Subject: [PATCH 02/51] inpage - add idRemapMiddleware --- app/scripts/lib/inpage-provider.js | 4 +++- package.json | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js index de6e8b811..c095846e1 100644 --- a/app/scripts/lib/inpage-provider.js +++ b/app/scripts/lib/inpage-provider.js @@ -1,5 +1,6 @@ const pipe = require('pump') const RpcEngine = require('json-rpc-engine') +const createIdRemapMiddleware = require('json-rpc-engine/src/idRemapMiddleware') const createStreamMiddleware = require('json-rpc-middleware-stream') const LocalStorageStore = require('obs-store') const ObjectMultiplex = require('./obj-multiplex') @@ -27,7 +28,7 @@ function MetamaskInpageProvider (connectionStream) { ) // ignore phishing warning message (handled elsewhere) - multiStream.ignoreStream('phishing') + multiStream.ignoreStream('phishing') // connect to async provider const streamMiddleware = createStreamMiddleware() @@ -41,6 +42,7 @@ function MetamaskInpageProvider (connectionStream) { // handle sendAsync requests via dapp-side rpc engine const engine = new RpcEngine() + engine.push(createIdRemapMiddleware()) engine.push(streamMiddleware) self.sendAsync = engine.handle.bind(engine) diff --git a/package.json b/package.json index a0d241341..7712707c6 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ "iframe-stream": "^3.0.0", "inject-css": "^0.1.1", "jazzicon": "^1.2.0", + "json-rpc-engine": "^3.1.0", "json-rpc-middleware-stream": "^1.0.0", "loglevel": "^1.4.1", "metamask-logo": "^2.1.2", From 57e4805c621155cd86169064f4aaba34b73644c6 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 7 Sep 2017 21:17:49 -0700 Subject: [PATCH 03/51] streams - use pump and published obj-multiplex --- app/scripts/lib/obj-multiplex.js | 48 ------------------------------ app/scripts/lib/port-stream.js | 16 ++-------- app/scripts/lib/stream-utils.js | 23 +++++++------- app/scripts/metamask-controller.js | 34 ++++++++++++++++----- package.json | 4 ++- 5 files changed, 44 insertions(+), 81 deletions(-) delete mode 100644 app/scripts/lib/obj-multiplex.js diff --git a/app/scripts/lib/obj-multiplex.js b/app/scripts/lib/obj-multiplex.js deleted file mode 100644 index 0034febe0..000000000 --- a/app/scripts/lib/obj-multiplex.js +++ /dev/null @@ -1,48 +0,0 @@ -const through = require('through2') - -module.exports = ObjectMultiplex - -function ObjectMultiplex (opts) { - opts = opts || {} - // create multiplexer - const mx = through.obj(function (chunk, enc, cb) { - const name = chunk.name - const data = chunk.data - if (!name) { - console.warn(`ObjectMultiplex - Malformed chunk without name "${chunk}"`) - return cb() - } - const substream = mx.streams[name] - if (!substream) { - console.warn(`ObjectMultiplex - orphaned data for stream "${name}"`) - } else { - if (substream.push) substream.push(data) - } - return cb() - }) - mx.streams = {} - // create substreams - mx.createStream = function (name) { - const substream = mx.streams[name] = through.obj(function (chunk, enc, cb) { - mx.push({ - name: name, - data: chunk, - }) - return cb() - }) - mx.on('end', function () { - return substream.emit('end') - }) - if (opts.error) { - mx.on('error', function () { - return substream.emit('error') - }) - } - return substream - } - // ignore streams (dont display orphaned data warning) - mx.ignoreStream = function (name) { - mx.streams[name] = true - } - return mx -} diff --git a/app/scripts/lib/port-stream.js b/app/scripts/lib/port-stream.js index 607a9c9ed..648d88087 100644 --- a/app/scripts/lib/port-stream.js +++ b/app/scripts/lib/port-stream.js @@ -1,5 +1,6 @@ const Duplex = require('readable-stream').Duplex const inherits = require('util').inherits +const noop = function(){} module.exports = PortDuplexStream @@ -20,20 +21,14 @@ PortDuplexStream.prototype._onMessage = function (msg) { if (Buffer.isBuffer(msg)) { delete msg._isBuffer var data = new Buffer(msg) - // console.log('PortDuplexStream - saw message as buffer', data) this.push(data) } else { - // console.log('PortDuplexStream - saw message', msg) this.push(msg) } } PortDuplexStream.prototype._onDisconnect = function () { - try { - this.push(null) - } catch (err) { - this.emit('error', err) - } + this.destroy() } // stream plumbing @@ -45,19 +40,12 @@ PortDuplexStream.prototype._write = function (msg, encoding, cb) { if (Buffer.isBuffer(msg)) { var data = msg.toJSON() data._isBuffer = true - // console.log('PortDuplexStream - sent message as buffer', data) this._port.postMessage(data) } else { - // console.log('PortDuplexStream - sent message', msg) this._port.postMessage(msg) } } catch (err) { - // console.error(err) return cb(new Error('PortDuplexStream - disconnected')) } cb() } - -// util - -function noop () {} diff --git a/app/scripts/lib/stream-utils.js b/app/scripts/lib/stream-utils.js index ba79990cc..89e2a359e 100644 --- a/app/scripts/lib/stream-utils.js +++ b/app/scripts/lib/stream-utils.js @@ -1,6 +1,7 @@ const Through = require('through2') const endOfStream = require('end-of-stream') -const ObjectMultiplex = require('./obj-multiplex') +const ObjectMultiplex = require('obj-multiplex') +const pump = require('pump') module.exports = { jsonParseStream: jsonParseStream, @@ -23,14 +24,14 @@ function jsonStringifyStream () { } function setupMultiplex (connectionStream) { - var mx = ObjectMultiplex() - connectionStream.pipe(mx).pipe(connectionStream) - endOfStream(mx, function (err) { - if (err) console.error(err) - }) - endOfStream(connectionStream, function (err) { - if (err) console.error(err) - mx.destroy() - }) - return mx + const mux = new ObjectMultiplex() + pump( + connectionStream, + mux, + connectionStream, + (err) => { + if (err) console.error(err) + } + ) + return mux } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index e4b1b5975..1a6732338 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1,7 +1,7 @@ const EventEmitter = require('events') const extend = require('xtend') const promiseToCallback = require('promise-to-callback') -const pipe = require('pump') +const pump = require('pump') const Dnode = require('dnode') const ObservableStore = require('obs-store') const EthStore = require('./lib/eth-store') @@ -367,7 +367,14 @@ module.exports = class MetamaskController extends EventEmitter { setupControllerConnection (outStream) { const api = this.getApi() const dnode = Dnode(api) - outStream.pipe(dnode).pipe(outStream) + pump( + outStream, + dnode, + outStream, + (err) => { + if (err) console.error(err) + } + ) dnode.on('remote', (remote) => { // push updates to popup const sendUpdate = remote.sendUpdate.bind(remote) @@ -376,20 +383,29 @@ module.exports = class MetamaskController extends EventEmitter { } setupProviderConnection (outStream, originDomain) { + // setup json rpc engine stack const engine = new RpcEngine() engine.push(originMiddleware) engine.push(loggerMiddleware) engine.push(createProviderMiddleware({ provider: this.provider })) - + // setup connection const providerStream = createEngineStream({ engine }) - outStream.pipe(providerStream).pipe(outStream) - + pump( + outStream, + providerStream, + outStream, + (err) => { + if (err) console.error(err) + } + ) + // append dapp origin domain to request function originMiddleware (req, res, next, end) { req.origin = originDomain next() } + // log rpc activity function loggerMiddleware (req, res, next, end) { next((cb) => { @@ -401,6 +417,7 @@ module.exports = class MetamaskController extends EventEmitter { cb() }) } + // forward requests to provider function createProviderMiddleware({ provider }) { return (req, res, next, end) => { @@ -414,9 +431,12 @@ module.exports = class MetamaskController extends EventEmitter { } setupPublicConfig (outStream) { - pipe( + pump( this.publicConfigStore, - outStream + outStream, + (err) => { + if (err) console.error(err) + } ) } diff --git a/package.json b/package.json index 7712707c6..295d33409 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "eth-bin-to-ops": "^1.0.1", "eth-contract-metadata": "^1.1.4", "eth-hd-keyring": "^1.1.1", + "eth-json-rpc-filters": "^1.0.1", "eth-phishing-detect": "^1.1.4", "eth-query": "^2.1.2", "eth-sig-util": "^1.2.2", @@ -101,6 +102,7 @@ "mkdirp": "^0.5.1", "multiplex": "^6.7.0", "number-to-bn": "^1.7.0", + "obj-multiplex": "^1.0.0", "obs-store": "^2.3.1", "once": "^1.3.3", "ping-pong-stream": "^1.0.0", @@ -121,7 +123,7 @@ "react-select": "^1.0.0-rc.2", "react-simple-file-input": "^1.0.0", "react-tooltip-component": "^0.3.0", - "readable-stream": "^2.1.2", + "readable-stream": "^2.3.3", "redux": "^3.0.5", "redux-logger": "^3.0.6", "redux-thunk": "^2.2.0", From 0e8e655fdb9b65df151f23ede74807025226ab66 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 7 Sep 2017 21:19:24 -0700 Subject: [PATCH 04/51] inpage - distinguish pump vs pipe --- app/scripts/lib/inpage-provider.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js index c095846e1..eb24dfcab 100644 --- a/app/scripts/lib/inpage-provider.js +++ b/app/scripts/lib/inpage-provider.js @@ -1,4 +1,4 @@ -const pipe = require('pump') +const pump = require('pump') const RpcEngine = require('json-rpc-engine') const createIdRemapMiddleware = require('json-rpc-engine/src/idRemapMiddleware') const createStreamMiddleware = require('json-rpc-middleware-stream') @@ -12,7 +12,7 @@ function MetamaskInpageProvider (connectionStream) { // setup connectionStream multiplexing var multiStream = self.multiStream = ObjectMultiplex() - pipe( + pump( connectionStream, multiStream, connectionStream, @@ -21,7 +21,7 @@ function MetamaskInpageProvider (connectionStream) { // subscribe to metamask public config (one-way) self.publicConfigStore = new LocalStorageStore({ storageKey: 'MetaMask-Config' }) - pipe( + pump( multiStream.createStream('publicConfig'), self.publicConfigStore, (err) => logStreamDisconnectWarning('MetaMask PublicConfigStore', err) @@ -32,7 +32,7 @@ function MetamaskInpageProvider (connectionStream) { // connect to async provider const streamMiddleware = createStreamMiddleware() - pipe( + pump( streamMiddleware.stream, multiStream.createStream('provider'), streamMiddleware.stream, From 9d4c02e57f2b147759d979d8a6c051aa008cdff0 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 7 Sep 2017 21:26:25 -0700 Subject: [PATCH 05/51] metamask - add jsonrpc filter middleware on per-connection engine --- app/scripts/metamask-controller.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 1a6732338..735fc4af0 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -8,6 +8,7 @@ const EthStore = require('./lib/eth-store') const EthQuery = require('eth-query') const RpcEngine = require('json-rpc-engine') const createEngineStream = require('json-rpc-middleware-stream/engineStream') +const createFilterMiddleware = require('eth-json-rpc-filters') const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex const KeyringController = require('./keyring-controller') const NetworkController = require('./controllers/network') @@ -78,12 +79,13 @@ module.exports = class MetamaskController extends EventEmitter { // rpc provider this.provider = this.initializeProvider() + this.blockTracker = this.provider // eth data query tools this.ethQuery = new EthQuery(this.provider) this.ethStore = new EthStore({ provider: this.provider, - blockTracker: this.provider, + blockTracker: this.blockTracker, }) // key mgmt @@ -110,7 +112,7 @@ module.exports = class MetamaskController extends EventEmitter { getNetwork: this.networkController.getNetworkState.bind(this), signTransaction: this.keyringController.signTransaction.bind(this.keyringController), provider: this.provider, - blockTracker: this.provider, + blockTracker: this.blockTracker, ethQuery: this.ethQuery, ethStore: this.ethStore, }) @@ -387,6 +389,10 @@ module.exports = class MetamaskController extends EventEmitter { const engine = new RpcEngine() engine.push(originMiddleware) engine.push(loggerMiddleware) + engine.push(createFilterMiddleware({ + provider: this.provider, + blockTracker: this.blockTracker, + })) engine.push(createProviderMiddleware({ provider: this.provider })) // setup connection From f5d0a0b07ac835810bc6faef88384c2872846414 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 7 Sep 2017 22:25:08 -0700 Subject: [PATCH 06/51] deps - bump jsonrpc filters for log filter formate fix --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 295d33409..3fede273c 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "eth-bin-to-ops": "^1.0.1", "eth-contract-metadata": "^1.1.4", "eth-hd-keyring": "^1.1.1", - "eth-json-rpc-filters": "^1.0.1", + "eth-json-rpc-filters": "^1.0.2", "eth-phishing-detect": "^1.1.4", "eth-query": "^2.1.2", "eth-sig-util": "^1.2.2", From 70401626e2544f254ddb06a21b5d3de7fdd7fb82 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 7 Sep 2017 22:35:38 -0700 Subject: [PATCH 07/51] lint - remove dead code --- app/scripts/lib/stream-utils.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/scripts/lib/stream-utils.js b/app/scripts/lib/stream-utils.js index 89e2a359e..8bb0b4f3c 100644 --- a/app/scripts/lib/stream-utils.js +++ b/app/scripts/lib/stream-utils.js @@ -1,5 +1,4 @@ const Through = require('through2') -const endOfStream = require('end-of-stream') const ObjectMultiplex = require('obj-multiplex') const pump = require('pump') From ef3bf810bf14da6651ef849e481eb0253be3c8d1 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 7 Sep 2017 22:47:08 -0700 Subject: [PATCH 08/51] inpage - use obj-multiplex module --- app/scripts/lib/inpage-provider.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js index db46e4f17..b2515bfb8 100644 --- a/app/scripts/lib/inpage-provider.js +++ b/app/scripts/lib/inpage-provider.js @@ -3,7 +3,7 @@ const RpcEngine = require('json-rpc-engine') const createIdRemapMiddleware = require('json-rpc-engine/src/idRemapMiddleware') const createStreamMiddleware = require('json-rpc-middleware-stream') const LocalStorageStore = require('obs-store') -const ObjectMultiplex = require('./obj-multiplex') +const ObjectMultiplex = require('obj-multiplex') module.exports = MetamaskInpageProvider @@ -11,10 +11,10 @@ function MetamaskInpageProvider (connectionStream) { const self = this // setup connectionStream multiplexing - var multiStream = self.multiStream = ObjectMultiplex() + const mux = self.mux = new ObjectMultiplex() pump( connectionStream, - multiStream, + mux, connectionStream, (err) => logStreamDisconnectWarning('MetaMask', err) ) @@ -22,19 +22,19 @@ function MetamaskInpageProvider (connectionStream) { // subscribe to metamask public config (one-way) self.publicConfigStore = new LocalStorageStore({ storageKey: 'MetaMask-Config' }) pump( - multiStream.createStream('publicConfig'), + mux.createStream('publicConfig'), self.publicConfigStore, (err) => logStreamDisconnectWarning('MetaMask PublicConfigStore', err) ) // ignore phishing warning message (handled elsewhere) - multiStream.ignoreStream('phishing') + mux.ignoreStream('phishing') // connect to async provider const streamMiddleware = createStreamMiddleware() pump( streamMiddleware.stream, - multiStream.createStream('provider'), + mux.createStream('provider'), streamMiddleware.stream, (err) => logStreamDisconnectWarning('MetaMask RpcProvider', err) ) From 8545453a9d58d7a54c10beef52e38107ed937117 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 11 Sep 2017 14:30:30 -0700 Subject: [PATCH 09/51] contentscript - fix obj-multiplex instantiation and use pump for streams --- app/scripts/contentscript.js | 49 +++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index acacf5d4c..90a0f1f22 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -1,11 +1,12 @@ -const LocalMessageDuplexStream = require('post-message-stream') -const PongStream = require('ping-pong-stream/pong') -const PortStream = require('./lib/port-stream.js') -const ObjectMultiplex = require('./lib/obj-multiplex') -const extension = require('extensionizer') - const fs = require('fs') const path = require('path') +const pump = require('pump') +const LocalMessageDuplexStream = require('post-message-stream') +const PongStream = require('ping-pong-stream/pong') +const ObjectMultiplex = require('obj-multiplex') +const extension = require('extensionizer') +const PortStream = require('./lib/port-stream.js') + const inpageText = fs.readFileSync(path.join(__dirname, 'inpage.js')).toString() // Eventually this streaming injection could be replaced with: @@ -50,22 +51,42 @@ function setupStreams () { pageStream.pipe(pluginStream).pipe(pageStream) // setup local multistream channels - const mx = ObjectMultiplex() - mx.on('error', console.error) - mx.pipe(pageStream).pipe(mx) - mx.pipe(pluginStream).pipe(mx) + const mux = new ObjectMultiplex() + pump( + mux, + pageStream, + mux, + (err) => logStreamDisconnectWarning('MetaMask Inpage', err) + ) + pump( + mux, + pluginStream, + mux, + (err) => logStreamDisconnectWarning('MetaMask Background', err) + ) // connect ping stream const pongStream = new PongStream({ objectMode: true }) - pongStream.pipe(mx.createStream('pingpong')).pipe(pongStream) + pump( + mux, + pongStream, + mux, + (err) => logStreamDisconnectWarning('MetaMask PingPongStream', err) + ) // connect phishing warning stream - const phishingStream = mx.createStream('phishing') + const phishingStream = mux.createStream('phishing') phishingStream.once('data', redirectToPhishingWarning) // ignore unused channels (handled by background, inpage) - mx.ignoreStream('provider') - mx.ignoreStream('publicConfig') + mux.ignoreStream('provider') + mux.ignoreStream('publicConfig') +} + +function logStreamDisconnectWarning (remoteLabel, err) { + let warningMsg = `MetamaskContentscript - lost connection to ${remoteLabel}` + if (err) warningMsg += '\n' + err.stack + console.warn(warningMsg) } function shouldInjectWeb3 () { From 7810880f0c96ff8628cfd87bf7e6cb8145bb147a Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 12 Sep 2017 12:23:21 -0700 Subject: [PATCH 10/51] Add specific error message for failed address checksum. --- ui/app/send.js | 5 +++++ ui/app/util.js | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/ui/app/send.js b/ui/app/send.js index a21a219eb..e59c1130e 100644 --- a/ui/app/send.js +++ b/ui/app/send.js @@ -262,6 +262,11 @@ SendTransactionScreen.prototype.onSubmit = function () { return this.props.dispatch(actions.displayWarning(message)) } + if ((util.isInvalidChecksumAddress(recipient))) { + message = 'Recipient address checksum is invalid.' + return this.props.dispatch(actions.displayWarning(message)) + } + if ((!util.isValidAddress(recipient) && !txData) || (!recipient && !txData)) { message = 'Recipient address is invalid.' return this.props.dispatch(actions.displayWarning(message)) diff --git a/ui/app/util.js b/ui/app/util.js index 1368ebf11..860a17224 100644 --- a/ui/app/util.js +++ b/ui/app/util.js @@ -37,6 +37,7 @@ module.exports = { bnTable: bnTable, isHex: isHex, exportAsFile: exportAsFile, + isInvalidChecksumAddress, } function valuesFor (obj) { @@ -66,6 +67,12 @@ function isValidAddress (address) { return (isAllOneCase(prefixed) && ethUtil.isValidAddress(prefixed)) || ethUtil.isValidChecksumAddress(prefixed) } +function isInvalidChecksumAddress (address) { + var prefixed = ethUtil.addHexPrefix(address) + if (address === '0x0000000000000000000000000000000000000000') return false + return ethUtil.isValidAddress(prefixed) && !ethUtil.isValidChecksumAddress(prefixed) +} + function isAllOneCase (address) { if (!address) return true var lower = address.toLowerCase() From 243be92ac165d028d532a02f5b32ff9edc526f23 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 12 Sep 2017 12:24:02 -0700 Subject: [PATCH 11/51] Changelogaroo. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89eaefa79..54ddb4a1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Add ability to export private keys as a file. - Add ability to export seed words as a file. - Changed state logs to a file download than a clipboard copy. +- Add specific error for failed recipient address checksum. ## 3.10.0 2017-9-11 From ef6967325e43de26edc0d99ac5853804648024ae Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 12 Sep 2017 12:52:13 -0700 Subject: [PATCH 12/51] Check if all lower or upper before doing checksum. --- ui/app/util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/util.js b/ui/app/util.js index 860a17224..3f8b4dcc3 100644 --- a/ui/app/util.js +++ b/ui/app/util.js @@ -70,7 +70,7 @@ function isValidAddress (address) { function isInvalidChecksumAddress (address) { var prefixed = ethUtil.addHexPrefix(address) if (address === '0x0000000000000000000000000000000000000000') return false - return ethUtil.isValidAddress(prefixed) && !ethUtil.isValidChecksumAddress(prefixed) + return !isAllOneCase(prefixed) && !ethUtil.isValidChecksumAddress(prefixed) && ethUtil.isValidAddress(prefixed) } function isAllOneCase (address) { From 7ed1fe90f84284dd714b6f36165ed4b853272bb1 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 12 Sep 2017 15:07:08 -0700 Subject: [PATCH 13/51] Fix support link --- ui/app/info.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/info.js b/ui/app/info.js index c69d83715..4c7d4cb4c 100644 --- a/ui/app/info.js +++ b/ui/app/info.js @@ -103,7 +103,7 @@ InfoScreen.prototype.render = function () { [ h('div.fa.fa-support', [ h('a.info', { - href: 'https://support.metamask.com', + href: 'https://support.metamask.io', target: '_blank', }, 'Visit our Support Center'), ]), From c8736b70b7613e869e15cb76a550cb5b031ba84f Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 12 Sep 2017 15:07:41 -0700 Subject: [PATCH 14/51] Bump changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89eaefa79..f30c04985 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Add ability to export private keys as a file. - Add ability to export seed words as a file. - Changed state logs to a file download than a clipboard copy. +- Fix link to support center. ## 3.10.0 2017-9-11 From 962794d0257da705b2424351b7a187ccd4ec76ea Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 12 Sep 2017 16:15:56 -0700 Subject: [PATCH 15/51] fixed position of tooltips to avoid overflow. --- ui/app/components/tooltip.js | 2 +- ui/app/components/transaction-list-item-icon.js | 2 +- ui/app/components/transaction-list-item.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/app/components/tooltip.js b/ui/app/components/tooltip.js index edbc074bb..efab2c497 100644 --- a/ui/app/components/tooltip.js +++ b/ui/app/components/tooltip.js @@ -17,6 +17,6 @@ Tooltip.prototype.render = function () { return h(ReactTooltip, { position: position || 'left', title, - fixed: false, + fixed: true, }, children) } diff --git a/ui/app/components/transaction-list-item-icon.js b/ui/app/components/transaction-list-item-icon.js index 431054340..f442b05af 100644 --- a/ui/app/components/transaction-list-item-icon.js +++ b/ui/app/components/transaction-list-item-icon.js @@ -35,7 +35,7 @@ TransactionIcon.prototype.render = function () { case 'submitted': return h(Tooltip, { title: 'Pending', - position: 'bottom', + position: 'right', }, [ h('i.fa.fa-ellipsis-h', { style: { diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js index 5d5d0bcc5..0e5c0b5a3 100644 --- a/ui/app/components/transaction-list-item.js +++ b/ui/app/components/transaction-list-item.js @@ -65,7 +65,7 @@ TransactionListItem.prototype.render = function () { h(Tooltip, { title: 'Transaction Number', - position: 'bottom', + position: 'right', }, [ h('span', { style: { From 5305d414b5901ff68ee58a3eca182af9c69debb0 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 12 Sep 2017 16:16:29 -0700 Subject: [PATCH 16/51] changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f30c04985..4fb81c350 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Add ability to export seed words as a file. - Changed state logs to a file download than a clipboard copy. - Fix link to support center. +- Fixed tooltip icon locations to avoid overflow. ## 3.10.0 2017-9-11 From a265144176658220c5d8279ecb18c3ac0810e2c2 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 13 Sep 2017 10:21:00 -0700 Subject: [PATCH 17/51] metamask cont - standardize multiplex stream naming --- app/scripts/metamask-controller.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 735fc4af0..b28f5042a 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -346,23 +346,23 @@ module.exports = class MetamaskController extends EventEmitter { } // setup multiplexing - const mx = setupMultiplex(connectionStream) + const mux = setupMultiplex(connectionStream) // connect features - this.setupProviderConnection(mx.createStream('provider'), originDomain) - this.setupPublicConfig(mx.createStream('publicConfig')) + this.setupProviderConnection(mux.createStream('provider'), originDomain) + this.setupPublicConfig(mux.createStream('publicConfig')) } setupTrustedCommunication (connectionStream, originDomain) { // setup multiplexing - const mx = setupMultiplex(connectionStream) + const mux = setupMultiplex(connectionStream) // connect features - this.setupControllerConnection(mx.createStream('controller')) - this.setupProviderConnection(mx.createStream('provider'), originDomain) + this.setupControllerConnection(mux.createStream('controller')) + this.setupProviderConnection(mux.createStream('provider'), originDomain) } sendPhishingWarning (connectionStream, hostname) { - const mx = setupMultiplex(connectionStream) - const phishingStream = mx.createStream('phishing') + const mux = setupMultiplex(connectionStream) + const phishingStream = mux.createStream('phishing') phishingStream.write({ hostname }) } From 96d1175834bb5d400f6a70d228cebbd23bded4db Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 13 Sep 2017 10:28:29 -0700 Subject: [PATCH 18/51] debug - prefer logger over console --- app/scripts/background.js | 8 ++++---- app/scripts/metamask-controller.js | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index f077ca7a8..1b96d68b5 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -1,6 +1,8 @@ const urlUtil = require('url') const endOfStream = require('end-of-stream') const pipe = require('pump') +const log = require('loglevel') +const extension = require('extensionizer') const LocalStorageStore = require('obs-store/lib/localStorage') const storeTransform = require('obs-store/lib/transform') const ExtensionPlatform = require('./platforms/extension') @@ -9,13 +11,11 @@ const migrations = require('./migrations/') const PortStream = require('./lib/port-stream.js') const NotificationManager = require('./lib/notification-manager.js') const MetamaskController = require('./metamask-controller') -const extension = require('extensionizer') const firstTimeState = require('./first-time-state') const STORAGE_KEY = 'metamask-config' const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' -const log = require('loglevel') window.log = log log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn') @@ -29,12 +29,12 @@ let popupIsOpen = false const diskStore = new LocalStorageStore({ storageKey: STORAGE_KEY }) // initialization flow -initialize().catch(console.error) +initialize().catch(log.error) async function initialize () { const initState = await loadStateFromPersistence() await setupController(initState) - console.log('MetaMask initialization complete.') + log.debug('MetaMask initialization complete.') } // diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index b28f5042a..0c9602568 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -340,7 +340,7 @@ module.exports = class MetamaskController extends EventEmitter { setupUntrustedCommunication (connectionStream, originDomain) { // Check if new connection is blacklisted if (this.blacklistController.checkForPhishing(originDomain)) { - console.log('MetaMask - sending phishing warning for', originDomain) + log.debug('MetaMask - sending phishing warning for', originDomain) this.sendPhishingWarning(connectionStream, originDomain) return } @@ -374,7 +374,7 @@ module.exports = class MetamaskController extends EventEmitter { dnode, outStream, (err) => { - if (err) console.error(err) + if (err) log.error(err) } ) dnode.on('remote', (remote) => { @@ -402,7 +402,7 @@ module.exports = class MetamaskController extends EventEmitter { providerStream, outStream, (err) => { - if (err) console.error(err) + if (err) log.error(err) } ) @@ -416,7 +416,7 @@ module.exports = class MetamaskController extends EventEmitter { function loggerMiddleware (req, res, next, end) { next((cb) => { if (res.error) { - console.error('Error in RPC response:\n', res) + log.error('Error in RPC response:\n', res) } if (req.isMetamaskInternal) return log.info(`RPC (${originDomain}):`, req, '->', res) @@ -441,7 +441,7 @@ module.exports = class MetamaskController extends EventEmitter { this.publicConfigStore, outStream, (err) => { - if (err) console.error(err) + if (err) log.error(err) } ) } From 06153dd47d16c2e3c6eed7470501d7534c29cbd4 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 13 Sep 2017 12:17:42 -0700 Subject: [PATCH 19/51] Add warning of higher failure risk since app proposed gasLimit. --- ui/app/components/pending-tx.js | 64 ++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 3e53d47f9..37b242728 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -52,7 +52,8 @@ PendingTx.prototype.render = function () { const gas = txParams.gas const gasBn = hexToBn(gas) const gasLimit = new BN(parseInt(blockGasLimit)) - const safeGasLimit = this.bnMultiplyByFraction(gasLimit, 19, 20).toString(10) + const safeGasLimitBN = this.bnMultiplyByFraction(gasLimit, 19, 20) + const safeGasLimit = safeGasLimitBN.toString(10) // Gas Price const gasPrice = txParams.gasPrice || MIN_GAS_PRICE_BN.toString(16) @@ -66,6 +67,8 @@ PendingTx.prototype.render = function () { const balanceBn = hexToBn(balance) const insufficientBalance = balanceBn.lt(maxCost) + const dangerousGasLimit = gasBn.gte(safeGasLimitBN) + const gasLimitSpecified = txMeta.gasLimitSpecified const buyDisabled = insufficientBalance || !this.state.valid || !isValidAddress || this.state.submitting const showRejectAll = props.unconfTxListLength > 1 @@ -263,33 +266,44 @@ PendingTx.prototype.render = function () { text-transform: uppercase; } `), + h('.cell.row', { + style: { + textAlign: 'center', + }, + }, [ + txMeta.simulationFails ? + h('.error', { + style: { + fontSize: '0.9em', + }, + }, 'Transaction Error. Exception thrown in contract code.') + : null, - txMeta.simulationFails ? - h('.error', { - style: { - marginLeft: 50, - fontSize: '0.9em', - }, - }, 'Transaction Error. Exception thrown in contract code.') - : null, + !isValidAddress ? + h('.error', { + style: { + fontSize: '0.9em', + }, + }, 'Recipient address is invalid. Sending this transaction will result in a loss of ETH.') + : null, - !isValidAddress ? - h('.error', { - style: { - marginLeft: 50, - fontSize: '0.9em', - }, - }, 'Recipient address is invalid. Sending this transaction will result in a loss of ETH.') - : null, + insufficientBalance ? + h('span.error', { + style: { + fontSize: '0.9em', + }, + }, 'Insufficient balance for transaction') + : null, + + (dangerousGasLimit && gasLimitSpecified) ? + h('span.error', { + style: { + fontSize: '0.9em', + }, + }, 'Gas limit set dangerously high. Approving this transaction is likely to fail.') + : null, + ]), - insufficientBalance ? - h('span.error', { - style: { - marginLeft: 50, - fontSize: '0.9em', - }, - }, 'Insufficient balance for transaction') - : null, // send + cancel h('.flex-row.flex-space-around.conf-buttons', { From 6e725b123b878da12494904b121237ee41af7f76 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 13 Sep 2017 12:22:08 -0700 Subject: [PATCH 20/51] Lower warning threshold for high tx fee to account for fluctuating blockGasLimits --- ui/app/components/pending-tx.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 37b242728..ec2b15de4 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -53,6 +53,7 @@ PendingTx.prototype.render = function () { const gasBn = hexToBn(gas) const gasLimit = new BN(parseInt(blockGasLimit)) const safeGasLimitBN = this.bnMultiplyByFraction(gasLimit, 19, 20) + const saferGasLimitBN = this.bnMultiplyByFraction(gasLimit, 18, 20) const safeGasLimit = safeGasLimitBN.toString(10) // Gas Price @@ -67,7 +68,7 @@ PendingTx.prototype.render = function () { const balanceBn = hexToBn(balance) const insufficientBalance = balanceBn.lt(maxCost) - const dangerousGasLimit = gasBn.gte(safeGasLimitBN) + const dangerousGasLimit = gasBn.gte(saferGasLimitBN) const gasLimitSpecified = txMeta.gasLimitSpecified const buyDisabled = insufficientBalance || !this.state.valid || !isValidAddress || this.state.submitting const showRejectAll = props.unconfTxListLength > 1 From 3d36d565afe908ca7c4424ff8a7d3659d0978aca Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 13 Sep 2017 12:26:24 -0700 Subject: [PATCH 21/51] Bump changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f30c04985..6f357c92c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Add ability to export seed words as a file. - Changed state logs to a file download than a clipboard copy. - Fix link to support center. +- Warn users when a dapp proposes a high gas limit (90% of blockGasLimit or higher) ## 3.10.0 2017-9-11 From a22a2586abcca35d2a4bc1a0407aeaacc55f0a27 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 13 Sep 2017 13:24:16 -0700 Subject: [PATCH 22/51] Haha silly me, only when gas is estimated and not explicit. --- ui/app/components/pending-tx.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index ec2b15de4..c3350fcc1 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -296,7 +296,7 @@ PendingTx.prototype.render = function () { }, 'Insufficient balance for transaction') : null, - (dangerousGasLimit && gasLimitSpecified) ? + (dangerousGasLimit && !gasLimitSpecified) ? h('span.error', { style: { fontSize: '0.9em', From 245c0f0c2741d1dcb706faa93ff681333a40b9c8 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 13 Sep 2017 15:17:26 -0700 Subject: [PATCH 23/51] metamask controller - move middleware into seperate files --- app/scripts/lib/createLoggerMiddleware.js | 15 ++++++++ app/scripts/lib/createOriginMiddleware.js | 9 +++++ app/scripts/lib/createProviderMiddleware.js | 13 +++++++ app/scripts/metamask-controller.js | 41 ++++----------------- package.json | 2 +- 5 files changed, 45 insertions(+), 35 deletions(-) create mode 100644 app/scripts/lib/createLoggerMiddleware.js create mode 100644 app/scripts/lib/createOriginMiddleware.js create mode 100644 app/scripts/lib/createProviderMiddleware.js diff --git a/app/scripts/lib/createLoggerMiddleware.js b/app/scripts/lib/createLoggerMiddleware.js new file mode 100644 index 000000000..b92a965de --- /dev/null +++ b/app/scripts/lib/createLoggerMiddleware.js @@ -0,0 +1,15 @@ +// log rpc activity +module.exports = createLoggerMiddleware + +function createLoggerMiddleware({ origin }) { + return function loggerMiddleware (req, res, next, end) { + next((cb) => { + if (res.error) { + log.error('Error in RPC response:\n', res) + } + if (req.isMetamaskInternal) return + log.info(`RPC (${origin}):`, req, '->', res) + cb() + }) + } +} \ No newline at end of file diff --git a/app/scripts/lib/createOriginMiddleware.js b/app/scripts/lib/createOriginMiddleware.js new file mode 100644 index 000000000..f21d79512 --- /dev/null +++ b/app/scripts/lib/createOriginMiddleware.js @@ -0,0 +1,9 @@ +// append dapp origin domain to request +module.exports = createOriginMiddleware + +function createOriginMiddleware({ origin }) { + return function originMiddleware (req, res, next, end) { + req.origin = originDomain + next() + } +} \ No newline at end of file diff --git a/app/scripts/lib/createProviderMiddleware.js b/app/scripts/lib/createProviderMiddleware.js new file mode 100644 index 000000000..6dd192411 --- /dev/null +++ b/app/scripts/lib/createProviderMiddleware.js @@ -0,0 +1,13 @@ + +module.exports = createProviderMiddleware + +// forward requests to provider +function createProviderMiddleware({ provider }) { + return (req, res, next, end) => { + provider.sendAsync(req, (err, _res) => { + if (err) return end(err) + res.result = _res.result + end() + }) + } +} \ No newline at end of file diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 0c9602568..f114d22f3 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -7,8 +7,12 @@ const ObservableStore = require('obs-store') const EthStore = require('./lib/eth-store') const EthQuery = require('eth-query') const RpcEngine = require('json-rpc-engine') +const debounce = require('debounce') const createEngineStream = require('json-rpc-middleware-stream/engineStream') const createFilterMiddleware = require('eth-json-rpc-filters') +const createOriginMiddleware = require('./lib/createOriginMiddleware') +const createLoggerMiddleware = require('./lib/createLoggerMiddleware') +const createProviderMiddleware = require('./lib/createProviderMiddleware') const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex const KeyringController = require('./keyring-controller') const NetworkController = require('./controllers/network') @@ -26,8 +30,6 @@ const ConfigManager = require('./lib/config-manager') const nodeify = require('./lib/nodeify') const accountImporter = require('./account-import-strategies') const getBuyEthUrl = require('./lib/buy-eth-url') -const debounce = require('debounce') - const version = require('../manifest.json').version module.exports = class MetamaskController extends EventEmitter { @@ -384,11 +386,11 @@ module.exports = class MetamaskController extends EventEmitter { }) } - setupProviderConnection (outStream, originDomain) { + setupProviderConnection (outStream, origin) { // setup json rpc engine stack const engine = new RpcEngine() - engine.push(originMiddleware) - engine.push(loggerMiddleware) + engine.push(createOriginMiddleware({ origin })) + engine.push(createLoggerMiddleware({ origin })) engine.push(createFilterMiddleware({ provider: this.provider, blockTracker: this.blockTracker, @@ -405,35 +407,6 @@ module.exports = class MetamaskController extends EventEmitter { if (err) log.error(err) } ) - - // append dapp origin domain to request - function originMiddleware (req, res, next, end) { - req.origin = originDomain - next() - } - - // log rpc activity - function loggerMiddleware (req, res, next, end) { - next((cb) => { - if (res.error) { - log.error('Error in RPC response:\n', res) - } - if (req.isMetamaskInternal) return - log.info(`RPC (${originDomain}):`, req, '->', res) - cb() - }) - } - - // forward requests to provider - function createProviderMiddleware({ provider }) { - return (req, res, next, end) => { - provider.sendAsync(req, (err, _res) => { - if (err) return end(err) - res.result = _res.result - end() - }) - } - } } setupPublicConfig (outStream) { diff --git a/package.json b/package.json index b241ccfc6..3f9d9c538 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "eth-bin-to-ops": "^1.0.1", "eth-contract-metadata": "^1.1.4", "eth-hd-keyring": "^1.1.1", - "eth-json-rpc-filters": "^1.0.2", + "eth-json-rpc-filters": "^1.1.0", "eth-phishing-detect": "^1.1.4", "eth-query": "^2.1.2", "eth-sig-util": "^1.2.2", From 765ef640610584a3fdd7fa0ef716f6c7a553407c Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 13 Sep 2017 15:19:44 -0700 Subject: [PATCH 24/51] metamask controller - destroy filter polyfill on disconnect --- app/scripts/metamask-controller.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index f114d22f3..fef16c3a9 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -389,12 +389,16 @@ module.exports = class MetamaskController extends EventEmitter { setupProviderConnection (outStream, origin) { // setup json rpc engine stack const engine = new RpcEngine() - engine.push(createOriginMiddleware({ origin })) - engine.push(createLoggerMiddleware({ origin })) - engine.push(createFilterMiddleware({ + + // create filter polyfill middleware + const filterMiddleware = createFilterMiddleware({ provider: this.provider, blockTracker: this.blockTracker, - })) + }) + + engine.push(createOriginMiddleware({ origin })) + engine.push(createLoggerMiddleware({ origin })) + engine.push(filterMiddleware) engine.push(createProviderMiddleware({ provider: this.provider })) // setup connection @@ -404,6 +408,8 @@ module.exports = class MetamaskController extends EventEmitter { providerStream, outStream, (err) => { + // cleanup filter polyfill middleware + filterMiddleware.destroy() if (err) log.error(err) } ) From 6ba448edaf64d1122ab0b5c8af21f9806164c309 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 13 Sep 2017 15:26:00 -0700 Subject: [PATCH 25/51] changelog - add note of filter memory leak fix --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89eaefa79..e3cf3505a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Add ability to export private keys as a file. - Add ability to export seed words as a file. - Changed state logs to a file download than a clipboard copy. +- Fixed a long standing memory leak associated with filters installed by dapps ## 3.10.0 2017-9-11 From d7097db0221cf6b6958ceb65e9ce4c893a8dfb61 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 13 Sep 2017 15:29:44 -0700 Subject: [PATCH 26/51] createOriginMiddleware - fix var name --- app/scripts/lib/createOriginMiddleware.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/lib/createOriginMiddleware.js b/app/scripts/lib/createOriginMiddleware.js index f21d79512..e1e097cc4 100644 --- a/app/scripts/lib/createOriginMiddleware.js +++ b/app/scripts/lib/createOriginMiddleware.js @@ -3,7 +3,7 @@ module.exports = createOriginMiddleware function createOriginMiddleware({ origin }) { return function originMiddleware (req, res, next, end) { - req.origin = originDomain + req.origin = origin next() } } \ No newline at end of file From dd90e8e364a7023431be081d293d10129b76422c Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 13 Sep 2017 16:04:48 -0700 Subject: [PATCH 27/51] tests - break out coveralls npm script --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 12f79ba35..d618983bd 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "test-unit": "METAMASK_ENV=test mocha --require test/helper.js --recursive \"test/unit/**/*.js\"", "single-test": "METAMASK_ENV=test mocha --require test/helper.js", "test-integration": "npm run buildMock && npm run buildCiUnits && karma start", - "test-coverage": "nyc npm run test-unit && if [ $COVERALLS_REPO_TOKEN ]; then nyc report --reporter=text-lcov | coveralls; fi", + "test-coverage": "nyc npm run test-unit && npm run coveralls-upload", + "coveralls-upload": "if [ $COVERALLS_REPO_TOKEN ]; then nyc report --reporter=text-lcov | coveralls; fi", "ci": "npm run lint && npm run test-coverage && npm run test-integration", "lint": "gulp lint", "buildCiUnits": "node test/integration/index.js", From 56db3999adeaec1733218173b260bf74fa63ccbe Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 13 Sep 2017 16:45:31 -0700 Subject: [PATCH 28/51] tests - start migrating mascara tests to karma --- mascara/src/ui.js | 9 +++++++-- mascara/test/test-ui.js | 13 +++++++++++++ mascara/test/window-load.js | 5 ----- mock-dev.js | 1 - package.json | 11 ++++++----- karma.conf.js => test/base.conf.js | 6 ++---- test/mascara.conf.js | 14 ++++++++++++++ test/together.conf.js | 8 ++++++++ 8 files changed, 50 insertions(+), 17 deletions(-) create mode 100644 mascara/test/test-ui.js delete mode 100644 mascara/test/window-load.js rename karma.conf.js => test/base.conf.js (95%) create mode 100644 test/mascara.conf.js create mode 100644 test/together.conf.js diff --git a/mascara/src/ui.js b/mascara/src/ui.js index 5f9be542f..5662270c1 100644 --- a/mascara/src/ui.js +++ b/mascara/src/ui.js @@ -44,13 +44,18 @@ background.on('ready', (sw) => { background.removeListener('updatefound', connectApp) connectApp(sw) }) -background.on('updatefound', () => window.location.reload()) +background.on('updatefound', windowReload) background.startWorker() .then(() => { setTimeout(() => { const appContent = document.getElementById(`app-content`) - if (!appContent.children.length) window.location.reload() + if (!appContent.children.length) windowReload() }, 2000) }) console.log('hello from MetaMascara ui!') + +function windowReload() { + if (window.METAMASK_SKIP_RELOAD) return + window.location.reload() +} diff --git a/mascara/test/test-ui.js b/mascara/test/test-ui.js new file mode 100644 index 000000000..ebeffe8cb --- /dev/null +++ b/mascara/test/test-ui.js @@ -0,0 +1,13 @@ +const Helper = require('./util/mascara-test-helper.js') + +window.METAMASK_SKIP_RELOAD = true +window.addEventListener('load', () => { + // inject app container + const body = document.body + const container = document.createElement('div') + container.id = 'app-content' + body.appendChild(container) + + // start ui + require('../src/ui.js') +}) diff --git a/mascara/test/window-load.js b/mascara/test/window-load.js deleted file mode 100644 index d3f44f05f..000000000 --- a/mascara/test/window-load.js +++ /dev/null @@ -1,5 +0,0 @@ -const Helper = require('./util/mascara-test-helper.js') - -window.addEventListener('load', () => { - require('../src/ui.js') -}) diff --git a/mock-dev.js b/mock-dev.js index b6652bdf7..452fe37c5 100644 --- a/mock-dev.js +++ b/mock-dev.js @@ -96,7 +96,6 @@ function startApp(){ const container = document.createElement('div') container.id = 'app-content' body.appendChild(container) - console.log('container', container) render( h('.super-dev-container', [ diff --git a/package.json b/package.json index d618983bd..8ba6e16cb 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "test": "npm run lint && npm run test-unit && npm run test-integration", "test-unit": "METAMASK_ENV=test mocha --require test/helper.js --recursive \"test/unit/**/*.js\"", "single-test": "METAMASK_ENV=test mocha --require test/helper.js", - "test-integration": "npm run buildMock && npm run buildCiUnits && karma start", + "test-integration": "npm run buildMock && npm run buildCiUnits && karma start test/together.conf.js", "test-coverage": "nyc npm run test-unit && npm run coveralls-upload", "coveralls-upload": "if [ $COVERALLS_REPO_TOKEN ]; then nyc report --reporter=text-lcov | coveralls; fi", "ci": "npm run lint && npm run test-coverage && npm run test-integration", @@ -27,10 +27,11 @@ "generateNotice": "node notices/notice-generator.js", "deleteNotice": "node notices/notice-delete.js", "mascara": "node ./mascara/example/server", - "buildMascaraCi": "browserify mascara/test/window-load.js -o mascara/test/bundle.js", - "buildMascaraSWCi": "browserify mascara/src/background.js -o mascara/test/background.js", - "mascaraCi": "npm run buildMascaraCi && npm run buildMascaraSWCi && node mascara/test/index.js", - "testMascara": "cd mascara/test && npm run mascaraCi && testem ci -P 3" + "testMascara": "npm run buildMascara && karma start test/mascara.conf.js", + "buildMascara": "npm run buildMascaraUi && npm run buildMascaraBackground && npm run buildMascaraTests", + "buildMascaraUi": "browserify mascara/test/test-ui.js -o mascara/test/ui-bundle.js", + "buildMascaraBackground": "browserify mascara/src/background.js -o mascara/test/background.js", + "buildMascaraTests": "browserify test/integration/lib/first-time.js -o mascara/test/test-bundle.js" }, "browserify": { "transform": [ diff --git a/karma.conf.js b/test/base.conf.js similarity index 95% rename from karma.conf.js rename to test/base.conf.js index 8e6d55972..122392822 100644 --- a/karma.conf.js +++ b/test/base.conf.js @@ -2,7 +2,7 @@ // Generated on Mon Sep 11 2017 18:45:48 GMT-0700 (PDT) module.exports = function(config) { - config.set({ + return { // base path that will be used to resolve all patterns (eg. files, exclude) basePath: process.cwd(), @@ -16,9 +16,7 @@ module.exports = function(config) { // list of files / patterns to load in the browser files: [ - 'development/bundle.js', 'test/integration/jquery-3.1.0.min.js', - 'test/integration/bundle.js', { pattern: 'dist/chrome/images/**/*.*', watched: false, included: false, served: true }, { pattern: 'dist/chrome/fonts/**/*.*', watched: false, included: false, served: true }, ], @@ -57,5 +55,5 @@ module.exports = function(config) { // Concurrency level // how many browser should be started simultaneous concurrency: Infinity - }) + } } diff --git a/test/mascara.conf.js b/test/mascara.conf.js new file mode 100644 index 000000000..8e8fa2cdf --- /dev/null +++ b/test/mascara.conf.js @@ -0,0 +1,14 @@ +const getBaseConfig = require('./base.conf.js') + +module.exports = function(config) { + const settings = getBaseConfig(config) + settings.files.push('mascara/test/ui-bundle.js') + settings.files.push('mascara/test/test-bundle.js') + // settings.files.push('test/integration/bundle.js') + settings.files.push({ pattern: 'mascara/test/background.js', watched: false, included: false, served: true }), + // /background.js + + settings.proxies['/background.js'] = '/base/mascara/test/background.js' + + config.set(settings) +} diff --git a/test/together.conf.js b/test/together.conf.js new file mode 100644 index 000000000..cd2dbdcdc --- /dev/null +++ b/test/together.conf.js @@ -0,0 +1,8 @@ +const getBaseConfig = require('./base.conf.js') + +module.exports = function(config) { + const settings = getBaseConfig(config) + settings.files.push('development/bundle.js') + settings.files.push('test/integration/bundle.js') + config.set(settings) +} From cbff5fd450ac0a55a9ef7b48b0ce540b66dfca6b Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 13 Sep 2017 16:52:38 -0700 Subject: [PATCH 29/51] tests - mascara - move temp build files into dist/mascara --- package.json | 8 ++++---- test/mascara.conf.js | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 8ba6e16cb..c4a19a52c 100644 --- a/package.json +++ b/package.json @@ -28,10 +28,10 @@ "deleteNotice": "node notices/notice-delete.js", "mascara": "node ./mascara/example/server", "testMascara": "npm run buildMascara && karma start test/mascara.conf.js", - "buildMascara": "npm run buildMascaraUi && npm run buildMascaraBackground && npm run buildMascaraTests", - "buildMascaraUi": "browserify mascara/test/test-ui.js -o mascara/test/ui-bundle.js", - "buildMascaraBackground": "browserify mascara/src/background.js -o mascara/test/background.js", - "buildMascaraTests": "browserify test/integration/lib/first-time.js -o mascara/test/test-bundle.js" + "buildMascara": "mkdir -p dist/mascara && npm run buildMascaraUi && npm run buildMascaraBackground && npm run buildMascaraTests", + "buildMascaraUi": "browserify mascara/test/test-ui.js -o dist/mascara/ui.js", + "buildMascaraBackground": "browserify mascara/src/background.js -o dist/mascara/background.js", + "buildMascaraTests": "browserify test/integration/lib/first-time.js -o dist/mascara/tests.js" }, "browserify": { "transform": [ diff --git a/test/mascara.conf.js b/test/mascara.conf.js index 8e8fa2cdf..b4ec5846f 100644 --- a/test/mascara.conf.js +++ b/test/mascara.conf.js @@ -2,13 +2,13 @@ const getBaseConfig = require('./base.conf.js') module.exports = function(config) { const settings = getBaseConfig(config) - settings.files.push('mascara/test/ui-bundle.js') - settings.files.push('mascara/test/test-bundle.js') - // settings.files.push('test/integration/bundle.js') - settings.files.push({ pattern: 'mascara/test/background.js', watched: false, included: false, served: true }), - // /background.js - settings.proxies['/background.js'] = '/base/mascara/test/background.js' + // ui and tests + settings.files.push('dist/mascara/ui.js') + settings.files.push('dist/mascara/tests.js') + // service worker background + settings.files.push({ pattern: 'dist/mascara/background.js', watched: false, included: false, served: true }), + settings.proxies['/background.js'] = '/base/dist/mascara/background.js' config.set(settings) } From 610f09262d8686cf975b25ffb6e2b5371e3d55c0 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 13 Sep 2017 17:17:09 -0700 Subject: [PATCH 30/51] tests - refactor and rename npm scripts --- circle.yml | 2 +- package.json | 47 +++++++++++++------------ test/{together.conf.js => flat.conf.js} | 0 3 files changed, 25 insertions(+), 24 deletions(-) rename test/{together.conf.js => flat.conf.js} (100%) diff --git a/circle.yml b/circle.yml index f5da6857d..6aba5c1be 100644 --- a/circle.yml +++ b/circle.yml @@ -3,7 +3,7 @@ machine: version: 8.1.4 test: override: - - "npm run ci" + - "npm test" dependencies: pre: - sudo apt-get update diff --git a/package.json b/package.json index c4a19a52c..7a022d47a 100644 --- a/package.json +++ b/package.json @@ -6,32 +6,33 @@ "scripts": { "start": "npm run dev", "dev": "gulp dev --debug", - "disc": "gulp disc --debug", - "clear": "rm -rf node_modules/eth-contract-metadata && rm -rf node_modules/eth-phishing-detect", - "dist": "npm run clear && npm install && gulp dist", - "test": "npm run lint && npm run test-unit && npm run test-integration", - "test-unit": "METAMASK_ENV=test mocha --require test/helper.js --recursive \"test/unit/**/*.js\"", - "single-test": "METAMASK_ENV=test mocha --require test/helper.js", - "test-integration": "npm run buildMock && npm run buildCiUnits && karma start test/together.conf.js", - "test-coverage": "nyc npm run test-unit && npm run coveralls-upload", - "coveralls-upload": "if [ $COVERALLS_REPO_TOKEN ]; then nyc report --reporter=text-lcov | coveralls; fi", - "ci": "npm run lint && npm run test-coverage && npm run test-integration", - "lint": "gulp lint", - "buildCiUnits": "node test/integration/index.js", - "watch": "mocha watch --recursive \"test/unit/**/*.js\"", - "genStates": "node development/genStates.js", - "ui": "npm run genStates && beefy ui-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./", + "ui": "npm run test:flat:build:states && beefy ui-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./", "mock": "beefy mock-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./", - "buildMock": "npm run genStates && browserify ./mock-dev.js -o ./development/bundle.js", + "watch": "mocha watch --recursive \"test/unit/**/*.js\"", + "mascara": "node ./mascara/example/server", + "dist": "npm run dist:clear && npm install && gulp dist", + "dist:clear": "rm -rf node_modules/eth-contract-metadata && rm -rf node_modules/eth-phishing-detect", + "test": "npm run lint && npm run test:coverage && npm run test:integration", + "test:unit": "METAMASK_ENV=test mocha --require test/helper.js --recursive \"test/unit/**/*.js\"", + "test:single": "METAMASK_ENV=test mocha --require test/helper.js", + "test:integration": "npm run test:flat", + "test:coverage": "nyc npm run test:unit && npm run test:coveralls-upload", + "test:coveralls-upload": "if [ $COVERALLS_REPO_TOKEN ]; then nyc report --reporter=text-lcov | coveralls; fi", + "test:flat": "npm run test:flat:build && karma start test/flat.conf.js", + "test:flat:build": "npm run test:flat:build:ui && npm run test:flat:build:tests", + "test:flat:build:tests": "node test/integration/index.js", + "test:flat:build:states": "node development/genStates.js", + "test:flat:build:ui": "npm run test:flat:build:states && browserify ./mock-dev.js -o ./development/bundle.js", + "test:mascara": "npm run test:mascara:build && karma start test/mascara.conf.js", + "test:mascara:build": "mkdir -p dist/mascara && npm run test:mascara:build:ui && npm run test:mascara:build:background && npm run test:mascara:build:tests", + "test:mascara:build:ui": "browserify mascara/test/test-ui.js -o dist/mascara/ui.js", + "test:mascara:build:background": "browserify mascara/src/background.js -o dist/mascara/background.js", + "test:mascara:build:tests": "browserify test/integration/lib/first-time.js -o dist/mascara/tests.js", + "lint": "gulp lint", + "disc": "gulp disc --debug", "announce": "node development/announcer.js", "generateNotice": "node notices/notice-generator.js", - "deleteNotice": "node notices/notice-delete.js", - "mascara": "node ./mascara/example/server", - "testMascara": "npm run buildMascara && karma start test/mascara.conf.js", - "buildMascara": "mkdir -p dist/mascara && npm run buildMascaraUi && npm run buildMascaraBackground && npm run buildMascaraTests", - "buildMascaraUi": "browserify mascara/test/test-ui.js -o dist/mascara/ui.js", - "buildMascaraBackground": "browserify mascara/src/background.js -o dist/mascara/background.js", - "buildMascaraTests": "browserify test/integration/lib/first-time.js -o dist/mascara/tests.js" + "deleteNotice": "node notices/notice-delete.js" }, "browserify": { "transform": [ diff --git a/test/together.conf.js b/test/flat.conf.js similarity index 100% rename from test/together.conf.js rename to test/flat.conf.js From b53d04c40975adc0fadf20bc6ca875bd9c39e595 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 13 Sep 2017 20:25:27 -0700 Subject: [PATCH 31/51] tests - integration - get flat and mascara tests closer to compatible --- development/index.html | 4 +- development/test.html | 5 +- mascara/src/ui.js | 8 +- mascara/test/lib/first-time.js | 119 ----------------------------- mascara/test/test-ui.js | 1 - mock-dev.js | 4 +- test/integration/lib/first-time.js | 12 +-- test/mascara.conf.js | 3 + ui-dev.js | 4 +- 9 files changed, 18 insertions(+), 142 deletions(-) delete mode 100644 mascara/test/lib/first-time.js diff --git a/development/index.html b/development/index.html index 048aa3f35..a0814cb55 100644 --- a/development/index.html +++ b/development/index.html @@ -14,13 +14,13 @@ diff --git a/development/test.html b/development/test.html index 702be7fa0..49084c0a4 100644 --- a/development/test.html +++ b/development/test.html @@ -18,13 +18,14 @@ diff --git a/mascara/src/ui.js b/mascara/src/ui.js index 5662270c1..7506532d2 100644 --- a/mascara/src/ui.js +++ b/mascara/src/ui.js @@ -2,8 +2,6 @@ const injectCss = require('inject-css') const SWcontroller = require('client-sw-ready-event/lib/sw-client.js') const SwStream = require('sw-stream/lib/sw-stream.js') const MetaMaskUiCss = require('../../ui/css') -const setupIframe = require('./lib/setup-iframe.js') -const MetamaskInpageProvider = require('../../app/scripts/lib/inpage-provider.js') const MetamascaraPlatform = require('../../app/scripts/platforms/window') const startPopup = require('../../app/scripts/popup-core') @@ -32,7 +30,7 @@ const connectApp = function (readSw) { serviceWorker: background.controller, context: name, }) - startPopup({container, connectionStream}, (err, store) => { + startPopup({ container, connectionStream }, (err, store) => { if (err) return displayCriticalError(err) store.subscribe(() => { const state = store.getState() @@ -49,8 +47,8 @@ background.on('updatefound', windowReload) background.startWorker() .then(() => { setTimeout(() => { - const appContent = document.getElementById(`app-content`) - if (!appContent.children.length) windowReload() + const container = document.getElementById(`app-content`) + if (!container.children.length) windowReload() }, 2000) }) console.log('hello from MetaMascara ui!') diff --git a/mascara/test/lib/first-time.js b/mascara/test/lib/first-time.js deleted file mode 100644 index e42c9e39d..000000000 --- a/mascara/test/lib/first-time.js +++ /dev/null @@ -1,119 +0,0 @@ -const PASSWORD = 'password123' - -QUnit.module('first time usage') - -QUnit.test('render init screen', function (assert) { - var done = assert.async() - let app - - wait(1000).then(function() { - app = $('#app-content').contents() - const recurseNotices = function () { - let button = app.find('button') - if (button.html() === 'Accept') { - let termsPage = app.find('.markdown')[0] - termsPage.scrollTop = termsPage.scrollHeight - return wait().then(() => { - button.click() - return wait() - }).then(() => { - return recurseNotices() - }) - } else { - return wait() - } - } - return recurseNotices() - }).then(function() { - // Scroll through terms - var title = app.find('h1').text() - assert.equal(title, 'MetaMask', 'title screen') - - // enter password - var pwBox = app.find('#password-box')[0] - var confBox = app.find('#password-box-confirm')[0] - pwBox.value = PASSWORD - confBox.value = PASSWORD - - return wait() - }).then(function() { - - // create vault - var createButton = app.find('button.primary')[0] - createButton.click() - - return wait(1500) - }).then(function() { - - var created = app.find('h3')[0] - assert.equal(created.textContent, 'Vault Created', 'Vault created screen') - - // Agree button - var button = app.find('button')[0] - assert.ok(button, 'button present') - button.click() - - return wait(1000) - }).then(function() { - - var detail = app.find('.account-detail-section')[0] - assert.ok(detail, 'Account detail section loaded.') - - var sandwich = app.find('.sandwich-expando')[0] - sandwich.click() - - return wait() - }).then(function() { - - var sandwich = app.find('.menu-droppo')[0] - var children = sandwich.children - var lock = children[children.length - 2] - assert.ok(lock, 'Lock menu item found') - lock.click() - - return wait(1000) - }).then(function() { - - var pwBox = app.find('#password-box')[0] - pwBox.value = PASSWORD - - var createButton = app.find('button.primary')[0] - createButton.click() - - return wait(1000) - }).then(function() { - - var detail = app.find('.account-detail-section')[0] - assert.ok(detail, 'Account detail section loaded again.') - - return wait() - }).then(function (){ - - var qrButton = app.find('.fa.fa-qrcode')[0] - qrButton.click() - - return wait(1000) - }).then(function (){ - - var qrHeader = app.find('.qr-header')[0] - var qrContainer = app.find('#qr-container')[0] - assert.equal(qrHeader.textContent, 'Account 1', 'Should show account label.') - assert.ok(qrContainer, 'QR Container found') - - return wait() - }).then(function (){ - - var networkMenu = app.find('.network-indicator')[0] - networkMenu.click() - - return wait() - }).then(function (){ - - var networkMenu = app.find('.network-indicator')[0] - var children = networkMenu.children - children.length[3] - assert.ok(children, 'All network options present') - - done() - }) -}) diff --git a/mascara/test/test-ui.js b/mascara/test/test-ui.js index ebeffe8cb..201eec601 100644 --- a/mascara/test/test-ui.js +++ b/mascara/test/test-ui.js @@ -7,7 +7,6 @@ window.addEventListener('load', () => { const container = document.createElement('div') container.id = 'app-content' body.appendChild(container) - // start ui require('../src/ui.js') }) diff --git a/mock-dev.js b/mock-dev.js index 452fe37c5..a47f1ed4d 100644 --- a/mock-dev.js +++ b/mock-dev.js @@ -94,7 +94,7 @@ startApp() function startApp(){ const body = document.body const container = document.createElement('div') - container.id = 'app-content' + container.id = 'test-container' body.appendChild(container) render( @@ -112,7 +112,7 @@ function startApp(){ h(Selector, { actions, selectedKey: selectedView, states, store }), - h('.mock-app-root', { + h('#app-content', { style: { height: '500px', width: '360px', diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js index 38a94e551..e023351bc 100644 --- a/test/integration/lib/first-time.js +++ b/test/integration/lib/first-time.js @@ -10,19 +10,11 @@ QUnit.test('render init screen', (assert) => { }) }) -// QUnit.testDone(({ module, name, total, passed, failed, skipped, todo, runtime }) => { -// if (failed > 0) { -// const app = $('iframe').contents()[0].documentElement -// console.warn('Test failures - dumping DOM:') -// console.log(app.innerHTML) -// } -// }) - async function runFirstTimeUsageTest(assert, done) { await timeout() - const app = $('#app-content .mock-app-root') + const app = $('#app-content') // recurse notices while (true) { @@ -32,10 +24,12 @@ async function runFirstTimeUsageTest(assert, done) { const termsPage = app.find('.markdown')[0] termsPage.scrollTop = termsPage.scrollHeight await timeout() + console.log('Clearing notice') button.click() await timeout() } else { // exit loop + console.log('No more notices...') break } } diff --git a/test/mascara.conf.js b/test/mascara.conf.js index b4ec5846f..97e53fc2b 100644 --- a/test/mascara.conf.js +++ b/test/mascara.conf.js @@ -10,5 +10,8 @@ module.exports = function(config) { settings.files.push({ pattern: 'dist/mascara/background.js', watched: false, included: false, served: true }), settings.proxies['/background.js'] = '/base/dist/mascara/background.js' + // use this to keep the browser open for debugging + settings.browserNoActivityTimeout = 10000000 + config.set(settings) } diff --git a/ui-dev.js b/ui-dev.js index 367b5d546..de5dfd8ef 100644 --- a/ui-dev.js +++ b/ui-dev.js @@ -61,7 +61,7 @@ const actions = { var css = MetaMaskUiCss() injectCss(css) -const container = document.querySelector('#app-content') +const container = document.querySelector('#test-container') // parse opts var store = configureStore(states[selectedView]) @@ -72,7 +72,7 @@ render( h(Selector, { actions, selectedKey: selectedView, states, store }), - h('.mock-app-root', { + h('#app-content', { style: { height: '500px', width: '360px', From 6268272c834250e9cc6eb92bc1d7d6293fe5980d Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 14 Sep 2017 13:31:10 -0700 Subject: [PATCH 32/51] Add guide to porting to new platforms Adds a new guide to porting MetaMask to new platforms. Intended for all those devs asking us how to make a mobile MetaMask. --- README.md | 1 + docs/porting_to_new_environment.md | 65 ++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 docs/porting_to_new_environment.md diff --git a/README.md b/README.md index 075db79c2..b549ade08 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ To write tests that will be run in the browser using QUnit, add your test files - [How to live reload on local dependency changes](./docs/developing-on-deps.md) - [How to add new networks to the Provider Menu](./docs/adding-new-networks.md) - [How to manage notices that appear when the app starts up](./docs/notices.md) +- [How to port MetaMask to a new platform](./docs/porting_to_new_environment.md) - [How to generate a visualization of this repository's development](./docs/development-visualization.md) [1]: http://www.nomnoml.com/#view/%5B%3Cactor%3Euser%5D%0A%0A%5Bmetamask-ui%7C%0A%20%20%20%5Btools%7C%0A%20%20%20%20%20react%0A%20%20%20%20%20redux%0A%20%20%20%20%20thunk%0A%20%20%20%20%20ethUtils%0A%20%20%20%20%20jazzicon%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20account-detail%0A%20%20%20%20%20accounts%0A%20%20%20%20%20locked-screen%0A%20%20%20%20%20restore-vault%0A%20%20%20%20%20identicon%0A%20%20%20%20%20config%0A%20%20%20%20%20info%0A%20%20%20%5D%0A%20%20%20%5Breducers%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20metamask%0A%20%20%20%20%20identities%0A%20%20%20%5D%0A%20%20%20%5Bactions%7C%0A%20%20%20%20%20%5BaccountManager%5D%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%5D%3A-%3E%5Bactions%5D%0A%20%20%20%5Bactions%5D%3A-%3E%5Breducers%5D%0A%20%20%20%5Breducers%5D%3A-%3E%5Bcomponents%5D%0A%5D%0A%0A%5Bweb%20dapp%7C%0A%20%20%5Bui%20code%5D%0A%20%20%5Bweb3%5D%0A%20%20%5Bmetamask-inpage%5D%0A%20%20%0A%20%20%5B%3Cactor%3Eui%20developer%5D%0A%20%20%5Bui%20developer%5D-%3E%5Bui%20code%5D%0A%20%20%5Bui%20code%5D%3C-%3E%5Bweb3%5D%0A%20%20%5Bweb3%5D%3C-%3E%5Bmetamask-inpage%5D%0A%5D%0A%0A%5Bmetamask-background%7C%0A%20%20%5Bprovider-engine%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bid%20store%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%3E%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%3C-%3E%5Bid%20store%5D%0A%20%20%5Bconfig%20manager%7C%0A%20%20%20%20%5Brpc%20configuration%5D%0A%20%20%20%20%5Bencrypted%20keys%5D%0A%20%20%20%20%5Bwallet%20nicknames%5D%0A%20%20%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%5Bconfig%20manager%5D%0A%20%20%5Bid%20store%5D%3C-%3E%5Bconfig%20manager%5D%0A%5D%0A%0A%5Buser%5D%3C-%3E%5Bmetamask-ui%5D%0A%0A%5Buser%5D%3C%3A--%3A%3E%5Bweb%20dapp%5D%0A%0A%5Bmetamask-contentscript%7C%0A%20%20%5Bplugin%20restart%20detector%5D%0A%20%20%5Brpc%20passthrough%5D%0A%5D%0A%0A%5Brpc%20%7C%0A%20%20%5Bethereum%20blockchain%20%7C%0A%20%20%20%20%5Bcontracts%5D%0A%20%20%20%20%5Baccounts%5D%0A%20%20%5D%0A%5D%0A%0A%5Bweb%20dapp%5D%3C%3A--%3A%3E%5Bmetamask-contentscript%5D%0A%5Bmetamask-contentscript%5D%3C-%3E%5Bmetamask-background%5D%0A%5Bmetamask-background%5D%3C-%3E%5Bmetamask-ui%5D%0A%5Bmetamask-background%5D%3C-%3E%5Brpc%5D%0A diff --git a/docs/porting_to_new_environment.md b/docs/porting_to_new_environment.md new file mode 100644 index 000000000..85670efa7 --- /dev/null +++ b/docs/porting_to_new_environment.md @@ -0,0 +1,65 @@ +# Guide to Porting MetaMask to a New Environment + +MetaMask has been under continuous development for nearly two years now, and we’ve gradually discovered some very useful abstractions, that have allowed us to grow more easily. A couple of those layers together allow MetaMask to be ported to new environments and contexts increasingly easily. + +### The MetaMask Controller + +The core functionality of MetaMask all lives in what we call [The MetaMask Controller](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/metamask-controller.js). Our goal for this file is for it to eventually be its own javascript module that can be imported into any JS-compatible context, allowing it to fully manage an app's relationship to Ethereum. + +The MM Controller exposes most of its functionality via two methods: + +- [metamask.getState()](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/metamask-controller.js#L241) - This method returns a javascript object representing the current MetaMask state. This includes things like known accounts, sent transactions, current exchange rates, and more! The controller is also an event emitter, so you can subscribe to state updates via `metamask.on('update', handleStateUpdate)`. State examples available [here](https://github.com/MetaMask/metamask-extension/tree/master/development/states) under the `metamask` key. (Warning: some are outdated) +- [metamask.getApi()](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/metamask-controller.js#L274-L335) - Returns a JavaScript object filled with callback functions representing every operation our user interface ever performs. Everything from creating new accounts, changing the current network, to sending a transaction, is provided via these API methods. We export this external API on an object because it allows us to easily expose this API over a port using [dnode](https://www.npmjs.com/package/dnode), which is how our WebExtension's UI works! + +### The UI + +The MetaMask UI is essentially just a website that can be configured by passing it the API and state subscriptions from above. Anyone could make a UI that consumes these, effectively reskinning MetaMask. + +You can see this in action in our file [ui/index.js](https://github.com/MetaMask/metamask-extension/blob/master/ui/index.js). There you can see an argument being passed in named `accountManager`, which is essentially a MetaMask controller (forgive its really outdated parameter name!). With access to that object, the UI is able to initialize a whole React/Redux app that relies on this API for its account/blockchain-related/persistent states. + +## Putting it Together + +As an example, a WebExtension is always defined by a `manifest.json` file. [In ours](https://github.com/MetaMask/metamask-extension/blob/master/app/manifest.json#L31), you can see that [background.js](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/background.js) is defined as a script to run in the background, and this is the file that we use to initialize the MetaMask controller. + +In that file, there's a lot going on, so it's maybe worth focusing on our MetaMask controller constructor to start. It looks something like this: + +```javascript +const controller = new MetamaskController({ + // User confirmation callbacks: + showUnconfirmedMessage: triggerUi, + unlockAccountMessage: triggerUi, + showUnapprovedTx: triggerUi, + // initial state + initState, + // platform specific api + platform, +}) +``` +Since `background.js` is essentially the Extension setup file, we can see it doing all the things specific to the extension platform: +- Defining how to open the UI for new messages, transactions, and even requests to unlock (reveal to the site) their account. +- Provide the instance's initial state, leaving MetaMask persistence to the platform. +- Providing a `platform` object. This is becoming our catch-all adapter for platforms to define a few other platform-variant features we require, like opening a web link. (Soon we will be moving encryption out here too, since our browser-encryption isn't portable enough!) + +## Ports, streams, and Web3! + +Everything so far has been enough to create a MetaMask wallet on virtually any platform that runs JS, but MetaMask's most unique feature isn't being a wallet, it's providing an Ethereum-enabled JavaScript context to websites. + +MetaMask has two kinds of [duplex stream APIs](https://github.com/substack/stream-handbook#duplex) that it exposes: +- [metamask.setupTrustedCommunication(connectionStream, originDomain)](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/metamask-controller.js#L352) - This stream is used to connect the user interface over a remote port, and may not be necessary for contexts where the interface and the metamask-controller share a process. +- [metamask.setupUntrustedCommunication(connectionStream, originDomain)](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/metamask-controller.js#L337) - This method is used to connect a new web site's web3 API to MetaMask's blockchain connection. Additionally, the `originDomain` is used to block detected phishing sites. + +### Web3 as a Stream + +If you are making a MetaMask-powered browser for a new platform, one of the trickiest tasks will be injecting the Web3 API into websites that are visited. On WebExtensions, we actually have to pipe data through a total of three JS contexts just to let sites talk to our background process (site -> contentscript -> background). + +To make this as easy as possible, we use one of our favorite internal tools, [web3-provider-engine](https://www.npmjs.com/package/web3-provider-engine) to construct a custom web3 provider object whose source of truth is a stream that we connect to remotely. + +To see how we do that, you can refer to the [inpage script](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/inpage.js) that we inject into every website. There you can see it creates a multiplex stream to the background, and uses it to initialize what we call the [inpage-provider](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/lib/inpage-provider.js), which you can see stubs a few methods out, but mostly just passes calls to `sendAsync` through the stream it's passed! That's really all the magic that's needed to create a web3-like API in a remote context, once you have a stream to MetaMask available. + +In `inpage.js` you can see we create a `PortStream`, that's just a class we use to wrap WebExtension ports as streams, so we can reuse our favorite stream abstraction over the more irregular API surface of the WebExtension. In a new platform, you will probably need to construct this stream differently. The key is that you need to construct a stream that talks from the site context to the background. Once you have that set up, it works like magic! + +If streams seem new and confusing to you, that's ok, they can seem strange at first. To help learn them, we highly recommend reading Substack's [Stream Handbook](https://github.com/substack/stream-handbook), or going through NodeSchool's interactive command-line class [Stream Adventure](https://github.com/workshopper/stream-adventure), also maintained by Substack. + +## Conclusion + +I hope this has been helpful to you! If you have any other questionsm, or points you think need clarification in this guide, please [open an issue on our GitHub](https://github.com/MetaMask/metamask-plugin/issues/new)! \ No newline at end of file From 7fb862356873fc4c5dc28e5c7389f71424985b0f Mon Sep 17 00:00:00 2001 From: frankiebee Date: Fri, 15 Sep 2017 11:09:19 -0700 Subject: [PATCH 33/51] dont reload on initial download and wait before passing sw to ui --- mascara/src/ui.js | 41 +++++++++++++++++++----------- mascara/test/test-ui.js | 1 - test/integration/lib/first-time.js | 4 +-- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/mascara/src/ui.js b/mascara/src/ui.js index 7506532d2..05521d095 100644 --- a/mascara/src/ui.js +++ b/mascara/src/ui.js @@ -30,30 +30,41 @@ const connectApp = function (readSw) { serviceWorker: background.controller, context: name, }) - startPopup({ container, connectionStream }, (err, store) => { - if (err) return displayCriticalError(err) - store.subscribe(() => { - const state = store.getState() - if (state.appState.shouldClose) window.close() + return new Promise((resolve, reject) => { + startPopup({ container, connectionStream }, (err, store) => { + console.log('hello from MetaMascara ui!') + if (err) reject(err) + store.subscribe(() => { + const state = store.getState() + if (state.appState.shouldClose) window.close() + }) + resolve() }) }) } -background.on('ready', (sw) => { - background.removeListener('updatefound', connectApp) - connectApp(sw) +background.on('ready', async (sw) => { + try { + background.removeListener('updatefound', connectApp) + await timeout(1000) + await connectApp(sw) + console.log('hello from cb ready event!') + } catch (e) { + console.error(e) + } }) background.on('updatefound', windowReload) background.startWorker() -.then(() => { - setTimeout(() => { - const container = document.getElementById(`app-content`) - if (!container.children.length) windowReload() - }, 2000) -}) -console.log('hello from MetaMascara ui!') function windowReload() { if (window.METAMASK_SKIP_RELOAD) return window.location.reload() } + +function timeout (time) { + return new Promise(function (resolve, reject) { + setTimeout(function () { + resolve() + }, time || 1500) + }) +} \ No newline at end of file diff --git a/mascara/test/test-ui.js b/mascara/test/test-ui.js index 201eec601..bf27338d9 100644 --- a/mascara/test/test-ui.js +++ b/mascara/test/test-ui.js @@ -1,6 +1,5 @@ const Helper = require('./util/mascara-test-helper.js') -window.METAMASK_SKIP_RELOAD = true window.addEventListener('load', () => { // inject app container const body = document.body diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js index e023351bc..4140dfd78 100644 --- a/test/integration/lib/first-time.js +++ b/test/integration/lib/first-time.js @@ -12,7 +12,7 @@ QUnit.test('render init screen', (assert) => { async function runFirstTimeUsageTest(assert, done) { - await timeout() + await timeout(10000) const app = $('#app-content') @@ -123,7 +123,7 @@ async function runFirstTimeUsageTest(assert, done) { assert.ok(children2, 'All network options present') } -function timeout(time) { +function timeout (time) { return new Promise(function (resolve, reject) { setTimeout(function () { resolve() From 7bcca782cbea545fedabf33d73311c0e361690c2 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Fri, 15 Sep 2017 11:10:52 -0700 Subject: [PATCH 34/51] remove testem files --- mascara/test/index.html | 21 --------------------- mascara/test/testem.yml | 13 ------------- test/integration/lib/first-time.js | 2 +- 3 files changed, 1 insertion(+), 35 deletions(-) delete mode 100644 mascara/test/index.html delete mode 100644 mascara/test/testem.yml diff --git a/mascara/test/index.html b/mascara/test/index.html deleted file mode 100644 index 6495c2cfc..000000000 --- a/mascara/test/index.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - QUnit Example - - - -
-
- - - - - - -
- - - diff --git a/mascara/test/testem.yml b/mascara/test/testem.yml deleted file mode 100644 index f1f5844bd..000000000 --- a/mascara/test/testem.yml +++ /dev/null @@ -1,13 +0,0 @@ -launch_in_dev: - - Chrome - - Firefox - - Opera -launch_in_ci: - - Chrome - - Firefox - - Opera -framework: - - qunit -before_tests: "npm run mascaraCi" -after_tests: "rm ./background.js ./test-bundle.js ./bundle.js" -test_page: "./index.html" diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js index 4140dfd78..3f0fe165b 100644 --- a/test/integration/lib/first-time.js +++ b/test/integration/lib/first-time.js @@ -12,7 +12,7 @@ QUnit.test('render init screen', (assert) => { async function runFirstTimeUsageTest(assert, done) { - await timeout(10000) + await timeout() const app = $('#app-content') From c5a2527c17f9c7f47645dc4d99694645d1985022 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Fri, 15 Sep 2017 11:33:37 -0700 Subject: [PATCH 35/51] set time if platform is mascara --- mascara/src/ui.js | 1 + test/integration/lib/first-time.js | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/mascara/src/ui.js b/mascara/src/ui.js index 05521d095..d70dca724 100644 --- a/mascara/src/ui.js +++ b/mascara/src/ui.js @@ -15,6 +15,7 @@ const container = document.getElementById('app-content') var name = 'popup' window.METAMASK_UI_TYPE = name +window.METAMASK_PLATFORM_TYPE = 'mascara' let intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000 diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js index 3f0fe165b..e84a51c64 100644 --- a/test/integration/lib/first-time.js +++ b/test/integration/lib/first-time.js @@ -11,8 +11,10 @@ QUnit.test('render init screen', (assert) => { }) async function runFirstTimeUsageTest(assert, done) { - - await timeout() + let waitTime = 0 + window.METAMASK_SKIP_RELOAD = true + if (window.METAMASK_PLATFORM_TYPE === 'mascara') waitTime = 1000 + await timeout(waitTime) const app = $('#app-content') From 779e973b45ac3c5c48d471218ed63848a6653a56 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 15 Sep 2017 12:19:52 -0700 Subject: [PATCH 36/51] tests - integration - mascara - small cleanup and timeout adjustments --- mascara/src/ui.js | 6 ++---- test/integration/lib/first-time.js | 8 +++----- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/mascara/src/ui.js b/mascara/src/ui.js index d70dca724..2f940ad1a 100644 --- a/mascara/src/ui.js +++ b/mascara/src/ui.js @@ -63,9 +63,7 @@ function windowReload() { } function timeout (time) { - return new Promise(function (resolve, reject) { - setTimeout(function () { - resolve() - }, time || 1500) + return new Promise((resolve) => { + setTimeout(resolve, time || 1500) }) } \ No newline at end of file diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js index e84a51c64..8b3997867 100644 --- a/test/integration/lib/first-time.js +++ b/test/integration/lib/first-time.js @@ -13,7 +13,7 @@ QUnit.test('render init screen', (assert) => { async function runFirstTimeUsageTest(assert, done) { let waitTime = 0 window.METAMASK_SKIP_RELOAD = true - if (window.METAMASK_PLATFORM_TYPE === 'mascara') waitTime = 1000 + if (window.METAMASK_PLATFORM_TYPE === 'mascara') waitTime = 2000 await timeout(waitTime) const app = $('#app-content') @@ -126,9 +126,7 @@ async function runFirstTimeUsageTest(assert, done) { } function timeout (time) { - return new Promise(function (resolve, reject) { - setTimeout(function () { - resolve() - }, time * 3 || 1500) + return new Promise((resolve, reject) => { + setTimeout(resolve, time || 1500) }) } \ No newline at end of file From d2d6f6a858a1ffa1871830f0fbc39ecc76935321 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 15 Sep 2017 12:20:43 -0700 Subject: [PATCH 37/51] tests - integration - add mascara to integration test run --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7a022d47a..4bb410a8d 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "test": "npm run lint && npm run test:coverage && npm run test:integration", "test:unit": "METAMASK_ENV=test mocha --require test/helper.js --recursive \"test/unit/**/*.js\"", "test:single": "METAMASK_ENV=test mocha --require test/helper.js", - "test:integration": "npm run test:flat", + "test:integration": "npm run test:flat && npm run test:mascara", "test:coverage": "nyc npm run test:unit && npm run test:coveralls-upload", "test:coveralls-upload": "if [ $COVERALLS_REPO_TOKEN ]; then nyc report --reporter=text-lcov | coveralls; fi", "test:flat": "npm run test:flat:build && karma start test/flat.conf.js", From 0ab37b52c0540e8877321b40fc742928e96f7ee3 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 15 Sep 2017 13:13:13 -0700 Subject: [PATCH 38/51] mascara - proxy - small cleanup --- mascara/src/proxy.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mascara/src/proxy.js b/mascara/src/proxy.js index 5b95175f1..07c5b0e3c 100644 --- a/mascara/src/proxy.js +++ b/mascara/src/proxy.js @@ -1,7 +1,6 @@ const createParentStream = require('iframe-stream').ParentStream const SWcontroller = require('client-sw-ready-event/lib/sw-client.js') const SwStream = require('sw-stream/lib/sw-stream.js') -const SetupUntrustedComunication = ('./lib/setup-untrusted-connection.js') let intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000 const background = new SWcontroller({ @@ -12,7 +11,7 @@ const background = new SWcontroller({ }) const pageStream = createParentStream() -background.on('ready', (_) => { +background.on('ready', () => { let swStream = SwStream({ serviceWorker: background.controller, context: 'dapp', From a9900be08562c16b2f77e41f18cb80076ded09ec Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 15 Sep 2017 13:13:53 -0700 Subject: [PATCH 39/51] test - mascara - move skip reload test flag --- mascara/test/test-ui.js | 1 + test/integration/lib/first-time.js | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/mascara/test/test-ui.js b/mascara/test/test-ui.js index bf27338d9..b9bc42dff 100644 --- a/mascara/test/test-ui.js +++ b/mascara/test/test-ui.js @@ -1,6 +1,7 @@ const Helper = require('./util/mascara-test-helper.js') window.addEventListener('load', () => { + window.METAMASK_SKIP_RELOAD = true // inject app container const body = document.body const container = document.createElement('div') diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js index 8b3997867..c72abf81d 100644 --- a/test/integration/lib/first-time.js +++ b/test/integration/lib/first-time.js @@ -12,7 +12,6 @@ QUnit.test('render init screen', (assert) => { async function runFirstTimeUsageTest(assert, done) { let waitTime = 0 - window.METAMASK_SKIP_RELOAD = true if (window.METAMASK_PLATFORM_TYPE === 'mascara') waitTime = 2000 await timeout(waitTime) From 5d01ca10e46953999bf9e626b6aab4b99f27a4c4 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 15 Sep 2017 13:19:31 -0700 Subject: [PATCH 40/51] tests - mascara - increase timeout before test starts --- test/integration/lib/first-time.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js index c72abf81d..104f22772 100644 --- a/test/integration/lib/first-time.js +++ b/test/integration/lib/first-time.js @@ -12,7 +12,7 @@ QUnit.test('render init screen', (assert) => { async function runFirstTimeUsageTest(assert, done) { let waitTime = 0 - if (window.METAMASK_PLATFORM_TYPE === 'mascara') waitTime = 2000 + if (window.METAMASK_PLATFORM_TYPE === 'mascara') waitTime = 4000 await timeout(waitTime) const app = $('#app-content') From c90c904f95aa725ebd48c6f90939c9d9cba55d47 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 18 Sep 2017 11:47:59 -0700 Subject: [PATCH 41/51] Version 3.10.1 --- CHANGELOG.md | 2 ++ app/manifest.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce2327c24..5ef0ce17a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +## 3.10.1 2017-9-18 + - Add ability to export private keys as a file. - Add ability to export seed words as a file. - Changed state logs to a file download than a clipboard copy. diff --git a/app/manifest.json b/app/manifest.json index bd25c1f6f..8febf91aa 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.10.0", + "version": "3.10.1", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From 784510f89fcb460bcf77ba477fe9a7c144c25f63 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 18 Sep 2017 12:31:44 -0700 Subject: [PATCH 42/51] tests - integration - remove failing mascara integration tests from normal run until fixed --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4bb410a8d..7a022d47a 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "test": "npm run lint && npm run test:coverage && npm run test:integration", "test:unit": "METAMASK_ENV=test mocha --require test/helper.js --recursive \"test/unit/**/*.js\"", "test:single": "METAMASK_ENV=test mocha --require test/helper.js", - "test:integration": "npm run test:flat && npm run test:mascara", + "test:integration": "npm run test:flat", "test:coverage": "nyc npm run test:unit && npm run test:coveralls-upload", "test:coveralls-upload": "if [ $COVERALLS_REPO_TOKEN ]; then nyc report --reporter=text-lcov | coveralls; fi", "test:flat": "npm run test:flat:build && karma start test/flat.conf.js", From 9f665d8eda6d290511d2ee8ca0121d9a8b8c892b Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 18 Sep 2017 13:06:35 -0700 Subject: [PATCH 43/51] test - integration - bump timeout --- test/integration/lib/first-time.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js index 104f22772..cedb14f6e 100644 --- a/test/integration/lib/first-time.js +++ b/test/integration/lib/first-time.js @@ -53,7 +53,7 @@ async function runFirstTimeUsageTest(assert, done) { const createButton = app.find('button.primary')[0] createButton.click() - await timeout(1500) + await timeout(3000) const created = app.find('h3')[0] assert.equal(created.textContent, 'Vault Created', 'Vault created screen') From eaa85f283df55053024f9e561e3a816f033a7755 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 18 Sep 2017 13:17:08 -0700 Subject: [PATCH 44/51] tests - integration - re-add mascara to normal run --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7a022d47a..4bb410a8d 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "test": "npm run lint && npm run test:coverage && npm run test:integration", "test:unit": "METAMASK_ENV=test mocha --require test/helper.js --recursive \"test/unit/**/*.js\"", "test:single": "METAMASK_ENV=test mocha --require test/helper.js", - "test:integration": "npm run test:flat", + "test:integration": "npm run test:flat && npm run test:mascara", "test:coverage": "nyc npm run test:unit && npm run test:coveralls-upload", "test:coveralls-upload": "if [ $COVERALLS_REPO_TOKEN ]; then nyc report --reporter=text-lcov | coveralls; fi", "test:flat": "npm run test:flat:build && karma start test/flat.conf.js", From 2b7b1db851e484cda68352a365215d49fa1c30aa Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 18 Sep 2017 14:34:25 -0700 Subject: [PATCH 45/51] Do not mark a retry tx failed that has been broadcast successfully Fixes #2115 If a tx has been braodcast, the only failures we should accept are: - Never mined - On chain failure We had a section of code that would mark a tx failed during any unknown error during a retry. Now no retry > 1 will ever mark a tx failed, since it has been broadcast, and may be mined. --- app/scripts/lib/pending-tx-tracker.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/scripts/lib/pending-tx-tracker.js b/app/scripts/lib/pending-tx-tracker.js index b90851b58..44e9d50fa 100644 --- a/app/scripts/lib/pending-tx-tracker.js +++ b/app/scripts/lib/pending-tx-tracker.js @@ -76,6 +76,9 @@ module.exports = class PendingTransactionTracker extends EventEmitter { Dont marked as failed if the error is a "known" transaction warning "there is already a transaction with the same sender-nonce but higher/same gas price" + + Also don't mark as failed if it has ever been broadcast successfully. + A successful broadcast means it may still be mined. */ const errorMessage = err.message.toLowerCase() const isKnownTx = ( @@ -88,6 +91,7 @@ module.exports = class PendingTransactionTracker extends EventEmitter { // other || errorMessage.includes('gateway timeout') || errorMessage.includes('nonce too low') + || txMeta.retryCount > 1 ) // ignore resubmit warnings, return early if (isKnownTx) return @@ -117,10 +121,12 @@ module.exports = class PendingTransactionTracker extends EventEmitter { // Only auto-submit already-signed txs: if (!('rawTx' in txMeta)) return - // Increment a try counter. - txMeta.retryCount++ const rawTx = txMeta.rawTx - return await this.publishTransaction(rawTx) + const txHash = await this.publishTransaction(rawTx) + + // Increment successful tries: + txMeta.retryCount++ + return txHash } async _checkPendingTx (txMeta) { From 92e738d17c71d43487b4037df766453d6d8c9273 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 18 Sep 2017 14:37:28 -0700 Subject: [PATCH 46/51] Bump changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ef0ce17a..464cbe43c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Fix bug that would sometimes display transactions as failed that could be successfully mined. + ## 3.10.1 2017-9-18 - Add ability to export private keys as a file. From 51b40adecd5586e6ede75362fcfc4756a8ec0062 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 18 Sep 2017 22:42:04 -0700 Subject: [PATCH 47/51] v3.10.2 published `v3.10.2` as an emergency rollback to `v3.10.0` --- app/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/manifest.json b/app/manifest.json index 8febf91aa..67fb543b9 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.10.1", + "version": "3.10.2", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From 0424ab3e48596ec682cc58992879da377dc9dc55 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 18 Sep 2017 22:44:40 -0700 Subject: [PATCH 48/51] v3.10.2 - changelog add changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 464cbe43c..c4366db45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ - Fix bug that would sometimes display transactions as failed that could be successfully mined. +## 3.10.2 2017-9-18 + +rollback to 3.10.0 due to bug + ## 3.10.1 2017-9-18 - Add ability to export private keys as a file. From d2ded61cc9ae9a9354d947c24929f74e8139a2bc Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 19 Sep 2017 10:54:41 -0700 Subject: [PATCH 49/51] deps - bump json-rpc-engine --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 3f9d9c538..986e6e187 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "iframe-stream": "^3.0.0", "inject-css": "^0.1.1", "jazzicon": "^1.2.0", - "json-rpc-engine": "^3.1.0", + "json-rpc-engine": "^3.2.0", "json-rpc-middleware-stream": "^1.0.0", "loglevel": "^1.4.1", "metamask-logo": "^2.1.2", @@ -174,7 +174,6 @@ "jsdom": "^11.1.0", "jsdom-global": "^3.0.2", "jshint-stylish": "~2.2.1", - "json-rpc-engine": "^3.0.1", "karma": "^1.7.1", "karma-chrome-launcher": "^2.2.0", "karma-cli": "^1.0.1", From b979c6a2f3856525faaff0de94a3e97e322d18b6 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 19 Sep 2017 11:22:55 -0700 Subject: [PATCH 50/51] deps - bump json-rpc-middleware-stream --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 986e6e187..7d5f01f2d 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "inject-css": "^0.1.1", "jazzicon": "^1.2.0", "json-rpc-engine": "^3.2.0", - "json-rpc-middleware-stream": "^1.0.0", + "json-rpc-middleware-stream": "^1.0.1", "loglevel": "^1.4.1", "metamask-logo": "^2.1.2", "mississippi": "^1.2.0", From 3a3e1511e5cbeab1b94bb4052050c67eb3635ecc Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 19 Sep 2017 11:30:06 -0700 Subject: [PATCH 51/51] changelog - add note on filter fixes --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4366db45..3ff062cf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Current Master +- Fix bug where metamask-dapp connections are lost on rpc error - Fix bug that would sometimes display transactions as failed that could be successfully mined. ## 3.10.2 2017-9-18