diff options
Diffstat (limited to 'app/scripts/inpage.js')
-rw-r--r-- | app/scripts/inpage.js | 144 |
1 files changed, 128 insertions, 16 deletions
diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index b885a7e05..f8c6cf573 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -6,14 +6,36 @@ const LocalMessageDuplexStream = require('post-message-stream') const setupDappAutoReload = require('./lib/auto-reload.js') const MetamaskInpageProvider = require('metamask-inpage-provider') +let isEnabled = false +let warned = false +let providerHandle +let isApprovedHandle +let isUnlockedHandle + restoreContextAfterImports() log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn') -console.warn('ATTENTION: In an effort to improve user privacy, MetaMask will ' + -'stop exposing user accounts to dapps by default beginning November 2nd, 2018. ' + -'Dapps should call provider.enable() in order to view and use accounts. Please see ' + -'https://bit.ly/2QQHXvF for complete information and up-to-date example code.') +console.warn('ATTENTION: In an effort to improve user privacy, MetaMask ' + +'stopped exposing user accounts to dapps if "privacy mode" is enabled on ' + +'November 2nd, 2018. Dapps should now call provider.enable() in order to view and use ' + +'accounts. Please see https://bit.ly/2QQHXvF for complete information and up-to-date ' + +'example code.') + +/** + * Adds a postMessage listener for a specific message type + * + * @param {string} messageType - postMessage type to listen for + * @param {Function} handler - event handler + * @param {boolean} remove - removes this handler after being triggered + */ +function onMessage (messageType, handler, remove) { + window.addEventListener('message', function ({ data }) { + if (!data || data.type !== messageType) { return } + remove && window.removeEventListener('message', handler) + handler.apply(window, arguments) + }) +} // // setup plugin communication @@ -27,26 +49,103 @@ var metamaskStream = new LocalMessageDuplexStream({ // compose the inpage provider var inpageProvider = new MetamaskInpageProvider(metamaskStream) + // set a high max listener count to avoid unnecesary warnings inpageProvider.setMaxListeners(100) -// Augment the provider with its enable method -inpageProvider.enable = function (options = {}) { +// set up a listener for when MetaMask is locked +onMessage('metamasksetlocked', () => { isEnabled = false }) + +// set up a listener for privacy mode responses +onMessage('ethereumproviderlegacy', ({ data: { selectedAddress } }) => { + isEnabled = true + setTimeout(() => { + inpageProvider.publicConfigStore.updateState({ selectedAddress }) + }, 0) +}, true) + +// augment the provider with its enable method +inpageProvider.enable = function ({ force } = {}) { return new Promise((resolve, reject) => { - if (options.mockRejection) { - reject('User rejected account access') - } else { - inpageProvider.sendAsync({ method: 'eth_accounts', params: [] }, (error, response) => { - if (error) { - reject(error) - } else { - resolve(response.result) - } - }) + providerHandle = ({ data: { error, selectedAddress } }) => { + if (typeof error !== 'undefined') { + reject(error) + } else { + window.removeEventListener('message', providerHandle) + setTimeout(() => { + inpageProvider.publicConfigStore.updateState({ selectedAddress }) + }, 0) + + // wait for the background to update with an account + inpageProvider.sendAsync({ method: 'eth_accounts', params: [] }, (error, response) => { + if (error) { + reject(error) + } else { + isEnabled = true + resolve(response.result) + } + }) + } } + onMessage('ethereumprovider', providerHandle, true) + window.postMessage({ type: 'ETHEREUM_ENABLE_PROVIDER', force }, '*') }) } +// add metamask-specific convenience methods +inpageProvider._metamask = new Proxy({ + /** + * Determines if this domain is currently enabled + * + * @returns {boolean} - true if this domain is currently enabled + */ + isEnabled: function () { + return isEnabled + }, + + /** + * Determines if this domain has been previously approved + * + * @returns {Promise<boolean>} - Promise resolving to true if this domain has been previously approved + */ + isApproved: function () { + return new Promise((resolve) => { + isApprovedHandle = ({ data: { caching, isApproved } }) => { + if (caching) { + resolve(!!isApproved) + } else { + resolve(false) + } + } + onMessage('ethereumisapproved', isApprovedHandle, true) + window.postMessage({ type: 'ETHEREUM_IS_APPROVED' }, '*') + }) + }, + + /** + * Determines if MetaMask is unlocked by the user + * + * @returns {Promise<boolean>} - Promise resolving to true if MetaMask is currently unlocked + */ + isUnlocked: function () { + return new Promise((resolve) => { + isUnlockedHandle = ({ data: { isUnlocked } }) => { + resolve(!!isUnlocked) + } + onMessage('metamaskisunlocked', isUnlockedHandle, true) + window.postMessage({ type: 'METAMASK_IS_UNLOCKED' }, '*') + }) + }, +}, { + get: function (obj, prop) { + !warned && console.warn('Heads up! ethereum._metamask exposes methods that have ' + + 'not been standardized yet. This means that these methods may not be implemented ' + + 'in other dapp browsers and may be removed from MetaMask in the future.') + warned = true + return obj[prop] + }, +}) + // Work around for web3@1.0 deleting the bound `sendAsync` but not the unbound // `sendAsync` method on the prototype, causing `this` reference issues with drizzle const proxiedInpageProvider = new Proxy(inpageProvider, { @@ -57,6 +156,19 @@ const proxiedInpageProvider = new Proxy(inpageProvider, { window.ethereum = proxiedInpageProvider +// detect eth_requestAccounts and pipe to enable for now +function detectAccountRequest (method) { + const originalMethod = inpageProvider[method] + inpageProvider[method] = function ({ method }) { + if (method === 'eth_requestAccounts') { + return window.ethereum.enable() + } + return originalMethod.apply(this, arguments) + } +} +detectAccountRequest('send') +detectAccountRequest('sendAsync') + // // setup web3 // |