Skip to content

Commit

Permalink
Refactor ProviderApprovalController to use rpc and publicConfigStore (#…
Browse files Browse the repository at this point in the history
…6410)

* Ensure home screen does not render if there are unapproved txs (#6501)

* Ensure that the confirm screen renders before the home screen if there are unapproved txs.

* Only render confirm screen before home screen on mount.

* inpage - revert _metamask api to isEnabled isApproved isUnlocked
  • Loading branch information
kumavis authored and frankiebee committed May 3, 2019
1 parent 2ff5226 commit 2845398
Show file tree
Hide file tree
Showing 17 changed files with 298 additions and 409 deletions.
213 changes: 93 additions & 120 deletions app/scripts/contentscript.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
const fs = require('fs')
const path = require('path')
const pump = require('pump')
const log = require('loglevel')
const Dnode = require('dnode')
const querystring = require('querystring')
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('extension-port-stream')
const {Transform: TransformStream} = require('stream')

const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'inpage.js')).toString()
const inpageSuffix = '//# sourceURL=' + extension.extension.getURL('inpage.js') + '\n'
const inpageBundle = inpageContent + inpageSuffix
let isEnabled = false

// Eventually this streaming injection could be replaced with:
// https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Language_Bindings/Components.utils.exportFunction
Expand All @@ -23,9 +22,7 @@ let isEnabled = false

if (shouldInjectWeb3()) {
injectScript(inpageBundle)
setupStreams()
listenForProviderRequest()
checkPrivacyMode()
start()
}

/**
Expand All @@ -46,149 +43,108 @@ function injectScript (content) {
}
}

/**
* Sets up the stream communication and submits site metadata
*
*/
async function start () {
await setupStreams()
await domIsReady()
}

