From e92b910240366f11f4eee3f8e9db0412921f4730 Mon Sep 17 00:00:00 2001 From: Erik Marks Date: Tue, 10 Mar 2020 11:54:17 -0700 Subject: [PATCH 1/2] use rpc-engine notifications for accountsChanged --- app/components/Views/BrowserTab/index.js | 62 +++++++++++++++++++----- app/core/AppConstants.js | 5 +- app/core/BackgroundBridge.js | 28 +++++------ 3 files changed, 67 insertions(+), 28 deletions(-) diff --git a/app/components/Views/BrowserTab/index.js b/app/components/Views/BrowserTab/index.js index f599b6aac9a..f6ab4e76c70 100644 --- a/app/components/Views/BrowserTab/index.js +++ b/app/components/Views/BrowserTab/index.js @@ -62,7 +62,7 @@ import { resemblesAddress } from '../../../util/address'; import createAsyncMiddleware from 'json-rpc-engine/src/createAsyncMiddleware'; import { ethErrors } from 'eth-json-rpc-errors'; -const { HOMEPAGE_URL, USER_AGENT } = AppConstants; +const { HOMEPAGE_URL, USER_AGENT, NOTIFICATION_NAMES } = AppConstants; const HOMEPAGE_HOST = 'home.metamask.io'; const styles = StyleSheet.create({ @@ -454,15 +454,33 @@ export class BrowserTab extends PureComponent { webview: this.webview, url, getRpcMethodMiddleware: this.getRpcMethodMiddleware.bind(this), - shouldExposeAccounts: hostname => { - const { approvedHosts, privacyMode } = this.props; - return !privacyMode || approvedHosts[hostname]; - }, isMainFrame }); this.backgroundBridges.push(newBridge); }; + notifyConnection = (payload, hostname, restricted = true) => { + const { privacyMode, approvedHosts } = this.props; + + // TODO:permissions move permissioning logic elsewhere + this.backgroundBridges.forEach(bridge => { + if (bridge.hostname === hostname && (!restricted || !privacyMode || approvedHosts[bridge.hostname])) { + bridge.sendNotification(payload); + } + }); + }; + + notifyAllConnections = (payload, restricted = true) => { + const { privacyMode, approvedHosts } = this.props; + + // TODO:permissions move permissioning logic elsewhere + this.backgroundBridges.forEach(bridge => { + if (!privacyMode || !restricted || approvedHosts[bridge.hostname]) { + bridge.sendNotification(payload); + } + }); + }; + getRpcMethodMiddleware = ({ hostname }) => // all user facing RPC calls not implemented by the provider createAsyncMiddleware(async (req, res, next) => { @@ -492,11 +510,6 @@ export class BrowserTab extends PureComponent { if (approved) { res.result = [selectedAddress.toLowerCase()]; - this.backgroundBridges.forEach(bridge => { - if (bridge.hostname === hostname) { - bridge.emit('update'); - } - }); } else { throw ethErrors.provider.userRejectedRequest('User denied account authorization.'); } @@ -845,8 +858,12 @@ export class BrowserTab extends PureComponent { } componentDidUpdate(prevProps) { - const prevNavigation = prevProps.navigation; - const { navigation } = this.props; + const { + approvedHosts: prevApprovedHosts, + navigation: prevNavigation, + selectedAddress: prevSelectedAddress + } = prevProps; + const { approvedHosts, navigation, selectedAddress } = this.props; // If tab wasn't activated and we detect an tab change // we need to check if it's time to activate the tab @@ -864,6 +881,27 @@ export class BrowserTab extends PureComponent { this.loadUrl(); } } + + const numApprovedHosts = Object.keys(approvedHosts).length; + const prevNumApprovedHosts = Object.keys(prevApprovedHosts).length; + + // this will happen if the approved hosts were cleared + if (numApprovedHosts === 0 && prevNumApprovedHosts > 0) { + this.notifyAllConnections( + { + method: NOTIFICATION_NAMES.accountsChanged, + result: [] + }, + false + ); // notification should be sent regardless of approval status + } + + if (numApprovedHosts > 0 && selectedAddress !== prevSelectedAddress) { + this.notifyAllConnections({ + method: NOTIFICATION_NAMES.accountsChanged, + result: [selectedAddress] + }); + } } componentWillUnmount() { diff --git a/app/core/AppConstants.js b/app/core/AppConstants.js index dd194600dc4..7bc31803467 100644 --- a/app/core/AppConstants.js +++ b/app/core/AppConstants.js @@ -30,5 +30,8 @@ export default { INSTAPAY_GAS_PONDERATOR: 1.2, USER_AGENT: Device.isAndroid() ? 'Mozilla/5.0 (Linux; Android 10; Android SDK built for x86 Build/OSM1.180201.023) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36' - : 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/76.0.3809.123 Mobile/15E148 Safari/605.1' + : 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/76.0.3809.123 Mobile/15E148 Safari/605.1', + NOTIFICATION_NAMES: { + accountsChanged: 'wallet_accountsChanged' + } }; diff --git a/app/core/BackgroundBridge.js b/app/core/BackgroundBridge.js index 97d3f723b73..0d89cdb5f5f 100644 --- a/app/core/BackgroundBridge.js +++ b/app/core/BackgroundBridge.js @@ -40,7 +40,7 @@ class Port extends EventEmitter { } export class BackgroundBridge extends EventEmitter { - constructor({ webview, url, getRpcMethodMiddleware, shouldExposeAccounts, isMainFrame }) { + constructor({ webview, url, getRpcMethodMiddleware, isMainFrame }) { super(); this.url = url; this.hostname = new URL(url).hostname; @@ -48,11 +48,12 @@ export class BackgroundBridge extends EventEmitter { this._webviewRef = webview && webview.current; this.createMiddleware = getRpcMethodMiddleware; - this.shouldExposeAccounts = shouldExposeAccounts; this.provider = Engine.context.NetworkController.provider; this.blockTracker = this.provider._blockTracker; this.port = new Port(this._webviewRef, isMainFrame); + this.engine = null; + const portStream = new MobilePortStream(this.port, url); // setup multiplexing const mux = setupMultiplex(portStream); @@ -81,14 +82,14 @@ export class BackgroundBridge extends EventEmitter { * @param {*} outStream - The stream to provide over. */ setupProviderConnection(outStream) { - const engine = this.setupProviderEngine(); + this.engine = this.setupProviderEngine(); // setup connection - const providerStream = createEngineStream({ engine }); + const providerStream = createEngineStream({ engine: this.engine }); pump(outStream, providerStream, outStream, err => { // handle any middleware cleanup - engine._middleware.forEach(mid => { + this.engine._middleware.forEach(mid => { if (mid.destroy && typeof mid.destroy === 'function') { mid.destroy(); } @@ -146,10 +147,7 @@ export class BackgroundBridge extends EventEmitter { * @param {*} outStream - The stream to provide public config over. */ setupPublicConfig(outStream) { - const configStore = this.createPublicConfigStore({ - // check the providerApprovalController's approvedOrigins - checkIsEnabled: () => this.shouldExposeAccounts(this.hostname) - }); + const configStore = this.createPublicConfigStore(); const configStream = asStream(configStore); @@ -166,19 +164,15 @@ export class BackgroundBridge extends EventEmitter { * Constructor helper: initialize a public config store. * This store is used to make some config info available to Dapps synchronously. */ - createPublicConfigStore({ checkIsEnabled }) { + createPublicConfigStore() { // subset of state for metamask inpage provider const publicConfigStore = new ObservableStore(); - const selectPublicState = ({ isUnlocked, selectedAddress, network }) => { - const isEnabled = checkIsEnabled(); - const isReady = isUnlocked && isEnabled; + const selectPublicState = ({ isUnlocked, network }) => { const networkType = Engine.context.NetworkController.state.provider.type; const chainId = Object.keys(NetworkList).indexOf(networkType) > -1 && NetworkList[networkType].chainId; const result = { isUnlocked, - isEnabled, - selectedAddress: isReady ? selectedAddress.toLowerCase() : null, networkVersion: network, chainId: chainId ? `0x${parseInt(chainId, 10).toString(16)}` : null }; @@ -204,6 +198,10 @@ export class BackgroundBridge extends EventEmitter { return publicConfigStore; } + sendNotification(payload) { + this.engine && this.engine.emit('notification', payload); + } + /** * The metamask-state of the various controllers, made available to the UI * From 078d13a880e15ce188664b4d188887b1b22fe82e Mon Sep 17 00:00:00 2001 From: Erik Marks Date: Tue, 10 Mar 2020 12:11:10 -0700 Subject: [PATCH 2/2] only notify current web page --- app/components/Views/BrowserTab/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/components/Views/BrowserTab/index.js b/app/components/Views/BrowserTab/index.js index f6ab4e76c70..1954401116a 100644 --- a/app/components/Views/BrowserTab/index.js +++ b/app/components/Views/BrowserTab/index.js @@ -472,10 +472,11 @@ export class BrowserTab extends PureComponent { notifyAllConnections = (payload, restricted = true) => { const { privacyMode, approvedHosts } = this.props; + const { fullHostname } = this.state; // TODO:permissions move permissioning logic elsewhere this.backgroundBridges.forEach(bridge => { - if (!privacyMode || !restricted || approvedHosts[bridge.hostname]) { + if (bridge.hostname === fullHostname && (!privacyMode || !restricted || approvedHosts[bridge.hostname])) { bridge.sendNotification(payload); } });