diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 60c283a29..76e3fafaa 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -909,10 +909,13 @@ "message": "Message" }, "metaMaskConnectStatusParagraphOne": { - "message": "This is the new MetaMask Connect status indicator. From here you can easily see and manage sites you’ve connected to with your MetaMask wallet." + "message": "You now have more control over your account connections in MetaMask." }, "metaMaskConnectStatusParagraphTwo": { - "message": "Click the Connect status to see your connected sites and their permissions." + "message": "The connection status button shows if the website you’re visiting is connected to your currently selected account." + }, + "metaMaskConnectStatusParagraphThree": { + "message": "Click it to manage your connected accounts." }, "metamaskDescription": { "message": "Connecting you to Ethereum and the Decentralized Web." diff --git a/app/scripts/controllers/permissions/enums.js b/app/scripts/controllers/permissions/enums.js index 12dc74a70..efd994c10 100644 --- a/app/scripts/controllers/permissions/enums.js +++ b/app/scripts/controllers/permissions/enums.js @@ -75,6 +75,7 @@ export const SAFE_METHODS = [ 'eth_sendTransaction', 'eth_sign', 'personal_sign', + 'personal_ecRecover', 'eth_signTypedData', 'eth_signTypedData_v1', 'eth_signTypedData_v3', diff --git a/app/scripts/controllers/transactions/lib/util.js b/app/scripts/controllers/transactions/lib/util.js index c77d70dd4..da58bf6f3 100644 --- a/app/scripts/controllers/transactions/lib/util.js +++ b/app/scripts/controllers/transactions/lib/util.js @@ -1,8 +1,8 @@ import { addHexPrefix, isValidAddress } from 'ethereumjs-util' const normalizers = { - from: (from, lowerCase = true) => (lowerCase ? addHexPrefix(from).toLowerCase() : addHexPrefix(from)), - to: (to, lowerCase = true) => (lowerCase ? addHexPrefix(to).toLowerCase() : addHexPrefix(to)), + from: (from) => addHexPrefix(from), + to: (to, lowerCase) => (lowerCase ? addHexPrefix(to).toLowerCase() : addHexPrefix(to)), nonce: (nonce) => addHexPrefix(nonce), value: (value) => addHexPrefix(value), data: (data) => addHexPrefix(data), @@ -12,11 +12,12 @@ const normalizers = { /** * Normalizes the given txParams - * @param {Object} txParams - the tx params - * @param {boolean} lowerCase - whether to return the addresses lower cased + * @param {Object} txParams - The transaction params + * @param {boolean} [lowerCase] - Whether to lowercase the 'to' address. + * Default: true * @returns {Object} the normalized tx params */ -export function normalizeTxParams (txParams, lowerCase) { +export function normalizeTxParams (txParams, lowerCase = true) { // apply only keys in the normalizers const normalizedTxParams = {} for (const key in normalizers) { diff --git a/app/scripts/controllers/transactions/pending-tx-tracker.js b/app/scripts/controllers/transactions/pending-tx-tracker.js index d56421fda..304246565 100644 --- a/app/scripts/controllers/transactions/pending-tx-tracker.js +++ b/app/scripts/controllers/transactions/pending-tx-tracker.js @@ -211,7 +211,7 @@ export default class PendingTransactionTracker extends EventEmitter { const { hash: txHash, txParams: { nonce, from } } = txMeta const networkNextNonce = await this.query.getTransactionCount(from) - if (parseInt(nonce, 16) >= parseInt(networkNextNonce, 16)) { + if (parseInt(nonce, 16) >= networkNextNonce.toNumber()) { return false } diff --git a/app/scripts/lib/encryption-public-key-manager.js b/app/scripts/lib/encryption-public-key-manager.js index 0ae12442b..f568c62db 100644 --- a/app/scripts/lib/encryption-public-key-manager.js +++ b/app/scripts/lib/encryption-public-key-manager.js @@ -283,5 +283,4 @@ export default class EncryptionPublicKeyManager extends EventEmitter { this.memStore.updateState({ unapprovedEncryptionPublicKeyMsgs, unapprovedEncryptionPublicKeyMsgCount }) this.emit('updateBadge') } - } diff --git a/app/scripts/lib/typed-message-manager.js b/app/scripts/lib/typed-message-manager.js index 147180ffd..49e71e59f 100644 --- a/app/scripts/lib/typed-message-manager.js +++ b/app/scripts/lib/typed-message-manager.js @@ -4,9 +4,11 @@ import createId from './random-id' import assert from 'assert' import { ethErrors } from 'eth-json-rpc-errors' import sigUtil from 'eth-sig-util' +import { isValidAddress } from 'ethereumjs-util' import log from 'loglevel' import jsonschema from 'jsonschema' import { MESSAGE_TYPE } from './enums' + /** * Represents, and contains data about, an 'eth_signTypedData' type signature request. These are created when a * signature for an eth_signTypedData call is requested. @@ -102,14 +104,15 @@ export default class TypedMessageManager extends EventEmitter { * */ addUnapprovedMessage (msgParams, req, version) { + msgParams.version = version - this.validateParams(msgParams) - // add origin from request if (req) { msgParams.origin = req.origin } + this.validateParams(msgParams) log.debug(`TypedMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`) + // create txData obj with parameters and meta data const time = (new Date()).getTime() const msgId = createId() @@ -134,37 +137,38 @@ export default class TypedMessageManager extends EventEmitter { * */ validateParams (params) { + + assert.ok(params && typeof params === 'object', 'Params must be an object.') + assert.ok('data' in params, 'Params must include a "data" field.') + assert.ok('from' in params, 'Params must include a "from" field.') + assert.ok( + typeof params.from === 'string' && isValidAddress(params.from), + '"from" field must be a valid, lowercase, hexadecimal Ethereum address string.' + ) + switch (params.version) { case 'V1': - assert.equal(typeof params, 'object', 'Params should ben an object.') - assert.ok('data' in params, 'Params must include a data field.') - assert.ok('from' in params, 'Params must include a from field.') - assert.ok(Array.isArray(params.data), 'Data should be an array.') - assert.equal(typeof params.from, 'string', 'From field must be a string.') + assert.ok(Array.isArray(params.data), '"params.data" must be an array.') assert.doesNotThrow(() => { sigUtil.typedSignatureHash(params.data) - }, 'Expected EIP712 typed data') + }, 'Signing data must be valid EIP-712 typed data.') break case 'V3': case 'V4': + assert.equal(typeof params.data, 'string', '"params.data" must be a string.') let data - assert.equal(typeof params, 'object', 'Params should be an object.') - assert.ok('data' in params, 'Params must include a data field.') - assert.ok('from' in params, 'Params must include a from field.') - assert.equal(typeof params.from, 'string', 'From field must be a string.') - assert.equal(typeof params.data, 'string', 'Data must be passed as a valid JSON string.') assert.doesNotThrow(() => { data = JSON.parse(params.data) - }, 'Data must be passed as a valid JSON string.') + }, '"data" must be a valid JSON string.') const validation = jsonschema.validate(data, sigUtil.TYPED_MESSAGE_SCHEMA) assert.ok(data.primaryType in data.types, `Primary type of "${data.primaryType}" has no type definition.`) - assert.equal(validation.errors.length, 0, 'Data must conform to EIP-712 schema. See https://git.io/fNtcx.') + assert.equal(validation.errors.length, 0, 'Signing data must conform to EIP-712 schema. See https://git.io/fNtcx.') const chainId = data.domain.chainId const activeChainId = parseInt(this.networkController.getNetworkState()) - chainId && assert.equal(chainId, activeChainId, `Provided chainId (${chainId}) must match the active chainId (${activeChainId})`) + chainId && assert.equal(chainId, activeChainId, `Provided chainId "${chainId}" must match the active chainId "${activeChainId}"`) break default: - assert.fail(`Unknown params.version ${params.version}`) + assert.fail(`Unknown typed data version "${params.version}"`) } } diff --git a/package.json b/package.json index 7901bbbaf..1a68dfdb9 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "@formatjs/intl-relativetimeformat": "^5.2.6", "@fortawesome/fontawesome-free": "^5.13.0", "@material-ui/core": "1.0.0", - "@metamask/controllers": "^2.0.0", + "@metamask/controllers": "^2.0.1", "@metamask/eth-ledger-bridge-keyring": "^0.2.6", "@metamask/eth-token-tracker": "^2.0.0", "@metamask/etherscan-link": "^1.1.0", @@ -106,7 +106,7 @@ "eth-json-rpc-errors": "^2.0.2", "eth-json-rpc-filters": "^4.1.1", "eth-json-rpc-infura": "^4.0.2", - "eth-json-rpc-middleware": "^4.4.1", + "eth-json-rpc-middleware": "^5.0.0", "eth-keyring-controller": "^6.0.0", "eth-method-registry": "^1.2.0", "eth-phishing-detect": "^1.1.4", @@ -260,7 +260,7 @@ "mocha": "^7.2.0", "nock": "^9.0.14", "node-fetch": "^2.6.0", - "node-sass": "^4.12.0", + "node-sass": "^4.14.1", "nyc": "^15.0.0", "polyfill-crypto.getrandomvalues": "^1.0.0", "proxyquire": "^2.1.3", diff --git a/test/e2e/metamask-responsive-ui.spec.js b/test/e2e/metamask-responsive-ui.spec.js index 0969af53f..7e98e4227 100644 --- a/test/e2e/metamask-responsive-ui.spec.js +++ b/test/e2e/metamask-responsive-ui.spec.js @@ -119,7 +119,7 @@ describe('MetaMask', function () { it('show account details dropdown menu', async function () { await driver.clickElement(By.css('[data-testid="account-options-menu-button"]')) const options = await driver.findElements(By.css('.account-options-menu .menu-item')) - assert.equal(options.length, 4) // HD Wallet type does not have to show the Remove Account option + assert.equal(options.length, 3) // HD Wallet type does not have to show the Remove Account option // click outside of menu to dismiss // account menu button chosen because the menu never covers it. await driver.clickPoint(By.css('.account-menu__icon'), 0, 0) diff --git a/test/unit/app/controllers/metamask-controller-test.js b/test/unit/app/controllers/metamask-controller-test.js index 262202419..4628aabc0 100644 --- a/test/unit/app/controllers/metamask-controller-test.js +++ b/test/unit/app/controllers/metamask-controller-test.js @@ -35,9 +35,33 @@ const ExtensionizerMock = { }, } +let loggerMiddlewareMock +const initializeMockMiddlewareLog = () => { + loggerMiddlewareMock = { + requests: [], + responses: [], + } +} +const tearDownMockMiddlewareLog = () => { + loggerMiddlewareMock = undefined +} + +const createLoggerMiddlewareMock = () => (req, res, next) => { + if (loggerMiddlewareMock) { + loggerMiddlewareMock.requests.push(req) + next((cb) => { + loggerMiddlewareMock.responses.push(res) + cb() + }) + } else { + next() + } +} + const MetaMaskController = proxyquire('../../../../app/scripts/metamask-controller', { './controllers/threebox': { default: ThreeBoxControllerMock }, 'extensionizer': ExtensionizerMock, + './lib/createLoggerMiddleware': { default: createLoggerMiddlewareMock }, }).default const currentNetworkId = 42 @@ -96,7 +120,6 @@ describe('MetaMaskController', function () { // add sinon method spies sandbox.spy(metamaskController.keyringController, 'createNewVaultAndKeychain') sandbox.spy(metamaskController.keyringController, 'createNewVaultAndRestore') - sandbox.spy(metamaskController.txController, 'newUnapprovedTransaction') }) afterEach(function () { @@ -776,6 +799,17 @@ describe('MetaMaskController', function () { }) describe('#setupUntrustedCommunication', function () { + + const mockTxParams = { from: TEST_ADDRESS } + + beforeEach(function () { + initializeMockMiddlewareLog() + }) + + after(function () { + tearDownMockMiddlewareLog() + }) + it('sets up phishing stream for untrusted communication', async function () { const phishingMessageSender = { url: 'http://myethereumwalletntw.com', @@ -815,7 +849,7 @@ describe('MetaMaskController', function () { const message = { id: 1999133338649204, jsonrpc: '2.0', - params: ['mock tx params'], + params: [{ ...mockTxParams }], method: 'eth_sendTransaction', } streamTest.write({ @@ -824,15 +858,12 @@ describe('MetaMaskController', function () { }, null, () => { setTimeout(() => { assert.deepStrictEqual( - metamaskController.txController.newUnapprovedTransaction.getCall(0).args, - [ - 'mock tx params', - { - ...message, - origin: 'http://mycrypto.com', - tabId: 456, - }, - ] + loggerMiddlewareMock.requests[0], + { + ...message, + origin: 'http://mycrypto.com', + tabId: 456, + }, ) done() }) @@ -856,7 +887,7 @@ describe('MetaMaskController', function () { const message = { id: 1999133338649204, jsonrpc: '2.0', - params: ['mock tx params'], + params: [{ ...mockTxParams }], method: 'eth_sendTransaction', } streamTest.write({ @@ -865,14 +896,11 @@ describe('MetaMaskController', function () { }, null, () => { setTimeout(() => { assert.deepStrictEqual( - metamaskController.txController.newUnapprovedTransaction.getCall(0).args, - [ - 'mock tx params', - { - ...message, - origin: 'http://mycrypto.com', - }, - ] + loggerMiddlewareMock.requests[0], + { + ...message, + origin: 'http://mycrypto.com', + }, ) done() }) diff --git a/test/unit/app/controllers/transactions/pending-tx-tracker-test.js b/test/unit/app/controllers/transactions/pending-tx-tracker-test.js index e2b68c775..facc0bb12 100644 --- a/test/unit/app/controllers/transactions/pending-tx-tracker-test.js +++ b/test/unit/app/controllers/transactions/pending-tx-tracker-test.js @@ -1,5 +1,6 @@ import sinon from 'sinon' import { strict as assert } from 'assert' +import BN from 'bn.js' import PendingTransactionTracker from '../../../../../app/scripts/controllers/transactions/pending-tx-tracker' describe('PendingTransactionTracker', function () { @@ -311,10 +312,11 @@ describe('PendingTransactionTracker', function () { describe('#_checkIfTxWasDropped', function () { it('should return true when the given nonce is lower than the network nonce', async function () { + const nonceBN = new BN(2) const pendingTxTracker = new PendingTransactionTracker({ query: { getTransactionReceipt: sinon.stub(), - getTransactionCount: sinon.stub().resolves('0x02'), + getTransactionCount: sinon.stub().resolves(nonceBN), }, nonceTracker: { getGlobalLock: sinon.stub().resolves({ @@ -343,10 +345,11 @@ describe('PendingTransactionTracker', function () { }) it('should return false when the given nonce is the network nonce', async function () { + const nonceBN = new BN(1) const pendingTxTracker = new PendingTransactionTracker({ query: { getTransactionReceipt: sinon.stub(), - getTransactionCount: sinon.stub().resolves('0x01'), + getTransactionCount: sinon.stub().resolves(nonceBN), }, nonceTracker: { getGlobalLock: sinon.stub().resolves({ @@ -487,10 +490,11 @@ describe('PendingTransactionTracker', function () { history: [{}], rawTx: '0xf86c808504a817c80082471d', } + const nonceBN = new BN(2) const pendingTxTracker = new PendingTransactionTracker({ query: { getTransactionReceipt: sinon.stub().rejects(), - getTransactionCount: sinon.stub().resolves('0x02'), + getTransactionCount: sinon.stub().resolves(nonceBN), }, nonceTracker: { getGlobalLock: sinon.stub().resolves({ @@ -647,6 +651,7 @@ describe('PendingTransactionTracker', function () { }) it("should emit 'tx:dropped' with the txMetas id only after the fourth call", async function () { + const nonceBN = new BN(2) const txMeta = { id: 1, hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', @@ -662,7 +667,7 @@ describe('PendingTransactionTracker', function () { const pendingTxTracker = new PendingTransactionTracker({ query: { getTransactionReceipt: sinon.stub().resolves(null), - getTransactionCount: sinon.stub().resolves('0x02'), + getTransactionCount: sinon.stub().resolves(nonceBN), }, nonceTracker: { getGlobalLock: sinon.stub().resolves({ diff --git a/ui/app/components/app/asset-list-item/asset-list-item.js b/ui/app/components/app/asset-list-item/asset-list-item.js index 8c0848abe..3737698d1 100644 --- a/ui/app/components/app/asset-list-item/asset-list-item.js +++ b/ui/app/components/app/asset-list-item/asset-list-item.js @@ -97,7 +97,7 @@ const AssetListItem = ({ data-testid={dataTestId} title={primary} titleIcon={titleIcon} - subtitle={secondary} + subtitle={

{secondary}

} onClick={onClick} icon={( {permissions.map(({ key: permissionName }) => (
  • - {t(permissionName)} + +
  • ))} diff --git a/ui/app/components/app/connected-accounts-permissions/index.scss b/ui/app/components/app/connected-accounts-permissions/index.scss index 838e727dc..712d0b431 100644 --- a/ui/app/components/app/connected-accounts-permissions/index.scss +++ b/ui/app/components/app/connected-accounts-permissions/index.scss @@ -41,13 +41,11 @@ &__list-item { display: flex; + } - i { - display: block; - padding-right: 8px; - font-size: 18px; - color: $Grey-800; - } + & &__checkbox { + margin: 0 8px 0 0; + font-size: 18px; } &__list-container { diff --git a/ui/app/components/app/menu-bar/account-options-menu.js b/ui/app/components/app/menu-bar/account-options-menu.js index c87416cdb..9edf908d4 100644 --- a/ui/app/components/app/menu-bar/account-options-menu.js +++ b/ui/app/components/app/menu-bar/account-options-menu.js @@ -10,6 +10,8 @@ import genAccountLink from '../../../../lib/account-link' import { getCurrentKeyring, getCurrentNetwork, getRpcPrefsForCurrentProvider, getSelectedIdentity } from '../../../selectors' import { useI18nContext } from '../../../hooks/useI18nContext' import { useMetricEvent } from '../../../hooks/useMetricEvent' +import { getEnvironmentType } from '../../../../../app/scripts/lib/util' +import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../../app/scripts/lib/enums' export default function AccountOptionsMenu ({ anchorElement, onClose }) { const t = useI18nContext() @@ -58,16 +60,22 @@ export default function AccountOptionsMenu ({ anchorElement, onClose }) { className="account-options-menu" onHide={onClose} > - { - openFullscreenEvent() - global.platform.openExtensionInBrowser() - onClose() - }} - iconClassName="fas fa-expand-alt" - > - { t('expandView') } - + { + getEnvironmentType() === ENVIRONMENT_TYPE_FULLSCREEN + ? null + : ( + { + openFullscreenEvent() + global.platform.openExtensionInBrowser() + onClose() + }} + iconClassName="fas fa-expand-alt" + > + { t('expandView') } + + ) + } { diff --git a/ui/app/components/app/permission-page-container/index.scss b/ui/app/components/app/permission-page-container/index.scss index 9a17655ec..576147aed 100644 --- a/ui/app/components/app/permission-page-container/index.scss +++ b/ui/app/components/app/permission-page-container/index.scss @@ -62,11 +62,6 @@ display: flex; align-items: center; - i { - font-size: 1.4rem; - color: $Grey-200; - } - label { font-size: 14px; margin-left: 16px; @@ -75,6 +70,11 @@ } } + & &__checkbox { + font-size: 1.4rem; + margin: 0; + } + &__content-container { display: flex; flex-direction: column; 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 c15e93a87..268c973bf 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 @@ -2,6 +2,7 @@ import PropTypes from 'prop-types' import React, { PureComponent } from 'react' import PermissionsConnectHeader from '../../permissions-connect-header' import Tooltip from '../../../ui/tooltip-v2' +import CheckBox from '../../../ui/check-box' export default class PermissionPageContainerContent extends PureComponent { @@ -33,6 +34,8 @@ export default class PermissionPageContainerContent extends PureComponent { const description = t(permissionName) // don't allow deselecting eth_accounts const isDisabled = permissionName === 'eth_accounts' + const isChecked = Boolean(selectedPermissions[permissionName]) + const title = isChecked ? t('permissionCheckedIconDescription') : t('permissionUncheckedIconDescription') return (
    - { selectedPermissions[permissionName] - ? - : - } - + +
    ) }) diff --git a/ui/app/components/app/token-cell/token-cell.test.js b/ui/app/components/app/token-cell/token-cell.test.js index 0492ec8ff..3e87fd90f 100644 --- a/ui/app/components/app/token-cell/token-cell.test.js +++ b/ui/app/components/app/token-cell/token-cell.test.js @@ -69,7 +69,7 @@ describe('Token Cell', function () { }) it('renders token balance and symbol', function () { - assert.equal(wrapper.find('.list-item__heading').text(), '5.000 TEST ') + assert.equal(wrapper.find('.list-item__heading').text(), '5.000 TEST') }) it('renders converted fiat amount', function () { diff --git a/ui/app/components/app/transaction-list-item/index.scss b/ui/app/components/app/transaction-list-item/index.scss index 71282e718..d0c3c786f 100644 --- a/ui/app/components/app/transaction-list-item/index.scss +++ b/ui/app/components/app/transaction-list-item/index.scss @@ -31,4 +31,27 @@ padding-top: 0; } } + + .list-item__subheading > h3 { + overflow: visible; + display: flex; + white-space: nowrap; + text-overflow: initial; + } + + .transaction-status:after { + content: "·"; + margin: 0 4px; + } + + &__origin, &__address { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + &__origin { + /*rtl:ignore*/ + direction: rtl; + } } diff --git a/ui/app/components/app/transaction-list-item/transaction-list-item.component.js b/ui/app/components/app/transaction-list-item/transaction-list-item.component.js index a92880c52..d056b0202 100644 --- a/ui/app/components/app/transaction-list-item/transaction-list-item.component.js +++ b/ui/app/components/app/transaction-list-item/transaction-list-item.component.js @@ -40,6 +40,7 @@ export default function TransactionListItem ({ transactionGroup, isEarliestNonce const { title, subtitle, + subtitleContainsOrigin, date, category, primaryCurrency, @@ -122,15 +123,19 @@ export default function TransactionListItem ({ transactionGroup, isEarliestNonce /> )} icon={} - subtitle={subtitle} - subtitleStatus={( - + subtitle={( +

    + + + {subtitle} + +

    )} rightContent={!isSignatureReq && ( <> diff --git a/ui/app/components/app/transaction-status/tests/transaction-status.component.test.js b/ui/app/components/app/transaction-status/tests/transaction-status.component.test.js index 939090875..9cf9e59ad 100644 --- a/ui/app/components/app/transaction-status/tests/transaction-status.component.test.js +++ b/ui/app/components/app/transaction-status/tests/transaction-status.component.test.js @@ -20,7 +20,7 @@ describe('TransactionStatus Component', function () { ) assert.ok(wrapper) - assert.equal(wrapper.text(), 'June 1 · ') + assert.equal(wrapper.text(), 'June 1') }) it('should render PENDING properly when status is APPROVED', function () { @@ -33,7 +33,7 @@ describe('TransactionStatus Component', function () { ) assert.ok(wrapper) - assert.equal(wrapper.text(), 'PENDING · ') + assert.equal(wrapper.text(), 'PENDING') assert.equal(wrapper.find(Tooltip).props().title, 'test-title') }) @@ -47,7 +47,7 @@ describe('TransactionStatus Component', function () { ) assert.ok(wrapper) - assert.equal(wrapper.text(), 'PENDING · ') + assert.equal(wrapper.text(), 'PENDING') }) it('should render QUEUED properly', function () { @@ -59,7 +59,7 @@ describe('TransactionStatus Component', function () { assert.ok(wrapper) assert.ok(wrapper.find('.transaction-status--queued').length, 'queued className not found') - assert.equal(wrapper.text(), 'QUEUED · ') + assert.equal(wrapper.text(), 'QUEUED') }) it('should render UNAPPROVED properly', function () { @@ -71,7 +71,7 @@ describe('TransactionStatus Component', function () { assert.ok(wrapper) assert.ok(wrapper.find('.transaction-status--unapproved').length, 'unapproved className not found') - assert.equal(wrapper.text(), 'UNAPPROVED · ') + assert.equal(wrapper.text(), 'UNAPPROVED') }) after(function () { diff --git a/ui/app/components/app/transaction-status/transaction-status.component.js b/ui/app/components/app/transaction-status/transaction-status.component.js index c525f7484..2fd37efae 100644 --- a/ui/app/components/app/transaction-status/transaction-status.component.js +++ b/ui/app/components/app/transaction-status/transaction-status.component.js @@ -56,16 +56,13 @@ export default function TransactionStatus ({ status, date, error, isEarliestNonc const statusText = statusKey === CONFIRMED_STATUS ? date : t(statusKey) return ( - - - { statusText } - - {' · '} - + + { statusText } + ) } diff --git a/ui/app/components/ui/check-box/check-box.component.js b/ui/app/components/ui/check-box/check-box.component.js index 5d062e0e8..b1c62433a 100644 --- a/ui/app/components/ui/check-box/check-box.component.js +++ b/ui/app/components/ui/check-box/check-box.component.js @@ -10,7 +10,7 @@ const CHECKBOX_STATE = { export const { CHECKED, INDETERMINATE, UNCHECKED } = CHECKBOX_STATE -const CheckBox = ({ className, disabled, id, onClick, checked }) => { +const CheckBox = ({ className, disabled, id, onClick, checked, title }) => { if (typeof checked === 'boolean') { checked = checked ? CHECKBOX_STATE.CHECKED @@ -41,6 +41,7 @@ const CheckBox = ({ className, disabled, id, onClick, checked }) => { } readOnly ref={ref} + title={title} type="checkbox" /> ) @@ -52,6 +53,7 @@ CheckBox.propTypes = { id: PropTypes.string, onClick: PropTypes.func, checked: PropTypes.oneOf([...Object.keys(CHECKBOX_STATE), true, false]).isRequired, + title: PropTypes.string, } CheckBox.defaultProps = { diff --git a/ui/app/components/ui/check-box/index.scss b/ui/app/components/ui/check-box/index.scss index 40273973d..446ce1683 100644 --- a/ui/app/components/ui/check-box/index.scss +++ b/ui/app/components/ui/check-box/index.scss @@ -18,6 +18,5 @@ &:disabled { color: $Grey-100; cursor: not-allowed; - opacity: 0.5; } } diff --git a/ui/app/components/ui/list-item/index.scss b/ui/app/components/ui/list-item/index.scss index b6991ea22..1aca82d2a 100644 --- a/ui/app/components/ui/list-item/index.scss +++ b/ui/app/components/ui/list-item/index.scss @@ -41,6 +41,11 @@ display: flex; align-items: center; + > h2 { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } &-wrap { display: inline-block; margin-left: 8px; @@ -53,6 +58,13 @@ line-height: 14px; color: $Grey-500; margin-top: 4px; + // all direct descendants should be truncated with ellipses + // allows flexibility in consuming components to use h3/other tag + > * { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } &:empty { display: none; } diff --git a/ui/app/components/ui/list-item/list-item.component.js b/ui/app/components/ui/list-item/list-item.component.js index 60d395ab3..7481d45db 100644 --- a/ui/app/components/ui/list-item/list-item.component.js +++ b/ui/app/components/ui/list-item/list-item.component.js @@ -6,7 +6,6 @@ export default function ListItem ({ title, subtitle, onClick, - subtitleStatus, children, titleIcon, icon, @@ -24,16 +23,19 @@ export default function ListItem ({ {icon} )} -

    - { title } {titleIcon && ( +
    +

    { title }

    + {titleIcon && (
    {titleIcon}
    )} -

    -

    - {subtitleStatus}{subtitle} -

    + + {subtitle && ( +
    + {subtitle} +
    + )} {children && (
    { children } @@ -56,8 +58,7 @@ export default function ListItem ({ ListItem.propTypes = { title: PropTypes.string.isRequired, titleIcon: PropTypes.node, - subtitle: PropTypes.string, - subtitleStatus: PropTypes.node, + subtitle: PropTypes.node, children: PropTypes.node, icon: PropTypes.node, rightContent: PropTypes.node, diff --git a/ui/app/hooks/tests/useTransactionDisplayData.test.js b/ui/app/hooks/tests/useTransactionDisplayData.test.js index e8281cc72..2cae72bbf 100644 --- a/ui/app/hooks/tests/useTransactionDisplayData.test.js +++ b/ui/app/hooks/tests/useTransactionDisplayData.test.js @@ -16,6 +16,7 @@ const expectedResults = [ { title: 'Send ETH', category: 'send', subtitle: 'To: 0xffe5...1a97', + subtitleContainsOrigin: false, date: 'May 12', primaryCurrency: '-1 ETH', senderAddress: '0x9eca64466f257793eaa52fcfff5066894b76a149', @@ -26,6 +27,7 @@ const expectedResults = [ { title: 'Send ETH', category: 'send', subtitle: 'To: 0x0ccc...8848', + subtitleContainsOrigin: false, date: 'May 12', primaryCurrency: '-2 ETH', senderAddress: '0x9eca64466f257793eaa52fcfff5066894b76a149', @@ -36,6 +38,7 @@ const expectedResults = [ { title: 'Send ETH', category: 'send', subtitle: 'To: 0xffe5...1a97', + subtitleContainsOrigin: false, date: 'May 12', primaryCurrency: '-2 ETH', senderAddress: '0x9eca64466f257793eaa52fcfff5066894b76a149', @@ -46,6 +49,7 @@ const expectedResults = [ { title: 'Receive', category: 'receive', subtitle: 'From: 0x31b9...4523', + subtitleContainsOrigin: false, date: 'May 12', primaryCurrency: '18.75 ETH', senderAddress: '0x31b98d14007bdee637298086988a0bbd31184523', @@ -56,6 +60,7 @@ const expectedResults = [ { title: 'Receive', category: 'receive', subtitle: 'From: 0x9eca...a149', + subtitleContainsOrigin: false, date: 'May 8', primaryCurrency: '0 ETH', senderAddress: '0x9eca64466f257793eaa52fcfff5066894b76a149', @@ -66,6 +71,7 @@ const expectedResults = [ { title: 'Receive', category: 'receive', subtitle: 'From: 0xee01...febb', + subtitleContainsOrigin: false, date: 'May 24', primaryCurrency: '1 ETH', senderAddress: '0xee014609ef9e09776ac5fe00bdbfef57bcdefebb', diff --git a/ui/app/hooks/useTransactionDisplayData.js b/ui/app/hooks/useTransactionDisplayData.js index a0509966f..f5e17f4b3 100644 --- a/ui/app/hooks/useTransactionDisplayData.js +++ b/ui/app/hooks/useTransactionDisplayData.js @@ -7,7 +7,7 @@ import { useTokenFiatAmount } from './useTokenFiatAmount' import { PRIMARY, SECONDARY } from '../helpers/constants/common' import { getTokenToAddress } from '../helpers/utils/token-util' import { useUserPreferencedCurrency } from './useUserPreferencedCurrency' -import { formatDateWithYearContext, shortenAddress } from '../helpers/utils/util' +import { formatDateWithYearContext, shortenAddress, stripHttpSchemes } from '../helpers/utils/util' import { CONTRACT_INTERACTION_KEY, DEPLOY_CONTRACT_ACTION_KEY, @@ -31,14 +31,15 @@ import { getTokens } from '../ducks/metamask/metamask' /** * @typedef {Object} TransactionDisplayData - * @property {string} title - primary description of the transaction - * @property {string} subtitle - supporting text describing the transaction - * @property {string} category - the transaction category - * @property {string} primaryCurrency - the currency string to display in the primary position - * @property {string} [secondaryCurrency] - the currency string to display in the secondary position - * @property {string} status - the status of the transaction - * @property {string} senderAddress - the Ethereum address of the sender - * @property {string} recipientAddress - the Ethereum address of the recipient + * @property {string} title - primary description of the transaction + * @property {string} subtitle - supporting text describing the transaction + * @property {bool} subtitleContainsOrigin - true if the subtitle includes the origin of the tx + * @property {string} category - the transaction category + * @property {string} primaryCurrency - the currency string to display in the primary position + * @property {string} [secondaryCurrency] - the currency string to display in the secondary position + * @property {string} status - the status of the transaction + * @property {string} senderAddress - the Ethereum address of the sender + * @property {string} recipientAddress - the Ethereum address of the recipient */ /** @@ -70,6 +71,7 @@ export function useTransactionDisplayData (transactionGroup) { let prefix = '-' const date = formatDateWithYearContext(initialTransaction.time || 0) let subtitle + let subtitleContainsOrigin = false let recipientAddress = to // This value is used to determine whether we should look inside txParams.data @@ -87,27 +89,31 @@ export function useTransactionDisplayData (transactionGroup) { const tokenDisplayValue = useTokenDisplayValue(initialTransaction?.txParams?.data, token, isTokenCategory) const tokenFiatAmount = useTokenFiatAmount(token?.address, tokenDisplayValue, token?.symbol) + const origin = stripHttpSchemes(initialTransaction.origin || initialTransaction.msgParams?.origin || '') + let category let title // There are four types of transaction entries that are currently differentiated in the design - // 1. (PENDING DESIGN) signature request + // 1. signature request // 2. Send (sendEth sendTokens) // 3. Deposit // 4. Site interaction // 5. Approval if (transactionCategory == null) { - const origin = initialTransaction.msgParams?.origin || initialTransaction.origin category = TRANSACTION_CATEGORY_SIGNATURE_REQUEST title = t('signatureRequest') - subtitle = origin || '' + subtitle = origin + subtitleContainsOrigin = true } else if (transactionCategory === TOKEN_METHOD_APPROVE) { category = TRANSACTION_CATEGORY_APPROVAL title = t('approve') - subtitle = initialTransaction.origin + subtitle = origin + subtitleContainsOrigin = true } else if (transactionCategory === DEPLOY_CONTRACT_ACTION_KEY || transactionCategory === CONTRACT_INTERACTION_KEY) { category = TRANSACTION_CATEGORY_INTERACTION title = (methodData?.name && camelCaseToCapitalize(methodData.name)) || (actionKey && t(actionKey)) || '' - subtitle = initialTransaction.origin + subtitle = origin + subtitleContainsOrigin = true } else if (transactionCategory === INCOMING_TRANSACTION) { category = TRANSACTION_CATEGORY_RECEIVE title = t('receive') @@ -146,6 +152,7 @@ export function useTransactionDisplayData (transactionGroup) { category, date, subtitle, + subtitleContainsOrigin, primaryCurrency, senderAddress, recipientAddress, diff --git a/ui/app/pages/home/home.component.js b/ui/app/pages/home/home.component.js index 4f470ebab..8f942de03 100644 --- a/ui/app/pages/home/home.component.js +++ b/ui/app/pages/home/home.component.js @@ -25,6 +25,8 @@ import { CONNECTED_ACCOUNTS_ROUTE, } from '../../helpers/constants/routes' +const LEARN_MORE_URL = 'https://metamask.zendesk.com/hc/en-us/articles/360045129011-Intro-to-MetaMask-v8-extension' + export default class Home extends PureComponent { static contextTypes = { t: PropTypes.func, @@ -187,17 +189,27 @@ export default class Home extends PureComponent { ) }} footer={( - + <> + + { t('learnMore') } + + + )} >
    { t('metaMaskConnectStatusParagraphOne') }
    { t('metaMaskConnectStatusParagraphTwo') }
    +
    { t('metaMaskConnectStatusParagraphThree') }
    ) diff --git a/ui/app/pages/home/index.scss b/ui/app/pages/home/index.scss index 95fc20746..bb2ece13b 100644 --- a/ui/app/pages/home/index.scss +++ b/ui/app/pages/home/index.scss @@ -47,19 +47,24 @@ padding-right: 24px; div { + margin-bottom: 20px; + &:last-child { - margin-top: 26px; + margin-bottom: 0px; } } } &__connected-status-popover { width: 329px; - height: 295px; - margin-top: -12px; + margin-top: -15px; .popover-header { - padding-bottom: 4px; + padding-bottom: 20px; + + &__title { + padding-bottom: 0; + } } .popover-content { @@ -72,7 +77,11 @@ } .popover-footer { - justify-content: flex-end; + border-top: 0; + justify-content: space-between; + align-items: center; + padding-top: 20px; + font-size: 14px; & :only-child { margin: 0; @@ -84,16 +93,20 @@ border-radius: 39px; padding: 0; } + + a, a:hover { + color: $dodger-blue; + cursor: pointer; + } } } &__connected-status-popover-bg { - height: 34px; - width: 110px; + height: 55px; + width: 120px; border-radius: 34px; position: absolute; - top: 82px; - left: 12px; + top: 73px; opacity: 1; box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.2); background: none; diff --git a/ui/app/store/actions.js b/ui/app/store/actions.js index c99a566ba..b118b0227 100644 --- a/ui/app/store/actions.js +++ b/ui/app/store/actions.js @@ -2160,7 +2160,7 @@ export function getContractMethodData (data = '') { const prefixedData = ethUtil.addHexPrefix(data) const fourBytePrefix = prefixedData.slice(0, 10) const { knownMethodData } = getState().metamask - if (knownMethodData && knownMethodData[fourBytePrefix]) { + if (knownMethodData && knownMethodData[fourBytePrefix] && Object.keys(knownMethodData[fourBytePrefix]).length !== 0) { return Promise.resolve(knownMethodData[fourBytePrefix]) } diff --git a/yarn.lock b/yarn.lock index dd57fc376..f5cb5825c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1629,10 +1629,10 @@ scroll "^2.0.3" warning "^3.0.0" -"@metamask/controllers@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@metamask/controllers/-/controllers-2.0.0.tgz#e57762c213d2722fd178d4c5c288ad98a2ee8bdf" - integrity sha512-J2DSoyyPRAilTdohl43O5tZdqOSMvB94Kx1ApRpkPZxQwtDUIzWHRpTXptyCEwkhaOdkNz9BvWMoaRQxjktfKw== +"@metamask/controllers@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@metamask/controllers/-/controllers-2.0.1.tgz#33b0e2826fb6d4aa582acaff6b347e6abe0f54f5" + integrity sha512-xioh4h+4D2pUSJ9H2CaffxKGmlg0kUK2bbRJ8c9GXPVTo8KhRHryvNKfkVCyoSt35FLROzzwTEdVJ4dmUFuELQ== dependencies: await-semaphore "^0.1.3" eth-contract-metadata "^1.11.0" @@ -10174,7 +10174,7 @@ eth-json-rpc-middleware@^1.5.0: promise-to-callback "^1.0.0" tape "^4.6.3" -eth-json-rpc-middleware@^4.1.4, eth-json-rpc-middleware@^4.1.5, eth-json-rpc-middleware@^4.4.1: +eth-json-rpc-middleware@^4.1.4, eth-json-rpc-middleware@^4.1.5: version "4.4.1" resolved "https://registry.yarnpkg.com/eth-json-rpc-middleware/-/eth-json-rpc-middleware-4.4.1.tgz#07d3dd0724c24a8d31e4a172ee96271da71b4228" integrity sha512-yoSuRgEYYGFdVeZg3poWOwAlRI+MoBIltmOB86MtpoZjvLbou9EB/qWMOWSmH2ryCWLW97VYY6NWsmWm3OAA7A== @@ -10194,6 +10194,26 @@ eth-json-rpc-middleware@^4.1.4, eth-json-rpc-middleware@^4.1.5, eth-json-rpc-mid pify "^3.0.0" safe-event-emitter "^1.0.1" +eth-json-rpc-middleware@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/eth-json-rpc-middleware/-/eth-json-rpc-middleware-5.0.0.tgz#9a202a41a2b1678c094cbffd0db0da619ec86f7b" + integrity sha512-31zLHvElrQV7opFZs47UqjMGFM4QCgMms7HJSQAZ3wjPuE3rFLxFUTeGHXwM3fAp9zh7n0M+B2gK7Ds4OUKfog== + dependencies: + btoa "^1.2.1" + clone "^2.1.1" + eth-query "^2.1.2" + eth-rpc-errors "^2.1.1" + eth-sig-util "^1.4.2" + ethereumjs-block "^1.6.0" + ethereumjs-tx "^1.3.7" + ethereumjs-util "^5.1.2" + ethereumjs-vm "^2.6.0" + fetch-ponyfill "^4.0.0" + json-rpc-engine "^5.1.3" + json-stable-stringify "^1.0.1" + pify "^3.0.0" + safe-event-emitter "^1.0.1" + eth-keyring-controller@^5.3.0, eth-keyring-controller@^5.6.1: version "5.6.1" resolved "https://registry.yarnpkg.com/eth-keyring-controller/-/eth-keyring-controller-5.6.1.tgz#7b7268400704c8f5ce98a055910341177dd207ca" @@ -10275,6 +10295,13 @@ eth-query@^2.0.2, eth-query@^2.1.0, eth-query@^2.1.2: json-rpc-random-id "^1.0.0" xtend "^4.0.1" +eth-rpc-errors@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/eth-rpc-errors/-/eth-rpc-errors-2.1.1.tgz#00a7d6c8a9c864a8ab7d0356be20964e5bee4b13" + integrity sha512-MY3zAa5ZF8hvgQu1HOF9agaK5GgigBRGpTJ8H0oVlE0NqMu13CW6syyjLXdeIDCGQTbUeHliU1z9dVmvMKx1Tg== + dependencies: + fast-safe-stringify "^2.0.6" + eth-sig-util@2.3.0, eth-sig-util@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/eth-sig-util/-/eth-sig-util-2.3.0.tgz#c54a6ac8e8796f7e25f59cf436982a930e645231" @@ -13060,9 +13087,9 @@ gulp-babel@^8.0.0: vinyl-sourcemaps-apply "^0.2.0" gulp-cli@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/gulp-cli/-/gulp-cli-2.2.0.tgz#5533126eeb7fe415a7e3e84a297d334d5cf70ebc" - integrity sha512-rGs3bVYHdyJpLqR0TUBnlcZ1O5O++Zs4bA0ajm+zr3WFCfiSLjGwoCBqFs18wzN+ZxahT9DkOK5nDf26iDsWjA== + version "2.3.0" + resolved "https://registry.yarnpkg.com/gulp-cli/-/gulp-cli-2.3.0.tgz#ec0d380e29e52aa45e47977f0d32e18fd161122f" + integrity sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A== dependencies: ansi-colors "^1.0.1" archy "^1.0.0" @@ -13072,7 +13099,7 @@ gulp-cli@^2.2.0: copy-props "^2.0.1" fancy-log "^1.3.2" gulplog "^1.0.0" - interpret "^1.1.0" + interpret "^1.4.0" isobject "^3.0.1" liftoff "^3.1.0" matchdep "^2.0.0" @@ -13080,7 +13107,7 @@ gulp-cli@^2.2.0: pretty-hrtime "^1.0.0" replace-homedir "^1.0.0" semver-greatest-satisfied-range "^1.1.0" - v8flags "^3.0.1" + v8flags "^3.2.0" yargs "^7.1.0" gulp-debug@^3.2.0: @@ -14269,10 +14296,10 @@ internal-slot@^1.0.2: has "^1.0.3" side-channel "^1.0.2" -interpret@^1.0.0, interpret@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" - integrity sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ= +interpret@^1.0.0, interpret@^1.1.0, interpret@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" + integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== interpret@^2.0.0: version "2.0.0" @@ -19345,10 +19372,10 @@ node-releases@^1.1.46: dependencies: semver "^6.3.0" -node-sass@^4.12.0, node-sass@^4.8.3: - version "4.13.1" - resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.13.1.tgz#9db5689696bb2eec2c32b98bfea4c7a2e992d0a3" - integrity sha512-TTWFx+ZhyDx1Biiez2nB0L3YrCZ/8oHagaDalbuBSlqXgUPsdkUSzJsVxeDO9LtPB49+Fh3WQl3slABo6AotNw== +node-sass@^4.14.1, node-sass@^4.8.3: + version "4.14.1" + resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.14.1.tgz#99c87ec2efb7047ed638fb4c9db7f3a42e2217b5" + integrity sha512-sjCuOlvGyCJS40R8BscF5vhVlQjNN069NtQ1gSxyK1u9iqvn6tf7O1R4GNowVZfiZUCRt5MmMs1xd+4V/7Yr0g== dependencies: async-foreach "^0.1.3" chalk "^1.1.1" @@ -19364,7 +19391,7 @@ node-sass@^4.12.0, node-sass@^4.8.3: node-gyp "^3.8.0" npmlog "^4.0.0" request "^2.88.0" - sass-graph "^2.2.4" + sass-graph "2.2.5" stdout-stream "^1.4.0" "true-case-path" "^1.0.2" @@ -24044,15 +24071,15 @@ sanitize-filename@^1.6.1: dependencies: truncate-utf8-bytes "^1.0.0" -sass-graph@^2.2.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49" - integrity sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k= +sass-graph@2.2.5: + version "2.2.5" + resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.5.tgz#a981c87446b8319d96dce0671e487879bd24c2e8" + integrity sha512-VFWDAHOe6mRuT4mZRd4eKE+d8Uedrk6Xnh7Sh9b4NGufQLQjOrvf/MQoOdx+0s92L89FeyUUNfU597j/3uNpag== dependencies: glob "^7.0.0" lodash "^4.0.0" scss-tokenizer "^0.2.3" - yargs "^7.0.0" + yargs "^13.3.2" sass-loader@^7.0.1: version "7.0.1" @@ -27548,10 +27575,10 @@ uuid@^3.3.2, uuid@^3.3.3: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866" integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ== -v8flags@^3.0.1, v8flags@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.1.1.tgz#42259a1461c08397e37fe1d4f1cfb59cad85a053" - integrity sha512-iw/1ViSEaff8NJ3HLyEjawk/8hjJib3E7pvG4pddVXfUg1983s3VGsiClDjhK64MQVDGqc1Q8r18S4VKQZS9EQ== +v8flags@^3.1.1, v8flags@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.2.0.tgz#b243e3b4dfd731fa774e7492128109a0fe66d656" + integrity sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg== dependencies: homedir-polyfill "^1.0.1" @@ -28795,7 +28822,7 @@ yargs@13.2.4: y18n "^4.0.0" yargs-parser "^13.1.0" -yargs@13.3.2, yargs@^13.2.2, yargs@^13.2.4, yargs@^13.3.0: +yargs@13.3.2, yargs@^13.2.2, yargs@^13.2.4, yargs@^13.3.0, yargs@^13.3.2: version "13.3.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== @@ -28864,7 +28891,7 @@ yargs@^15.0.0, yargs@^15.0.2: y18n "^4.0.0" yargs-parser "^18.1.1" -yargs@^7.0.0, yargs@^7.1.0: +yargs@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8" integrity sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=