/**
* Sets up two-way communication streams between the
* browser extension and local per-page browser context
* browser extension and local per-page browser context.
*
*/
function setupStreams () {
// setup communication to page and plugin
async function setupStreams () {
// the transport-specific streams for communication between inpage and background
const pageStream = new LocalMessageDuplexStream({
name: 'contentscript',
target: 'inpage',
})
const pluginPort = extension.runtime.connect({ name: 'contentscript' })
const pluginStream = new PortStream(pluginPort)
const extensionPort = extension.runtime.connect({ name: 'contentscript' })
const extensionStream = new PortStream(extensionPort)

// Filter out selectedAddress until this origin is enabled
const approvalTransform = new TransformStream({
objectMode: true,
transform: (data, _, done) => {
if (typeof data === 'object' && data.name && data.name === 'publicConfig' && !isEnabled) {
data.data.selectedAddress = undefined
}
done(null, { ...data })
},
})
// create and connect channel muxers
// so we can handle the channels individually
const pageMux = new ObjectMultiplex()
pageMux.setMaxListeners(25)
const extensionMux = new ObjectMultiplex()
extensionMux.setMaxListeners(25)

// forward communication plugin->inpage
pump(
pageMux,
pageStream,
pluginStream,
approvalTransform,
pageStream,
(err) => logStreamDisconnectWarning('MetaMask Contentscript Forwarding', err)
pageMux,
(err) => logStreamDisconnectWarning('MetaMask Inpage Multiplex', err)
)

// setup local multistream channels
const mux = new ObjectMultiplex()
mux.setMaxListeners(25)

pump(
mux,
pageStream,
mux,
(err) => logStreamDisconnectWarning('MetaMask Inpage', err)
)
pump(
mux,
pluginStream,
mux,
(err) => logStreamDisconnectWarning('MetaMask Background', err)
extensionMux,
extensionStream,
extensionMux,
(err) => logStreamDisconnectWarning('MetaMask Background Multiplex', err)
)

// connect ping stream
const pongStream = new PongStream({ objectMode: true })
pump(
mux,
pongStream,
mux,
(err) => logStreamDisconnectWarning('MetaMask PingPongStream', err)
)
// forward communication across inpage-background for these channels only
forwardTrafficBetweenMuxers('provider', pageMux, extensionMux)
forwardTrafficBetweenMuxers('publicConfig', pageMux, extensionMux)

// connect phishing warning stream
const phishingStream = mux.createStream('phishing')
// connect "phishing" channel to warning system
const phishingStream = extensionMux.createStream('phishing')
phishingStream.once('data', redirectToPhishingWarning)

// ignore unused channels (handled by background, inpage)
mux.ignoreStream('provider')
mux.ignoreStream('publicConfig')
// connect "publicApi" channel to submit page metadata
const publicApiStream = extensionMux.createStream('publicApi')
const background = await setupPublicApi(publicApiStream)

return { background }
}

/**
* Establishes listeners for requests to fully-enable the provider from the dapp context
* and for full-provider approvals and rejections from the background script context. Dapps
* should not post messages directly and should instead call provider.enable(), which
* handles posting these messages internally.
*/
function listenForProviderRequest () {
window.addEventListener('message', ({ source, data }) => {
if (source !== window || !data || !data.type) { return }
switch (data.type) {
case 'ETHEREUM_ENABLE_PROVIDER':
extension.runtime.sendMessage({
action: 'init-provider-request',
force: data.force,
origin: source.location.hostname,
siteImage: getSiteIcon(source),
siteTitle: getSiteName(source),
})
break
case 'ETHEREUM_IS_APPROVED':
extension.runtime.sendMessage({
action: 'init-is-approved',
origin: source.location.hostname,
})
break
case 'METAMASK_IS_UNLOCKED':
extension.runtime.sendMessage({
action: 'init-is-unlocked',
})
break
}
})
function forwardTrafficBetweenMuxers (channelName, muxA, muxB) {
const channelA = muxA.createStream(channelName)
const channelB = muxB.createStream(channelName)
pump(
channelA,
channelB,
channelA,
(err) => logStreamDisconnectWarning(`MetaMask muxed traffic for channel "${channelName}" failed.`, err)
)
}

extension.runtime.onMessage.addListener(({ action = '', isApproved, caching, isUnlocked, selectedAddress }) => {
switch (action) {
case 'approve-provider-request':
isEnabled = true
window.postMessage({ type: 'ethereumprovider', selectedAddress }, '*')
break
case 'approve-legacy-provider-request':
isEnabled = true
window.postMessage({ type: 'ethereumproviderlegacy', selectedAddress }, '*')
break
case 'reject-provider-request':
window.postMessage({ type: 'ethereumprovider', error: 'User denied account authorization' }, '*')
break
case 'answer-is-approved':
window.postMessage({ type: 'ethereumisapproved', isApproved, caching }, '*')
break
case 'answer-is-unlocked':
window.postMessage({ type: 'metamaskisunlocked', isUnlocked }, '*')
break
case 'metamask-set-locked':
isEnabled = false
window.postMessage({ type: 'metamasksetlocked' }, '*')
break
case 'ethereum-ping-success':
window.postMessage({ type: 'ethereumpingsuccess' }, '*')
break
case 'ethereum-ping-error':
window.postMessage({ type: 'ethereumpingerror' }, '*')
async function setupPublicApi (outStream) {
const api = {
getSiteMetadata: (cb) => cb(null, getSiteMetadata()),
}
const dnode = Dnode(api)
pump(
outStream,
dnode,
outStream,
(err) => {
// report any error
if (err) log.error(err)
}
})
)
const background = await new Promise(resolve => dnode.once('remote', resolve))
return background
}

/**
* Checks if MetaMask is currently operating in "privacy mode", meaning
* dapps must call ethereum.enable in order to access user accounts
* Gets site metadata and returns it
*
*/
function checkPrivacyMode () {
extension.runtime.sendMessage({ action: 'init-privacy-request' })
function getSiteMetadata () {
// get metadata
const metadata = {
name: getSiteName(window),
icon: getSiteIcon(window),
}
return metadata
}

/**
* Error handler for page to plugin stream disconnections
* Error handler for page to extension stream disconnections
*
* @param {string} remoteLabel Remote stream name
* @param {Error} err Stream connection error
Expand Down Expand Up @@ -301,6 +257,10 @@ function redirectToPhishingWarning () {
})}`
}


/**
* Extracts a name for the site from the DOM
*/
function getSiteName (window) {
const document = window.document
const siteName = document.querySelector('head > meta[property="og:site_name"]')
Expand All @@ -316,6 +276,9 @@ function getSiteName (window) {
return document.title
}

/**
* Extracts an icon for the site from the DOM
*/
function getSiteIcon (window) {
const document = window.document

Expand All @@ -333,3 +296,13 @@ function getSiteIcon (window) {

return null
}

/**
* Returns a promise that resolves when the DOM is loaded (does not wait for images to load)
*/
async function domIsReady () {
// already loaded
if (['interactive', 'complete'].includes(document.readyState)) return
// wait for load
await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve, { once: true }))
}
19 changes: 0 additions & 19 deletions app/scripts/controllers/network/createBlockTracker.js

This file was deleted.

6 changes: 3 additions & 3 deletions app/scripts/controllers/network/createInfuraClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ const createInflightMiddleware = require('eth-json-rpc-middleware/inflight-cache
const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector')
const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware')
const createInfuraMiddleware = require('eth-json-rpc-infura')
const createBlockTracker = require('./createBlockTracker')
const BlockTracker = require('eth-block-tracker')

module.exports = createInfuraClient

function createInfuraClient ({ network, platform }) {
function createInfuraClient ({ network }) {
const infuraMiddleware = createInfuraMiddleware({ network, maxAttempts: 5, source: 'metamask' })
const infuraProvider = providerFromMiddleware(infuraMiddleware)
const blockTracker = createBlockTracker({ provider: infuraProvider }, platform)
const blockTracker = new BlockTracker({ provider: infuraProvider })

const networkMiddleware = mergeMiddleware([
createNetworkAndChainIdMiddleware({ network }),
Expand Down
6 changes: 3 additions & 3 deletions app/scripts/controllers/network/createJsonRpcClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ const createBlockCacheMiddleware = require('eth-json-rpc-middleware/block-cache'
const createInflightMiddleware = require('eth-json-rpc-middleware/inflight-cache')
const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector')
const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware')
const createBlockTracker = require('./createBlockTracker')
const BlockTracker = require('eth-block-tracker')

module.exports = createJsonRpcClient

function createJsonRpcClient ({ rpcUrl, platform }) {
function createJsonRpcClient ({ rpcUrl }) {
const fetchMiddleware = createFetchMiddleware({ rpcUrl })
const blockProvider = providerFromMiddleware(fetchMiddleware)
const blockTracker = createBlockTracker({ provider: blockProvider }, platform)
const blockTracker = new BlockTracker({ provider: blockProvider })

const networkMiddleware = mergeMiddleware([
createBlockRefRewriteMiddleware({ blockTracker }),
Expand Down
6 changes: 3 additions & 3 deletions app/scripts/controllers/network/createLocalhostClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ const createFetchMiddleware = require('eth-json-rpc-middleware/fetch')
const createBlockRefRewriteMiddleware = require('eth-json-rpc-middleware/block-ref-rewrite')
const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector')
const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware')
const createBlockTracker = require('./createBlockTracker')
const BlockTracker = require('eth-block-tracker')

module.exports = createLocalhostClient

function createLocalhostClient ({ platform }) {
function createLocalhostClient () {
const fetchMiddleware = createFetchMiddleware({ rpcUrl: 'http://localhost:8545/' })
const blockProvider = providerFromMiddleware(fetchMiddleware)
const blockTracker = createBlockTracker({ provider: blockProvider, pollingInterval: 1000 }, platform)
const blockTracker = new BlockTracker({ provider: blockProvider, pollingInterval: 1000 })

const networkMiddleware = mergeMiddleware([
createBlockRefRewriteMiddleware({ blockTracker }),
Expand Down
9 changes: 4 additions & 5 deletions app/scripts/controllers/network/network.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,8 @@ const defaultNetworkConfig = {

module.exports = class NetworkController extends EventEmitter {

constructor (opts = {}, platform) {
constructor (opts = {}) {
super()
this.platform = platform

// parse options
const providerConfig = opts.provider || defaultProviderConfig
Expand Down Expand Up @@ -190,7 +189,7 @@ module.exports = class NetworkController extends EventEmitter {

_configureInfuraProvider ({ type }) {
log.info('NetworkController - configureInfuraProvider', type)
const networkClient = createInfuraClient({ network: type, platform: this.platform })
const networkClient = createInfuraClient({ network: type })
this._setNetworkClient(networkClient)
// setup networkConfig
var settings = {
Expand All @@ -201,13 +200,13 @@ module.exports = class NetworkController extends EventEmitter {

_configureLocalhostProvider () {
log.info('NetworkController - configureLocalhostProvider')
const networkClient = createLocalhostClient({ platform: this.platform })
const networkClient = createLocalhostClient()
this._setNetworkClient(networkClient)
}

_configureStandardProvider ({ rpcUrl, chainId, ticker, nickname }) {
log.info('NetworkController - configureStandardProvider', rpcUrl)
const networkClient = createJsonRpcClient({ rpcUrl, platform: this.platform })
const networkClient = createJsonRpcClient({ rpcUrl })
// hack to add a 'rpc' network with chainId
networks.networkList['rpc'] = {
chainId: chainId,
Expand Down
Loading

0 comments on commit 2845398

Please sign in to comment.