diff options
author | kumavis <kumavis@users.noreply.github.com> | 2019-05-04 01:32:05 +0800 |
---|---|---|
committer | Frankie <frankie.diamond@gmail.com> | 2019-05-04 01:32:05 +0800 |
commit | 2845398c3d824e5da1830ba7905ffdbf8149cf9e (patch) | |
tree | a95ca83b7cf8ac42cef5ad08d29e8240f8bf1e30 /app/scripts/contentscript.js | |
parent | 2ff522604b2a5f45697087613de5efb9bba58790 (diff) | |
download | tangerine-wallet-browser-2845398c3d824e5da1830ba7905ffdbf8149cf9e.tar tangerine-wallet-browser-2845398c3d824e5da1830ba7905ffdbf8149cf9e.tar.gz tangerine-wallet-browser-2845398c3d824e5da1830ba7905ffdbf8149cf9e.tar.bz2 tangerine-wallet-browser-2845398c3d824e5da1830ba7905ffdbf8149cf9e.tar.lz tangerine-wallet-browser-2845398c3d824e5da1830ba7905ffdbf8149cf9e.tar.xz tangerine-wallet-browser-2845398c3d824e5da1830ba7905ffdbf8149cf9e.tar.zst tangerine-wallet-browser-2845398c3d824e5da1830ba7905ffdbf8149cf9e.zip |
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
Diffstat (limited to 'app/scripts/contentscript.js')
-rw-r--r-- | app/scripts/contentscript.js | 213 |
1 files changed, 93 insertions, 120 deletions
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() } /** @@ -47,148 +44,107 @@ 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 })) +} |