mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-29 23:58:06 +01:00
14d85b1332
A few inconsistencies in JSDoc formatting have been fixed throughout the project. Many issues remain; these were just the few things that were easy to fix with a regular expression. The changes include: * Using lower-case for primitive types, but capitalizing non-primitive types * Separating the parameter identifier and the description with a dash * Omitting a dash between the return type and the return description * Ensuring the parameter type is first and the identifier is second (in a few places it was backwards) * Using square brackets to denote when a parameter is optional, rather than putting "(optional)" in the parameter description * Including a type and identifier with every parameter * Fixing inconsistent spacing, except where it's used for alignment * Remove incorrectly formatted `@deprecated` tags that reference non- existent properties * Remove lone comment block without accompanying function Additionally, one parameter was renamed for clarity.
238 lines
6.8 KiB
JavaScript
238 lines
6.8 KiB
JavaScript
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'
|
|
|
|
// 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
|
|
|
|
// Eventually this streaming injection could be replaced with:
|
|
// https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Language_Bindings/Components.utils.exportFunction
|
|
//
|
|
// But for now that is only Firefox
|
|
// If we create a FireFox-only code path using that API,
|
|
// MetaMask will be much faster loading and performant on Firefox.
|
|
|
|
if (shouldInjectProvider()) {
|
|
injectScript(inpageBundle)
|
|
start()
|
|
}
|
|
|
|
/**
|
|
* 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 (e) {
|
|
console.error('MetaMask provider injection failed.', e)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
*/
|
|
async function setupStreams() {
|
|
// the transport-specific streams for communication between inpage and background
|
|
const pageStream = new LocalMessageDuplexStream({
|
|
name: 'contentscript',
|
|
target: 'inpage',
|
|
})
|
|
const extensionPort = extension.runtime.connect({ name: 'contentscript' })
|
|
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)
|
|
|
|
pump(pageMux, pageStream, pageMux, (err) =>
|
|
logStreamDisconnectWarning('MetaMask Inpage Multiplex', err),
|
|
)
|
|
pump(extensionMux, extensionStream, extensionMux, (err) =>
|
|
logStreamDisconnectWarning('MetaMask Background Multiplex', err),
|
|
)
|
|
|
|
// forward communication across inpage-background for these channels only
|
|
forwardTrafficBetweenMuxers('provider', pageMux, extensionMux)
|
|
forwardTrafficBetweenMuxers('publicConfig', pageMux, extensionMux)
|
|
|
|
// connect "phishing" channel to warning system
|
|
const phishingStream = extensionMux.createStream('phishing')
|
|
phishingStream.once('data', redirectToPhishingWarning)
|
|
}
|
|
|
|
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,
|
|
),
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Error handler for page to extension stream disconnections
|
|
*
|
|
* @param {string} remoteLabel - Remote stream name
|
|
* @param {Error} err - Stream connection error
|
|
*/
|
|
function logStreamDisconnectWarning(remoteLabel, err) {
|
|
let warningMsg = `MetamaskContentscript - lost connection to ${remoteLabel}`
|
|
if (err) {
|
|
warningMsg += `\n${err.stack}`
|
|
}
|
|
console.warn(warningMsg)
|
|
}
|
|
|
|
/**
|
|
* 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.log('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,
|
|
})}`
|
|
}
|
|
|
|
/**
|
|
* 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 undefined
|
|
}
|
|
// wait for load
|
|
return new Promise((resolve) =>
|
|
window.addEventListener('DOMContentLoaded', resolve, { once: true }),
|
|
)
|
|
}
|