diff options
34 files changed, 607 insertions, 116 deletions
@@ -1,4 +1,4 @@ { - "presets": ["es2015", "stage-0", "react"], - "plugins": ["transform-runtime", "transform-async-to-generator"] + "presets": [["env", { "debug": true }], "react", "stage-0"], + "plugins": ["transform-runtime", "transform-async-to-generator", "transform-class-properties"] } diff --git a/.circleci/config.yml b/.circleci/config.yml index c0262fcfa..6d4bf270c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -102,30 +102,29 @@ jobs: - checkout - restore_cache: keys: - - dependency-cache-{{ checksum "package-lock.json" }} + - v1.0-dependency-cache-{{ checksum "package-lock.json" }} # fallback to using the latest cache if no exact match is found - - dependency-cache- + - v1.0-dependency-cache- - run: name: Install npm 6 + deps via npm command: | - sudo npm install -g npm@6.1.0 && npm install + sudo npm install -g npm@6.1.0 && npm install --no-save - save_cache: - key: dependency-cache-{{ checksum "package-lock.json" }} + key: v1.0-dependency-cache-{{ checksum "package-lock.json" }} paths: - node_modules - prep-deps-firefox: docker: - image: circleci/node:8.11.3-browsers steps: - checkout - restore_cache: - key: dependency-cache-firefox- + key: v1.0-dependency-cache-firefox- - run: name: Download Firefox If needed command: ./.circleci/scripts/firefox-download.sh - save_cache: - key: dependency-cache-firefox- + key: v1.0-dependency-cache-firefox- paths: - firefox @@ -135,7 +134,7 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-{{ checksum "package-lock.json" }} + key: v1.0-dependency-cache-{{ checksum "package-lock.json" }} - run: name: build:dist command: npm run dist @@ -154,7 +153,7 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-{{ checksum "package-lock.json" }} + key: v1.0-dependency-cache-{{ checksum "package-lock.json" }} - run: name: build:dist command: npm run doc @@ -169,7 +168,7 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-{{ checksum "package-lock.json" }} + key: v1.0-dependency-cache-{{ checksum "package-lock.json" }} - run: name: Get Scss Cache key # this allows us to checksum against a whole directory @@ -188,7 +187,7 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-{{ checksum "package-lock.json" }} + key: v1.0-dependency-cache-{{ checksum "package-lock.json" }} - run: name: Test command: npm run lint @@ -199,7 +198,7 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-{{ checksum "package-lock.json" }} + key: v1.0-dependency-cache-{{ checksum "package-lock.json" }} - run: name: Test command: npx nsp check @@ -210,7 +209,7 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-{{ checksum "package-lock.json" }} + key: v1.0-dependency-cache-{{ checksum "package-lock.json" }} - restore_cache: key: build-cache-{{ .Revision }} - run: @@ -226,12 +225,12 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-firefox- + key: v1.0-dependency-cache-firefox- - run: name: Install firefox command: ./.circleci/scripts/firefox-install.sh - restore_cache: - key: dependency-cache-{{ checksum "package-lock.json" }} + key: v1.0-dependency-cache-{{ checksum "package-lock.json" }} - restore_cache: key: build-cache-{{ .Revision }} - run: @@ -247,7 +246,7 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-{{ checksum "package-lock.json" }} + key: v1.0-dependency-cache-{{ checksum "package-lock.json" }} - restore_cache: key: build-cache-{{ .Revision }} - run: @@ -263,12 +262,12 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-firefox- + key: v1.0-dependency-cache-firefox- - run: name: Install firefox command: ./.circleci/scripts/firefox-install.sh - restore_cache: - key: dependency-cache-{{ checksum "package-lock.json" }} + key: v1.0-dependency-cache-{{ checksum "package-lock.json" }} - restore_cache: key: build-cache-{{ .Revision }} - run: @@ -284,7 +283,7 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-{{ checksum "package-lock.json" }} + key: v1.0-dependency-cache-{{ checksum "package-lock.json" }} - restore_cache: key: build-cache-{{ .Revision }} - run: @@ -301,7 +300,7 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-{{ checksum "package-lock.json" }} + key: v1.0-dependency-cache-{{ checksum "package-lock.json" }} - restore_cache: key: build-cache-{{ .Revision }} - restore_cache: @@ -328,7 +327,7 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-{{ checksum "package-lock.json" }} + key: v1.0-dependency-cache-{{ checksum "package-lock.json" }} - restore_cache: key: build-cache-{{ .Revision }} - restore_cache: @@ -351,7 +350,7 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-{{ checksum "package-lock.json" }} + key: v1.0-dependency-cache-{{ checksum "package-lock.json" }} - run: name: test:coverage command: npm run test:coverage @@ -364,12 +363,12 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-firefox- + key: v1.0-dependency-cache-firefox- - run: name: Install firefox command: ./.circleci/scripts/firefox-install.sh - restore_cache: - key: dependency-cache-{{ checksum "package-lock.json" }} + key: v1.0-dependency-cache-{{ checksum "package-lock.json" }} - run: name: Get Scss Cache key # this allows us to checksum against a whole directory @@ -388,7 +387,7 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-{{ checksum "package-lock.json" }} + key: v1.0-dependency-cache-{{ checksum "package-lock.json" }} - run: name: Get Scss Cache key # this allows us to checksum against a whole directory @@ -407,12 +406,12 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-firefox- + key: v1.0-dependency-cache-firefox- - run: name: Install firefox command: ./.circleci/scripts/firefox-install.sh - restore_cache: - key: dependency-cache-{{ checksum "package-lock.json" }} + key: v1.0-dependency-cache-{{ checksum "package-lock.json" }} - run: name: Get Scss Cache key # this allows us to checksum against a whole directory @@ -431,7 +430,7 @@ jobs: steps: - checkout - restore_cache: - key: dependency-cache-{{ checksum "package-lock.json" }} + key: v1.0-dependency-cache-{{ checksum "package-lock.json" }} - run: name: Get Scss Cache key # this allows us to checksum against a whole directory diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..1cdadda65 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,6 @@ +# Lines starting with '#' are comments. +# Each line is a file pattern followed by one or more owners. + +ui/ @danjm @alextsg @whymarrh +app/scripts/controllers/transactions @frankiebee + diff --git a/.gitignore b/.gitignore index 0e91a7d04..e53133361 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,10 @@ package .idea .vscode +# VIM +*.swp +*.swo + temp .tmp .sass-cache @@ -0,0 +1 @@ +engine-strict=true diff --git a/CHANGELOG.md b/CHANGELOG.md index b2d78b6b1..7e1e1ff4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Add Trezor Support - Allow to remove accounts (Imported and Hardware Wallets) - [#4840](https://github.com/MetaMask/metamask-extension/pull/4840): Now shows notifications when transactions are completed. +- [#4855](https://github.com/MetaMask/metamask-extension/pull/4855): network.js: convert rpc protocol to lower case. ## 4.8.0 Thur Jun 14 2018 @@ -1,6 +1,8 @@ # MetaMask Browser Extension [![Build Status](https://circleci.com/gh/MetaMask/metamask-extension.svg?style=shield&circle-token=a1ddcf3cd38e29267f254c9c59d556d513e3a1fd)](https://circleci.com/gh/MetaMask/metamask-extension) [![Coverage Status](https://coveralls.io/repos/github/MetaMask/metamask-extension/badge.svg?branch=master)](https://coveralls.io/github/MetaMask/metamask-extension?branch=master) [![Greenkeeper badge](https://badges.greenkeeper.io/MetaMask/metamask-extension.svg)](https://greenkeeper.io/) [![Stories in Ready](https://badge.waffle.io/MetaMask/metamask-extension.png?label=in%20progress&title=waffle.io)](https://waffle.io/MetaMask/metamask-extension) +🚨 As of 7/25/18, the MetaMask extension has been removed from the Chrome Web Store. In the meantime, you can download the latest version of MetaMask on our [Releases](https://github.com/MetaMask/metamask-extension/releases) page and load it in Chrome by visiting `chrome://extensions`. For more detailed steps, see our [help center](https://consensys.zendesk.com/hc/en-us/articles/360004134152-How-to-Install-MetaMask-Manually). Follow [@metamask_io](https://twitter.com/metamask_io) on Twitter for updates. 🚨 + ## Support If you're a user seeking support, [here is our support site](https://metamask.helpscoutdocs.com/). @@ -26,10 +28,9 @@ If you're a web dapp developer, we've got two types of guides for you: ## Building locally - - Install [Node.js](https://nodejs.org/en/) version 6.3.1 or later. + - Install [Node.js](https://nodejs.org/en/) version 8.11.3 and npm version 6.1.0 - Install dependencies: - - For node versions up to and including 9, install local dependencies with `npm install`. - - For node versions 10 and later, install [Yarn](https://yarnpkg.com/lang/en/docs/install/) and use `yarn install`. + - If you are using nvm (recommended) running `nvm use` will automatically choose the right node version for you. - Install gulp globally with `npm install -g gulp-cli`. - Build the project to the `./dist/` folder with `gulp build`. - Optionally, to rebuild on file changes, run `gulp dev`. diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 7c775fb04..7b7114c35 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -178,6 +178,7 @@ function blacklistedDomainCheck () { 'adyen.com', 'gravityforms.com', 'harbourair.com', + 'ani.gamer.com.tw', 'blueskybooking.com', ] var currentUrl = window.location.href diff --git a/app/scripts/controllers/detect-tokens.js b/app/scripts/controllers/detect-tokens.js index b30dc00f1..195ec918a 100644 --- a/app/scripts/controllers/detect-tokens.js +++ b/app/scripts/controllers/detect-tokens.js @@ -117,7 +117,7 @@ class DetectTokensController { } }) } - + /** * Internal isActive state * @type {Object} diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js index a50f6dc45..b6f7705b5 100644 --- a/app/scripts/controllers/network/network.js +++ b/app/scripts/controllers/network/network.js @@ -9,6 +9,7 @@ const extend = require('xtend') const EthQuery = require('eth-query') const createEventEmitterProxy = require('../../lib/events-proxy.js') const log = require('loglevel') +const urlUtil = require('url') const { ROPSTEN, RINKEBY, @@ -155,6 +156,8 @@ module.exports = class NetworkController extends EventEmitter { } _configureStandardProvider ({ rpcUrl }) { + // urlUtil handles malformed urls + rpcUrl = urlUtil.parse(rpcUrl).format() const providerParams = extend(this._baseProviderParams, { rpcUrl, engineParams: { diff --git a/app/scripts/controllers/transactions/tx-gas-utils.js b/app/scripts/controllers/transactions/tx-gas-utils.js index ab4031faa..5cd0f5407 100644 --- a/app/scripts/controllers/transactions/tx-gas-utils.js +++ b/app/scripts/controllers/transactions/tx-gas-utils.js @@ -30,14 +30,10 @@ class TxGasUtil { try { estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit) } catch (err) { - const simulationFailed = ( - err.message.includes('Transaction execution error.') || - err.message.includes('gas required exceeds allowance or always failing transaction') - ) - if (simulationFailed) { - txMeta.simulationFails = true - return txMeta + txMeta.simulationFails = { + reason: err.message, } + return txMeta } this.setTxGas(txMeta, block.gasLimit, estimatedGasHex) return txMeta diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 3b303a95c..bcc7075c2 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1436,7 +1436,7 @@ module.exports = class MetamaskController extends EventEmitter { } /** - * A method for activating the retrieval of price data and auto detect tokens, + * A method for activating the retrieval of price data and auto detect tokens, * which should only be fetched when the UI is visible. * @private * @param {boolean} active - True if price data should be getting fetched. diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index 04d90bfff..bd0005221 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -37,4 +37,5 @@ module.exports = [ require('./024'), require('./025'), require('./026'), + require('./027'), ] diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js index 901c26cab..0803164e8 100644 --- a/app/scripts/platforms/extension.js +++ b/app/scripts/platforms/extension.js @@ -14,6 +14,12 @@ class ExtensionPlatform { extension.tabs.create({ url }) } + closeCurrentWindow () { + return extension.windows.getCurrent((windowDetails) => { + return extension.windows.remove(windowDetails.id) + }) + } + getVersion () { return extension.runtime.getManifest().version } diff --git a/app/scripts/ui.js b/app/scripts/ui.js index 9bf97be87..da100f928 100644 --- a/app/scripts/ui.js +++ b/app/scripts/ui.js @@ -64,7 +64,6 @@ async function start () { css = betaUIState ? NewMetaMaskUiCss() : OldMetaMaskUiCss() deleteInjectedCss = injectCss(css) } - if (state.appState.shouldClose) notificationManager.closePopup() }) }) diff --git a/old-ui/app/components/transaction-list-item.js b/old-ui/app/components/transaction-list-item.js index e9280419a..f479ce666 100644 --- a/old-ui/app/components/transaction-list-item.js +++ b/old-ui/app/components/transaction-list-item.js @@ -36,14 +36,23 @@ TransactionListItem.prototype.showRetryButton = function () { return false } + let currentTxIsLatest = false const currentNonce = txParams.nonce const currentNonceTxs = transactions.filter(tx => tx.txParams.nonce === currentNonce) const currentNonceSubmittedTxs = currentNonceTxs.filter(tx => tx.status === 'submitted') + const currentSubmittedTxs = transactions.filter(tx => tx.status === 'submitted') const lastSubmittedTxWithCurrentNonce = currentNonceSubmittedTxs[0] const currentTxIsLatestWithNonce = lastSubmittedTxWithCurrentNonce && lastSubmittedTxWithCurrentNonce.id === transaction.id + if (currentSubmittedTxs.length > 0) { + const lastTx = currentSubmittedTxs.reduce((tx1, tx2) => { + if (tx1.submittedTime < tx2.submittedTime) return tx1 + return tx2 + }) + currentTxIsLatest = lastTx.id === transaction.id + } - return currentTxIsLatestWithNonce && Date.now() - submittedTime > 30000 + return currentTxIsLatestWithNonce && Date.now() - submittedTime > 30000 && currentTxIsLatest } TransactionListItem.prototype.render = function () { diff --git a/package-lock.json b/package-lock.json index fcbed7788..1a2897a9e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3618,9 +3618,9 @@ } }, "babel-preset-env": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.6.1.tgz", - "integrity": "sha512-W6VIyA6Ch9ePMI7VptNn2wBM6dbG0eSz25HEiL40nQXCsXGTGZSTZu1Iap+cj3Q0S5a7T9+529l/5Bkvd+afNA==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.7.0.tgz", + "integrity": "sha512-9OR2afuKDneX2/q2EurSftUYM0xGu4O2D9adAhVfADDhrYDaxXV0rBbevVYoY9n6nyX1PmQW/0jtpJvUNr9CHg==", "requires": { "babel-plugin-check-es2015-constants": "^6.22.0", "babel-plugin-syntax-trailing-function-commas": "^6.22.0", @@ -3649,7 +3649,7 @@ "babel-plugin-transform-es2015-unicode-regex": "^6.22.0", "babel-plugin-transform-exponentiation-operator": "^6.22.0", "babel-plugin-transform-regenerator": "^6.22.0", - "browserslist": "^2.1.2", + "browserslist": "^3.2.6", "invariant": "^2.2.2", "semver": "^5.3.0" } @@ -4664,12 +4664,19 @@ } }, "browserslist": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.0.tgz", - "integrity": "sha512-mNYp0RNeu1xueGuJFSXkU+K0nH+dBE/gcjtyhtNKfU8hwdrVIfoA7i5iFSjOmzkGdL2QaO7YX9ExiVPE7AY9JA==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-3.2.8.tgz", + "integrity": "sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ==", "requires": { - "caniuse-lite": "^1.0.30000784", - "electron-to-chromium": "^1.3.30" + "caniuse-lite": "^1.0.30000844", + "electron-to-chromium": "^1.3.47" + }, + "dependencies": { + "electron-to-chromium": { + "version": "1.3.52", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.52.tgz", + "integrity": "sha1-0tnxJwuko7lnuDHEDvcftNmrXOA=" + } } }, "bs58": { @@ -4996,9 +5003,9 @@ "integrity": "sha1-MN/YMAnVcE8C3/s3clBo7RKjZrs=" }, "caniuse-lite": { - "version": "1.0.30000786", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000786.tgz", - "integrity": "sha1-G0Jc2FaNgFvFY4veSQXNhjVod0Y=" + "version": "1.0.30000865", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000865.tgz", + "integrity": "sha512-vs79o1mOSKRGv/1pSkp4EXgl4ZviWeYReXw60XfacPU64uQWZwJT6vZNmxRF9O+6zu71sJwMxLK5JXxbzuVrLw==" }, "capture-stack-trace": { "version": "1.0.0", @@ -8390,13 +8397,12 @@ "resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-2.0.1.tgz", "integrity": "sha512-lxHZOQspexk3DaGj4RBbWy4C/qNOWRnxpaJzNnYD3WEmC8shcJ4tHs7Xv878rzvILfJnSFSCCiKQhng1m80oBQ==", "requires": { - "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7", "ethereumjs-util": "^5.1.1" }, "dependencies": { "ethereumjs-abi": { "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7", - "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", + "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7", "requires": { "bn.js": "^4.10.0", "ethereumjs-util": "^5.0.0" @@ -8674,13 +8680,12 @@ "resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz", "integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=", "requires": { - "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7", "ethereumjs-util": "^5.1.1" }, "dependencies": { "ethereumjs-abi": { "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7", - "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", + "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7", "requires": { "bn.js": "^4.10.0", "ethereumjs-util": "^5.0.0" @@ -8722,14 +8727,12 @@ "integrity": "sha512-lxHZOQspexk3DaGj4RBbWy4C/qNOWRnxpaJzNnYD3WEmC8shcJ4tHs7Xv878rzvILfJnSFSCCiKQhng1m80oBQ==", "dev": true, "requires": { - "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7", "ethereumjs-util": "^5.1.1" }, "dependencies": { "ethereumjs-abi": { "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7", - "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", - "dev": true, + "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7", "requires": { "bn.js": "^4.10.0", "ethereumjs-util": "^5.0.0" @@ -8741,7 +8744,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", - "dev": true, "requires": { "bn.js": "^4.11.0", "create-hash": "^1.1.2", @@ -13475,9 +13477,9 @@ "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==" }, "node-sass": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.9.1.tgz", - "integrity": "sha512-m6H1I6cHXsHsJ7BIWdnJsz9S9gVMyh+/H2cOTXgl2/2WqyyWlBcl4PHJcqrXo5RZVCfCUFqOtjPN0+0XbVHR5Q==", + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.9.2.tgz", + "integrity": "sha512-LdxoJLZutx0aQXHtWIYwJKMj+9pTjneTcLWJgzf2XbGu0q5pRNqW5QvFCEdm3mc5rJOdru/mzln5d0EZLacf6g==", "requires": { "async-foreach": "^0.1.3", "chalk": "^1.1.1", @@ -20658,9 +20660,9 @@ } }, "node-sass": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.9.1.tgz", - "integrity": "sha512-m6H1I6cHXsHsJ7BIWdnJsz9S9gVMyh+/H2cOTXgl2/2WqyyWlBcl4PHJcqrXo5RZVCfCUFqOtjPN0+0XbVHR5Q==", + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.9.2.tgz", + "integrity": "sha512-LdxoJLZutx0aQXHtWIYwJKMj+9pTjneTcLWJgzf2XbGu0q5pRNqW5QvFCEdm3mc5rJOdru/mzln5d0EZLacf6g==", "dev": true, "requires": { "async-foreach": "^0.1.3", @@ -30689,7 +30691,6 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, "requires": { "is-typedarray": "^1.0.0" } @@ -31713,7 +31714,6 @@ "resolved": "https://registry.npmjs.org/web3/-/web3-0.20.3.tgz", "integrity": "sha1-yqRDc9yIFayHZ73ba6cwc5ZMqos=", "requires": { - "bignumber.js": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934", "crypto-js": "^3.1.4", "utf8": "^2.1.1", "xhr2": "*", @@ -31722,7 +31722,7 @@ "dependencies": { "bignumber.js": { "version": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934", - "from": "git+https://github.com/frozeman/bignumber.js-nolookahead.git" + "from": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934" } } }, @@ -32221,8 +32221,7 @@ "dev": true, "requires": { "underscore": "1.8.3", - "web3-core-helpers": "1.0.0-beta.34", - "websocket": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2" + "web3-core-helpers": "1.0.0-beta.34" }, "dependencies": { "underscore": { @@ -32233,8 +32232,7 @@ }, "websocket": { "version": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2", - "from": "git://github.com/frozeman/WebSocket-Node.git#browserifyCompatible", - "dev": true, + "from": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2", "requires": { "debug": "^2.2.0", "nan": "^2.3.3", @@ -33590,8 +33588,7 @@ "yaeti": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", - "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=", - "dev": true + "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=" }, "yallist": { "version": "2.1.2", diff --git a/package.json b/package.json index b7b138d24..96b0829db 100644 --- a/package.json +++ b/package.json @@ -54,9 +54,19 @@ "babelify", { "presets": [ - "es2015", + [ + "env", + { + "debug": true + } + ], "stage-0" ] + }, + { + "plugins": [ + "transform-class-properties" + ] } ], "reactify", @@ -94,6 +104,7 @@ "eslint-plugin-react": "^7.4.0", "eth-bin-to-ops": "^1.0.1", "eth-contract-metadata": "github:MetaMask/eth-contract-metadata#master", + "eth-ens-namehash": "^2.0.8", "eth-hd-keyring": "^2.0.0", "eth-json-rpc-filters": "^1.2.6", "eth-json-rpc-infura": "^3.0.0", @@ -142,6 +153,7 @@ "metamascara": "^2.0.0", "metamask-logo": "^2.1.4", "mkdirp": "^0.5.1", + "multihashes": "^0.4.12", "multiplex": "^6.7.0", "number-to-bn": "^1.7.0", "obj-multiplex": "^1.0.0", @@ -195,9 +207,7 @@ "web3": "^0.20.1", "web3-provider-engine": "^14.0.5", "web3-stream-provider": "^3.0.1", - "xtend": "^4.0.1", - "multihashes": "^0.4.12", - "eth-ens-namehash": "^2.0.8" + "xtend": "^4.0.1" }, "devDependencies": { "@sentry/cli": "^1.30.3", @@ -209,6 +219,7 @@ "babel-plugin-transform-async-to-generator": "^6.24.1", "babel-plugin-transform-runtime": "^6.23.0", "babel-polyfill": "^6.23.0", + "babel-preset-env": "^1.7.0", "babel-preset-react": "^6.24.1", "babel-preset-stage-0": "^6.24.1", "babel-register": "^6.7.2", @@ -274,7 +285,7 @@ "mocha-jsdom": "^1.1.0", "mocha-sinon": "^2.0.0", "nock": "^9.0.14", - "node-sass": "^4.9.1", + "node-sass": "^4.9.2", "nsp": "^3.2.1", "nyc": "^13.0.0", "open": "0.0.5", @@ -307,6 +318,7 @@ "watchify": "^3.11.0" }, "engines": { - "node": ">=8.0.0" + "node": "8.11.3", + "npm": "^6.1.0" } } diff --git a/test/e2e/beta/metamask-beta-ui.spec.js b/test/e2e/beta/metamask-beta-ui.spec.js index ee1aa0ff1..ca1977c5a 100644 --- a/test/e2e/beta/metamask-beta-ui.spec.js +++ b/test/e2e/beta/metamask-beta-ui.spec.js @@ -472,7 +472,7 @@ describe('MetaMask', function () { await confirmButton.click() await delay(regularDelayMs) - await closeAllWindowHandlesExcept(driver, [extension, dapp]) + await waitUntilXWindowHandles(driver, 2) await driver.switchTo().window(extension) await delay(regularDelayMs) }) diff --git a/ui/app/actions.js b/ui/app/actions.js index 6c947fc35..7a8d9667d 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -10,6 +10,8 @@ const { const ethUtil = require('ethereumjs-util') const { fetchLocale } = require('../i18n-helper') const log = require('loglevel') +const { ENVIRONMENT_TYPE_NOTIFICATION } = require('../../app/scripts/lib/enums') +const { hasUnconfirmedTransactions } = require('./helpers/confirm-transaction/util') var actions = { _setBackgroundConnection: _setBackgroundConnection, @@ -743,7 +745,7 @@ function setCurrentCurrency (currencyCode) { function signMsg (msgData) { log.debug('action - signMsg') - return (dispatch) => { + return (dispatch, getState) => { dispatch(actions.showLoadingIndication()) return new Promise((resolve, reject) => { @@ -760,6 +762,12 @@ function signMsg (msgData) { } dispatch(actions.completedTx(msgData.metamaskId)) + + if (global.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION && + !hasUnconfirmedTransactions(getState())) { + return global.platform.closeCurrentWindow() + } + return resolve(msgData) }) }) @@ -768,7 +776,7 @@ function signMsg (msgData) { function signPersonalMsg (msgData) { log.debug('action - signPersonalMsg') - return dispatch => { + return (dispatch, getState) => { dispatch(actions.showLoadingIndication()) return new Promise((resolve, reject) => { @@ -785,6 +793,12 @@ function signPersonalMsg (msgData) { } dispatch(actions.completedTx(msgData.metamaskId)) + + if (global.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION && + !hasUnconfirmedTransactions(getState())) { + return global.platform.closeCurrentWindow() + } + return resolve(msgData) }) }) @@ -793,7 +807,7 @@ function signPersonalMsg (msgData) { function signTypedMsg (msgData) { log.debug('action - signTypedMsg') - return (dispatch) => { + return (dispatch, getState) => { dispatch(actions.showLoadingIndication()) return new Promise((resolve, reject) => { @@ -810,6 +824,12 @@ function signTypedMsg (msgData) { } dispatch(actions.completedTx(msgData.metamaskId)) + + if (global.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION && + !hasUnconfirmedTransactions(getState())) { + return global.platform.closeCurrentWindow() + } + return resolve(msgData) }) }) @@ -1003,7 +1023,7 @@ function clearSend () { function sendTx (txData) { log.info(`actions - sendTx: ${JSON.stringify(txData.txParams)}`) - return (dispatch) => { + return (dispatch, getState) => { log.debug(`actions calling background.approveTransaction`) background.approveTransaction(txData.id, (err) => { if (err) { @@ -1011,6 +1031,11 @@ function sendTx (txData) { return log.error(err.message) } dispatch(actions.completedTx(txData.id)) + + if (global.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION && + !hasUnconfirmedTransactions(getState())) { + return global.platform.closeCurrentWindow() + } }) } } @@ -1059,7 +1084,7 @@ function updateTransaction (txData) { function updateAndApproveTx (txData) { log.info('actions: updateAndApproveTx: ' + JSON.stringify(txData)) - return dispatch => { + return (dispatch, getState) => { log.debug(`actions calling background.updateAndApproveTx`) dispatch(actions.showLoadingIndication()) @@ -1084,6 +1109,12 @@ function updateAndApproveTx (txData) { dispatch(actions.clearSend()) dispatch(actions.completedTx(txData.id)) dispatch(actions.hideLoadingIndication()) + + if (global.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION && + !hasUnconfirmedTransactions(getState())) { + return global.platform.closeCurrentWindow() + } + return txData }) } @@ -1112,7 +1143,7 @@ function txError (err) { } function cancelMsg (msgData) { - return dispatch => { + return (dispatch, getState) => { dispatch(actions.showLoadingIndication()) return new Promise((resolve, reject) => { @@ -1126,6 +1157,12 @@ function cancelMsg (msgData) { } dispatch(actions.completedTx(msgData.id)) + + if (global.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION && + !hasUnconfirmedTransactions(getState())) { + return global.platform.closeCurrentWindow() + } + return resolve(msgData) }) }) @@ -1133,7 +1170,7 @@ function cancelMsg (msgData) { } function cancelPersonalMsg (msgData) { - return dispatch => { + return (dispatch, getState) => { dispatch(actions.showLoadingIndication()) return new Promise((resolve, reject) => { @@ -1147,6 +1184,12 @@ function cancelPersonalMsg (msgData) { } dispatch(actions.completedTx(id)) + + if (global.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION && + !hasUnconfirmedTransactions(getState())) { + return global.platform.closeCurrentWindow() + } + return resolve(msgData) }) }) @@ -1154,7 +1197,7 @@ function cancelPersonalMsg (msgData) { } function cancelTypedMsg (msgData) { - return dispatch => { + return (dispatch, getState) => { dispatch(actions.showLoadingIndication()) return new Promise((resolve, reject) => { @@ -1168,6 +1211,12 @@ function cancelTypedMsg (msgData) { } dispatch(actions.completedTx(id)) + + if (global.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION && + !hasUnconfirmedTransactions(getState())) { + return global.platform.closeCurrentWindow() + } + return resolve(msgData) }) }) @@ -1175,7 +1224,7 @@ function cancelTypedMsg (msgData) { } function cancelTx (txData) { - return dispatch => { + return (dispatch, getState) => { log.debug(`background.cancelTransaction`) dispatch(actions.showLoadingIndication()) @@ -1194,6 +1243,12 @@ function cancelTx (txData) { dispatch(actions.clearSend()) dispatch(actions.completedTx(txData.id)) dispatch(actions.hideLoadingIndication()) + + if (global.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION && + !hasUnconfirmedTransactions(getState())) { + return global.platform.closeCurrentWindow() + } + return txData }) } diff --git a/ui/app/components/alert/index.js b/ui/app/components/alert/index.js index fc39d41e2..5620d847a 100644 --- a/ui/app/components/alert/index.js +++ b/ui/app/components/alert/index.js @@ -4,13 +4,53 @@ const h = require('react-hyperscript') class Alert extends Component { + constructor (props) { + super(props) + + this.state = { + visble: false, + msg: false, + className: '', + } + } + + componentWillReceiveProps (nextProps) { + if (!this.props.visible && nextProps.visible) { + this.animateIn(nextProps) + } else if (this.props.visible && !nextProps.visible) { + this.animateOut(nextProps) + } + } + + animateIn (props) { + this.setState({ + msg: props.msg, + visible: true, + className: '.visible', + }) + } + + animateOut (props) { + this.setState({ + msg: null, + className: '.hidden', + }) + + setTimeout(_ => { + this.setState({visible: false}) + }, 500) + + } + render () { - const className = `.global-alert${this.props.visible ? '.visible' : '.hidden'}` - return ( - h(`div${className}`, {}, - h('a.msg', {}, this.props.msg) - ) - ) + if (this.state.visible) { + return ( + h(`div.global-alert${this.state.className}`, {}, + h('a.msg', {}, this.state.msg) + ) + ) + } + return null } } diff --git a/ui/app/components/button-group/button-group.component.js b/ui/app/components/button-group/button-group.component.js new file mode 100644 index 000000000..f99f710ce --- /dev/null +++ b/ui/app/components/button-group/button-group.component.js @@ -0,0 +1,61 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import classnames from 'classnames' + +export default class ButtonGroup extends PureComponent { + static propTypes = { + defaultActiveButtonIndex: PropTypes.number, + disabled: PropTypes.bool, + children: PropTypes.array, + className: PropTypes.string, + style: PropTypes.object, + } + + static defaultProps = { + className: 'button-group', + } + + state = { + activeButtonIndex: this.props.defaultActiveButtonIndex || 0, + } + + handleButtonClick (activeButtonIndex) { + this.setState({ activeButtonIndex }) + } + + renderButtons () { + const { children, disabled } = this.props + + return React.Children.map(children, (child, index) => { + return child && ( + <button + className={classnames( + 'button-group__button', + { 'button-group__button--active': index === this.state.activeButtonIndex }, + )} + onClick={() => { + this.handleButtonClick(index) + child.props.onClick && child.props.onClick() + }} + disabled={disabled || child.props.disabled} + key={index} + > + { child.props.children } + </button> + ) + }) + } + + render () { + const { className, style } = this.props + + return ( + <div + className={className} + style={style} + > + { this.renderButtons() } + </div> + ) + } +} diff --git a/ui/app/components/button-group/button-group.stories.js b/ui/app/components/button-group/button-group.stories.js new file mode 100644 index 000000000..14e1a7e49 --- /dev/null +++ b/ui/app/components/button-group/button-group.stories.js @@ -0,0 +1,49 @@ +import React from 'react' +import { storiesOf } from '@storybook/react' +import { action } from '@storybook/addon-actions' +import ButtonGroup from './' +import Button from '../button' +import { text, boolean } from '@storybook/addon-knobs/react' + +storiesOf('ButtonGroup', module) + .add('with Buttons', () => + <ButtonGroup + style={{ width: '300px' }} + disabled={boolean('Disabled', false)} + defaultActiveButtonIndex={1} + > + <Button + onClick={action('cheap')} + > + {text('Button1', 'Cheap')} + </Button> + <Button + onClick={action('average')} + > + {text('Button2', 'Average')} + </Button> + <Button + onClick={action('fast')} + > + {text('Button3', 'Fast')} + </Button> + </ButtonGroup> + ) + .add('with a disabled Button', () => + <ButtonGroup + style={{ width: '300px' }} + disabled={boolean('Disabled', false)} + > + <Button + onClick={action('enabled')} + > + {text('Button1', 'Enabled')} + </Button> + <Button + onClick={action('disabled')} + disabled + > + {text('Button2', 'Disabled')} + </Button> + </ButtonGroup> + ) diff --git a/ui/app/components/button-group/index.js b/ui/app/components/button-group/index.js new file mode 100644 index 000000000..df470bd57 --- /dev/null +++ b/ui/app/components/button-group/index.js @@ -0,0 +1 @@ +export { default } from './button-group.component' diff --git a/ui/app/components/button-group/index.scss b/ui/app/components/button-group/index.scss new file mode 100644 index 000000000..29713c75b --- /dev/null +++ b/ui/app/components/button-group/index.scss @@ -0,0 +1,38 @@ +.button-group { + display: flex; + justify-content: center; + align-items: center; + + &__button { + font-family: Roboto; + font-size: 1rem; + color: $tundora; + border-style: solid; + border-color: $alto; + border-width: 1px 1px 1px; + border-left: 0; + flex: 1; + padding: 12px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + &:first-child { + border-left: 1px solid $alto; + border-radius: 4px 0 0 4px; + } + + &:last-child { + border-radius: 0 4px 4px 0; + } + + &--active { + background-color: $dodger-blue; + color: $white; + } + + &:disabled { + opacity: .5; + } + } +}
\ No newline at end of file diff --git a/ui/app/components/button-group/tests/button-group-component.test.js b/ui/app/components/button-group/tests/button-group-component.test.js new file mode 100644 index 000000000..f07bb97c8 --- /dev/null +++ b/ui/app/components/button-group/tests/button-group-component.test.js @@ -0,0 +1,97 @@ +import React from 'react' +import assert from 'assert' +import { shallow } from 'enzyme' +import sinon from 'sinon' +import ButtonGroup from '../button-group.component.js' + +const childButtonSpies = { + onClick: sinon.spy(), +} + +sinon.spy(ButtonGroup.prototype, 'handleButtonClick') +sinon.spy(ButtonGroup.prototype, 'renderButtons') + +const mockButtons = [ + <button onClick={childButtonSpies.onClick} key={'a'}><div className="mockClass" /></button>, + <button onClick={childButtonSpies.onClick} key={'b'}></button>, + <button onClick={childButtonSpies.onClick} key={'c'}></button>, +] + +describe('ButtonGroup Component', function () { + let wrapper + + beforeEach(() => { + wrapper = shallow(<ButtonGroup + defaultActiveButtonIndex={1} + disabled={false} + className="someClassName" + style={ { color: 'red' } } + >{mockButtons}</ButtonGroup>) + }) + + afterEach(() => { + childButtonSpies.onClick.resetHistory() + ButtonGroup.prototype.handleButtonClick.resetHistory() + ButtonGroup.prototype.renderButtons.resetHistory() + }) + + describe('handleButtonClick', () => { + it('should set the activeButtonIndex', () => { + assert.equal(wrapper.state('activeButtonIndex'), 1) + wrapper.instance().handleButtonClick(2) + assert.equal(wrapper.state('activeButtonIndex'), 2) + }) + }) + + describe('renderButtons', () => { + it('should render a button for each child', () => { + const childButtons = wrapper.find('.button-group__button') + assert.equal(childButtons.length, 3) + }) + + it('should render the correct button with an active state', () => { + const childButtons = wrapper.find('.button-group__button') + const activeChildButton = wrapper.find('.button-group__button--active') + assert.deepEqual(childButtons.get(1), activeChildButton.get(0)) + }) + + it('should call handleButtonClick and the respective button\'s onClick method when a button is clicked', () => { + assert.equal(ButtonGroup.prototype.handleButtonClick.callCount, 0) + assert.equal(childButtonSpies.onClick.callCount, 0) + const childButtons = wrapper.find('.button-group__button') + childButtons.at(0).props().onClick() + childButtons.at(1).props().onClick() + childButtons.at(2).props().onClick() + assert.equal(ButtonGroup.prototype.handleButtonClick.callCount, 3) + assert.equal(childButtonSpies.onClick.callCount, 3) + }) + + it('should render all child buttons as disabled if props.disabled is true', () => { + const childButtons = wrapper.find('.button-group__button') + childButtons.forEach(button => { + assert.equal(button.props().disabled, undefined) + }) + wrapper.setProps({ disabled: true }) + const disabledChildButtons = wrapper.find('[disabled=true]') + assert.equal(disabledChildButtons.length, 3) + }) + + it('should render the children of the button', () => { + const mockClass = wrapper.find('.mockClass') + assert.equal(mockClass.length, 1) + }) + }) + + describe('render', () => { + it('should render a div with the expected class and style', () => { + assert.equal(wrapper.find('div').at(0).props().className, 'someClassName') + assert.deepEqual(wrapper.find('div').at(0).props().style, { color: 'red' }) + }) + + it('should call renderButtons when rendering', () => { + assert.equal(ButtonGroup.prototype.renderButtons.callCount, 1) + wrapper.instance().render() + assert.equal(ButtonGroup.prototype.renderButtons.callCount, 2) + }) + }) +}) diff --git a/ui/app/components/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js b/ui/app/components/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js new file mode 100644 index 000000000..6f2489071 --- /dev/null +++ b/ui/app/components/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js @@ -0,0 +1,64 @@ +import React from 'react' +import assert from 'assert' +import { shallow } from 'enzyme' +import ConfirmDetailRow from '../confirm-detail-row.component.js' +import sinon from 'sinon' + +const propsMethodSpies = { + onHeaderClick: sinon.spy(), +} + +describe('Confirm Detail Row Component', function () { + let wrapper + + beforeEach(() => { + wrapper = shallow(<ConfirmDetailRow + errorType={'mockErrorType'} + label={'mockLabel'} + showError={false} + fiatText = {'mockFiatText'} + ethText = {'mockEthText'} + fiatTextColor= {'mockColor'} + onHeaderClick= {propsMethodSpies.onHeaderClick} + headerText = {'mockHeaderText'} + headerTextClassName = {'mockHeaderClass'} + />) + }) + + describe('render', () => { + it('should render a div with a confirm-detail-row class', () => { + assert.equal(wrapper.find('div.confirm-detail-row').length, 1) + }) + + it('should render the label as a child of the confirm-detail-row__label', () => { + assert.equal(wrapper.find('.confirm-detail-row > .confirm-detail-row__label').childAt(0).text(), 'mockLabel') + }) + + it('should render the headerText as a child of the confirm-detail-row__header-text', () => { + assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__header-text').childAt(0).text(), 'mockHeaderText') + }) + + it('should render the fiatText as a child of the confirm-detail-row__fiat', () => { + assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__fiat').childAt(0).text(), 'mockFiatText') + }) + + it('should render the ethText as a child of the confirm-detail-row__eth', () => { + assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__eth').childAt(0).text(), 'mockEthText') + }) + + it('should set the fiatTextColor on confirm-detail-row__fiat', () => { + assert.equal(wrapper.find('.confirm-detail-row__fiat').props().style.color, 'mockColor') + }) + + it('should assure the confirm-detail-row__header-text classname is correct', () => { + assert.equal(wrapper.find('.confirm-detail-row__header-text').props().className, 'confirm-detail-row__header-text mockHeaderClass') + }) + + it('should call onHeaderClick when headerText div gets clicked', () => { + wrapper.find('.confirm-detail-row__header-text').props().onClick() + assert.equal(assert.equal(propsMethodSpies.onHeaderClick.callCount, 1)) + }) + + + }) +}) diff --git a/ui/app/components/index.scss b/ui/app/components/index.scss index 32f0e90e4..b3e14ce23 100644 --- a/ui/app/components/index.scss +++ b/ui/app/components/index.scss @@ -1,3 +1,5 @@ +@import './button-group/index'; + @import './export-text-container/index'; @import './selected-account/index'; diff --git a/ui/app/components/pages/create-account/connect-hardware/index.js b/ui/app/components/pages/create-account/connect-hardware/index.js index cc3761c04..3f66e7098 100644 --- a/ui/app/components/pages/create-account/connect-hardware/index.js +++ b/ui/app/components/pages/create-account/connect-hardware/index.js @@ -17,6 +17,7 @@ class ConnectHardwareForm extends Component { selectedAccount: null, accounts: [], browserSupported: true, + unlocked: false, } } @@ -32,9 +33,14 @@ class ConnectHardwareForm extends Component { } - async componentDidMount () { + componentDidMount () { + this.checkIfUnlocked() + } + + async checkIfUnlocked () { const unlocked = await this.props.checkHardwareStatus('trezor') if (unlocked) { + this.setState({unlocked: true}) this.getPage(0) } } @@ -66,12 +72,12 @@ class ConnectHardwareForm extends Component { if (accounts.length) { // If we just loaded the accounts for the first time - // show the global alert - if (this.state.accounts.length === 0) { + // (device previously locked) show the global alert + if (this.state.accounts.length === 0 && !this.state.unlocked) { this.showTemporaryAlert() } - const newState = {} + const newState = { unlocked: true } // Default to the first account if (this.state.selectedAccount === null) { accounts.forEach((a, i) => { @@ -112,6 +118,7 @@ class ConnectHardwareForm extends Component { btnText: this.context.t('connectToTrezor'), selectedAccount: null, accounts: [], + unlocked: false, }) }).catch(e => { this.setState({ error: e.toString() }) diff --git a/ui/app/components/pages/home.js b/ui/app/components/pages/home.js index 38aa02dae..5e3fdc9af 100644 --- a/ui/app/components/pages/home.js +++ b/ui/app/components/pages/home.js @@ -27,19 +27,17 @@ const { NOTICE_ROUTE, } = require('../../routes') +const { unconfirmedTransactionsCountSelector } = require('../../selectors/confirm-transaction') + class Home extends Component { componentDidMount () { const { history, - unapprovedTxs = {}, - unapprovedMsgCount = 0, - unapprovedPersonalMsgCount = 0, - unapprovedTypedMessagesCount = 0, + unconfirmedTransactionsCount = 0, } = this.props // unapprovedTxs and unapproved messages - if (Object.keys(unapprovedTxs).length || - unapprovedTypedMessagesCount + unapprovedMsgCount + unapprovedPersonalMsgCount > 0) { + if (unconfirmedTransactionsCount > 0) { history.push(CONFIRM_TRANSACTION_ROUTE) } } @@ -167,6 +165,7 @@ Home.propTypes = { isPopup: PropTypes.bool, isMouseUser: PropTypes.bool, t: PropTypes.func, + unconfirmedTransactionsCount: PropTypes.number, } function mapStateToProps (state) { @@ -230,6 +229,7 @@ function mapStateToProps (state) { // state needed to get account dropdown temporarily rendering from app bar selected, + unconfirmedTransactionsCount: unconfirmedTransactionsCountSelector(state), } } diff --git a/ui/app/components/tx-list-item.js b/ui/app/components/tx-list-item.js index 0d693b805..1a639d0b9 100644 --- a/ui/app/components/tx-list-item.js +++ b/ui/app/components/tx-list-item.js @@ -213,14 +213,23 @@ TxListItem.prototype.showRetryButton = function () { if (!txParams) { return false } + let currentTxIsLatest = false const currentNonce = txParams.nonce const currentNonceTxs = selectedAddressTxList.filter(tx => tx.txParams.nonce === currentNonce) const currentNonceSubmittedTxs = currentNonceTxs.filter(tx => tx.status === 'submitted') + const currentSubmittedTxs = selectedAddressTxList.filter(tx => tx.status === 'submitted') const lastSubmittedTxWithCurrentNonce = currentNonceSubmittedTxs[currentNonceSubmittedTxs.length - 1] const currentTxIsLatestWithNonce = lastSubmittedTxWithCurrentNonce && lastSubmittedTxWithCurrentNonce.id === transactionId + if (currentSubmittedTxs.length > 0) { + const lastTx = currentSubmittedTxs.reduce((tx1, tx2) => { + if (tx1.submittedTime < tx2.submittedTime) return tx1 + return tx2 + }) + currentTxIsLatest = lastTx.id === transactionId + } - return currentTxIsLatestWithNonce && Date.now() - transactionSubmittedTime > 30000 + return currentTxIsLatestWithNonce && Date.now() - transactionSubmittedTime > 30000 && currentTxIsLatest } TxListItem.prototype.setSelectedToken = function (tokenAddress) { diff --git a/ui/app/helpers/confirm-transaction/util.js b/ui/app/helpers/confirm-transaction/util.js index 1373d28df..f015b2bf5 100644 --- a/ui/app/helpers/confirm-transaction/util.js +++ b/ui/app/helpers/confirm-transaction/util.js @@ -16,6 +16,8 @@ import { conversionGreaterThan, } from '../../conversion-util' +import { unconfirmedTransactionsCountSelector } from '../../selectors/confirm-transaction' + export function getTokenData (data = {}) { return abiDecoder.decodeMethod(data) } @@ -131,3 +133,7 @@ export function convertTokenToFiat ({ conversionRate: totalExchangeRate, }) } + +export function hasUnconfirmedTransactions (state) { + return unconfirmedTransactionsCountSelector(state) > 0 +} diff --git a/ui/app/selectors/confirm-transaction.js b/ui/app/selectors/confirm-transaction.js index 54016a30e..8f8e0ea74 100644 --- a/ui/app/selectors/confirm-transaction.js +++ b/ui/app/selectors/confirm-transaction.js @@ -62,6 +62,34 @@ export const unconfirmedTransactionsHashSelector = createSelector( } ) +const unapprovedMsgCountSelector = state => state.metamask.unapprovedMsgCount +const unapprovedPersonalMsgCountSelector = state => state.metamask.unapprovedPersonalMsgCount +const unapprovedTypedMessagesCountSelector = state => state.metamask.unapprovedTypedMessagesCount + +export const unconfirmedTransactionsCountSelector = createSelector( + unapprovedTxsSelector, + unapprovedMsgCountSelector, + unapprovedPersonalMsgCountSelector, + unapprovedTypedMessagesCountSelector, + networkSelector, + ( + unapprovedTxs = {}, + unapprovedMsgCount = 0, + unapprovedPersonalMsgCount = 0, + unapprovedTypedMessagesCount = 0, + network + ) => { + const filteredUnapprovedTxIds = Object.keys(unapprovedTxs).filter(txId => { + const { metamaskNetworkId } = unapprovedTxs[txId] + return metamaskNetworkId === network + }) + + return filteredUnapprovedTxIds.length + unapprovedTypedMessagesCount + unapprovedMsgCount + + unapprovedPersonalMsgCount + } +) + + export const currentCurrencySelector = state => state.metamask.currentCurrency export const conversionRateSelector = state => state.metamask.conversionRate @@ -156,7 +184,6 @@ export const sendTokenTokenAmountAndToAddressSelector = createSelector( } ) - export const contractExchangeRateSelector = createSelector( contractExchangeRatesSelector, tokenAddressSelector, diff --git a/ui/app/token-util.js b/ui/app/token-util.js index cd6a47dbc..0d4233766 100644 --- a/ui/app/token-util.js +++ b/ui/app/token-util.js @@ -1,5 +1,6 @@ const log = require('loglevel') const util = require('./util') +const BigNumber = require('bignumber.js') function tokenInfoGetter () { const tokens = {} @@ -43,9 +44,7 @@ async function getSymbolAndDecimals (tokenAddress, existingTokens = []) { function calcTokenAmount (value, decimals) { const multiplier = Math.pow(10, Number(decimals || 0)) - const amount = Number(value / multiplier) - - return amount + return new BigNumber(value).div(multiplier).toNumber() } |