A Metamask fork with Infura removed and default networks editable
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
ciphermask/app/scripts/contentscript.js

309 lines
8.7 KiB

import querystring from 'querystring'
import pump from 'pump'
import LocalMessageDuplexStream from 'post-message-stream'
import ObjectMultiplex from 'obj-multiplex'
import extension from 'extensionizer'
import PortStream from 'extension-port-stream'
import { obj as createThoughStream } from 'through2'
// These require calls need to use require to be statically recognized by browserify
const fs = require('fs')
const path = require('path')
const inpageContent = fs.readFileSync(
path.join(__dirname, '..', '..', 'dist', 'chrome', 'inpage.js'),
'utf8',
)
const inpageSuffix = `//# sourceURL=${extension.runtime.getURL('inpage.js')}\n`
const inpageBundle = inpageContent + inpageSuffix
const CONTENT_SCRIPT = 'metamask-contentscript'
const INPAGE = 'metamask-inpage'
const PROVIDER = 'metamask-provider'
// TODO:LegacyProvider: Delete
const LEGACY_CONTENT_SCRIPT = 'contentscript'
const LEGACY_INPAGE = 'inpage'
const LEGACY_PROVIDER = 'provider'
const LEGACY_PUBLIC_CONFIG = 'publicConfig'
if (shouldInjectProvider()) {
injectScript(inpageBundle)
setupStreams()
}
/**
* Injects a script tag into the current document
*
* @param {string} content - Code to be executed in the current document
*/
function injectScript(content) {
try {
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 (error) {
console.error('MetaMask: Provider injection failed.', error)
}
}
/**
* Sets up two-way communication streams between the
* browser extension and local per-page browser context.
*
*/
async function setupStreams() {
// the transport-specific streams for communication between inpage and background
const pageStream = new LocalMessageDuplexStream({
name: CONTENT_SCRIPT,
target: INPAGE,
})
const extensionPort = extension.runtime.connect({ name: CONTENT_SCRIPT })
const extensionStream = new PortStream(extensionPort)
// 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)
extensionMux.ignoreStream(LEGACY_PUBLIC_CONFIG) // TODO:LegacyProvider: Delete
pump(pageMux, pageStream, pageMux, (err) =>
logStreamDisconnectWarning('MetaMask Inpage Multiplex', err),
)
pump(extensionMux, extensionStream, extensionMux, (err) => {
logStreamDisconnectWarning('MetaMask Background Multiplex', err)
notifyInpageOfStreamFailure()
})
// forward communication across inpage-background for these channels only
forwardTrafficBetweenMuxes(PROVIDER, pageMux, extensionMux)
// connect "phishing" channel to warning system
const phishingStream = extensionMux.createStream('phishing')
phishingStream.once('data', redirectToPhishingWarning)
// TODO:LegacyProvider: Delete
// handle legacy provider
const legacyPageStream = new LocalMessageDuplexStream({
name: LEGACY_CONTENT_SCRIPT,
target: LEGACY_INPAGE,
})
const legacyPageMux = new ObjectMultiplex()
legacyPageMux.setMaxListeners(25)
const legacyExtensionMux = new ObjectMultiplex()
legacyExtensionMux.setMaxListeners(25)
pump(legacyPageMux, legacyPageStream, legacyPageMux, (err) =>
logStreamDisconnectWarning('MetaMask Legacy Inpage Multiplex', err),
)
pump(
legacyExtensionMux,
extensionStream,
getNotificationTransformStream(),
legacyExtensionMux,
(err) => {
logStreamDisconnectWarning('MetaMask Background Legacy Multiplex', err)
notifyInpageOfStreamFailure()
},
)
forwardNamedTrafficBetweenMuxes(
LEGACY_PROVIDER,
PROVIDER,
legacyPageMux,
legacyExtensionMux,
)
forwardTrafficBetweenMuxes(
LEGACY_PUBLIC_CONFIG,
legacyPageMux,
legacyExtensionMux,
)
}
function forwardTrafficBetweenMuxes(channelName, muxA, muxB) {
const channelA = muxA.createStream(channelName)
const channelB = muxB.createStream(channelName)
pump(channelA, channelB, channelA, (error) =>
console.debug(
`MetaMask: Muxed traffic for channel "${channelName}" failed.`,
error,
),
)
}
// TODO:LegacyProvider: Delete
function forwardNamedTrafficBetweenMuxes(
channelAName,
channelBName,
muxA,
muxB,
) {
const channelA = muxA.createStream(channelAName)
const channelB = muxB.createStream(channelBName)
pump(channelA, channelB, channelA, (error) =>
console.debug(
`MetaMask: Muxed traffic between channels "${channelAName}" and "${channelBName}" failed.`,
error,
),
)
}
// TODO:LegacyProvider: Delete
function getNotificationTransformStream() {
return createThoughStream((chunk, _, cb) => {
if (chunk?.name === PROVIDER) {
if (chunk.data?.method === 'metamask_accountsChanged') {
chunk.data.method = 'wallet_accountsChanged'
chunk.data.result = chunk.data.params
delete chunk.data.params
}
}
cb(null, chunk)
})
}
/**
* Error handler for page to extension stream disconnections
*
* @param {string} remoteLabel - Remote stream name
* @param {Error} error - Stream connection error
*/
function logStreamDisconnectWarning(remoteLabel, error) {
console.debug(
`MetaMask: Content script lost connection to "${remoteLabel}".`,
error,
)
}
/**
* This function must ONLY be called in pump destruction/close callbacks.
* Notifies the inpage context that streams have failed, via window.postMessage.
* Relies on obj-multiplex and post-message-stream implementation details.
*/
function notifyInpageOfStreamFailure() {
window.postMessage(
{
target: INPAGE, // the post-message-stream "target"
data: {
// this object gets passed to obj-multiplex
name: PROVIDER, // the obj-multiplex channel name
data: {
jsonrpc: '2.0',
method: 'METAMASK_STREAM_FAILURE',
},
},
},
window.location.origin,
)
}
/**
* Determines if the provider should be injected
*
* @returns {boolean} {@code true} Whether the provider should be injected
*/
function shouldInjectProvider() {
return (
doctypeCheck() &&
suffixCheck() &&
documentElementCheck() &&
!blockedDomainCheck()
)
}
/**
* Checks the doctype of the current document if it exists
*
* @returns {boolean} {@code true} if the doctype is html or if none exists
*/
function doctypeCheck() {
const { doctype } = window.document
if (doctype) {
return doctype.name === 'html'
}
return true
}
/**
* Returns whether or not the extension (suffix) of the current document is prohibited
*
* This checks {@code window.location.pathname} against a set of file extensions
* that we should not inject the provider into. This check is indifferent of
* query parameters in the location.
*
* @returns {boolean} whether or not the extension of the current document is prohibited
*/
function suffixCheck() {
const prohibitedTypes = [/\.xml$/u, /\.pdf$/u]
const currentUrl = window.location.pathname
for (let i = 0; i < prohibitedTypes.length; i++) {
if (prohibitedTypes[i].test(currentUrl)) {
return false
}
}
return true
}
/**
* Checks the documentElement of the current document
*
* @returns {boolean} {@code true} if the documentElement is an html node or if none exists
*/
function documentElementCheck() {
const documentElement = document.documentElement.nodeName
if (documentElement) {
return documentElement.toLowerCase() === 'html'
}
return true
}
/**
* Checks if the current domain is blocked
*
* @returns {boolean} {@code true} if the current domain is blocked
*/
function blockedDomainCheck() {
const blockedDomains = [
'uscourts.gov',
'dropbox.com',
'webbyawards.com',
'cdn.shopify.com/s/javascripts/tricorder/xtld-read-only-frame.html',
'adyen.com',
'gravityforms.com',
'harbourair.com',
'ani.gamer.com.tw',
'blueskybooking.com',
'sharefile.com',
]
const currentUrl = window.location.href
let currentRegex
for (let i = 0; i < blockedDomains.length; i++) {
const blockedDomain = blockedDomains[i].replace('.', '\\.')
currentRegex = new RegExp(
`(?:https?:\\/\\/)(?:(?!${blockedDomain}).)*$`,
'u',
)
if (!currentRegex.test(currentUrl)) {
return true
}
}
return false
}
/**
* Redirects the current page to a phishing information page
*/
function redirectToPhishingWarning() {
console.debug('MetaMask: Routing to Phishing Warning component.')
const extensionURL = extension.runtime.getURL('phishing.html')
window.location.href = `${extensionURL}#${querystring.stringify({
hostname: window.location.hostname,
href: window.location.href,
})}`
}