From 1ce3591c8ea844eab2dbd9b585e7f5dd3cf18ce8 Mon Sep 17 00:00:00 2001 From: bitpshr Date: Wed, 7 Nov 2018 11:11:08 -0500 Subject: wip --- app/scripts/contentscript.js | 10 ++++----- app/scripts/inpage.js | 53 ++++++++++++++++++++++++++++---------------- 2 files changed, 39 insertions(+), 24 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 1a10cdb34..efb14233d 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -151,20 +151,20 @@ function listenForProviderRequest () { switch (action) { case 'approve-provider-request': isEnabled = true - injectScript(`window.dispatchEvent(new CustomEvent('ethereumprovider', { detail: {}}))`) + window.postMessage({ type: 'ethereumprovider' }, '*') break case 'reject-provider-request': - injectScript(`window.dispatchEvent(new CustomEvent('ethereumprovider', { detail: { error: 'User rejected provider access' }}))`) + window.postMessage({ type: 'ethereumprovider', error: 'User rejected provider access' }, '*') break case 'answer-is-approved': - injectScript(`window.dispatchEvent(new CustomEvent('ethereumisapproved', { detail: { isApproved: ${isApproved}, caching: ${caching}}}))`) + window.postMessage({ type: 'ethereumisapproved', isApproved, caching }, '*') break case 'answer-is-unlocked': - injectScript(`window.dispatchEvent(new CustomEvent('metamaskisunlocked', { detail: { isUnlocked: ${isUnlocked}}}))`) + window.postMessage({ type: 'metamaskisunlocked', isUnlocked }, '*') break case 'metamask-set-locked': isEnabled = false - injectScript(`window.dispatchEvent(new CustomEvent('metamasksetlocked', { detail: {}}))`) + window.postMessage({ type: 'metamasksetlocked' }, '*') break } }) diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 327e25042..8ba3ea4a9 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -22,6 +22,14 @@ console.warn('ATTENTION: In an effort to improve user privacy, MetaMask ' + 'accounts. Please see https://bit.ly/2QQHXvF for complete information and up-to-date ' + 'example code.') +function once(messageType, handler) { + window.addEventListener('message', function ({ data: { type } }) { + if (type !== messageType) { return } + window.removeEventListener('message', handler) + handler.apply(window, arguments) + }) +} + // // setup plugin communication // @@ -39,18 +47,21 @@ var inpageProvider = new MetamaskInpageProvider(metamaskStream) inpageProvider.setMaxListeners(100) // set up a listener for when MetaMask is locked -window.addEventListener('metamasksetlocked', () => { +window.addEventListener('message', ({ data: { type } }) => { + if (type !== 'metamasksetlocked') { return } isEnabled = false }) // augment the provider with its enable method inpageProvider.enable = function ({ force } = {}) { return new Promise((resolve, reject) => { - window.removeEventListener('ethereumprovider', providerHandle) - providerHandle = ({ detail }) => { - if (typeof detail.error !== 'undefined') { - reject(detail.error) + window.removeEventListener('message', providerHandle) + providerHandle = ({ data: { type, error } }) => { + if (type !== 'ethereumprovider') { return } + if (typeof error !== 'undefined') { + reject(error) } else { + window.removeEventListener('message', providerHandle) // wait for the publicConfig store to populate with an account const publicConfig = new Promise((resolve) => { const { selectedAddress } = inpageProvider.publicConfigStore.getState() @@ -84,7 +95,7 @@ inpageProvider.enable = function ({ force } = {}) { .catch(reject) } } - window.addEventListener('ethereumprovider', providerHandle) + window.addEventListener('message', providerHandle) window.postMessage({ type: 'ETHEREUM_ENABLE_PROVIDER', force }, '*') }) } @@ -107,19 +118,21 @@ inpageProvider._metamask = new Proxy({ */ isApproved: function() { return new Promise((resolve, reject) => { - window.removeEventListener('ethereumisapproved', isApprovedHandle) - isApprovedHandle = ({ detail }) => { - if (typeof detail.error !== 'undefined') { - reject(detail.error) + window.removeEventListener('message', isApprovedHandle) + isApprovedHandle = ({ data: { caching, isApproved, error, type } }) => { + if (type !== 'ethereumisapproved') { return } + window.removeEventListener('message', isApprovedHandle) + if (typeof error !== 'undefined') { + reject(error) } else { - if (detail.caching) { - resolve(!!detail.isApproved) + if (caching) { + resolve(!!isApproved) } else { resolve(false) } } } - window.addEventListener('ethereumisapproved', isApprovedHandle) + window.addEventListener('message', isApprovedHandle) window.postMessage({ type: 'ETHEREUM_IS_APPROVED' }, '*') }) }, @@ -131,15 +144,17 @@ inpageProvider._metamask = new Proxy({ */ isUnlocked: function () { return new Promise((resolve, reject) => { - window.removeEventListener('metamaskisunlocked', isUnlockedHandle) - isUnlockedHandle = ({ detail }) => { - if (typeof detail.error !== 'undefined') { - reject(detail.error) + window.removeEventListener('message', isUnlockedHandle) + isUnlockedHandle = ({ data: { isUnlocked, error, type } }) => { + if (type !== 'metamaskisunlocked') { return } + window.removeEventListener('message', isUnlockedHandle) + if (typeof error !== 'undefined') { + reject(error) } else { - resolve(!!detail.isUnlocked) + resolve(!!isUnlocked) } } - window.addEventListener('metamaskisunlocked', isUnlockedHandle) + window.addEventListener('message', isUnlockedHandle) window.postMessage({ type: 'METAMASK_IS_UNLOCKED' }, '*') }) }, -- cgit v1.2.3 From a224b71837a09af28213b5f6d3ac46a2416428e0 Mon Sep 17 00:00:00 2001 From: bitpshr Date: Wed, 7 Nov 2018 11:18:21 -0500 Subject: 1102: use postMessage instead of injected scripts to bypass CSP --- app/scripts/inpage.js | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 8ba3ea4a9..785237ff5 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -22,10 +22,17 @@ console.warn('ATTENTION: In an effort to improve user privacy, MetaMask ' + 'accounts. Please see https://bit.ly/2QQHXvF for complete information and up-to-date ' + 'example code.') -function once(messageType, handler) { +/** + * 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: { type } }) { if (type !== messageType) { return } - window.removeEventListener('message', handler) + remove && window.removeEventListener('message', handler) handler.apply(window, arguments) }) } @@ -47,17 +54,12 @@ var inpageProvider = new MetamaskInpageProvider(metamaskStream) inpageProvider.setMaxListeners(100) // set up a listener for when MetaMask is locked -window.addEventListener('message', ({ data: { type } }) => { - if (type !== 'metamasksetlocked') { return } - isEnabled = false -}) +onMessage('metamasksetlocked', ({ data: { type } }) => { isEnabled = false }) // augment the provider with its enable method inpageProvider.enable = function ({ force } = {}) { return new Promise((resolve, reject) => { - window.removeEventListener('message', providerHandle) providerHandle = ({ data: { type, error } }) => { - if (type !== 'ethereumprovider') { return } if (typeof error !== 'undefined') { reject(error) } else { @@ -95,7 +97,7 @@ inpageProvider.enable = function ({ force } = {}) { .catch(reject) } } - window.addEventListener('message', providerHandle) + onMessage('ethereumprovider', providerHandle, true) window.postMessage({ type: 'ETHEREUM_ENABLE_PROVIDER', force }, '*') }) } @@ -118,10 +120,7 @@ inpageProvider._metamask = new Proxy({ */ isApproved: function() { return new Promise((resolve, reject) => { - window.removeEventListener('message', isApprovedHandle) isApprovedHandle = ({ data: { caching, isApproved, error, type } }) => { - if (type !== 'ethereumisapproved') { return } - window.removeEventListener('message', isApprovedHandle) if (typeof error !== 'undefined') { reject(error) } else { @@ -132,7 +131,7 @@ inpageProvider._metamask = new Proxy({ } } } - window.addEventListener('message', isApprovedHandle) + onMessage('ethereumisapproved', isApprovedHandle, true) window.postMessage({ type: 'ETHEREUM_IS_APPROVED' }, '*') }) }, @@ -144,17 +143,14 @@ inpageProvider._metamask = new Proxy({ */ isUnlocked: function () { return new Promise((resolve, reject) => { - window.removeEventListener('message', isUnlockedHandle) isUnlockedHandle = ({ data: { isUnlocked, error, type } }) => { - if (type !== 'metamaskisunlocked') { return } - window.removeEventListener('message', isUnlockedHandle) if (typeof error !== 'undefined') { reject(error) } else { resolve(!!isUnlocked) } } - window.addEventListener('message', isUnlockedHandle) + onMessage('metamaskisunlocked', isUnlockedHandle, true) window.postMessage({ type: 'METAMASK_IS_UNLOCKED' }, '*') }) }, -- cgit v1.2.3 From 718393f89a1bf99ff15bdfe1aa5c4301fbd56621 Mon Sep 17 00:00:00 2001 From: bitpshr Date: Wed, 7 Nov 2018 11:31:21 -0500 Subject: Cleanup --- app/scripts/inpage.js | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 785237ff5..a7c0b0416 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -54,12 +54,12 @@ var inpageProvider = new MetamaskInpageProvider(metamaskStream) inpageProvider.setMaxListeners(100) // set up a listener for when MetaMask is locked -onMessage('metamasksetlocked', ({ data: { type } }) => { isEnabled = false }) +onMessage('metamasksetlocked', () => { isEnabled = false }) // augment the provider with its enable method inpageProvider.enable = function ({ force } = {}) { return new Promise((resolve, reject) => { - providerHandle = ({ data: { type, error } }) => { + providerHandle = ({ data: { error } }) => { if (typeof error !== 'undefined') { reject(error) } else { @@ -119,16 +119,12 @@ inpageProvider._metamask = new Proxy({ * @returns {Promise} - Promise resolving to true if this domain has been previously approved */ isApproved: function() { - return new Promise((resolve, reject) => { - isApprovedHandle = ({ data: { caching, isApproved, error, type } }) => { - if (typeof error !== 'undefined') { - reject(error) + return new Promise((resolve) => { + isApprovedHandle = ({ data: { caching, isApproved } }) => { + if (caching) { + resolve(!!isApproved) } else { - if (caching) { - resolve(!!isApproved) - } else { - resolve(false) - } + resolve(false) } } onMessage('ethereumisapproved', isApprovedHandle, true) @@ -142,13 +138,9 @@ inpageProvider._metamask = new Proxy({ * @returns {Promise} - Promise resolving to true if MetaMask is currently unlocked */ isUnlocked: function () { - return new Promise((resolve, reject) => { - isUnlockedHandle = ({ data: { isUnlocked, error, type } }) => { - if (typeof error !== 'undefined') { - reject(error) - } else { - resolve(!!isUnlocked) - } + return new Promise((resolve) => { + isUnlockedHandle = ({ data: { isUnlocked } }) => { + resolve(!!isUnlocked) } onMessage('metamaskisunlocked', isUnlockedHandle, true) window.postMessage({ type: 'METAMASK_IS_UNLOCKED' }, '*') -- cgit v1.2.3 From 8bed073f209a178d73d0a04cc72df034993b0998 Mon Sep 17 00:00:00 2001 From: Noah I Date: Fri, 9 Nov 2018 23:14:32 +1100 Subject: 1102: Fix inpage listener syntax (#5708) (#5709) --- app/scripts/inpage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index a7c0b0416..cfcf5e364 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -30,8 +30,8 @@ console.warn('ATTENTION: In an effort to improve user privacy, MetaMask ' + * @param {boolean} remove - removes this handler after being triggered */ function onMessage(messageType, handler, remove) { - window.addEventListener('message', function ({ data: { type } }) { - if (type !== messageType) { return } + window.addEventListener('message', function ({ data }) { + if (!data || data.type !== messageType) { return } remove && window.removeEventListener('message', handler) handler.apply(window, arguments) }) -- cgit v1.2.3 From c3176248544e3e0019389b1f177cac68087bfe8f Mon Sep 17 00:00:00 2001 From: Paul Bouchon Date: Fri, 9 Nov 2018 20:40:32 -0500 Subject: EIP-1102: Update publicConfig store concurrently with approval --- app/scripts/contentscript.js | 8 +++-- app/scripts/controllers/provider-approval.js | 10 +++++-- app/scripts/inpage.js | 44 ++++++++++------------------ 3 files changed, 29 insertions(+), 33 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index efb14233d..ee38ee3ab 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -147,11 +147,15 @@ function listenForProviderRequest () { } }) - extension.runtime.onMessage.addListener(({ action = '', isApproved, caching, isUnlocked }) => { + extension.runtime.onMessage.addListener(({ action = '', isApproved, caching, isUnlocked, selectedAddress }) => { switch (action) { case 'approve-provider-request': isEnabled = true - window.postMessage({ type: 'ethereumprovider' }, '*') + 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 rejected provider access' }, '*') diff --git a/app/scripts/controllers/provider-approval.js b/app/scripts/controllers/provider-approval.js index d3b7f6dff..21d7fd22e 100644 --- a/app/scripts/controllers/provider-approval.js +++ b/app/scripts/controllers/provider-approval.js @@ -88,7 +88,10 @@ class ProviderApprovalController { _handlePrivacyRequest () { const privacyMode = this.preferencesController.getFeatureFlags().privacyMode if (!privacyMode) { - this.platform && this.platform.sendMessage({ action: 'approve-provider-request' }, { active: true }) + this.platform && this.platform.sendMessage({ + action: 'approve-legacy-provider-request', + selectedAddress: this.publicConfigStore.getState().selectedAddress, + }, { active: true }) this.publicConfigStore.emit('update', this.publicConfigStore.getState()) } } @@ -101,7 +104,10 @@ class ProviderApprovalController { approveProviderRequest (origin) { this.closePopup && this.closePopup() const requests = this.store.getState().providerRequests || [] - this.platform && this.platform.sendMessage({ action: 'approve-provider-request' }, { active: true }) + this.platform && this.platform.sendMessage({ + action: 'approve-provider-request', + selectedAddress: this.publicConfigStore.getState().selectedAddress, + }, { active: true }) this.publicConfigStore.emit('update', this.publicConfigStore.getState()) const providerRequests = requests.filter(request => request.origin !== origin) this.store.updateState({ providerRequests }) diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index cfcf5e364..83392761e 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -56,45 +56,31 @@ inpageProvider.setMaxListeners(100) // 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 + inpageProvider.publicConfigStore.updateState({ selectedAddress }) +}, true) + // augment the provider with its enable method inpageProvider.enable = function ({ force } = {}) { return new Promise((resolve, reject) => { - providerHandle = ({ data: { error } }) => { + providerHandle = ({ data: { error, selectedAddress } }) => { if (typeof error !== 'undefined') { reject(error) } else { window.removeEventListener('message', providerHandle) - // wait for the publicConfig store to populate with an account - const publicConfig = new Promise((resolve) => { - const { selectedAddress } = inpageProvider.publicConfigStore.getState() - inpageProvider._metamask.isUnlocked().then(unlocked => { - if (!unlocked || selectedAddress) { - resolve() - } else { - inpageProvider.publicConfigStore.on('update', ({ selectedAddress }) => { - selectedAddress && resolve() - }) - } - }) - }) + inpageProvider.publicConfigStore.updateState({ selectedAddress }) // wait for the background to update with an account - const ethAccounts = new Promise((resolveAccounts, rejectAccounts) => { - inpageProvider.sendAsync({ method: 'eth_accounts', params: [] }, (error, response) => { - if (error) { - rejectAccounts(error) - } else { - resolveAccounts(response.result) - } - }) - }) - - Promise.all([ethAccounts, publicConfig]) - .then(([selectedAddress]) => { + inpageProvider.sendAsync({ method: 'eth_accounts', params: [] }, (error, response) => { + if (error) { + reject(error) + } else { isEnabled = true - resolve(selectedAddress) - }) - .catch(reject) + resolve(response.result) + } + }) } } onMessage('ethereumprovider', providerHandle, true) -- cgit v1.2.3 From 7ce2cf4572f80b71d5ab4d4c479a49bcca9ccdf1 Mon Sep 17 00:00:00 2001 From: PaddyMc Date: Tue, 13 Nov 2018 17:19:12 +0000 Subject: Fixes #3425: Better support for batch transactions (#5437) --- app/scripts/background.js | 1 + 1 file changed, 1 insertion(+) (limited to 'app/scripts') diff --git a/app/scripts/background.js b/app/scripts/background.js index 078e84928..a6fc5ed78 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -440,6 +440,7 @@ function triggerUi () { const currentlyActiveMetamaskTab = Boolean(tabs.find(tab => openMetamaskTabsIDs[tab.id])) if (!popupIsOpen && !currentlyActiveMetamaskTab && !notificationIsOpen) { notificationManager.showPopup() + notificationIsOpen = true } }) } -- cgit v1.2.3 From 0549782595335e8257d1b4abf7d6220020e2c8db Mon Sep 17 00:00:00 2001 From: Paul Bouchon Date: Tue, 13 Nov 2018 14:57:43 -0500 Subject: Update Balanc3 API (#5744) * Update balanc3 API used in TokenRatesController * Remove demo URL and use nativeCurrency when fetching token rates --- app/scripts/controllers/token-rates.js | 38 ++++++++++++++-------------------- app/scripts/metamask-controller.js | 1 + 2 files changed, 16 insertions(+), 23 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/token-rates.js b/app/scripts/controllers/token-rates.js index b6f084841..a8936f13b 100644 --- a/app/scripts/controllers/token-rates.js +++ b/app/scripts/controllers/token-rates.js @@ -14,8 +14,9 @@ class TokenRatesController { * * @param {Object} [config] - Options to configure controller */ - constructor ({ interval = DEFAULT_INTERVAL, preferences } = {}) { + constructor ({ interval = DEFAULT_INTERVAL, currency, preferences } = {}) { this.store = new ObservableStore() + this.currency = currency this.preferences = preferences this.interval = interval } @@ -26,32 +27,23 @@ class TokenRatesController { async updateExchangeRates () { if (!this.isActive) { return } const contractExchangeRates = {} - // copy array to ensure its not modified during iteration - const tokens = this._tokens.slice() - for (const token of tokens) { - if (!token) return log.error(`TokenRatesController - invalid tokens state:\n${JSON.stringify(tokens, null, 2)}`) - const address = token.address - contractExchangeRates[address] = await this.fetchExchangeRate(address) + const nativeCurrency = this.currency ? this.currency.getState().nativeCurrency.toUpperCase() : 'ETH' + const pairs = this._tokens.map(token => `pairs[]=${token.address}/${nativeCurrency}`) + const query = pairs.join('&') + if (this._tokens.length > 0) { + try { + const response = await fetch(`https://exchanges.balanc3.net/pie?${query}&autoConversion=true`) + const { prices = [] } = await response.json() + prices.forEach(({ pair, price }) => { + contractExchangeRates[pair.split('/')[0]] = typeof price === 'number' ? price : 0 + }) + } catch (error) { + log.warn(`MetaMask - TokenRatesController exchange rate fetch failed.`, error) + } } this.store.putState({ contractExchangeRates }) } - /** - * Fetches a token exchange rate by address - * - * @param {String} address - Token contract address - */ - async fetchExchangeRate (address) { - try { - const response = await fetch(`https://metamask.balanc3.net/prices?from=${address}&to=ETH&autoConversion=false&summaryOnly=true`) - const json = await response.json() - return json && json.length ? json[0].averagePrice : 0 - } catch (error) { - log.warn(`MetaMask - TokenRatesController exchange rate fetch failed for ${address}.`, error) - return 0 - } - } - /** * @type {Number} */ diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 5ae0f608d..641a7ef3f 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -117,6 +117,7 @@ module.exports = class MetamaskController extends EventEmitter { // token exchange rate tracker this.tokenRatesController = new TokenRatesController({ + currency: this.currencyController.store, preferences: this.preferencesController.store, }) -- cgit v1.2.3