aboutsummaryrefslogtreecommitdiffstats
path: root/app/scripts/contentscript.js
diff options
context:
space:
mode:
Diffstat (limited to 'app/scripts/contentscript.js')
-rw-r--r--app/scripts/contentscript.js128
1 files changed, 118 insertions, 10 deletions
diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js
index 33523eb46..1a10cdb34 100644
--- a/app/scripts/contentscript.js
+++ b/app/scripts/contentscript.js
@@ -7,10 +7,12 @@ const PongStream = require('ping-pong-stream/pong')
const ObjectMultiplex = require('obj-multiplex')
const extension = require('extensionizer')
const PortStream = require('extension-port-stream')
+const TransformStream = require('stream').Transform
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
@@ -20,24 +22,27 @@ const inpageBundle = inpageContent + inpageSuffix
// MetaMask will be much faster loading and performant on Firefox.
if (shouldInjectWeb3()) {
- setupInjection()
+ injectScript(inpageBundle)
setupStreams()
+ listenForProviderRequest()
+ checkPrivacyMode()
}
/**
- * Creates a script tag that injects inpage.js
+ * Injects a script tag into the current document
+ *
+ * @param {string} content - Code to be executed in the current document
*/
-function setupInjection () {
+function injectScript (content) {
try {
- // inject in-page script
- var scriptTag = document.createElement('script')
- scriptTag.textContent = inpageBundle
- scriptTag.onload = function () { this.parentNode.removeChild(this) }
- var container = document.head || document.documentElement
- // append as first child
+ const container = document.head || document.documentElement
+ const scriptTag = document.createElement('script')
+ scriptTag.setAttribute('async', false)
+ scriptTag.textContent = content
container.insertBefore(scriptTag, container.children[0])
+ container.removeChild(scriptTag)
} catch (e) {
- console.error('Metamask injection failed.', e)
+ console.error('MetaMask script injection failed', e)
}
}
@@ -54,10 +59,22 @@ function setupStreams () {
const pluginPort = extension.runtime.connect({ name: 'contentscript' })
const pluginStream = new PortStream(pluginPort)
+ // 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 })
+ },
+ })
+
// forward communication plugin->inpage
pump(
pageStream,
pluginStream,
+ approvalTransform,
pageStream,
(err) => logStreamDisconnectWarning('MetaMask Contentscript Forwarding', err)
)
@@ -97,6 +114,69 @@ function setupStreams () {
mux.ignoreStream('publicConfig')
}
+/**
+ * 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
+ }
+ })
+
+ extension.runtime.onMessage.addListener(({ action = '', isApproved, caching, isUnlocked }) => {
+ switch (action) {
+ case 'approve-provider-request':
+ isEnabled = true
+ injectScript(`window.dispatchEvent(new CustomEvent('ethereumprovider', { detail: {}}))`)
+ break
+ case 'reject-provider-request':
+ injectScript(`window.dispatchEvent(new CustomEvent('ethereumprovider', { detail: { error: 'User rejected provider access' }}))`)
+ break
+ case 'answer-is-approved':
+ injectScript(`window.dispatchEvent(new CustomEvent('ethereumisapproved', { detail: { isApproved: ${isApproved}, caching: ${caching}}}))`)
+ break
+ case 'answer-is-unlocked':
+ injectScript(`window.dispatchEvent(new CustomEvent('metamaskisunlocked', { detail: { isUnlocked: ${isUnlocked}}}))`)
+ break
+ case 'metamask-set-locked':
+ isEnabled = false
+ injectScript(`window.dispatchEvent(new CustomEvent('metamasksetlocked', { detail: {}}))`)
+ break
+ }
+ })
+}
+
+/**
+ * Checks if MetaMask is currently operating in "privacy mode", meaning
+ * dapps must call ethereum.enable in order to access user accounts
+ */
+function checkPrivacyMode () {
+ extension.runtime.sendMessage({ action: 'init-privacy-request' })
+}
/**
* Error handler for page to plugin stream disconnections
@@ -210,3 +290,31 @@ function redirectToPhishingWarning () {
href: window.location.href,
})}`
}
+
+function getSiteName (window) {
+ const document = window.document
+ const siteName = document.querySelector('head > meta[property="og:site_name"]')
+ if (siteName) {
+ return siteName.content
+ }
+
+ return document.title
+}
+
+function getSiteIcon (window) {
+ const document = window.document
+
+ // Use the site's favicon if it exists
+ const shortcutIcon = document.querySelector('head > link[rel="shortcut icon"]')
+ if (shortcutIcon) {
+ return shortcutIcon.href
+ }
+
+ // Search through available icons in no particular order
+ const icon = Array.from(document.querySelectorAll('head > link[rel="icon"]')).find((icon) => Boolean(icon.href))
+ if (icon) {
+ return icon.href
+ }
+
+ return null
+}