From 2845398c3d824e5da1830ba7905ffdbf8149cf9e Mon Sep 17 00:00:00 2001
From: kumavis <kumavis@users.noreply.github.com>
Date: Sat, 4 May 2019 01:32:05 +0800
Subject: Refactor ProviderApprovalController to use rpc and publicConfigStore
 (#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
---
 app/scripts/contentscript.js | 213 +++++++++++++++++++------------------------
 1 file changed, 93 insertions(+), 120 deletions(-)

(limited to 'app/scripts/contentscript.js')

diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js
index 2325cecdd..0c55ae39f 100644
--- a/app/scripts/contentscript.js
+++ b/app/scripts/contentscript.js
@@ -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
@@ -23,9 +22,7 @@ let isEnabled = false
 
 if (shouldInjectWeb3()) {
   injectScript(inpageBundle)
-  setupStreams()
-  listenForProviderRequest()
-  checkPrivacyMode()
+  start()
 }
 
 /**
@@ -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
@@ -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"]')
@@ -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
 
@@ -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 }))
+}
-- 
cgit