diff --git a/CHANGELOG.md b/CHANGELOG.md index 02d6a0b64..30554afd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,33 @@ ## Current Develop Branch +## 7.7.1 Wed Dec 04 2019 +- [#7488](https://github.com/MetaMask/metamask-extension/pull/7488): Fix text overlap when expanding transaction +- [#7491](https://github.com/MetaMask/metamask-extension/pull/7491): Update gas when asset is changed on send screen +- [#7500](https://github.com/MetaMask/metamask-extension/pull/7500): Remove unused onClick prop from Dropdown component +- [#7502](https://github.com/MetaMask/metamask-extension/pull/7502): Fix chainId for non standard networks +- [#7519](https://github.com/MetaMask/metamask-extension/pull/7519): Fixing hardware connect error display +- [#7501](https://github.com/MetaMask/metamask-extension/pull/7501): Fix accessibility of first-time-flow terms checkboxes +- [#7579](https://github.com/MetaMask/metamask-extension/pull/7579): Prevent Maker migration dismissal timeout state from being overwritten +- [#7581](https://github.com/MetaMask/metamask-extension/pull/7581): Persist Maker migration dismissal timeout +- [#7484](https://github.com/MetaMask/metamask-extension/pull/7484): Ensure transactions are shown in the order they are received +- [#7604](https://github.com/MetaMask/metamask-extension/pull/7604): Process URL fragment for ens-ipfs redirects +- [#7628](https://github.com/MetaMask/metamask-extension/pull/7628): Fix typo that resulted in degrated account menu performance +- [#7558](https://github.com/MetaMask/metamask-extension/pull/7558): Use localized messages for NotificationModal buttons + +## 7.7.0 Thu Nov 28 2019 [WITHDRAWN] +- [#7004](https://github.com/MetaMask/metamask-extension/pull/7004): Connect distinct accounts per site +- [#7480](https://github.com/MetaMask/metamask-extension/pull/7480): Fixed link on root README.md +- [#7482](https://github.com/MetaMask/metamask-extension/pull/7482): Update Wyre ETH purchase url +- [#7484](https://github.com/MetaMask/metamask-extension/pull/7484): Ensure transactions are shown in the order they are received +- [#7491](https://github.com/MetaMask/metamask-extension/pull/7491): Update gas when token is changed on the send screen +- [#7501](https://github.com/MetaMask/metamask-extension/pull/7501): Fix accessibility of first-time-flow terms checkboxes +- [#7502](https://github.com/MetaMask/metamask-extension/pull/7502): Fix chainId for non standard networks +- [#7579](https://github.com/MetaMask/metamask-extension/pull/7579): Fix timing of DAI migration notifications after dismissal +- [#7519](https://github.com/MetaMask/metamask-extension/pull/7519): Fixing hardware connect error display +- [#7558](https://github.com/MetaMask/metamask-extension/pull/7558): Use localized messages for NotificationModal buttons +- [#7488](https://github.com/MetaMask/metamask-extension/pull/7488): Fix text overlap when expanding transaction + ## 7.6.1 Tue Nov 19 2019 - [#7475](https://github.com/MetaMask/metamask-extension/pull/7475): Add 'Remind Me Later' to the Maker notification - [#7436](https://github.com/MetaMask/metamask-extension/pull/7436): Add additional rpcUrl verification diff --git a/app/manifest.json b/app/manifest.json index f9e25ceda..299158d7f 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "7.6.1", + "version": "7.7.1", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", diff --git a/app/scripts/controllers/app-state.js b/app/scripts/controllers/app-state.js index 8d67874ad..c60a1c4f5 100644 --- a/app/scripts/controllers/app-state.js +++ b/app/scripts/controllers/app-state.js @@ -45,7 +45,7 @@ class AppStateController { * @private */ _setInactiveTimeout (timeoutMinutes) { - this.store.putState({ + this.store.updateState({ timeoutMinutes, }) diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index df9fb6502..8e4e9c0a7 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -192,13 +192,18 @@ class TransactionController extends EventEmitter { throw new Error(`Transaction from address isn't valid for this account`) } txUtils.validateTxParams(normalizedTxParams) - // construct txMeta - const { transactionCategory, getCodeResponse } = await this._determineTransactionCategory(txParams) + /** + `generateTxMeta` adds the default txMeta properties to the passed object. + These include the tx's `id`. As we use the id for determining order of + txes in the tx-state-manager, it is necessary to call the asynchronous + method `this._determineTransactionCategory` after `generateTxMeta`. + */ let txMeta = this.txStateManager.generateTxMeta({ txParams: normalizedTxParams, type: TRANSACTION_TYPE_STANDARD, - transactionCategory, }) + const { transactionCategory, getCodeResponse } = await this._determineTransactionCategory(txParams) + txMeta.transactionCategory = transactionCategory this.addTx(txMeta) this.emit('newUnapprovedTx', txMeta) diff --git a/app/scripts/controllers/transactions/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js index 6a92c0601..cf254352f 100644 --- a/app/scripts/controllers/transactions/tx-state-manager.js +++ b/app/scripts/controllers/transactions/tx-state-manager.js @@ -159,7 +159,12 @@ class TransactionStateManager extends EventEmitter { transactions.splice(index, 1) } } - transactions.push(txMeta) + const newTxIndex = transactions + .findIndex((currentTxMeta) => currentTxMeta.time > txMeta.time) + + newTxIndex === -1 + ? transactions.push(txMeta) + : transactions.splice(newTxIndex, 0, txMeta) this._saveTxList(transactions) return txMeta } diff --git a/app/scripts/lib/ens-ipfs/setup.js b/app/scripts/lib/ens-ipfs/setup.js index a3711c5f9..8f19510c7 100644 --- a/app/scripts/lib/ens-ipfs/setup.js +++ b/app/scripts/lib/ens-ipfs/setup.js @@ -26,22 +26,22 @@ function setupEnsIpfsResolver ({ provider }) { if (tabId === -1) return // parse ens name const urlData = urlUtil.parse(url) - const { hostname: name, path, search } = urlData + const { hostname: name, path, search, hash: fragment } = urlData const domainParts = name.split('.') const topLevelDomain = domainParts[domainParts.length - 1] // if unsupported TLD, abort if (!supportedTopLevelDomains.includes(topLevelDomain)) return // otherwise attempt resolve - attemptResolve({ tabId, name, path, search }) + attemptResolve({ tabId, name, path, search, fragment }) } - async function attemptResolve ({ tabId, name, path, search }) { + async function attemptResolve ({ tabId, name, path, search, fragment }) { extension.tabs.update(tabId, { url: `loading.html` }) - let url = `https://manager.ens.domains/name/${name}` + let url = `https://app.ens.domains/name/${name}` try { const {type, hash} = await resolveEnsToIpfsContentId({ provider, name }) if (type === 'ipfs-ns') { - const resolvedUrl = `https://gateway.ipfs.io/ipfs/${hash}${path}${search || ''}` + const resolvedUrl = `https://gateway.ipfs.io/ipfs/${hash}${path}${search || ''}${fragment || ''}` try { // check if ipfs gateway has result const response = await fetch(resolvedUrl, { method: 'HEAD' }) @@ -50,11 +50,11 @@ function setupEnsIpfsResolver ({ provider }) { console.warn(err) } } else if (type === 'swarm-ns') { - url = `https://swarm-gateways.net/bzz:/${hash}${path}${search || ''}` + url = `https://swarm-gateways.net/bzz:/${hash}${path}${search || ''}${fragment || ''}` } else if (type === 'onion' || type === 'onion3') { - url = `http://${hash}.onion${path}${search || ''}` + url = `http://${hash}.onion${path}${search || ''}${fragment || ''}` } else if (type === 'zeronet') { - url = `http://127.0.0.1:43110/${hash}${path}${search || ''}` + url = `http://127.0.0.1:43110/${hash}${path}${search || ''}${fragment || ''}` } } catch (err) { console.warn(err) diff --git a/app/scripts/lib/select-chain-id.js b/app/scripts/lib/select-chain-id.js index 3171c9840..d94b35898 100644 --- a/app/scripts/lib/select-chain-id.js +++ b/app/scripts/lib/select-chain-id.js @@ -15,8 +15,8 @@ const standardNetworkId = { } function selectChainId (metamaskState) { - const { network, provider: { chaindId } } = metamaskState - return standardNetworkId[network] || `0x${parseInt(chaindId, 10).toString(16)}` + const { network, provider: { chainId } } = metamaskState + return standardNetworkId[network] || `0x${parseInt(chainId, 10).toString(16)}` } module.exports = selectChainId diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 14caf0706..9a43b8a80 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -109,6 +109,7 @@ module.exports = class MetamaskController extends EventEmitter { this.appStateController = new AppStateController({ preferencesStore: this.preferencesController.store, onInactiveTimeout: () => this.setLocked(), + initState: initState.AppStateController, }) // currency controller diff --git a/ui/app/components/app/account-menu/account-menu.component.js b/ui/app/components/app/account-menu/account-menu.component.js index 1b81e33a2..6cb2aa4b8 100644 --- a/ui/app/components/app/account-menu/account-menu.component.js +++ b/ui/app/components/app/account-menu/account-menu.component.js @@ -28,7 +28,6 @@ export default class AccountMenu extends PureComponent { history: PropTypes.object, identities: PropTypes.object, isAccountMenuOpen: PropTypes.bool, - prevIsAccountMenuOpen: PropTypes.bool, keyrings: PropTypes.array, lockMetamask: PropTypes.func, selectedAddress: PropTypes.string, @@ -42,7 +41,7 @@ export default class AccountMenu extends PureComponent { } componentDidUpdate (prevProps) { - const { prevIsAccountMenuOpen } = prevProps + const { isAccountMenuOpen: prevIsAccountMenuOpen } = prevProps const { isAccountMenuOpen } = this.props if (!prevIsAccountMenuOpen && isAccountMenuOpen) { diff --git a/ui/app/components/app/dropdowns/components/dropdown.js b/ui/app/components/app/dropdowns/components/dropdown.js index cc966ffa5..fd7055803 100644 --- a/ui/app/components/app/dropdowns/components/dropdown.js +++ b/ui/app/components/app/dropdowns/components/dropdown.js @@ -58,7 +58,6 @@ Dropdown.defaultProps = { Dropdown.propTypes = { isOpen: PropTypes.bool.isRequired, - onClick: PropTypes.func.isRequired, children: PropTypes.node, style: PropTypes.object.isRequired, onClickOutside: PropTypes.func, diff --git a/ui/app/components/app/modals/notification-modal.js b/ui/app/components/app/modals/notification-modal.js index 84d9004b7..a4282595f 100644 --- a/ui/app/components/app/modals/notification-modal.js +++ b/ui/app/components/app/modals/notification-modal.js @@ -5,6 +5,10 @@ const connect = require('react-redux').connect const actions = require('../../../store/actions') class NotificationModal extends Component { + static contextProps = { + t: PropTypes.func.isRequired, + } + render () { const { header, @@ -15,6 +19,8 @@ class NotificationModal extends Component { onConfirm, } = this.props + const { t } = this.context + const showButtons = showCancelButton || showConfirmButton return h('div', [ @@ -39,14 +45,14 @@ class NotificationModal extends Component { showCancelButton && h('div.btn-default.notification-modal__buttons__btn', { onClick: hideModal, - }, 'Cancel'), + }, t('cancel')), showConfirmButton && h('div.button.btn-secondary.notification-modal__buttons__btn', { onClick: () => { onConfirm() hideModal() }, - }, 'Confirm'), + }, t('confirm')), ]), diff --git a/ui/app/components/app/transaction-list-item/index.scss b/ui/app/components/app/transaction-list-item/index.scss index e0c62199e..9804ecd97 100644 --- a/ui/app/components/app/transaction-list-item/index.scss +++ b/ui/app/components/app/transaction-list-item/index.scss @@ -139,6 +139,7 @@ &__expander { max-height: 0px; width: 100%; + overflow: hidden; &--show { max-height: 1000px; diff --git a/ui/app/ducks/metamask/metamask.js b/ui/app/ducks/metamask/metamask.js index 23437610f..8a9739af7 100644 --- a/ui/app/ducks/metamask/metamask.js +++ b/ui/app/ducks/metamask/metamask.js @@ -322,7 +322,9 @@ function reduceMetamask (state, action) { let { selectedAddressTxList } = metamaskState selectedAddressTxList = selectedAddressTxList.map(tx => { if (tx.id === txId) { - tx.txParams = value + const newTx = Object.assign({}, tx) + newTx.txParams = value + return newTx } return tx }) diff --git a/ui/app/pages/create-account/connect-hardware/index.js b/ui/app/pages/create-account/connect-hardware/index.js index 66851c780..ff8506142 100644 --- a/ui/app/pages/create-account/connect-hardware/index.js +++ b/ui/app/pages/create-account/connect-hardware/index.js @@ -116,10 +116,11 @@ class ConnectHardwareForm extends Component { } }) .catch(e => { - if (e === 'Window blocked') { + const errorMessage = e.message + if (errorMessage === 'Window blocked') { this.setState({ browserSupported: false, error: null}) - } else if (e !== 'Window closed' && e !== 'Popup closed') { - this.setState({ error: e.toString() }) + } else if (errorMessage !== 'Window closed' && errorMessage !== 'Popup closed') { + this.setState({ error: errorMessage }) } }) } @@ -134,7 +135,7 @@ class ConnectHardwareForm extends Component { unlocked: false, }) }).catch(e => { - this.setState({ error: e.toString() }) + this.setState({ error: e.message }) }) } @@ -162,10 +163,10 @@ class ConnectHardwareForm extends Component { name: 'Error connecting hardware wallet', }, customVariables: { - error: e.toString(), + error: e.message, }, }) - this.setState({ error: e.toString() }) + this.setState({ error: e.message }) }) } diff --git a/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js b/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js index e1c0b21ed..1fae5351c 100644 --- a/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js +++ b/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js @@ -175,6 +175,12 @@ export default class ImportWithSeedPhrase extends PureComponent { return !passwordError && !confirmPasswordError && !seedPhraseError } + onTermsKeyPress = ({key}) => { + if (key === ' ' || key === 'Enter') { + this.toggleTermsCheck() + } + } + toggleTermsCheck = () => { this.context.metricsEvent({ eventOpts: { @@ -183,7 +189,6 @@ export default class ImportWithSeedPhrase extends PureComponent { name: 'Check ToS', }, }) - this.setState((prevState) => ({ termsChecked: !prevState.termsChecked, })) @@ -267,10 +272,17 @@ export default class ImportWithSeedPhrase extends PureComponent { largeLabel />
-
+
{termsChecked ? : null}
- + I have read and agree to the { - const { history } = this.props - - event.preventDefault() - history.push(INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE) - } - toggleTermsCheck = () => { this.context.metricsEvent({ eventOpts: { @@ -136,6 +128,12 @@ export default class NewAccount extends PureComponent { })) } + onTermsKeyPress = ({key}) => { + if (key === ' ' || key === 'Enter') { + this.toggleTermsCheck() + } + } + render () { const { t } = this.context const { password, confirmPassword, passwordError, confirmPasswordError, termsChecked } = this.state @@ -195,10 +193,17 @@ export default class NewAccount extends PureComponent { largeLabel />
-
+
{termsChecked ? : null}
- + I have read and agree to the
{ @@ -129,7 +130,7 @@ describe('Send Component', function () { prevBalance: '', prevGasTotal: undefined, prevTokenBalance: undefined, - selectedToken: 'mockSelectedToken', + selectedToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }, tokenBalance: 'mockTokenBalance', } ) @@ -162,7 +163,7 @@ describe('Send Component', function () { conversionRate: 10, gasTotal: 'mockGasTotal', primaryCurrency: 'mockPrimaryCurrency', - selectedToken: 'mockSelectedToken', + selectedToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }, tokenBalance: 'mockTokenBalance', } ) @@ -184,7 +185,7 @@ describe('Send Component', function () { conversionRate: 10, gasTotal: 'mockGasTotal', primaryCurrency: 'mockPrimaryCurrency', - selectedToken: 'mockSelectedToken', + selectedToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }, } ) }) @@ -225,7 +226,7 @@ describe('Send Component', function () { it('should call updateSendErrors with the expected params if selectedToken is truthy', () => { propsMethodSpies.updateSendErrors.resetHistory() - wrapper.setProps({ selectedToken: 'someToken' }) + wrapper.setProps({ selectedToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }}) wrapper.instance().componentDidUpdate({ from: { balance: 'balanceChanged', @@ -246,6 +247,7 @@ describe('Send Component', function () { balance: 'balanceChanged', }, network: '3', + selectedToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }, // Make sure not to hit updateGas when changing asset }) assert.equal(propsMethodSpies.updateSendTokenBalance.callCount, 0) assert.equal(SendTransactionScreen.prototype.updateGas.callCount, 0) @@ -260,6 +262,7 @@ describe('Send Component', function () { balance: 'balanceChanged', }, network: '3', + selectedToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }, // Make sure not to hit updateGas when changing asset }) assert.equal(propsMethodSpies.updateSendTokenBalance.callCount, 0) assert.equal(SendTransactionScreen.prototype.updateGas.callCount, 0) @@ -273,13 +276,14 @@ describe('Send Component', function () { balance: 'balanceChanged', }, network: '2', + selectedToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }, // Make sure not to hit updateGas when changing asset }) assert.equal(propsMethodSpies.updateSendTokenBalance.callCount, 1) assert.deepEqual( propsMethodSpies.updateSendTokenBalance.getCall(0).args[0], { - selectedToken: 'mockSelectedToken', - tokenContract: 'mockTokenContract', + selectedToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }, // Make sure not to hit updateGas when changing asset + tokenContract: { method: 'mockTokenMethod' }, address: 'mockAddress', } ) @@ -289,6 +293,20 @@ describe('Send Component', function () { [] ) }) + + it('should call updateGas when selectedToken.address is changed', () => { + SendTransactionScreen.prototype.updateGas.resetHistory() + propsMethodSpies.updateAndSetGasLimit.resetHistory() + wrapper.instance().componentDidUpdate({ + from: { + balance: 'balancedChanged', + }, + network: '3', // Make sure not to hit updateGas when changing network + selectedToken: { address: 'newSelectedToken' }, + }) + assert.equal(propsMethodSpies.updateToNicknameIfNecessary.callCount, 0) // Network did not change + assert.equal(propsMethodSpies.updateAndSetGasLimit.callCount, 1) + }) }) describe('updateGas', () => { @@ -305,7 +323,7 @@ describe('Send Component', function () { gasPrice: 'mockGasPrice', recentBlocks: ['mockBlock'], selectedAddress: 'mockSelectedAddress', - selectedToken: 'mockSelectedToken', + selectedToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }, to: '', value: 'mockAmount', data: undefined, @@ -431,9 +449,7 @@ describe('Send Component', function () { }) it('should warn when send to a known token contract address', () => { - wrapper.setProps({ - selectedToken: '0x888', - }) + wrapper.setProps({ address: '0x888', decimals: 18, symbol: '888' }) const instance = wrapper.instance() instance.onRecipientInputChange('0x13cb85823f78Cff38f0B0E90D3e975b8CB3AAd64')