diff --git a/.circleci/config.yml b/.circleci/config.yml index 6c3b38aed810..edf031ef032c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -56,8 +56,8 @@ workflows: - test-unit - test-unit-global - test-mozilla-lint - # - test-e2e-chrome - # - test-e2e-firefox + - test-e2e-chrome + - test-e2e-firefox - test-integration-flat-chrome - test-integration-flat-firefox - job-publish-prerelease: @@ -74,9 +74,9 @@ workflows: - prep-build # - prep-docs - all-tests-pass - # - coveralls-upload: - # requires: - # - test-unit + - coveralls-upload: + requires: + - test-unit jobs: create_release_pull_request: diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 56d5546ac6e7..31e5b0c62f98 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -18,7 +18,7 @@ const inpageBundle = inpageContent + inpageSuffix // If we create a FireFox-only code path using that API, // MetaMask will be much faster loading and performant on Firefox. -if (shouldInjectWeb3()) { +if (shouldInjectProvider()) { injectScript(inpageBundle) start() } @@ -37,7 +37,7 @@ function injectScript (content) { container.insertBefore(scriptTag, container.children[0]) container.removeChild(scriptTag) } catch (e) { - console.error('MetaMask script injection failed', e) + console.error('MetaMask provider injection failed.', e) } } @@ -117,11 +117,11 @@ function logStreamDisconnectWarning (remoteLabel, err) { } /** - * Determines if Web3 should be injected + * Determines if the provider should be injected * - * @returns {boolean} {@code true} if Web3 should be injected + * @returns {boolean} {@code true} if the provider should be injected */ -function shouldInjectWeb3 () { +function shouldInjectProvider () { return doctypeCheck() && suffixCheck() && documentElementCheck() && !blacklistedDomainCheck() } @@ -144,8 +144,8 @@ function doctypeCheck () { * 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 should not have web3 injected into them. This check is indifferent of query parameters - * in the location. + * 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 */ diff --git a/app/scripts/controllers/permissions/index.js b/app/scripts/controllers/permissions/index.js index a10c547dc65b..3f79bd2d8e0e 100644 --- a/app/scripts/controllers/permissions/index.js +++ b/app/scripts/controllers/permissions/index.js @@ -19,12 +19,13 @@ const WALLET_METHOD_PREFIX = 'wallet_' const CAVEAT_NAMES = { exposedAccounts: 'exposedAccounts', } +const ACCOUNTS_CHANGED_NOTIFICATION = 'wallet_accountsChanged' class PermissionsController { constructor ( { - openPopup, closePopup, notifyDomain, notifyAllDomains, keyringController + openPopup, closePopup, notifyDomain, notifyAllDomains, keyringController, } = {}, restoredPermissions = {}, restoredState = {}) { @@ -141,7 +142,10 @@ class PermissionsController { origin, 'eth_accounts', CAVEAT_NAMES.exposedAccounts, accounts ) - this.notifyDomain(origin, 'accountsChanged', accounts) + this.notifyDomain(origin, { + method: ACCOUNTS_CHANGED_NOTIFICATION, + result: accounts, + }) } /** @@ -212,7 +216,10 @@ class PermissionsController { perms.map(methodName => { if (methodName === 'eth_accounts') { - this.notifyDomain(origin, 'accountsChanged', []) + this.notifyDomain( + origin, + { method: ACCOUNTS_CHANGED_NOTIFICATION, result: [] } + ) } return { parentCapability: methodName } @@ -249,7 +256,10 @@ class PermissionsController { */ clearPermissions () { this.permissions.clearDomains() - this.notifyAllDomains('accountsChanged', []) + this.notifyAllDomains({ + method: ACCOUNTS_CHANGED_NOTIFICATION, + result: [], + }) } /** diff --git a/app/scripts/controllers/permissions/permissions-safe-methods.json b/app/scripts/controllers/permissions/permissions-safe-methods.json index 37042422eeaa..17b46b531346 100644 --- a/app/scripts/controllers/permissions/permissions-safe-methods.json +++ b/app/scripts/controllers/permissions/permissions-safe-methods.json @@ -5,6 +5,7 @@ "net_version", "eth_blockNumber", "eth_call", + "eth_chainId", "eth_coinbase", "eth_estimateGas", "eth_gasPrice", diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 9686cffa4c71..acf74d991d5b 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -191,11 +191,11 @@ class TransactionController extends EventEmitter { // validate const normalizedTxParams = txUtils.normalizeTxParams(txParams) - // TODO:lps once, I saw origin with a value of 'chrome-extension://...' for a - // transaction initiated from the UI. + // TODO:lps once, I saw 'origin' with a value of 'chrome-extension://...' + // for a transaction initiated from the UI. // When would this happen? It's a problem if it does. I don't think we want to // hard-code 'chrome-extension://' here - if (origin === 'MetaMask') { + if (origin === 'metamask') { // Assert the from address is the selected address if (normalizedTxParams.from !== this.getSelectedAddress()) { throw ethErrors.rpc.internal(`Internally initiated transaction is using invalid account.`) diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 5925e193ed2b..8cb1b569db19 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -31,13 +31,14 @@ const restoreContextAfterImports = () => { } cleanContextForImports() -require('web3/dist/web3.min.js') + const log = require('loglevel') const LocalMessageDuplexStream = require('post-message-stream') -const setupDappAutoReload = require('./lib/auto-reload.js') const MetamaskInpageProvider = require('metamask-inpage-provider') -let warned = false +// TODO:deprecate:2019-12-16 +require('web3/dist/web3.min.js') +const setupDappAutoReload = require('./lib/auto-reload.js') restoreContextAfterImports() @@ -59,53 +60,9 @@ const inpageProvider = new MetamaskInpageProvider(metamaskStream) // set a high max listener count to avoid unnecesary warnings inpageProvider.setMaxListeners(100) -// give the dapps control of a refresh they can toggle this off on the window.ethereum -// this will be default true so it does not break any old apps. -inpageProvider.autoRefreshOnNetworkChange = true - -// publicConfig isn't populated until we get a message from background. -// Using this getter will ensure the state is available -const getPublicConfigWhenReady = async () => { - const store = inpageProvider.publicConfigStore - let state = store.getState() - // if state is missing, wait for first update - if (!state.hasOwnProperty('isUnlocked')) { - state = await new Promise(resolve => store.once('update', resolve)) - } - return state -} - -// add metamask-specific convenience methods -inpageProvider._metamask = new Proxy({ - - /** - * Determines if MetaMask is unlocked by the user - * - * @returns {Promise} - Promise resolving to true if MetaMask is currently unlocked - */ - isUnlocked: async function () { - const { isUnlocked } = await getPublicConfigWhenReady() - return Boolean(isUnlocked) - }, - - /** - * WILL BE DEPRECATED. - * Asynchronously determines if this domain is currently enabled. - * - * @returns {Promise} - Promise resolving to true if this domain is currently enabled - */ - isApproved: async function () { - return Boolean(inpageProvider.selectedAddress) - }, -}, { - 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] - }, -}) +// +// TODO:deprecate:2019-12-16 +// // Work around for web3@1.0 deleting the bound `sendAsync` but not the unbound // `sendAsync` method on the prototype, causing `this` reference issues @@ -115,11 +72,7 @@ const proxiedInpageProvider = new Proxy(inpageProvider, { deleteProperty: () => true, }) -window.ethereum = proxiedInpageProvider - -// // setup web3 -// if (typeof window.web3 !== 'undefined') { throw new Error(`MetaMask detected another web3. @@ -137,6 +90,12 @@ log.debug('MetaMask - injected web3') setupDappAutoReload(web3, inpageProvider.publicConfigStore) +// +// end deprecate:2019-12-16 +// + +window.ethereum = proxiedInpageProvider + inpageProvider.publicConfigStore.subscribe(function (state) { if (state.onboardingcomplete) { window.postMessage('onboardingcomplete', '*') diff --git a/app/scripts/lib/auto-reload.js b/app/scripts/lib/auto-reload.js index fd209c230ae8..fdf591f33ef9 100644 --- a/app/scripts/lib/auto-reload.js +++ b/app/scripts/lib/auto-reload.js @@ -1,3 +1,6 @@ + +// TODO:deprecate:2019-12-16 + module.exports = setupDappAutoReload function setupDappAutoReload (web3, observable) { diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 9fe7c456709f..24fb4dea9aeb 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -291,7 +291,6 @@ module.exports = class MetamaskController extends EventEmitter { CachedBalancesController: this.cachedBalancesController.store, OnboardingController: this.onboardingController.store, IncomingTransactionsController: this.incomingTransactionsController.store, - ThreeBoxController: this.threeBoxController.store, ABTestController: this.abTestController.store, PermissionsController: this.permissionsController.permissions, PermissionsMetadata: this.permissionsController.store, @@ -339,7 +338,7 @@ module.exports = class MetamaskController extends EventEmitter { version, // account mgmt getAccounts: async ({ origin }) => { - if (origin === 'MetaMask') { + if (origin === 'metamask') { return [this.preferencesController.getSelectedAddress()] } else if ( this.keyringController.memStore.getState().isUnlocked @@ -1419,6 +1418,7 @@ module.exports = class MetamaskController extends EventEmitter { * @param {object} publicApi - The public API */ setupProviderConnection (outStream, senderUrl, extensionId) { + const origin = senderUrl.hostname const engine = this.setupProviderEngine(senderUrl, extensionId) // setup connection @@ -1505,10 +1505,10 @@ module.exports = class MetamaskController extends EventEmitter { // manage external connections /** - * Adds a reference to a connection by origin. Ignores the 'MetaMask' origin. + * Adds a reference to a connection by origin. Ignores the 'metamask' origin. * Caller must ensure that the returned id is stored such that the reference * can be deleted later. - * + * * @param {string} origin - The connection's origin string. * @param {Object} options - Data associated with the connection * @param {Object} options.engine - The connection's JSON Rpc Engine @@ -1516,7 +1516,7 @@ module.exports = class MetamaskController extends EventEmitter { */ addConnection (origin, { engine }) { - if (origin === 'MetaMask') return null + if (origin === 'metamask') return null if (!this.connections[origin]) { this.connections[origin] = {} @@ -1533,56 +1533,55 @@ module.exports = class MetamaskController extends EventEmitter { /** * Deletes a reference to a connection, by origin and id. * Ignores unknown origins. - * + * * @param {string} origin - The connection's origin string. * @param {string} id - The connection's id, as returned from addConnection. */ removeConnection (origin, id) { - if (!this.connections[origin]) return + const connections = this.connections[origin] + if (!connections) return - delete this.connections[origin][id] + delete connections[id] - if (Object.keys(this.connections[origin].length === 0)) { + if (Object.keys(connections.length === 0)) { delete this.connections[origin] } } /** * Causes the RPC engines associated with the connections to the given origin - * to emit an event with the given name and payload. + * to emit a notification event with the given payload. * Ignores unknown origins. - * + * * @param {string} origin - The connection's origin string. - * @param {string} eventName - The name of the event to emit. * @param {any} payload - The event payload. */ - notifyConnections (origin, eventName, payload) { + notifyConnections (origin, payload) { - if (!this.connections[origin]) return + const connections = this.connections[origin] + if (!connections) return - Object.values(this.connections[origin]).forEach(conn => { - conn.engine && conn.engine.emit(eventName, payload) + Object.values(connections).forEach(conn => { + conn.engine && conn.engine.emit('notification', payload) }) } /** - * Causes the RPC engines associated with all connections to emit an event - * with the given name and payload. - * - * @param {string} eventName - The name of the event to emit. + * Causes the RPC engines associated with all connections to emit a + * notification event with the given payload. + * * @param {any} payload - The event payload. */ - notifyAllConnections (eventName, payload) { + notifyAllConnections (payload) { Object.values(this.connections).forEach(origin => { Object.values(origin).forEach(conn => { - conn.engine && conn.engine.emit(eventName, payload) + conn.engine && conn.engine.emit('notification', payload) }) }) } - // handlers /** diff --git a/package.json b/package.json index fb69cd1fcc01..35b4aaa98a99 100644 --- a/package.json +++ b/package.json @@ -125,7 +125,7 @@ "lodash.shuffle": "^4.2.0", "loglevel": "^1.4.1", "luxon": "^1.8.2", - "metamask-inpage-provider": "rekmarks/metamask-inpage-provider#permissions", + "metamask-inpage-provider": "MetaMask/metamask-inpage-provider#LoginPerSite", "metamask-logo": "^2.1.4", "mkdirp": "^0.5.1", "multihashes": "^0.4.12", @@ -171,7 +171,7 @@ "redux-thunk": "^2.2.0", "request-promise": "^4.2.1", "reselect": "^3.0.1", - "rpc-cap": "^0.18.0", + "rpc-cap": "^1.0.0", "safe-event-emitter": "^1.0.1", "safe-json-stringify": "^1.2.0", "single-call-balance-checker-abi": "^1.0.0", diff --git a/ui/app/components/app/permission-page-container/permission-page-container-content/permission-page-container-content.component.js b/ui/app/components/app/permission-page-container/permission-page-container-content/permission-page-container-content.component.js index 3591899b4653..5fc9449d46c7 100644 --- a/ui/app/components/app/permission-page-container/permission-page-container-content/permission-page-container-content.component.js +++ b/ui/app/components/app/permission-page-container/permission-page-container-content/permission-page-container-content.component.js @@ -69,10 +69,7 @@ export default class PermissionPageContainerContent extends PureComponent { const items = Object.keys(selectedPermissions).map((methodName) => { - // TODO:lps:review is this how we want to handle permissions without - // descriptions? In all current cases, if this condition triggers, - // the request will still fail when rpc-cap attempts to approve the - // permissions. + // the request will almost certainly be reject by rpc-cap if this happens if (!permissionsDescriptions[methodName]) { console.warn(`Unknown permission requested: ${methodName}`) } diff --git a/ui/app/components/app/signature-request-original.js b/ui/app/components/app/signature-request-original.js index 70cc35d3d47f..b9e71767a539 100644 --- a/ui/app/components/app/signature-request-original.js +++ b/ui/app/components/app/signature-request-original.js @@ -30,9 +30,10 @@ const { DEFAULT_ROUTE } = require('../../helpers/constants/routes') /** * TODO:lps - * This call fails. The account must be grabbed from elsewhere. + * Signature requests will usually/always fail. The account must be retrieved differently. * In an old TODO getting it from "the sig request" was suggested? */ + function mapStateToProps (state) { return { balance: getSelectedAccount(state).balance, diff --git a/ui/app/helpers/constants/routes.js b/ui/app/helpers/constants/routes.js index 25c9166a2a89..6a536af3ad7c 100644 --- a/ui/app/helpers/constants/routes.js +++ b/ui/app/helpers/constants/routes.js @@ -3,7 +3,6 @@ const UNLOCK_ROUTE = '/unlock' const LOCK_ROUTE = '/lock' const SETTINGS_ROUTE = '/settings' const GENERAL_ROUTE = '/settings/general' -const CONNECTIONS_ROUTE = '/settings/connections' const ADVANCED_ROUTE = '/settings/advanced' const SECURITY_ROUTE = '/settings/security' const PERMISSIONS_ROUTE = '/settings/permissions' @@ -85,7 +84,6 @@ module.exports = { SECURITY_ROUTE, PERMISSIONS_ROUTE, GENERAL_ROUTE, - CONNECTIONS_ROUTE, ABOUT_US_ROUTE, CONTACT_LIST_ROUTE, CONTACT_EDIT_ROUTE, diff --git a/ui/app/pages/settings/connections-tab/connected-site-row/connected-site-row.component.js b/ui/app/pages/settings/connections-tab/connected-site-row/connected-site-row.component.js deleted file mode 100644 index 37c068ab5492..000000000000 --- a/ui/app/pages/settings/connections-tab/connected-site-row/connected-site-row.component.js +++ /dev/null @@ -1,31 +0,0 @@ -import React, { PureComponent } from 'react' -import PropTypes from 'prop-types' - -export default class ConnectedSiteRow extends PureComponent { - static defaultProps = { - siteTitle: null, - siteImage: null, - onDelete: () => {}, - } - - static propTypes = { - siteTitle: PropTypes.string, - siteImage: PropTypes.string, - origin: PropTypes.string.isRequired, - onDelete: PropTypes.func, - } - - render () { - const { - origin, - onDelete, - } = this.props - - return ( -
-
{origin}
-
-
- ) - } -} diff --git a/ui/app/pages/settings/connections-tab/connected-site-row/index.js b/ui/app/pages/settings/connections-tab/connected-site-row/index.js deleted file mode 100644 index 57023b6dacc0..000000000000 --- a/ui/app/pages/settings/connections-tab/connected-site-row/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './connected-site-row.component' diff --git a/ui/app/pages/settings/connections-tab/connected-site-row/index.scss b/ui/app/pages/settings/connections-tab/connected-site-row/index.scss deleted file mode 100644 index f0957a8e98f8..000000000000 --- a/ui/app/pages/settings/connections-tab/connected-site-row/index.scss +++ /dev/null @@ -1,14 +0,0 @@ -.connected-site-row { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - - &__origin { - font-family: monospace; - } - - &__delete { - padding: 8px; - } -} diff --git a/ui/app/pages/settings/connections-tab/connections-tab.component.js b/ui/app/pages/settings/connections-tab/connections-tab.component.js deleted file mode 100644 index 3309fcc0d984..000000000000 --- a/ui/app/pages/settings/connections-tab/connections-tab.component.js +++ /dev/null @@ -1,133 +0,0 @@ -import React, { PureComponent } from 'react' -import PropTypes from 'prop-types' -import ConnectedSiteEntry from './connected-site-row' -import TextField from '../../../components/ui/text-field' -import Button from '../../../components/ui/button' - -export default class ConnectionsTab extends PureComponent { - static contextTypes = { - t: PropTypes.func, - metricsEvent: PropTypes.func, - } - - static defaultProps = { - activeTab: {}, - } - - static propTypes = { - activeTab: PropTypes.object, - approvedOrigins: PropTypes.object.isRequired, - approveProviderRequestByOrigin: PropTypes.func.isRequired, - rejectProviderRequestByOrigin: PropTypes.func.isRequired, - showClearApprovalModal: PropTypes.func.isRequired, - } - - state = { - input: this.props.activeTab.origin || '', - } - - handleAddOrigin = () => { - const newOrigin = this.state.input - this.setState({ - input: '', - }, () => { - if (newOrigin && newOrigin.trim()) { - this.props.approveProviderRequestByOrigin(newOrigin) - } - }) - } - - renderNewOriginInput () { - const { t } = this.context - - return ( -
-
- { t('addSite') } -
- { t('addSiteDescription') } -
-
-
-
- this.setState({ input: e.target.value })} - fullWidth - margin="dense" - min={0} - /> - -
-
-
- ) - } - - renderApprovedOriginsList () { - const { t } = this.context - const { approvedOrigins, rejectProviderRequestByOrigin, showClearApprovalModal } = this.props - const approvedEntries = Object.entries(approvedOrigins) - const approvalListEmpty = approvedEntries.length === 0 - - return ( -
-
- { t('connected') } - - { t('connectedDescription') } - -
-
- { - approvalListEmpty - ?
- : null - } - { - approvedEntries.map(([origin, { siteTitle, siteImage }]) => ( - { - rejectProviderRequestByOrigin(origin) - }} - /> - )) - } -
-
- -
-
- ) - } - - render () { - return ( -
- { this.renderNewOriginInput() } - { this.renderApprovedOriginsList() } -
- ) - } -} diff --git a/ui/app/pages/settings/connections-tab/connections-tab.container.js b/ui/app/pages/settings/connections-tab/connections-tab.container.js deleted file mode 100644 index cf3efc2b46cb..000000000000 --- a/ui/app/pages/settings/connections-tab/connections-tab.container.js +++ /dev/null @@ -1,39 +0,0 @@ -import ConnectionsTab from './connections-tab.component' -import { compose } from 'recompose' -import { connect } from 'react-redux' -import { withRouter } from 'react-router-dom' -import { - approveProviderRequestByOrigin, - rejectProviderRequestByOrigin, - showModal, -} from '../../../store/actions' - -export const mapStateToProps = state => { - const { - activeTab, - metamask, - } = state - const { - approvedOrigins, - } = metamask - - return { - activeTab, - approvedOrigins, - } -} - -export const mapDispatchToProps = dispatch => { - return { - approveProviderRequestByOrigin: (origin) => dispatch(approveProviderRequestByOrigin(origin)), - rejectProviderRequestByOrigin: (origin) => dispatch(rejectProviderRequestByOrigin(origin)), - showClearApprovalModal: () => dispatch(showModal({ - name: 'CLEAR_APPROVED_ORIGINS', - })), - } -} - -export default compose( - withRouter, - connect(mapStateToProps, mapDispatchToProps) -)(ConnectionsTab) diff --git a/ui/app/pages/settings/connections-tab/index.js b/ui/app/pages/settings/connections-tab/index.js deleted file mode 100644 index b04f4e33a67a..000000000000 --- a/ui/app/pages/settings/connections-tab/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './connections-tab.container' diff --git a/ui/app/pages/settings/connections-tab/index.scss b/ui/app/pages/settings/connections-tab/index.scss deleted file mode 100644 index 249a7193f731..000000000000 --- a/ui/app/pages/settings/connections-tab/index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './connected-site-row/index'; diff --git a/ui/app/pages/settings/index.scss b/ui/app/pages/settings/index.scss index 16825c97bf8a..339dea4e3133 100644 --- a/ui/app/pages/settings/index.scss +++ b/ui/app/pages/settings/index.scss @@ -4,8 +4,6 @@ @import 'settings-tab/index'; -@import 'connections-tab/index'; - @import 'contact-list-tab/index'; .settings-page { diff --git a/ui/app/pages/settings/settings.component.js b/ui/app/pages/settings/settings.component.js index 95d84f46d8f7..6af666248743 100644 --- a/ui/app/pages/settings/settings.component.js +++ b/ui/app/pages/settings/settings.component.js @@ -4,7 +4,6 @@ import { Switch, Route, matchPath, withRouter } from 'react-router-dom' import TabBar from '../../components/app/tab-bar' import c from 'classnames' import SettingsTab from './settings-tab' -import ConnectionsTab from './connections-tab' import NetworksTab from './networks-tab' import AdvancedTab from './advanced-tab' import InfoTab from './info-tab' @@ -16,7 +15,6 @@ import { ADVANCED_ROUTE, SECURITY_ROUTE, GENERAL_ROUTE, - CONNECTIONS_ROUTE, ABOUT_US_ROUTE, SETTINGS_ROUTE, NETWORKS_ROUTE, @@ -156,7 +154,6 @@ class SettingsPage extends PureComponent { -