From f27bdc2b9edaff35b213cc64a4106d3b3561a3aa Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 5 Dec 2019 16:23:43 -0400 Subject: [PATCH 1/2] Post v7.7.0 changes --- .circleci/config.yml | 12 - .eslintrc | 6 +- .gitattributes | 1 - CHANGELOG.md | 13 - README.md | 2 +- app/_locales/cs/messages.json | 33 ++ app/_locales/de/messages.json | 25 +- app/_locales/en/messages.json | 135 ++--- app/_locales/es/messages.json | 44 +- app/_locales/fr/messages.json | 26 + app/_locales/gu/messages.json | 9 + app/_locales/hn/messages.json | 33 ++ app/_locales/ht/messages.json | 33 ++ app/_locales/it/messages.json | 63 ++- app/_locales/ja/messages.json | 37 +- app/_locales/ko/messages.json | 31 +- app/_locales/ml/messages.json | 9 + app/_locales/mr/messages.json | 9 + app/_locales/nl/messages.json | 33 ++ app/_locales/ph/messages.json | 30 ++ app/_locales/pl/messages.json | 23 + app/_locales/pt/messages.json | 33 ++ app/_locales/pt_PT/messages.json | 9 + app/_locales/ru/messages.json | 44 +- app/_locales/sk/messages.json | 20 + app/_locales/sl/messages.json | 23 + app/_locales/ta/messages.json | 36 ++ app/_locales/te/messages.json | 9 + app/_locales/th/messages.json | 26 + app/_locales/tr/messages.json | 29 + app/_locales/vi/messages.json | 30 ++ app/_locales/zh_CN/messages.json | 20 + app/_locales/zh_TW/messages.json | 34 +- app/images/broken-line.svg | 3 - app/images/connect-white.svg | 3 - ...-check.svg => provider-approval-check.svg} | 0 app/manifest.json | 2 +- app/scripts/background.js | 30 +- app/scripts/contentscript.js | 141 +++-- app/scripts/controllers/app-state.js | 2 +- app/scripts/controllers/detect-tokens.js | 42 +- .../controllers/incoming-transactions.js | 4 +- .../controllers/network/middleware/pending.js | 16 +- app/scripts/controllers/network/network.js | 8 +- app/scripts/controllers/onboarding.js | 46 +- app/scripts/controllers/permissions/index.js | 377 ------------- .../permissions/loggerMiddleware.js | 169 ------ .../permissions/methodMiddleware.js | 90 ---- .../permissions/permissions-safe-methods.json | 49 -- .../permissions/restrictedMethods.js | 20 - app/scripts/controllers/preferences.js | 62 +-- app/scripts/controllers/provider-approval.js | 175 ++++++ app/scripts/controllers/recent-blocks.js | 8 +- app/scripts/controllers/threebox.js | 4 +- app/scripts/controllers/token-rates.js | 20 +- app/scripts/controllers/transactions/index.js | 110 ++-- .../lib/tx-state-history-helper.js | 8 +- .../controllers/transactions/lib/util.js | 12 +- .../transactions/pending-tx-tracker.js | 20 +- .../controllers/transactions/tx-gas-utils.js | 8 +- .../transactions/tx-state-manager.js | 31 +- app/scripts/createStandardProvider.js | 73 +++ app/scripts/edge-encryptor.js | 28 +- app/scripts/inpage.js | 116 +++- app/scripts/lib/account-tracker.js | 12 +- app/scripts/lib/auto-reload.js | 21 +- app/scripts/lib/buy-eth-url.js | 12 +- app/scripts/lib/cleanErrorStack.js | 4 +- app/scripts/lib/createDnodeRemoteGetter.js | 4 +- app/scripts/lib/createLoggerMiddleware.js | 4 +- app/scripts/lib/createOriginMiddleware.js | 1 - app/scripts/lib/ens-ipfs/resolver.js | 2 - app/scripts/lib/ens-ipfs/setup.js | 12 +- app/scripts/lib/local-store.js | 19 +- app/scripts/lib/message-manager.js | 22 +- app/scripts/lib/migrator/index.js | 8 +- app/scripts/lib/nodeify.js | 6 +- app/scripts/lib/notification-manager.js | 20 +- app/scripts/lib/pending-balance-calculator.js | 4 +- app/scripts/lib/personal-message-manager.js | 22 +- app/scripts/lib/select-chain-id.js | 4 +- app/scripts/lib/setupFetchDebugging.js | 4 +- app/scripts/lib/setupMetamaskMeshMetrics.js | 4 +- app/scripts/lib/setupSentry.js | 12 +- app/scripts/lib/stream-utils.js | 4 +- app/scripts/lib/typed-message-manager.js | 28 +- app/scripts/lib/util.js | 29 - app/scripts/metamask-controller.js | 340 ++++-------- app/scripts/migrations/004.js | 5 +- app/scripts/migrations/015.js | 7 +- app/scripts/migrations/016.js | 4 +- app/scripts/migrations/017.js | 4 +- app/scripts/migrations/019.js | 4 +- app/scripts/migrations/022.js | 4 +- app/scripts/migrations/023.js | 11 +- app/scripts/migrations/024.js | 4 +- app/scripts/migrations/025.js | 8 +- app/scripts/migrations/029.js | 1 + app/scripts/migrations/031.js | 2 +- app/scripts/migrations/040.js | 23 - app/scripts/platforms/extension.js | 54 +- development/announcer.js | 12 +- development/auto-changelog.sh | 2 +- development/mock-3box.js | 4 +- development/mock-dev.js | 62 +-- development/rollback.sh | 14 +- development/selector.js | 81 ++- development/show-deps-install-scripts.js | 20 +- development/sourcemap-validator.js | 12 +- development/static-server.js | 92 ---- development/verify-locale-strings.js | 4 +- development/version-bump.js | 2 - docs/porting_to_new_environment.md | 13 +- gulpfile.js | 4 +- package.json | 2 +- test/e2e/contract-test/contract.js | 419 ++++++--------- test/e2e/contract-test/index.html | 104 ++-- test/e2e/ethereum-on.spec.js | 28 +- test/e2e/helpers.js | 2 - test/e2e/metamask-responsive-ui.spec.js | 2 +- test/e2e/metamask-ui.spec.js | 24 +- test/e2e/permissions.spec.js | 201 ------- test/e2e/run-all.sh | 8 - test/e2e/run-web3.sh | 2 +- test/e2e/signature-request.spec.js | 29 +- test/e2e/web3.spec.js | 37 +- test/helper.js | 16 +- test/integration/index.js | 4 +- test/lib/mock-encryptor.js | 4 +- test/lib/mock-simple-keychain.js | 8 +- test/lib/react-trigger-change.js | 24 +- test/lib/util.js | 8 +- test/setup.js | 8 +- test/unit/actions/config_test.js | 16 +- .../unit/actions/set_selected_account_test.js | 18 +- test/unit/actions/tx_test.js | 16 +- test/unit/actions/view_info_test.js | 14 +- test/unit/actions/warning_test.js | 12 +- test/unit/app/buy-eth-url.spec.js | 4 +- .../app/controllers/detect-tokens-test.js | 10 +- .../controllers/metamask-controller-test.js | 24 +- .../network/pending-middleware-test.js | 8 +- .../preferences-controller-test.js | 47 +- .../app/controllers/provider-approval-test.js | 330 ++++++++++++ .../transactions/pending-tx-test.js | 36 +- .../transactions/tx-controller-test.js | 46 +- .../tx-state-history-helper-test.js | 4 +- .../controllers/transactions/tx-utils-test.js | 24 +- test/unit/app/edge-encryptor-test.js | 4 +- test/unit/app/message-manager-test.js | 16 +- test/unit/app/nodeify-test.js | 16 +- .../unit/app/personal-message-manager-test.js | 28 +- test/unit/app/typed-message-manager.spec.js | 12 +- test/unit/migrations/023-test.js | 4 +- test/unit/migrations/024-test.js | 7 +- test/unit/migrations/025-test.js | 8 +- test/unit/migrations/027-test.js | 4 +- test/unit/migrations/029-test.js | 4 +- test/unit/migrations/migrator-test.js | 4 +- test/unit/reducers/unlock_vault_test.js | 10 +- .../responsive/components/dropdown-test.js | 32 +- test/unit/test-utils.js | 4 +- test/unit/ui/app/actions.spec.js | 7 +- test/unit/util_test.js | 124 +++-- test/web3/schema.js | 4 +- test/web3/web3.js | 2 +- .../account-details.component.js | 18 +- .../account-details.container.js | 17 +- .../components/app/account-details/index.scss | 6 - .../account-menu/account-menu.component.js | 35 +- .../account-menu/account-menu.container.js | 9 +- ui/app/components/app/account-menu/index.scss | 4 - ui/app/components/app/account-panel.js | 64 ++- .../app/app-header/app-header.component.js | 3 +- ui/app/components/app/bn-as-decimal-input.js | 188 +++++++ ...onfirm-page-container-summary.component.js | 8 +- ...confirm-page-container-header.component.js | 54 +- ...irm-page-container-navigation.component.js | 24 +- .../confirm-page-container.component.js | 20 +- .../connected-sites-list.component.js | 157 ------ .../connected-sites-list.container.js | 57 -- .../app/connected-sites-list/index.js | 1 - .../app/connected-sites-list/index.scss | 125 ----- .../recipient-group.component.js | 8 +- ui/app/components/app/copyable.js | 53 ++ .../app/customize-gas-modal/gas-modal-card.js | 48 +- .../app/customize-gas-modal/gas-slider.js | 50 ++ .../app/customize-gas-modal/index.js | 136 +++-- .../app/dropdowns/account-details-dropdown.js | 178 +++--- .../app/dropdowns/components/dropdown.js | 70 +-- .../app/dropdowns/components/menu.js | 92 ++-- .../components/network-dropdown-icon.js | 85 ++- .../app/dropdowns/network-dropdown.js | 507 +++++++++--------- .../app/dropdowns/simple-dropdown.js | 98 ++-- .../app/dropdowns/tests/menu.test.js | 14 +- .../tests/network-dropdown-icon.test.js | 14 +- .../dropdowns/tests/network-dropdown.test.js | 2 +- .../app/dropdowns/token-menu-dropdown.js | 51 +- .../advanced-gas-inputs.component.js | 22 +- .../advanced-tab-content.component.js | 22 +- .../advanced-tab-content-component.test.js | 26 +- .../basic-tab-content.component.js | 12 +- .../tests/basic-tab-content-component.test.js | 8 +- .../gas-modal-page-container.component.js | 15 +- ...gas-modal-page-container-component.test.js | 84 ++- .../gas-price-button-group.component.js | 34 +- .../gas-price-button-group-component.test.js | 8 +- .../gas-price-chart/gas-price-chart.utils.js | 4 +- .../gas-slider/gas-slider.component.js | 2 +- ui/app/components/app/index.scss | 12 +- ui/app/components/app/input-number.js | 62 +-- .../loading-network-screen.component.js | 64 ++- ui/app/components/app/menu-droppo.js | 88 +-- .../components/app/modal/modal.component.js | 41 +- .../account-details-modal.component.js | 18 +- .../app/modals/account-modal-container.js | 80 +++ .../account-modal-container.component.js | 52 -- .../account-modal-container.container.js | 20 - .../modals/account-modal-container/index.js | 1 - .../clear-approved-origins.component.js | 39 ++ .../clear-approved-origins.container.js} | 12 +- .../modals/clear-approved-origins/index.js | 1 + .../confirm-remove-account.component.js | 4 +- .../app/modals/deposit-ether-modal.js | 213 ++++++++ .../deposit-ether-modal.component.js | 168 ------ .../deposit-ether-modal.container.js | 34 -- .../app/modals/deposit-ether-modal/index.js | 1 - .../disconnect-account.component.js | 52 -- .../disconnect-account.container.js | 44 -- .../app/modals/disconnect-account/index.js | 1 - .../app/modals/disconnect-account/index.scss | 25 - .../disconnect-all.component.js | 54 -- .../app/modals/disconnect-all/index.js | 1 - .../app/modals/disconnect-all/index.scss | 38 -- .../edit-approval-permission.component.js | 40 +- .../app/modals/export-private-key-modal.js | 178 ++++++ .../export-private-key-modal.component.js | 168 ------ .../export-private-key-modal.container.js | 38 -- .../modals/export-private-key-modal/index.js | 1 - .../modals/hide-token-confirmation-modal.js | 76 +-- ui/app/components/app/modals/index.scss | 6 - .../metametrics-opt-in-modal.component.js | 3 +- ui/app/components/app/modals/modal.js | 191 +++---- .../app/modals/new-account-modal/index.js | 1 - .../app/modals/new-account-modal/index.scss | 37 -- .../new-account-modal.component.js | 78 --- .../new-account-modal.container.js | 44 -- .../app/modals/notification-modal.js | 90 ++-- .../multiple-notifications.component.js | 10 +- .../network-display.component.js | 32 +- ui/app/components/app/network.js | 197 +++---- .../app/permission-page-container/index.js | 3 - .../app/permission-page-container/index.scss | 281 ---------- .../index.js | 1 - ...ission-page-container-content.component.js | 163 ------ .../permission-page-container-header/index.js | 1 - .../permission-page-container.component.js | 151 ------ .../permission-page-container.container.js | 28 - .../app/provider-page-container/index.js | 3 + .../app/provider-page-container/index.scss | 121 +++++ .../provider-page-container-content/index.js | 1 + ...ovider-page-container-content.component.js | 87 +++ ...ovider-page-container-content.container.js | 11 + .../provider-page-container-header/index.js | 1 + ...ovider-page-container-header.component.js} | 4 +- .../provider-page-container.component.js | 107 ++++ .../tests/selected-account-component.test.js | 10 +- ui/app/components/app/shift-list-item.js | 204 +++++++ .../components/app/shift-list-item/index.js | 1 - .../shift-list-item.component.js | 240 --------- .../shift-list-item.container.js | 12 - .../app/sidebars/sidebar.component.js | 16 +- .../sidebars/tests/sidebars-component.test.js | 14 +- .../app/signature-request-original.js | 357 ++++++++++++ .../app/signature-request-original/index.js | 1 - .../signature-request-original.component.js | 324 ----------- .../signature-request-original.container.js | 72 --- .../signature-request-header.component.js | 10 +- .../tests/signature-request.test.js | 15 +- ui/app/components/app/token-cell.js | 177 ++++++ ui/app/components/app/token-cell/index.js | 1 - .../app/token-cell/token-cell.component.js | 141 ----- .../app/token-cell/token-cell.container.js | 25 - ui/app/components/app/token-list.js | 117 ++-- .../transaction-action.component.test.js | 12 +- .../transaction-breakdown.component.js | 26 +- ...transaction-list-item-details.component.js | 21 +- .../app/transaction-list-item/index.scss | 1 - .../transaction-list-item.component.js | 18 +- .../token-view-balance.component.test.js | 38 +- ui/app/components/app/wallet-view.js | 178 ++++++ ui/app/components/app/wallet-view/index.js | 1 - .../app/wallet-view/wallet-view.component.js | 147 ----- .../app/wallet-view/wallet-view.container.js | 33 -- ui/app/components/ui/alert/index.js | 29 +- .../ui/button-group/button-group.stories.js | 8 +- .../tests/button-group-component.test.js | 16 +- ui/app/components/ui/button/button.stories.js | 28 +- ui/app/components/ui/copyButton.js | 110 ++-- .../tests/currency-display.component.test.js | 22 +- ui/app/components/ui/editable-label.js | 96 ++-- ui/app/components/ui/eth-balance.js | 102 ++++ .../ui/eth-balance/eth-balance.component.js | 137 ----- .../ui/eth-balance/eth-balance.container.js | 10 - ui/app/components/ui/eth-balance/index.js | 1 - .../export-text-container.component.js | 49 +- ui/app/components/ui/fiat-value.js | 68 ++- .../tests/hex-to-decimal.component.test.js | 20 +- .../icon-with-fallback.component.js | 42 -- .../components/ui/icon-with-fallback/index.js | 1 - .../ui/icon-with-fallback/index.scss | 30 -- .../tests/identicon.component.test.js | 2 +- .../loading-screen.component.js | 34 +- ui/app/components/ui/mascot.js | 23 +- .../page-container-footer.component.js | 22 +- .../page-container-footer.component.test.js | 18 +- .../page-container-header.component.js | 26 +- .../page-container-header.component.test.js | 20 +- ui/app/components/ui/qr-code.js | 95 ++-- ui/app/components/ui/readonly-input.js | 27 +- .../sender-to-recipient.component.js | 7 +- ui/app/components/ui/snackbar/index.js | 1 - ui/app/components/ui/snackbar/index.scss | 11 - .../ui/snackbar/snackbar.component.js | 18 - .../ui/text-field/text-field.stories.js | 28 +- ui/app/components/ui/tooltip.js | 36 +- .../ui/unit-input/unit-input.component.js | 4 +- ui/app/css/itcss/components/confirm.scss | 4 + ui/app/css/itcss/components/index.scss | 1 - ui/app/css/itcss/components/network.scss | 1 + .../css/itcss/components/newui-sections.scss | 4 + ui/app/css/itcss/components/pages/index.scss | 2 +- ...n-approval.scss => provider-approval.scss} | 4 +- ui/app/css/itcss/components/sections.scss | 1 + ui/app/css/itcss/components/send.scss | 63 +++ .../itcss/components/transaction-list.scss | 8 + ui/app/css/itcss/settings/variables.scss | 29 - ui/app/ducks/app/app.js | 22 +- ui/app/ducks/index.js | 4 +- ui/app/ducks/metamask/metamask.js | 4 +- ui/app/helpers/constants/routes.js | 4 - ui/app/helpers/utils/util.js | 127 ++--- ui/app/pages/add-token/add-token.component.js | 6 +- .../token-list/token-list.component.js | 3 +- .../confirm-approve-content.component.js | 57 +- .../confirm-approve.component.js | 40 +- .../confirm-approve.container.js | 4 +- .../confirm-transaction-base.component.js | 68 ++- .../confirm-transaction-base.container.js | 8 +- ui/app/pages/confirm-transaction/conf-tx.js | 59 +- .../confirm-transaction.component.js | 4 +- .../connected-sites.component.js | 36 -- ui/app/pages/connected-sites/index.js | 1 - ui/app/pages/connected-sites/index.scss | 37 -- .../connect-hardware/account-list.js | 223 ++++---- .../connect-hardware/connect-screen.js | 372 ++++++------- .../create-account/connect-hardware/index.js | 77 ++- .../create-account.component.js | 18 +- .../create-account/import-account/index.js | 69 +-- .../create-account/import-account/json.js | 109 ++-- .../import-account/private-key.js | 77 ++- .../create-account/import-account/seed.js | 35 ++ .../create-account/new-account.component.js | 11 +- .../import-with-seed-phrase.component.js | 21 +- .../new-account/new-account.component.js | 28 +- .../end-of-flow/end-of-flow.component.js | 45 +- .../end-of-flow/end-of-flow.container.js | 7 +- .../first-time-flow/end-of-flow/index.scss | 2 +- .../first-time-flow.selectors.js | 28 +- .../metametrics-opt-in.component.js | 3 +- .../onboarding-initiator-util.js | 48 -- .../draggable-seed.component.js | 3 +- .../seed-phrase/reveal-seed-phrase/index.scss | 2 +- .../reveal-seed-phrase.component.js | 47 +- .../reveal-seed-phrase.container.js | 9 +- ui/app/pages/home/home.component.js | 99 ++-- ui/app/pages/home/home.container.js | 12 +- ui/app/pages/index.scss | 4 - ui/app/pages/keychains/reveal-seed.js | 171 +++--- ui/app/pages/mobile-sync/index.js | 416 +++++++++++++- .../mobile-sync/mobile-sync.component.js | 436 --------------- .../mobile-sync/mobile-sync.container.js | 25 - .../choose-account.component.js | 108 ---- .../choose-account/index.js | 1 - .../choose-account/index.scss | 97 ---- ui/app/pages/permissions-connect/index.js | 1 - ui/app/pages/permissions-connect/index.scss | 11 - .../permissions-connect-footer/index.js | 1 - .../permissions-connect-footer/index.scss | 27 - .../permissions-connect-footer.component.js | 27 - .../permissions-connect-header/index.js | 1 - .../permissions-connect-header/index.scss | 15 - .../permissions-connect-header.component.js | 25 - .../permissions-connect.component.js | 208 ------- .../permissions-connect.container.js | 66 --- ui/app/pages/provider-approval/index.js | 1 + .../provider-approval.component.js | 36 ++ .../provider-approval.container.js | 12 + ui/app/pages/routes/index.js | 68 +-- .../account-list-item.component.js | 72 ++- .../tests/account-list-item-component.test.js | 24 +- .../add-recipient/add-recipient.component.js | 3 +- .../add-recipient/ens-input.component.js | 17 +- .../tests/add-recipient-component.test.js | 36 +- .../tests/amount-max-button-component.test.js | 20 +- .../tests/send-amount-row-component.test.js | 36 +- .../send-asset-row.component.js | 3 +- .../send-dropdown-list.component.js | 40 +- .../send-dropdown-list-component.test.js | 24 +- .../gas-fee-display.component.js | 16 +- .../test/gas-fee-display.component.test.js | 18 +- .../send-gas-row/send-gas-row.component.js | 117 ++-- .../tests/send-gas-row-component.test.js | 30 +- .../send-row-error-message-component.test.js | 10 +- .../tests/send-row-wrapper-component.test.js | 52 +- .../tests/send-footer-component.test.js | 90 ++-- .../tests/send-header-component.test.js | 12 +- ui/app/pages/send/send.component.js | 5 +- ui/app/pages/send/send.utils.js | 12 +- .../pages/send/tests/send-component.test.js | 92 ++-- ui/app/pages/send/tests/send-utils.test.js | 8 +- .../pages/send/to-autocomplete.component.js | 141 +++++ ui/app/pages/send/to-autocomplete/index.js | 1 + .../send/to-autocomplete/to-autocomplete.js | 121 +++++ .../connected-site-row.component.js | 31 ++ .../connected-site-row/index.js | 1 + .../connected-site-row/index.scss | 14 + .../connections-tab.component.js | 133 +++++ .../connections-tab.container.js | 39 ++ .../pages/settings/connections-tab/index.js | 1 + .../pages/settings/connections-tab/index.scss | 1 + .../add-contact/add-contact.component.js | 18 +- .../contact-list-tab.component.js | 49 +- .../edit-contact/edit-contact.component.js | 2 +- ui/app/pages/settings/index.scss | 13 + .../networks-tab/networks-tab.component.js | 34 +- ui/app/pages/settings/settings.component.js | 28 +- ui/app/selectors/selectors.js | 189 ------- ui/app/store/actions.js | 153 ++---- ui/index.js | 22 +- ui/lib/icon-factory.js | 16 +- ui/lib/persistent-form.js | 4 +- yarn.lock | 7 +- 443 files changed, 8752 insertions(+), 11247 deletions(-) delete mode 100644 app/images/broken-line.svg delete mode 100644 app/images/connect-white.svg rename app/images/{permissions-check.svg => provider-approval-check.svg} (100%) delete mode 100644 app/scripts/controllers/permissions/index.js delete mode 100644 app/scripts/controllers/permissions/loggerMiddleware.js delete mode 100644 app/scripts/controllers/permissions/methodMiddleware.js delete mode 100644 app/scripts/controllers/permissions/permissions-safe-methods.json delete mode 100644 app/scripts/controllers/permissions/restrictedMethods.js create mode 100644 app/scripts/controllers/provider-approval.js create mode 100644 app/scripts/createStandardProvider.js delete mode 100644 app/scripts/migrations/040.js delete mode 100644 development/static-server.js delete mode 100644 test/e2e/permissions.spec.js create mode 100644 test/unit/app/controllers/provider-approval-test.js create mode 100644 ui/app/components/app/bn-as-decimal-input.js delete mode 100644 ui/app/components/app/connected-sites-list/connected-sites-list.component.js delete mode 100644 ui/app/components/app/connected-sites-list/connected-sites-list.container.js delete mode 100644 ui/app/components/app/connected-sites-list/index.js delete mode 100644 ui/app/components/app/connected-sites-list/index.scss create mode 100644 ui/app/components/app/copyable.js create mode 100644 ui/app/components/app/customize-gas-modal/gas-slider.js create mode 100644 ui/app/components/app/modals/account-modal-container.js delete mode 100644 ui/app/components/app/modals/account-modal-container/account-modal-container.component.js delete mode 100644 ui/app/components/app/modals/account-modal-container/account-modal-container.container.js delete mode 100644 ui/app/components/app/modals/account-modal-container/index.js create mode 100644 ui/app/components/app/modals/clear-approved-origins/clear-approved-origins.component.js rename ui/app/components/app/modals/{disconnect-all/disconnect-all.container.js => clear-approved-origins/clear-approved-origins.container.js} (53%) create mode 100644 ui/app/components/app/modals/clear-approved-origins/index.js create mode 100644 ui/app/components/app/modals/deposit-ether-modal.js delete mode 100644 ui/app/components/app/modals/deposit-ether-modal/deposit-ether-modal.component.js delete mode 100644 ui/app/components/app/modals/deposit-ether-modal/deposit-ether-modal.container.js delete mode 100644 ui/app/components/app/modals/deposit-ether-modal/index.js delete mode 100644 ui/app/components/app/modals/disconnect-account/disconnect-account.component.js delete mode 100644 ui/app/components/app/modals/disconnect-account/disconnect-account.container.js delete mode 100644 ui/app/components/app/modals/disconnect-account/index.js delete mode 100644 ui/app/components/app/modals/disconnect-account/index.scss delete mode 100644 ui/app/components/app/modals/disconnect-all/disconnect-all.component.js delete mode 100644 ui/app/components/app/modals/disconnect-all/index.js delete mode 100644 ui/app/components/app/modals/disconnect-all/index.scss create mode 100644 ui/app/components/app/modals/export-private-key-modal.js delete mode 100644 ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.component.js delete mode 100644 ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.container.js delete mode 100644 ui/app/components/app/modals/export-private-key-modal/index.js delete mode 100644 ui/app/components/app/modals/new-account-modal/index.js delete mode 100644 ui/app/components/app/modals/new-account-modal/index.scss delete mode 100644 ui/app/components/app/modals/new-account-modal/new-account-modal.component.js delete mode 100644 ui/app/components/app/modals/new-account-modal/new-account-modal.container.js delete mode 100644 ui/app/components/app/permission-page-container/index.js delete mode 100644 ui/app/components/app/permission-page-container/index.scss delete mode 100644 ui/app/components/app/permission-page-container/permission-page-container-content/index.js delete mode 100644 ui/app/components/app/permission-page-container/permission-page-container-content/permission-page-container-content.component.js delete mode 100644 ui/app/components/app/permission-page-container/permission-page-container-header/index.js delete mode 100644 ui/app/components/app/permission-page-container/permission-page-container.component.js delete mode 100644 ui/app/components/app/permission-page-container/permission-page-container.container.js create mode 100644 ui/app/components/app/provider-page-container/index.js create mode 100644 ui/app/components/app/provider-page-container/index.scss create mode 100644 ui/app/components/app/provider-page-container/provider-page-container-content/index.js create mode 100644 ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.component.js create mode 100644 ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.container.js create mode 100644 ui/app/components/app/provider-page-container/provider-page-container-header/index.js rename ui/app/components/app/{permission-page-container/permission-page-container-header/permission-page-container-header.component.js => provider-page-container/provider-page-container-header/provider-page-container-header.component.js} (58%) create mode 100644 ui/app/components/app/provider-page-container/provider-page-container.component.js create mode 100644 ui/app/components/app/shift-list-item.js delete mode 100644 ui/app/components/app/shift-list-item/index.js delete mode 100644 ui/app/components/app/shift-list-item/shift-list-item.component.js delete mode 100644 ui/app/components/app/shift-list-item/shift-list-item.container.js create mode 100644 ui/app/components/app/signature-request-original.js delete mode 100644 ui/app/components/app/signature-request-original/index.js delete mode 100644 ui/app/components/app/signature-request-original/signature-request-original.component.js delete mode 100644 ui/app/components/app/signature-request-original/signature-request-original.container.js create mode 100644 ui/app/components/app/token-cell.js delete mode 100644 ui/app/components/app/token-cell/index.js delete mode 100644 ui/app/components/app/token-cell/token-cell.component.js delete mode 100644 ui/app/components/app/token-cell/token-cell.container.js create mode 100644 ui/app/components/app/wallet-view.js delete mode 100644 ui/app/components/app/wallet-view/index.js delete mode 100644 ui/app/components/app/wallet-view/wallet-view.component.js delete mode 100644 ui/app/components/app/wallet-view/wallet-view.container.js create mode 100644 ui/app/components/ui/eth-balance.js delete mode 100644 ui/app/components/ui/eth-balance/eth-balance.component.js delete mode 100644 ui/app/components/ui/eth-balance/eth-balance.container.js delete mode 100644 ui/app/components/ui/eth-balance/index.js delete mode 100644 ui/app/components/ui/icon-with-fallback/icon-with-fallback.component.js delete mode 100644 ui/app/components/ui/icon-with-fallback/index.js delete mode 100644 ui/app/components/ui/icon-with-fallback/index.scss delete mode 100644 ui/app/components/ui/snackbar/index.js delete mode 100644 ui/app/components/ui/snackbar/index.scss delete mode 100644 ui/app/components/ui/snackbar/snackbar.component.js rename ui/app/css/itcss/components/pages/{permission-approval.scss => provider-approval.scss} (66%) delete mode 100644 ui/app/pages/connected-sites/connected-sites.component.js delete mode 100644 ui/app/pages/connected-sites/index.js delete mode 100644 ui/app/pages/connected-sites/index.scss create mode 100644 ui/app/pages/create-account/import-account/seed.js delete mode 100644 ui/app/pages/first-time-flow/onboarding-initiator-util.js delete mode 100644 ui/app/pages/mobile-sync/mobile-sync.component.js delete mode 100644 ui/app/pages/mobile-sync/mobile-sync.container.js delete mode 100644 ui/app/pages/permissions-connect/choose-account/choose-account.component.js delete mode 100644 ui/app/pages/permissions-connect/choose-account/index.js delete mode 100644 ui/app/pages/permissions-connect/choose-account/index.scss delete mode 100644 ui/app/pages/permissions-connect/index.js delete mode 100644 ui/app/pages/permissions-connect/index.scss delete mode 100644 ui/app/pages/permissions-connect/permissions-connect-footer/index.js delete mode 100644 ui/app/pages/permissions-connect/permissions-connect-footer/index.scss delete mode 100644 ui/app/pages/permissions-connect/permissions-connect-footer/permissions-connect-footer.component.js delete mode 100644 ui/app/pages/permissions-connect/permissions-connect-header/index.js delete mode 100644 ui/app/pages/permissions-connect/permissions-connect-header/index.scss delete mode 100644 ui/app/pages/permissions-connect/permissions-connect-header/permissions-connect-header.component.js delete mode 100644 ui/app/pages/permissions-connect/permissions-connect.component.js delete mode 100644 ui/app/pages/permissions-connect/permissions-connect.container.js create mode 100644 ui/app/pages/provider-approval/index.js create mode 100644 ui/app/pages/provider-approval/provider-approval.component.js create mode 100644 ui/app/pages/provider-approval/provider-approval.container.js create mode 100644 ui/app/pages/send/to-autocomplete.component.js create mode 100644 ui/app/pages/send/to-autocomplete/index.js create mode 100644 ui/app/pages/send/to-autocomplete/to-autocomplete.js create mode 100644 ui/app/pages/settings/connections-tab/connected-site-row/connected-site-row.component.js create mode 100644 ui/app/pages/settings/connections-tab/connected-site-row/index.js create mode 100644 ui/app/pages/settings/connections-tab/connected-site-row/index.scss create mode 100644 ui/app/pages/settings/connections-tab/connections-tab.component.js create mode 100644 ui/app/pages/settings/connections-tab/connections-tab.container.js create mode 100644 ui/app/pages/settings/connections-tab/index.js create mode 100644 ui/app/pages/settings/connections-tab/index.scss diff --git a/.circleci/config.yml b/.circleci/config.yml index 5270da98f1..af3b70ebbe 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,7 +22,6 @@ workflows: - test-lint: requires: - prep-deps - - test-lint-shellcheck - test-e2e-chrome: requires: - prep-deps @@ -50,7 +49,6 @@ workflows: - all-tests-pass: requires: - test-lint - - test-lint-shellcheck - test-unit - test-unit-global - test-mozilla-lint @@ -175,16 +173,6 @@ jobs: name: Verify locales command: yarn verify-locales --quiet - test-lint-shellcheck: - docker: - - image: circleci/node:10.17-browsers - steps: - - checkout - - run: sudo apt-get install shellcheck - - run: - name: Shellcheck Lint - command: yarn lint:shellcheck - test-deps: docker: - image: circleci/node:10.17-browsers diff --git a/.eslintrc b/.eslintrc index b5f1671544..5ab07152eb 100644 --- a/.eslintrc +++ b/.eslintrc @@ -51,13 +51,13 @@ "accessor-pairs": 2, "arrow-spacing": [2, { "before": true, "after": true }], "block-spacing": [2, "always"], - "brace-style": 2, + "brace-style": [2, "1tbs", { "allowSingleLine": true }], "camelcase": [2, { "properties": "never" }], "comma-dangle": [2, "always-multiline"], "comma-spacing": [2, { "before": false, "after": true }], "comma-style": [2, "last"], "constructor-super": 2, - "curly": 2, + "curly": [2, "multi-line"], "dot-location": [2, "property"], "eol-last": 2, "eqeqeq": [2, "allow-null"], @@ -144,14 +144,12 @@ "no-useless-computed-key": 2, "no-useless-constructor": 2, "no-useless-escape": 2, - "no-var": 2, "no-whitespace-before-property": 2, "no-with": 2, "one-var": [2, { "initialized": "never" }], "operator-linebreak": [2, "after", { "overrides": { "?": "ignore", ":": "ignore" } }], "padded-blocks": "off", "quotes": [2, "single", {"avoidEscape": true, "allowTemplateLiterals": true}], - "react/no-unused-state": 2, "react/jsx-boolean-value": 2, "react/jsx-curly-brace-presence": [2, { "props": "never", "children": "never" }], "react/jsx-equals-spacing": 2, diff --git a/.gitattributes b/.gitattributes index 561741e377..590ac71c00 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,3 @@ -* text=auto CHANGELOG.md merge=union # Reviewing the lockfile contents is an important step in verifying that diff --git a/CHANGELOG.md b/CHANGELOG.md index 65b25c13d7..02d6a0b647 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,19 +2,6 @@ ## Current Develop Branch -## 7.7.0 Thu Nov 28 2019 -- [#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/README.md b/README.md index c2f657f703..a79a5b66fa 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,6 @@ To write tests that will be run in the browser using QUnit, add your test files - [How to add new networks to the Provider Menu](./docs/adding-new-networks.md) - [How to port MetaMask to a new platform](./docs/porting_to_new_environment.md) - [How to use the TREZOR emulator](./docs/trezor-emulator.md) -- [How to generate a visualization of this repository's development](./development/gource-viz.sh) +- [How to generate a visualization of this repository's development](./docs/development-visualization.md) [1]: http://www.nomnoml.com/#view/%5B%3Cactor%3Euser%5D%0A%0A%5Bmetamask-ui%7C%0A%20%20%20%5Btools%7C%0A%20%20%20%20%20react%0A%20%20%20%20%20redux%0A%20%20%20%20%20thunk%0A%20%20%20%20%20ethUtils%0A%20%20%20%20%20jazzicon%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20account-detail%0A%20%20%20%20%20accounts%0A%20%20%20%20%20locked-screen%0A%20%20%20%20%20restore-vault%0A%20%20%20%20%20identicon%0A%20%20%20%20%20config%0A%20%20%20%20%20info%0A%20%20%20%5D%0A%20%20%20%5Breducers%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20metamask%0A%20%20%20%20%20identities%0A%20%20%20%5D%0A%20%20%20%5Bactions%7C%0A%20%20%20%20%20%5BbackgroundConnection%5D%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%5D%3A-%3E%5Bactions%5D%0A%20%20%20%5Bactions%5D%3A-%3E%5Breducers%5D%0A%20%20%20%5Breducers%5D%3A-%3E%5Bcomponents%5D%0A%5D%0A%0A%5Bweb%20dapp%7C%0A%20%20%5Bui%20code%5D%0A%20%20%5Bweb3%5D%0A%20%20%5Bmetamask-inpage%5D%0A%20%20%0A%20%20%5B%3Cactor%3Eui%20developer%5D%0A%20%20%5Bui%20developer%5D-%3E%5Bui%20code%5D%0A%20%20%5Bui%20code%5D%3C-%3E%5Bweb3%5D%0A%20%20%5Bweb3%5D%3C-%3E%5Bmetamask-inpage%5D%0A%5D%0A%0A%5Bmetamask-background%7C%0A%20%20%5Bprovider-engine%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bid%20store%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%3E%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%3C-%3E%5Bid%20store%5D%0A%20%20%5Bconfig%20manager%7C%0A%20%20%20%20%5Brpc%20configuration%5D%0A%20%20%20%20%5Bencrypted%20keys%5D%0A%20%20%20%20%5Bwallet%20nicknames%5D%0A%20%20%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%5Bconfig%20manager%5D%0A%20%20%5Bid%20store%5D%3C-%3E%5Bconfig%20manager%5D%0A%5D%0A%0A%5Buser%5D%3C-%3E%5Bmetamask-ui%5D%0A%0A%5Buser%5D%3C%3A--%3A%3E%5Bweb%20dapp%5D%0A%0A%5Bmetamask-contentscript%7C%0A%20%20%5Bplugin%20restart%20detector%5D%0A%20%20%5Brpc%20passthrough%5D%0A%5D%0A%0A%5Brpc%20%7C%0A%20%20%5Bethereum%20blockchain%20%7C%0A%20%20%20%20%5Bcontracts%5D%0A%20%20%20%20%5Baccounts%5D%0A%20%20%5D%0A%5D%0A%0A%5Bweb%20dapp%5D%3C%3A--%3A%3E%5Bmetamask-contentscript%5D%0A%5Bmetamask-contentscript%5D%3C-%3E%5Bmetamask-background%5D%0A%5Bmetamask-background%5D%3C-%3E%5Bmetamask-ui%5D%0A%5Bmetamask-background%5D%3C-%3E%5Brpc%5D%0A diff --git a/app/_locales/cs/messages.json b/app/_locales/cs/messages.json index 9d04ae048c..cd768d4d87 100644 --- a/app/_locales/cs/messages.json +++ b/app/_locales/cs/messages.json @@ -1,7 +1,16 @@ { + "confirmClear": { + "message": "Naozaj chcete vymazať schválené webové stránky?" + }, + "clearApprovalData": { + "message": "Jasné údaje o schválení" + }, "reject": { "message": "Odmítnout" }, + "providerRequestInfo": { + "message": "Níže uvedená doména se pokouší požádat o přístup k API Ethereum, aby mohla komunikovat s blokádou Ethereum. Před schválením přístupu Ethereum vždy zkontrolujte, zda jste na správném místě." + }, "account": { "message": "Účet" }, @@ -49,6 +58,10 @@ "balanceIsInsufficientGas": { "message": "Nedostatek prostředků pro aktuální množství paliva" }, + "betweenMinAndMax": { + "message": "musí být větší nebo roven $1 a menší nebo roven $2.", + "description": "helper for inputting hex as decimal input" + }, "blockiesIdenticon": { "message": "Použít Blockies Identicon" }, @@ -100,6 +113,9 @@ "copiedExclamation": { "message": "Zkopírováno!" }, + "copy": { + "message": "Kopírovat" + }, "copyToClipboard": { "message": "Kopírovat do schránky" }, @@ -211,6 +227,10 @@ "message": "Získejte Ether z faucetu za $1.", "description": "Displays network name for Ether faucet" }, + "greaterThanMin": { + "message": "musí být větší nebo roven $1.", + "description": "helper for inputting hex as decimal input" + }, "here": { "message": "zde", "description": "as in -click here- for more information (goes with troubleTokenBalances)" @@ -265,6 +285,10 @@ "learnMore": { "message": "Zjistěte více." }, + "lessThanMax": { + "message": "musí být menší nebo roven $1.", + "description": "helper for inputting hex as decimal input" + }, "likeToAddTokens": { "message": "Chcete přidat tyto tokeny?" }, @@ -343,6 +367,9 @@ "message": "Vložte zde svůj privátní klíč:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Svou klíčovou frázi vložte zde!" + }, "personalAddressDetected": { "message": "Detekována osobní adresa. Zadejte adresu kontraktu tokenu." }, @@ -449,6 +476,9 @@ "stateLogError": { "message": "Chyba během získávání stavových protokolů." }, + "submit": { + "message": "Odeslat" + }, "submitted": { "message": "Odesláno" }, @@ -505,6 +535,9 @@ "usedByClients": { "message": "Používána různými klienty" }, + "validFileImport": { + "message": "Musíte vybrat validní soubor k importu." + }, "viewAccount": { "message": "Zobrazit účet" }, diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index 8d11cd9b98..f43c3626cb 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -50,9 +50,12 @@ "reject": { "message": "Ablehnen" }, - "likeToConnect": { + "providerRequest": { "message": "$1 möchte sich mit deinem Account verbinden" }, + "providerRequestInfo": { + "message": "Diese Website fordert Zugriff auf Ihre aktuelle Kontoadresse. Stellen Sie immer sicher, dass Sie den Websites vertrauen, mit denen Sie interagieren." + }, "about": { "message": "Über" }, @@ -217,6 +220,10 @@ "basic": { "message": "Grundlegend" }, + "betweenMinAndMax": { + "message": "Muss größer oder gleich $1 und kleiner oder gleich $2 sein.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "Block-Explorer" }, @@ -386,6 +393,9 @@ "copiedExclamation": { "message": "Kopiert!" }, + "copy": { + "message": "Kopieren" + }, "copyAddress": { "message": "Adresse in die Zwischenablage kopieren" }, @@ -476,6 +486,9 @@ "directDepositEtherExplainer": { "message": "Wenn du bereits Ether besitzt, ist die sofortige Einzahlung die schnellste Methode Ether in deine neue Wallet zu bekommen." }, + "dismiss": { + "message": "Schließen" + }, "done": { "message": "Fertig" }, @@ -675,6 +688,10 @@ "getStarted": { "message": "Erste Schritte" }, + "greaterThanMin": { + "message": "Muss größer oder gleich $1 sein.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Wir freuen uns, Sie zu sehen." }, @@ -1063,6 +1080,9 @@ "message": "Füge deine Private Key Zeichenfolge hier ein:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Füge deine Seed-Wörterfolge hier ein!" + }, "pending": { "message": "ausstehend" }, @@ -1622,6 +1642,9 @@ "userName": { "message": "Nutzername" }, + "validFileImport": { + "message": "Du musst eine gültige Datei für den Import auswählen." + }, "viewAccount": { "message": " Account einsehen" }, diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index d85b0a80ef..e9e42f5600 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -14,39 +14,53 @@ "showIncomingTransactionsDescription": { "message": "Select this to use Etherscan to show incoming transactions in the transactions list" }, - "cancelledConnectionWithMetaMask": { - "message": "Cancelled Connection With MetaMask" - }, "chartOnlyAvailableEth": { "message": "Chart only available on Ethereum networks." }, - "connectedSites": { - "message": "Connected Sites" + "confirmClear": { + "message": "Are you sure you want to clear approved websites?" + }, + "connections": { + "message": "Connections" + }, + "connectionsSettingsDescription": { + "message": "Sites allowed to read your accounts" + }, + "addSite": { + "message": "Add Site" }, - "connectingWithMetaMask": { - "message": "Connecting With MetaMask..." + "addSiteDescription": { + "message": "Manually add a site to allow it access to your accounts, useful for older dapps" }, - "connectTo": { - "message": "Connect to $1", - "description": "$1 is the name/origin of a site/dapp that the user can connect to metamask" + "connected": { + "message": "Connected" }, - "chooseAnAcount": { - "message": "Choose an account" + "connectedDescription": { + "message": "The list of sites allowed access to your addresses" + }, + "privacyModeDefault": { + "message": "Privacy Mode is now enabled by default" }, "contractInteraction": { "message": "Contract Interaction" }, + "clearApprovalData": { + "message": "Remove all sites" + }, "reject": { "message": "Reject" }, - "redirectingBackToDapp": { - "message": "Redirecting back to dapp..." + "providerRequest": { + "message": "$1 would like to connect to your account" + }, + "providerRequestInfo": { + "message": "This site is requesting access to view your current account address. Always make sure you trust the sites you interact with." }, "about": { "message": "About" }, "aboutSettingsDescription": { - "message": "Version, support center, and contact info" + "message": "Version, support center, and contact info." }, "acceleratingATransaction": { "message": "* Accelerating a transaction by using a higher gas price increases its chances of getting processed by the network faster, but it is not always guaranteed." @@ -86,7 +100,7 @@ "message": "Advanced" }, "advancedSettingsDescription": { - "message": "Access developer features, download State Logs, Reset Account, setup testnets and custom RPC" + "message": "Access developer features, download State Logs, Reset Account, setup testnets and custom RPC." }, "advancedOptions": { "message": "Advanced Options" @@ -191,6 +205,10 @@ "basic": { "message": "Basic" }, + "betweenMinAndMax": { + "message": "must be greater than or equal to $1 and less than or equal to $2.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "Block Explorer" }, @@ -291,6 +309,9 @@ "connect": { "message": "Connect" }, + "connectRequest": { + "message": "Connect Request" + }, "connectingTo": { "message": "Connecting to $1" }, @@ -336,6 +357,9 @@ "copiedExclamation": { "message": "Copied!" }, + "copy": { + "message": "Copy" + }, "copyAddress": { "message": "Copy address to clipboard" }, @@ -414,36 +438,14 @@ "details": { "message": "Details" }, - "disconnectAccount": { - "message": "Disconnect account" - }, - "disconnectAll": { - "message": "Disconnect All" - }, - "disconnectAllModalDescription": { - "message": "Are you sure? You will be disconnected from all sites on all accounts." - }, - "disconnectAccountModalDescription": { - "message": "Are you sure? Your account (\"$1\") will be disconnected from this site." - }, - "disconnectAccountQuestion": { - "message": "Disconnect account?" - }, - "disconnectFromThisAccount": { - "message": "Disconnect from this account?" - }, - "disconnectAllAccountsQuestion": { - "message": "Disconnect all accounts?" - }, "directDepositEther": { "message": "Directly Deposit Ether" }, "directDepositEtherExplainer": { "message": "If you already have some Ether, the quickest way to get Ether in your new wallet by direct deposit." }, - "domainLastConnect": { - "message": "Last Connected: $1", - "description": "$1 is the date at which the user was last connected to a given domain" + "dismiss": { + "message": "Dismiss" }, "done": { "message": "Done" @@ -505,17 +507,6 @@ "endOfFlowMessage10": { "message": "All Done" }, - "extensionId": { - "message": "Extension ID: $1", - "description": "$1 is a string of random letters that are the id of another extension connecting to MetaMask" - }, - "externalExtension": { - "message": "External Extension" - }, - "onboardingReturnNotice": { - "message": "\"$1\" will close this tab and direct back to $2", - "description": "Return the user to the site that initiated onboarding" - }, "ensRegistrationError": { "message": "Error in ENS name registration" }, @@ -633,6 +624,10 @@ "getStarted": { "message": "Get Started" }, + "greaterThanMin": { + "message": "must be greater than or equal to $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "We’re happy to see you." }, @@ -745,28 +740,22 @@ "max": { "message": "Max" }, - "lastConnected": { - "message": "Last Connected" - }, "learnMore": { "message": "Learn more" }, - "learnAboutRisks": { - "message": "Learn about the risks here." - }, "ledgerAccountRestriction": { "message": "You need to make use your last account before you can add a new one." }, + "lessThanMax": { + "message": "must be less than or equal to $1.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "Yes, let’s get set up!" }, "likeToAddTokens": { "message": "Would you like to add these tokens?" }, - "likeToConnect": { - "message": "$1 would like to connect to your MetaMask account", - "description": "$1 is the name/url of a site/dapp asking to connect to MetaMask" - }, "links": { "message": "Links" }, @@ -898,9 +887,6 @@ "rpcUrl": { "message": "New RPC URL" }, - "onlyConnectTrust": { - "message": "Only connect with sites you trust." - }, "optionalChainId": { "message": "ChainID (optional)" }, @@ -975,6 +961,9 @@ "message": "Paste your private key string here:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Paste your seed phrase here!" + }, "pending": { "message": "pending" }, @@ -1103,9 +1092,6 @@ "readyToConnect": { "message": "Ready to Connect?" }, - "revokeInPermissions": { - "message": "* You can view and revoke permissions in MetaMask settings." - }, "rinkeby": { "message": "Rinkeby Test Network" }, @@ -1362,14 +1348,6 @@ "testFaucet": { "message": "Test Faucet" }, - "thisWillAllow": { - "message": "This will allow $1 to:", - "description": "$1 is the name or domain of a site/dapp that is requesting permissions" - }, - "thisWillAllowExternalExtension": { - "message": "This will allow an external extension with id $1 to:", - "description": "$1 is a string of random letters that are the id of another extension connecting to MetaMask" - }, "thisWillCreate": { "message": "This will create a new wallet and seed phrase" }, @@ -1382,10 +1360,6 @@ "toWithColon": { "message": "To:" }, - "toConnectWith": { - "message": "To connect with $1", - "description": "$1 is the name or domain of a site/dapp that asking to connect with MetaMask" - }, "toETHviaShapeShift": { "message": "$1 to ETH via ShapeShift", "description": "system will fill in deposit type in start of message" @@ -1515,6 +1489,9 @@ "userName": { "message": "Username" }, + "validFileImport": { + "message": "You must select a valid file to import." + }, "viewAccount": { "message": "View Account" }, diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index f64105f263..e503cd977e 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -1,16 +1,28 @@ { + "privacyModeDefault": { + "message": "Modo Privado está activado ahora por defecto" + }, "chartOnlyAvailableEth": { "message": "Tabla solo disponible en redes Ethereum." }, + "confirmClear": { + "message": "¿Seguro que quieres borrar los sitios web aprobados?" + }, "contractInteraction": { "message": "Interacción con contrato" }, + "clearApprovalData": { + "message": "Borrar datos de aprobación" + }, "reject": { "message": "Rechazar" }, - "likeToConnect": { + "providerRequest": { "message": "$1 quisiera conectar con tu cuenta" }, + "providerRequestInfo": { + "message": "El dominio que se muestra a continuación intenta solicitar acceso a la API Ethereum para que pueda interactuar con la blockchain de Ethereum. Siempre verifique que esté en el sitio correcto antes de aprobar el acceso Ethereum." + }, "about": { "message": "Acerca" }, @@ -133,6 +145,10 @@ "basic": { "message": "Básico" }, + "betweenMinAndMax": { + "message": "Debe ser mayor o igual a $1 y menor o igual a $2", + "description": "helper for inputting hex as decimal input" + }, "blockiesIdenticon": { "message": "Usar Blockies Identicon (Iconos)" }, @@ -193,6 +209,9 @@ "connect": { "message": "Conectar" }, + "connectRequest": { + "message": "Petición para conectar" + }, "connectingTo": { "message": "Conectánodse a $1" }, @@ -232,6 +251,9 @@ "copiedExclamation": { "message": "¡Copiado!" }, + "copy": { + "message": "Copiar" + }, "copyAddress": { "message": "Copiar la dirección al portapapeles" }, @@ -310,6 +332,9 @@ "directDepositEtherExplainer": { "message": "Si posees Ether, la forma más rápida de transferirlo a tu nueva billetera es depositándolo directamente" }, + "dismiss": { + "message": "Descartar" + }, "done": { "message": "Completo" }, @@ -424,6 +449,10 @@ "getHelp": { "message": "Pedir ayuda." }, + "greaterThanMin": { + "message": "Debe ser mayor o igual a $1", + "description": "helper for inputting hex as decimal input" + }, "hardwareWalletConnected": { "message": "Se ha conectado el monedero físico" }, @@ -524,6 +553,10 @@ "ledgerAccountRestriction": { "message": "Hay que hacer uso de tu última cuenta antes de agregarle una nueva." }, + "lessThanMax": { + "message": "Debe ser menor o igual a $1", + "description": "helper for inputting hex as decimal input" + }, "likeToAddTokens": { "message": "¿Te gustaría agregar estos tokens?" }, @@ -668,6 +701,9 @@ "message": "Pega tu clave privada aqui", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "¡Pega tu frase semilla aquí!" + }, "pending": { "message": "pendiente" }, @@ -930,6 +966,9 @@ "step3HardwareWalletMsg": { "message": "Usa tu cuenta física igual que harías con cualquier cuenta de Ethereum. Regístrate con dApps, manda Eth, compra y almacena tokens de ERC20 y otros tokens no-fungibles, como CryptoKitties." }, + "submit": { + "message": "Enviar" + }, "submitted": { "message": "Enviado" }, @@ -1058,6 +1097,9 @@ "usedByClients": { "message": "Utilizado por una variedad de clientes diferentes" }, + "validFileImport": { + "message": "Debes selecionar un archivo valido para importar" + }, "viewAccount": { "message": "Mirar cuenta" }, diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index 94fe260fdd..e9fef5d768 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -32,6 +32,9 @@ "confirmClear": { "message": "Êtes-vous sûr de vouloir supprimer les sites Web approuvés?" }, + "confirmClear": { + "message": "Êtes-vous sûr de vouloir supprimer les sites Web approuvés?" + }, "contractInteraction": { "message": "Interaction avec un contrat" }, @@ -223,6 +226,10 @@ "basic": { "message": "Général" }, + "betweenMinAndMax": { + "message": "doit être supérieur ou égal à $1 et inférieur ou égal à $2", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerView": { "message": "Afficher le compte à $1", "description": "$1 replaced by URL for custom block explorer" @@ -389,6 +396,9 @@ "copiedExclamation": { "message": "Copié!" }, + "copy": { + "message": "Copier" + }, "copyAddress": { "message": "Copier l'addresse dans le presse-papier" }, @@ -477,6 +487,9 @@ "directDepositEtherExplainer": { "message": "Si vous avez déjà de l'Ether, le moyen le plus rapide d'obtenir des Ether dans votre nouveau portefeuille est par dépôt direct." }, + "dismiss": { + "message": "Ignorer" + }, "done": { "message": "Terminé" }, @@ -672,6 +685,10 @@ "getStarted": { "message": "Démarrer" }, + "greaterThanMin": { + "message": "doit être supérieur ou égal à $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Nous sommes ravis de vous voir.\n" }, @@ -1054,6 +1071,9 @@ "message": "Collez votre clé privée ici:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Collez votre seed phrase ici!" + }, "pending": { "message": "En attente" }, @@ -1392,6 +1412,9 @@ "submit": { "message": "Soumettre" }, + "submit": { + "message": "Soumettre" + }, "submitted": { "message": "Envoyé" }, @@ -1577,6 +1600,9 @@ "userName": { "message": "Nom d'utilisateur" }, + "validFileImport": { + "message": "Vous devez selectionner un fichier valide à importer." + }, "viewAccount": { "message": "Afficher le compte" }, diff --git a/app/_locales/gu/messages.json b/app/_locales/gu/messages.json index d04514fd90..bcd467be18 100644 --- a/app/_locales/gu/messages.json +++ b/app/_locales/gu/messages.json @@ -42,6 +42,9 @@ "connect": { "message": "કનેક્ટ કરો" }, + "copy": { + "message": "કૉપિ કરો" + }, "copyToClipboard": { "message": "ક્લિપબોર્ડ પર કૉપિ કરો" }, @@ -54,6 +57,9 @@ "details": { "message": "વિગતો" }, + "dismiss": { + "message": "કાઢી નાખો" + }, "done": { "message": "થઈ ગયું" }, @@ -125,6 +131,9 @@ "settings": { "message": "સેટિંગ્સ" }, + "submit": { + "message": "સબમિટ કરો" + }, "tryAgain": { "message": "ફરી પ્રયાસ કરો" }, diff --git a/app/_locales/hn/messages.json b/app/_locales/hn/messages.json index 8009077982..91d9bde7ed 100644 --- a/app/_locales/hn/messages.json +++ b/app/_locales/hn/messages.json @@ -1,10 +1,19 @@ { + "confirmClear": { + "message": "क्या आप वाकई अनुमोदित वेबसाइटों को साफ़ करना चाहते हैं?" + }, + "clearApprovalData": { + "message": "अनुमोदन डेटा साफ़ करें" + }, "approve": { "message": "मंजूर" }, "reject": { "message": "अस्वीकार" }, + "providerRequestInfo": { + "message": "नीचे सूचीबद्ध डोमेन वेब 3 एपीआई तक पहुंच का अनुरोध करने का प्रयास कर रहा है ताकि यह एथेरियम ब्लॉकचेन से बातचीत कर सके। वेब 3 एक्सेस को मंजूरी देने से पहले हमेशा सही जांच करें कि आप सही साइट पर हैं।" + }, "account": { "message": "खाता" }, @@ -46,6 +55,10 @@ "balanceIsInsufficientGas": { "message": "वर्तमान गैस कुल के लिए अपर्याप्त शेष" }, + "betweenMinAndMax": { + "message": "$1 के बराबर या ज्यदा या, $2 के बराबर या कम होना चाहिए।", + "description": "हेक्स इनपुट के लिए दशमलव इनपुट के रूप में सहायक" + }, "blockiesIdenticon": { "message": "ब्लॉकीज पहचान का उपयोग करें" }, @@ -82,6 +95,9 @@ "copiedExclamation": { "message": "कॉपी कर दिया गया!" }, + "copy": { + "message": "कॉपी / प्रतिलिपि कर्रे" + }, "copyToClipboard": { "message": "क्लिपबोर्ड पर कॉपी करें" }, @@ -187,6 +203,10 @@ "message": "$1 के लिए एक नल से ईथर प्राप्त करें", "description": "ईथर नल के लिए नेटवर्क नाम प्रदर्शित करता है" }, + "greaterThanMin": { + "message": "$1 के बराबर या बराबर होना चाहिए।", + "description": "हेक्स इनपुट के लिए दशमलव इनपुट के रूप में सहायक" + }, "here": { "message": "यहां", "description": "अधिक जानकारी के लिए यहां क्लिक करें- (परेशानी के साथ जाता है टोकनबैलेंस) (troubleTokenBalances)" @@ -245,6 +265,10 @@ "kovan": { "message": "कोवान टेस्ट नेटवर्क" }, + "lessThanMax": { + "message": "$1 से कम या बराबर होना चाहिए।", + "description": "हेक्स इनपुट के लिए दशमलव इनपुट के रूप में सहायक" + }, "likeToAddTokens": { "message": "क्या आप इन टोकनों को जोड़ना चाहते हैं?" }, @@ -320,6 +344,9 @@ "message": "यहां अपनी निजी कुंजी स्ट्रिंग चिपकाएं:", "description": "किसी निजी कुंजी से किसी खाते को आयात करने के लिए" }, + "pasteSeed": { + "message": "यहां अपने बीज वाक्यांश पेस्ट करें!" + }, "personalAddressDetected": { "message": "व्यक्तिगत पता मिला। टोकन अनुबंध का पता इनपुट।" }, @@ -420,6 +447,9 @@ "stateLogsDescription": { "message": "स्थिति संदेश में आपका सार्वजनिक खाता, पतों और भेजे गए लेनदेन, होते हैं।" }, + "submit": { + "message": "सबमिट करें" + }, "supportCenter": { "message": "हमारे सहायता केंद्र पर जाएं" }, @@ -467,6 +497,9 @@ "usedByClients": { "message": "विभिन्न क्लाइंट्स द्वारा उपयोग किया जाता है" }, + "validFileImport": { + "message": "आयात करने के लिए आपको एक वैध फ़ाइल चुननी होगी।" + }, "viewAccount": { "message": "खाता देखें" }, diff --git a/app/_locales/ht/messages.json b/app/_locales/ht/messages.json index dd63dce0c1..d973aeef54 100644 --- a/app/_locales/ht/messages.json +++ b/app/_locales/ht/messages.json @@ -1,4 +1,13 @@ { + "confirmClear": { + "message": "Èske ou sèten ou vle klè sitwèb apwouve?" + }, + "clearApprovalData": { + "message": "Klè Done sou vi prive" + }, + "providerRequestInfo": { + "message": "Domèn ki nan lis anba a ap mande pou jwenn aksè a blòkchou Ethereum ak pou wè kont ou ye kounye a. Toujou double tcheke ke ou sou sit ki kòrèk la anvan apwouve aksè." + }, "accessingYourCamera": { "message": "Aksè a Kamera" }, @@ -70,6 +79,10 @@ "balanceIsInsufficientGas": { "message": "Ensifizan balans pou total gaz aktyèl la" }, + "betweenMinAndMax": { + "message": "dwe plis pase oswa egal a $ 1 mwens ke oswa egal a $ 2.", + "description": "helper for inputting hex as decimal input" + }, "blockiesIdenticon": { "message": "Itilize Blockies Identicon" }, @@ -148,6 +161,9 @@ "copiedExclamation": { "message": "Kopye!" }, + "copy": { + "message": "Kopye" + }, "copyAddress": { "message": "Kopi adrès clipboard" }, @@ -289,6 +305,10 @@ "getHelp": { "message": "Jwenn èd." }, + "greaterThanMin": { + "message": "dwe pi gran pase oswa egal a $ 1.", + "description": "helper for inputting hex as decimal input" + }, "hardware": { "message": "materyèl" }, @@ -383,6 +403,10 @@ "ledgerAccountRestriction": { "message": "Ou bezwen sèvi ak dènye kont ou anvan ou ka ajoute yon nouvo." }, + "lessThanMax": { + "message": "dwe mwens pase oswa egal a $ 1.", + "description": "helper for inputting hex as decimal input" + }, "likeToAddTokens": { "message": "Èske ou ta renmen ajoute sa nan tokens?" }, @@ -500,6 +524,9 @@ "message": "Kole fraz prive ou a la:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Kole seed fraz ou a la!" + }, "pending": { "message": "l ap mache" }, @@ -729,6 +756,9 @@ "step3HardwareWalletMsg": { "message": "Sèvi ak kont materyèl ou menm jan ou t ap fè pou kont Etherum. Ouvri sesyon an nan dApps, voye Eth, achte ak stòke ERC20 tokens ak e ki pake chanje tokens tankou CryptoKitties." }, + "submit": { + "message": "Soumèt" + }, "submitted": { "message": "Te Soumèt" }, @@ -827,6 +857,9 @@ "usedByClients": { "message": "Itilize pa yon varyete de kliyan diferan" }, + "validFileImport": { + "message": "Ou dwe chwazi yon dosye ki valab pou enpòte." + }, "viewAccount": { "message": "Wè Kont" }, diff --git a/app/_locales/it/messages.json b/app/_locales/it/messages.json index 3581e48cbe..40d8fd488b 100644 --- a/app/_locales/it/messages.json +++ b/app/_locales/it/messages.json @@ -26,20 +26,29 @@ "chartOnlyAvailableEth": { "message": "Grafico disponibile solo per le reti Ethereum." }, + "confirmClear": { + "message": "Sei sicuro di voler cancellare i siti Web approvati?" + }, "contractInteraction": { "message": "Interazione Contratto" }, + "clearApprovalData": { + "message": "Cancella i dati di approvazione" + }, "reject": { "message": "Annulla" }, - "likeToConnect": { + "providerRequest": { "message": "$1 vorrebbe connettersi al tuo account" }, + "providerRequestInfo": { + "message": "Il dominio elencato di seguito sta tentando di richiedere l'accesso all'API Ethereum in modo che possa interagire con la blockchain di Ethereum. Controlla sempre di essere sul sito corretto prima di approvare l'accesso a Ethereum." + }, "about": { "message": "Informazioni" }, "aboutSettingsDescription": { - "message": "Version, centro di supporto e contatti" + "message": "Version, centro di supporto e contatti." }, "acceleratingATransaction": { "message": "* Accelerare una transazione usando un prezzo del gas maggiore aumenta la probabilità che la rete la elabori più velocemente, ma non è garantito." @@ -75,7 +84,7 @@ "message": "Avanzate" }, "advancedSettingsDescription": { - "message": "Accedi alle funzionalità sviluppatore, download dei log di Stato, Reset Account, imposta reti di test e RPC personalizzata" + "message": "Accedi alle funzionalità sviluppatore, download dei log di Stato, Reset Account, imposta reti di test e RPC personalizzata." }, "advancedOptions": { "message": "Opzioni Avanzate" @@ -169,6 +178,10 @@ "basic": { "message": "Base" }, + "betweenMinAndMax": { + "message": "deve essere maggiore o uguale a $1 e minore o uguale a $2.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerView": { "message": "Visualizza account su $1", "description": "$1 replaced by URL for custom block explorer" @@ -245,6 +258,9 @@ "connect": { "message": "Connetti" }, + "connectRequest": { + "message": "Richiesta Connessione" + }, "connectingTo": { "message": "Connessione in corso a $1" }, @@ -305,6 +321,9 @@ "copiedExclamation": { "message": "Copiato!" }, + "copy": { + "message": "Copia" + }, "copyAddress": { "message": "Copia l'indirizzo" }, @@ -386,6 +405,9 @@ "directDepositEtherExplainer": { "message": "Se possiedi già degli Ether, questa è la via più veloce per aggiungere Ether al tuo portafoglio con un deposito diretto." }, + "dismiss": { + "message": "Ignora" + }, "done": { "message": "Finito" }, @@ -560,6 +582,10 @@ "getStarted": { "message": "Inizia" }, + "greaterThanMin": { + "message": "deve essere maggiore o uguale a $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Siamo contenti di vederti." }, @@ -675,6 +701,10 @@ "ledgerAccountRestriction": { "message": "E' necessario utilizzare l'ultimo account prima di poterne aggiungere uno nuovo." }, + "lessThanMax": { + "message": "deve essere minore o uguale a $1.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "Si, iniziamo!" }, @@ -873,6 +903,9 @@ "message": "Incolla la tua chiave privata qui:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Incolla la tua frase seed qui!" + }, "pending": { "message": "in corso" }, @@ -1189,6 +1222,9 @@ "storePhrase": { "message": "Conserva questa frase in un gestore di password come 1Password." }, + "submit": { + "message": "Invia" + }, "submitted": { "message": "Inviata" }, @@ -1353,6 +1389,9 @@ "userName": { "message": "Nome utente" }, + "validFileImport": { + "message": "Devi selezionare un file valido da importare." + }, "viewAccount": { "message": "Vedi Account" }, @@ -1401,6 +1440,24 @@ "zeroGasPriceOnSpeedUpError": { "message": "Prezzo del gas maggiore di zero" }, + "connections": { + "message": "Connessioni" + }, + "connectionsSettingsDescription": { + "message": "Siti autorizzati ad accedere ai tuoi accounts" + }, + "addSite": { + "message": "Aggiungi Sito" + }, + "addSiteDescription": { + "message": "Aggiungi un sito autorizzato ad accedere ai tuoi accounts, utile per dapps obsolete" + }, + "connected": { + "message": "Connesso" + }, + "connectedDescription": { + "message": "La lista di siti web autorizzati ad accedere ai tuoi indirizzi" + }, "contacts": { "message": "Contatti" }, diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index 18dfb068ee..5b85291b9a 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -1,16 +1,28 @@ { + "privacyModeDefault": { + "message": "プライバシーモードがデフォルトで有効になりました" + }, "chartOnlyAvailableEth": { "message": "チャートはEthereumネットワークでのみ利用可能です。" }, + "confirmClear": { + "message": "承認されたウェブサイトをクリアしてもよろしいですか?" + }, "contractInteraction": { "message": "コントラクトへのアクセス" }, + "clearApprovalData": { + "message": "承認データのクリア" + }, "reject": { "message": "拒否" }, - "likeToConnect": { + "providerRequest": { "message": "$1 はあなたのアカウントにアクセスしようとしています。" }, + "providerRequestInfo": { + "message": "下記のドメインは、Ethereumブロックチェーンとやり取りできるようにEthereum APIへのアクセスをリクエストしようとしています。 Web3アクセスを承認する前に、正しいサイトにいることを常に確認してください。" + }, "aboutSettingsDescription": { "message": "バージョンやサポート、問合せ先など" }, @@ -72,7 +84,7 @@ "message": "推奨トークンを追加" }, "addAcquiredTokens": { - "message": "MetaMaskで獲得したトークンを追加する" + "message": "Metamaskで獲得したトークンを追加する" }, "amount": { "message": "金額" @@ -124,6 +136,10 @@ "balanceIsInsufficientGas": { "message": "現在のガス総量に対して残高が不足しています" }, + "betweenMinAndMax": { + "message": " $1以上 $2以下にして下さい。", + "description": "helper for inputting hex as decimal input" + }, "blockiesIdenticon": { "message": "Blockies Identicon を使用" }, @@ -157,6 +173,9 @@ "copiedExclamation": { "message": "コピー完了!" }, + "copy": { + "message": "コピー" + }, "copyToClipboard": { "message": "クリップボードへコピー" }, @@ -259,6 +278,10 @@ "message": "フォーセットで $1のEtherを得ることができます。", "description": "Displays network name for Ether faucet" }, + "greaterThanMin": { + "message": " $1以上にして下さい。", + "description": "helper for inputting hex as decimal input" + }, "here": { "message": "ここ", "description": "as in -click here- for more information (goes with troubleTokenBalances)" @@ -311,6 +334,10 @@ "learnMore": { "message": "詳細" }, + "lessThanMax": { + "message": " $1以下にして下さい。", + "description": "helper for inputting hex as decimal input" + }, "likeToAddTokens": { "message": "トークンを追加しますか?" }, @@ -389,6 +416,9 @@ "message": "秘密鍵をここにペーストして下さい:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "パスフレーズをここにペーストして下さい!" + }, "privacyMsg": { "message": "プライバシーポリシー" }, @@ -480,6 +510,9 @@ "sigRequest": { "message": "署名リクエスト" }, + "submit": { + "message": "送信" + }, "terms": { "message": "利用規約" }, diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index 96d527f23b..2afce74af6 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -50,9 +50,12 @@ "reject": { "message": "거부" }, - "likeToConnect": { + "providerRequest": { "message": "$1이 당신의 계정에 연결하길 원합니다." }, + "providerRequestInfo": { + "message": "아래 나열된 도메인은 Web3 API에 대한 액세스를 요청하여 Ethereum 블록 체인과 상호 작용할 수 있습니다. Ethereum 액세스를 승인하기 전에 항상 올바른 사이트에 있는지 다시 확인하십시오." + }, "about": { "message": "정보" }, @@ -211,6 +214,10 @@ "balanceIsInsufficientGas": { "message": "현재 가스 총합에 대해 잔액이 부족합니다" }, + "betweenMinAndMax": { + "message": "$1 이상 $2 이하여야 합니다.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "익스플로러 차단" }, @@ -311,6 +318,9 @@ "connect": { "message": "연결" }, + "connectRequest": { + "message": "연결 요청" + }, "connectingTo": { "message": "$1에 연결" }, @@ -371,6 +381,9 @@ "copiedExclamation": { "message": "복사됨!" }, + "copy": { + "message": "복사" + }, "copyAddress": { "message": "클립보드로 주소 복사" }, @@ -452,6 +465,9 @@ "directDepositEtherExplainer": { "message": "약간의 이더를 이미 보유하고 있다면, 새로 만든 지갑에 직접 입금하여 이더를 보유할 수 있습니다." }, + "dismiss": { + "message": "숨기기" + }, "done": { "message": "완료" }, @@ -635,6 +651,10 @@ "getStarted": { "message": "시작하기" }, + "greaterThanMin": { + "message": "$1 이상이어야 합니다.", + "description": "helper for inputting hex as decimal input" + }, "hardware": { "message": "하드웨어" }, @@ -1005,6 +1025,9 @@ "message": "개인키를 입력해주세요:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "시드 구문을 이곳에 붙여넣어 주세요!" + }, "pending": { "message": "펜딩 중" }, @@ -1334,6 +1357,9 @@ "storePhrase": { "message": "이 구문을 1Password같은 암호 관리자에 저장하세요." }, + "submit": { + "message": "제출" + }, "submitted": { "message": "제출됨" }, @@ -1504,6 +1530,9 @@ "userName": { "message": "사용자이름" }, + "validFileImport": { + "message": "가져오기 위해 유효한 파일을 선택해야 합니다." + }, "viewAccount": { "message": "계정 보기" }, diff --git a/app/_locales/ml/messages.json b/app/_locales/ml/messages.json index c898d1dc2c..cbcee41866 100644 --- a/app/_locales/ml/messages.json +++ b/app/_locales/ml/messages.json @@ -42,6 +42,9 @@ "connect": { "message": "കണക്‌റ്റുചെയ്യുക" }, + "copy": { + "message": "പകര്‍ത്തുക" + }, "copyToClipboard": { "message": "ക്ലിപ്പ്ബോർഡിലേക്ക് പകർത്തുക" }, @@ -54,6 +57,9 @@ "details": { "message": "വിശദാംശങ്ങൾ‌" }, + "dismiss": { + "message": "ബഹിഷ്‌ക്കരിക്കുക" + }, "done": { "message": "പൂർത്തിയാക്കി" }, @@ -125,6 +131,9 @@ "settings": { "message": "ക്രമീകരണങ്ങള്‍" }, + "submit": { + "message": "സമര്‍പ്പിക്കൂ" + }, "tryAgain": { "message": "വീണ്ടും ശ്രമിക്കുക" }, diff --git a/app/_locales/mr/messages.json b/app/_locales/mr/messages.json index ef341f40ce..af06c03fb9 100644 --- a/app/_locales/mr/messages.json +++ b/app/_locales/mr/messages.json @@ -42,6 +42,9 @@ "connect": { "message": "कनेक्‍ट करा" }, + "copy": { + "message": "कॉपी करा" + }, "copyToClipboard": { "message": "क्लिपबोर्डवर कॉपी करा" }, @@ -54,6 +57,9 @@ "details": { "message": "तपशील" }, + "dismiss": { + "message": "डिसमिस करा" + }, "done": { "message": "पूर्ण झाले" }, @@ -125,6 +131,9 @@ "settings": { "message": "सेटिंग्ज" }, + "submit": { + "message": "सबमिट करा" + }, "tryAgain": { "message": "पुन्हा प्रयत्न करा" }, diff --git a/app/_locales/nl/messages.json b/app/_locales/nl/messages.json index 7ba285208d..ba83e37a11 100644 --- a/app/_locales/nl/messages.json +++ b/app/_locales/nl/messages.json @@ -1,7 +1,16 @@ { + "confirmClear": { + "message": "Weet je zeker dat je goedgekeurde websites wilt wissen?" + }, + "clearApprovalData": { + "message": "Gegevens over goedkeuring wissen" + }, "reject": { "message": "Afwijzen" }, + "providerRequestInfo": { + "message": "Het onderstaande domein probeert toegang tot de Ethereum API te vragen zodat deze kan communiceren met de Ethereum-blockchain. Controleer altijd eerst of u op de juiste site bent voordat u Ethereum-toegang goedkeurt." + }, "accountDetails": { "message": "Accountgegevens" }, @@ -43,6 +52,10 @@ "balanceIsInsufficientGas": { "message": "Onvoldoende saldo voor huidig ​​gastotaal" }, + "betweenMinAndMax": { + "message": "moet groter zijn dan of gelijk zijn aan $1 en kleiner dan of gelijk aan $2.", + "description": "helper for inputting hex as decimal input" + }, "blockiesIdenticon": { "message": "Gebruik Blockies Identicon" }, @@ -79,6 +92,9 @@ "copiedExclamation": { "message": "Gekopieerde!" }, + "copy": { + "message": "Kopiëren" + }, "copyToClipboard": { "message": "Kopieer naar klembord" }, @@ -181,6 +197,10 @@ "message": "Haal Ether uit een kraan voor de $1", "description": "Displays network name for Ether faucet" }, + "greaterThanMin": { + "message": "moet groter zijn dan of gelijk zijn aan $1.", + "description": "helper for inputting hex as decimal input" + }, "here": { "message": "hier", "description": "as in -click here- for more information (goes with troubleTokenBalances)" @@ -239,6 +259,10 @@ "kovan": { "message": "Kovan-testnetwerk" }, + "lessThanMax": { + "message": "moet kleiner zijn dan of gelijk zijn aan $1.", + "description": "helper for inputting hex as decimal input" + }, "likeToAddTokens": { "message": "Wil je deze tokens toevoegen?" }, @@ -307,6 +331,9 @@ "message": "Plak hier uw privésleutelstring:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Plak je back-up woorden hier!" + }, "personalAddressDetected": { "message": "Persoonlijk adres gedetecteerd. Voer het tokencontractadres in." }, @@ -410,6 +437,9 @@ "stateLogsDescription": { "message": "Staatslogboeken bevatten uw openbare accountadressen en verzonden transacties." }, + "submit": { + "message": "voorleggen" + }, "supportCenter": { "message": "Bezoek ons ​​ondersteuningscentrum" }, @@ -454,6 +484,9 @@ "usedByClients": { "message": "Gebruikt door verschillende klanten" }, + "validFileImport": { + "message": "U moet een geldig bestand selecteren om te importeren." + }, "viewAccount": { "message": "Bekijk account" }, diff --git a/app/_locales/ph/messages.json b/app/_locales/ph/messages.json index 1a3807ad55..a3df5d4edf 100644 --- a/app/_locales/ph/messages.json +++ b/app/_locales/ph/messages.json @@ -1,4 +1,10 @@ { + "confirmClear": { + "message": "Sigurado ka bang gusto mong i-clear ang mga naaprubahang website?" + }, + "clearApprovalData": { + "message": "Tanggalin ang data ng pag-apruba" + }, "appName": { "message": "MetaMask", "description": "The name of the application" @@ -9,6 +15,9 @@ "reject": { "message": "Tanggihan" }, + "providerRequestInfo": { + "message": "Ang domain na nakalista sa ibaba ay sinusubukang humiling ng access sa Ethereum API upang maaari itong makipag-ugnayan sa Ethereum blockchain. Laging i-double check na ikaw ay nasa tamang site bago aprubahan ang Ethereum access." + }, "accountDetails": { "message": "Detalye ng Account" }, @@ -37,6 +46,10 @@ "balanceIsInsufficientGas": { "message": "Kulang ang balanse para sa kasalukuyang gas total" }, + "betweenMinAndMax": { + "message": "dapat mas malaki o katumbas ng $1 at mas mababa o katumbas ng $2.", + "description": "helper para sa pag-input ng hex bilang decimal input" + }, "buyCoinSwitch": { "message": "Bumili sa CoinSwitch" }, @@ -67,6 +80,9 @@ "copiedExclamation": { "message": "Kinopya!" }, + "copy": { + "message": "Kinopya" + }, "copyToClipboard": { "message": "Kinopya sa clipboard" }, @@ -151,6 +167,10 @@ "message": "Kumuha ng Ether mula sa faucet para sa $1", "description": "Ipinapakita ang pangalan ng network para sa Ether faucet" }, + "greaterThanMin": { + "message": "dapat mas malaki o katumbas ng $1.", + "description": "helper para sa pag-input ng hex bilang decimal input" + }, "here": { "message": "i-click ito", "description": "tulad ng -i-click dito- para sa mas maraming impormasyon (kasama ng troubleTokenBalances)" @@ -184,6 +204,10 @@ "invalidInput": { "message": "Invalid ang input." }, + "lessThanMax": { + "message": "dapat mas mababa o katumbas ng $1.", + "description": "helper para sa pag-input ng hex bilang decimal input" + }, "loading": { "message": "Naglo-load..." }, @@ -228,6 +252,9 @@ "message": "I-paste dito ang iyong private key string:", "description": "Para sa pag-import ng account mula sa private key" }, + "pasteSeed": { + "message": "I-paste dito ang iyong seed phrase!" + }, "privateKeyWarning": { "message": "Babala: Huwag sabihin sa kahit na sino ang key na ito. Maaring makuha at manakaw ng sinumang nakakaalam ng iyong private key ang mga assets sa iyong account." }, @@ -276,6 +303,9 @@ "sigRequest": { "message": "Hiling na Signature" }, + "submit": { + "message": "I-submit" + }, "toETHviaShapeShift": { "message": "$1 sa ETH sa pamamagitan ng ShapeShift", "description": "Pupunan ng system ang deposit type sa simula ng mensahe" diff --git a/app/_locales/pl/messages.json b/app/_locales/pl/messages.json index 689b2d909a..243f4c7933 100644 --- a/app/_locales/pl/messages.json +++ b/app/_locales/pl/messages.json @@ -226,6 +226,10 @@ "basic": { "message": "Podstawy" }, + "betweenMinAndMax": { + "message": "musi być większe lub równe $1 i mniejsze lub równe $2,", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "Przeglądaj blok" }, @@ -317,6 +321,9 @@ "connect": { "message": "Połącz" }, + "connectRequest": { + "message": "Potwierdź żądanie" + }, "connectingTo": { "message": "Łączenie z $1" }, @@ -377,6 +384,9 @@ "copiedExclamation": { "message": "Skopiowane!" }, + "copy": { + "message": "Skopiuj" + }, "copyAddress": { "message": "Skopiuj adres do schowka" }, @@ -455,6 +465,9 @@ "directDepositEtherExplainer": { "message": "Jeśli już masz Eter, najszybciej umieścisz go w swoim nowym portfelu przy pomocy bezpośredniego depozytu." }, + "dismiss": { + "message": "Zamknij" + }, "done": { "message": "Gotowe" }, @@ -635,6 +648,10 @@ "getStarted": { "message": "Rozpocznij" }, + "greaterThanMin": { + "message": "musi być większe lub równe $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Cieszymy się, że tu jesteś." }, @@ -1017,6 +1034,9 @@ "message": "Tutaj wklej swój prywatny klucz:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Tutaj wklej swoją frazę seed!" + }, "pending": { "message": "oczekiwanie" }, @@ -1540,6 +1560,9 @@ "userName": { "message": "Nazwa użytkownika" }, + "validFileImport": { + "message": "Należy wybrać prawidłowy plik do zaimportowania." + }, "viewAccount": { "message": "Zobacz konto" }, diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index 9d9ed04509..70c2e52aeb 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -1,7 +1,16 @@ { + "confirmClear": { + "message": "Tem certeza de que deseja limpar sites aprovados?" + }, + "clearApprovalData": { + "message": "Limpar dados de aprovação" + }, "reject": { "message": "Rejeitar" }, + "providerRequestInfo": { + "message": "O domínio listado abaixo está tentando solicitar acesso à API Ethereum para que ele possa interagir com o blockchain Ethereum. Sempre verifique se você está no site correto antes de aprovar o acesso à Ethereum." + }, "account": { "message": "Conta" }, @@ -46,6 +55,10 @@ "balanceIsInsufficientGas": { "message": "Saldo insuficiente para a quantidade de gas total" }, + "betweenMinAndMax": { + "message": "tem de ser maior ou igual a $1 e menor ou igual a $2.", + "description": "helper for inputting hex as decimal input" + }, "blockiesIdenticon": { "message": "Usar Blockies Identicon" }, @@ -82,6 +95,9 @@ "copiedExclamation": { "message": "Copiado!" }, + "copy": { + "message": "Copiar" + }, "copyToClipboard": { "message": "Copiar para o clipboard" }, @@ -187,6 +203,10 @@ "message": "Obter Ether de um faucet por $1", "description": "Displays network name for Ether faucet" }, + "greaterThanMin": { + "message": "tem de ser maior ou igual a $1.", + "description": "helper for inputting hex as decimal input" + }, "here": { "message": "aqui", "description": "as in -click here- for more information (goes with troubleTokenBalances)" @@ -245,6 +265,10 @@ "kovan": { "message": "Rede de Teste Kovan" }, + "lessThanMax": { + "message": "tem de ser menor ou igual a $1.", + "description": "helper for inputting hex as decimal input" + }, "likeToAddTokens": { "message": "Gostaria de adicionar estes tokens?" }, @@ -317,6 +341,9 @@ "message": "Cole aqui a sua chave privada:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Cole aqui a sua frase seed!" + }, "personalAddressDetected": { "message": "Endereço pessoal detectado. Introduza o endereço do contrato do token." }, @@ -420,6 +447,9 @@ "stateLogsDescription": { "message": "Registo de estado podem conter o seu endereço e transações enviadas da sua conta pública." }, + "submit": { + "message": "Submeter" + }, "supportCenter": { "message": "Visitar o nosso Centro de Suporte" }, @@ -464,6 +494,9 @@ "usedByClients": { "message": "Utilizado por vários tipos de clientes" }, + "validFileImport": { + "message": "Deve selecionar um ficheiro válido para importar." + }, "viewAccount": { "message": "Ver Conta" }, diff --git a/app/_locales/pt_PT/messages.json b/app/_locales/pt_PT/messages.json index ae85eb26c1..ce35721de0 100644 --- a/app/_locales/pt_PT/messages.json +++ b/app/_locales/pt_PT/messages.json @@ -48,6 +48,9 @@ "copiedExclamation": { "message": "Copiado!" }, + "copy": { + "message": "Copiar" + }, "copyToClipboard": { "message": "Copiar para a área de transferência" }, @@ -63,6 +66,9 @@ "details": { "message": "Detalhes" }, + "dismiss": { + "message": "Ignorar" + }, "done": { "message": "Concluído" }, @@ -150,6 +156,9 @@ "settings": { "message": "Definições" }, + "submit": { + "message": "Submeter" + }, "tips": { "message": "Doações" }, diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index 43249d6bcd..082e450da1 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -1,7 +1,16 @@ { + "confirmClear": { + "message": "Вы уверены, что хотите очистить утвержденные веб-сайты?Tem certeza de que deseja limpar sites aprovados?" + }, + "clearApprovalData": { + "message": "Четкие данные об утверждении" + }, "reject": { "message": "Отклонить" }, + "providerRequestInfo": { + "message": "Домен, указанный ниже, пытается запросить доступ к API-интерфейсу Ethereum, чтобы он мог взаимодействовать с блокчейном Ethereum. Всегда проверяйте, что вы находитесь на правильном сайте, прежде чем одобрять доступ к веб-сайту." + }, "account": { "message": "Счет" }, @@ -49,6 +58,10 @@ "balanceIsInsufficientGas": { "message": "Недостаточный баланс для текущего объема газа" }, + "betweenMinAndMax": { + "message": "должно быть больше или равно $1 и меньше или равно $2.", + "description": "helper for inputting hex as decimal input" + }, "blockiesIdenticon": { "message": "Использовать Blockies Identicon" }, @@ -103,6 +116,9 @@ "copiedExclamation": { "message": "Скопировано!" }, + "copy": { + "message": "Скопировать" + }, "copyToClipboard": { "message": "Скопировать в буфер обмена" }, @@ -214,6 +230,10 @@ "message": "Получить Ether из крана для $1", "description": "Displays network name for Ether faucet" }, + "greaterThanMin": { + "message": "должно быть больше или равно $1.", + "description": "helper for inputting hex as decimal input" + }, "here": { "message": "тут", "description": "as in -click here- for more information (goes with troubleTokenBalances)" @@ -278,6 +298,10 @@ "learnMore": { "message": "Узнать больше." }, + "lessThanMax": { + "message": "должно быть меньше или равно $1.", + "description": "helper for inputting hex as decimal input" + }, "likeToAddTokens": { "message": "Вы хотите добавить эти токены?" }, @@ -353,6 +377,9 @@ "message": "Вставьте ваш закрытый ключ тут:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Вставьте вашу ключевую фразу!" + }, "personalAddressDetected": { "message": "Обнаружен персональный адрес. Введите адрес контракта токена." }, @@ -465,6 +492,9 @@ "stateLogError": { "message": "Ошибка при получении журнала состояния." }, + "submit": { + "message": "Отправить" + }, "submitted": { "message": "Отправлена" }, @@ -518,6 +548,9 @@ "usedByClients": { "message": "Используется различными клиентами" }, + "validFileImport": { + "message": "Вам нужно выбрать правильный файл для импорта." + }, "viewAccount": { "message": "Посмотреть счет" }, @@ -536,13 +569,16 @@ "youSign": { "message": "Вы подписываете" }, + "privacyModeDefault": { + "message": "Режим конфиденциальности теперь включен по умолчанию" + }, "chartOnlyAvailableEth": { "message": "Диаграмма доступна только в сетях Ethereum." }, "contractInteraction": { "message": "Взаимодействие с контрактом" }, - "likeToConnect": { + "providerRequest": { "message": "$1 запрашивает доступ к вашему аккаунту" }, "about": { @@ -695,6 +731,9 @@ "connect": { "message": "Подключиться" }, + "connectRequest": { + "message": "Запрос на подключение" + }, "connectingTo": { "message": "Подключение к $1" }, @@ -737,6 +776,9 @@ "deleteAccount": { "message": "Удалить аккаунт" }, + "dismiss": { + "message": "Отклюнить" + }, "downloadGoogleChrome": { "message": "Скачать Google Chrome" }, diff --git a/app/_locales/sk/messages.json b/app/_locales/sk/messages.json index 9c333786cb..1d235da380 100644 --- a/app/_locales/sk/messages.json +++ b/app/_locales/sk/messages.json @@ -229,6 +229,10 @@ "basic": { "message": "Základné" }, + "betweenMinAndMax": { + "message": "musí být větší nebo roven $1 a menší nebo roven $2.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerView": { "message": "Zobraziť účet na $1", "description": "$1 replaced by URL for custom block explorer" @@ -398,6 +402,9 @@ "copiedExclamation": { "message": "Zkopírováno!" }, + "copy": { + "message": "Kopírovat" + }, "copyAddress": { "message": "Kopírovať adresu do schránky" }, @@ -492,6 +499,9 @@ "directDepositEtherExplainer": { "message": "Pokud už vlastníte nějaký Ether, nejrychleji ho dostanete do peněženky přímým vkladem." }, + "dismiss": { + "message": "Zatvoriť" + }, "done": { "message": "Hotovo" }, @@ -697,6 +707,10 @@ "getStarted": { "message": "Začať" }, + "greaterThanMin": { + "message": "musí být větší nebo roven $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Sme radi, že vás vidíme." }, @@ -1072,6 +1086,9 @@ "message": "Vložte zde svůj privátní klíč:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Svou klíčovou frázi vložte zde!" + }, "pending": { "message": "prebieha" }, @@ -1629,6 +1646,9 @@ "userName": { "message": "Meno používateľa" }, + "validFileImport": { + "message": "Musíte vybrat validní soubor k importu." + }, "viewAccount": { "message": "Zobrazit účet" }, diff --git a/app/_locales/sl/messages.json b/app/_locales/sl/messages.json index b25e30efe5..30965aa1b2 100644 --- a/app/_locales/sl/messages.json +++ b/app/_locales/sl/messages.json @@ -35,6 +35,9 @@ "contractInteraction": { "message": "Interakcija s pogodbo" }, + "clearApprovalData": { + "message": "Počisti podatke o odobritvi" + }, "reject": { "message": "Zavrni" }, @@ -205,6 +208,10 @@ "basic": { "message": "Osnovno" }, + "betweenMinAndMax": { + "message": "mora biti večji ali enak $1 in manjši ali enak $1.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "Blokiraj Explorer" }, @@ -374,6 +381,9 @@ "copiedExclamation": { "message": "Kopirano!" }, + "copy": { + "message": "Kopiraj" + }, "copyAddress": { "message": "Kopiraj naslov v odložišče" }, @@ -458,6 +468,9 @@ "directDepositEtherExplainer": { "message": "Če že imate Ether, ga lahko najhitreje dobite v MetaMask z neposrednim vplačilom." }, + "dismiss": { + "message": "Opusti" + }, "done": { "message": "Končano" }, @@ -635,6 +648,10 @@ "getStarted": { "message": "Začnite" }, + "greaterThanMin": { + "message": "mora biti večji ali enak $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Veseli nas, da ste nas spet obiskali." }, @@ -1008,6 +1025,9 @@ "message": "Tukaj prilepite vaš zasebni ključ:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Tukaj prilepite seed phase!" + }, "pending": { "message": "v obdelavi" }, @@ -1519,6 +1539,9 @@ "userName": { "message": "Uporabniško ime" }, + "validFileImport": { + "message": "Za uvoz morate izbrati pravilno datoteko." + }, "viewAccount": { "message": "Poglej račun" }, diff --git a/app/_locales/ta/messages.json b/app/_locales/ta/messages.json index dc48abb02d..b5be675206 100644 --- a/app/_locales/ta/messages.json +++ b/app/_locales/ta/messages.json @@ -1,10 +1,19 @@ { + "confirmClear": { + "message": "அங்கீகரிக்கப்பட்ட வலைத்தளங்களை நிச்சயமாக நீக்க விரும்புகிறீர்களா?" + }, + "clearApprovalData": { + "message": "ஒப்புதல் தரவை அழி" + }, "approve": { "message": "ஒப்புதல்" }, "reject": { "message": "நிராகரி" }, + "providerRequestInfo": { + "message": "கீழே பட்டியலிடப்பட்டுள்ள டொமைன் Web3 ஏபிஐ அணுகலைக் கோருவதற்கு முயற்சிக்கிறது, எனவே இது Ethereum blockchain உடன் தொடர்பு கொள்ள முடியும். Web3 அணுகுமுறையை அங்கீகரிப்பதற்கு முன் சரியான தளத்தில் இருப்பதை எப்போதும் இருமுறை சரிபார்க்கவும்." + }, "account": { "message": "கணக்கு" }, @@ -49,6 +58,10 @@ "balanceIsInsufficientGas": { "message": "நடப்பு வாயு மொத்தம் போதுமான சமநிலை" }, + "betweenMinAndMax": { + "message": "$ 1 க்கும் அதிகமாகவும் அல்லது $ 2 க்கு சமமாகவும் இருக்க வேண்டும்.", + "description": "ஹெக்ஸ் உள்ளீடு தசம உள்ளீடு என உதவி" + }, "blockiesIdenticon": { "message": "ப்ளாக்கிஸ் ஐடென்டிகோன் பயன்பாட்டு" }, @@ -91,6 +104,9 @@ "copiedExclamation": { "message": "நகலெடுக்கப்பட்டன!" }, + "copy": { + "message": "நகலெடு" + }, "copyToClipboard": { "message": "கிளிப்போர்டுக்கு நகலெடு" }, @@ -208,6 +224,10 @@ "message": "$ 1 க்கு ஒரு குழாய் இருந்து ஈதர் கிடைக்கும்$1", "description": "ஈத்தர் குழாய் ஐந்து பிணைய பெயர் காட்டுகிறது" }, + "greaterThanMin": { + "message": "$ 1 க்கும் அதிகமாகவோ அல்லது சமமாகவோ இருக்க வேண்டும்", + "description": "ஹெக்ஸ் உள்ளீடு தசம உள்ளீடு என உதவி" + }, "here": { "message": "இங்கே", "description": "இங்கே-கிளிக் செய்யவும்- மேலும் தகவலுக்கு (troubleTokenBalances செல்கிறது)" @@ -272,6 +292,10 @@ "learnMore": { "message": "மேலும் அறிக" }, + "lessThanMax": { + "message": "$ 1 க்கும் குறைவாகவோ அல்லது சமமாகவோ இருக்க வேண்டும்.", + "description": "ஹெக்ஸ் உள்ளீடு தசம உள்ளீடு என உதவி" + }, "likeToAddTokens": { "message": "இந்த டோக்கன்களைச் சேர்க்க விரும்புகிறீர்களா?" }, @@ -350,6 +374,9 @@ "message": "இங்கே உங்கள் தனிப்பட்ட விசை சரத்தை ஒட்டுக:", "description": "ஒரு தனிப்பட்ட விசை ஒரு கணக்கை இறக்குமதி செய்ய" }, + "pasteSeed": { + "message": "இங்கே உங்கள் விதை சொற்றொடரை ஒட்டவும்!" + }, "personalAddressDetected": { "message": "தனிப்பட்ட முகவரி கண்டறியப்பட்டது. டோக்கன் ஒப்பந்த முகவரியை உள்ளிடவும்." }, @@ -474,6 +501,9 @@ "stateLogError": { "message": "மாநில பதிவுகளை மீட்டெடுப்பதில் பிழை." }, + "submit": { + "message": "சமர்ப்பி" + }, "submitted": { "message": "சமர்ப்பிக்கப்பட்டது" }, @@ -530,6 +560,9 @@ "usedByClients": { "message": "பல்வேறு வாடிக்கையாளர்கள் பல்வேறு பயன்படுத்திய" }, + "validFileImport": { + "message": "இறக்குமதி செய்ய சரியான கோப்பு தேர்ந்தெடுக்க வேண்டும்." + }, "viewAccount": { "message": "கணக்கைப் பார்" }, @@ -569,6 +602,9 @@ "delete": { "message": "நீக்கு" }, + "dismiss": { + "message": "நிராகரி" + }, "fast": { "message": "வேகமான" }, diff --git a/app/_locales/te/messages.json b/app/_locales/te/messages.json index 2df11217d5..ba588ca469 100644 --- a/app/_locales/te/messages.json +++ b/app/_locales/te/messages.json @@ -42,6 +42,9 @@ "connect": { "message": "కనెక్ట్ చేయండి" }, + "copy": { + "message": "కాపీ చెయ్యి" + }, "copyToClipboard": { "message": "క్లిప్‌బోర్డ్‌కు కాపీ చేయి" }, @@ -54,6 +57,9 @@ "details": { "message": "వివరాలు" }, + "dismiss": { + "message": "తొలగించు" + }, "done": { "message": "పూర్తయింది" }, @@ -125,6 +131,9 @@ "settings": { "message": "సెట్టింగ్‌లు" }, + "submit": { + "message": "సమర్పించు" + }, "tryAgain": { "message": "మళ్లీ ప్రయత్నించు" }, diff --git a/app/_locales/th/messages.json b/app/_locales/th/messages.json index 45ebd50309..072281dd42 100644 --- a/app/_locales/th/messages.json +++ b/app/_locales/th/messages.json @@ -1,4 +1,10 @@ { + "confirmClear": { + "message": "คุณแน่ใจหรือไม่ว่าต้องการล้างเว็บไซต์ที่ผ่านการอนุมัติ" + }, + "clearApprovalData": { + "message": "ล้างข้อมูลการอนุมัติ" + }, "reject": { "message": "ปฏิเสธ" }, @@ -64,6 +70,10 @@ "balanceIsInsufficientGas": { "message": "ยอดคงเหลือไม่พอสำหรับจ่ายค่าแก๊สทั้งหมด" }, + "betweenMinAndMax": { + "message": "ต้องมากกว่าหรือเท่ากับ $1 และน้อยกว่าหรือเท่ากับ $2", + "description": "helper for inputting hex as decimal input" + }, "blockiesIdenticon": { "message": "ใช้งาน Blockies Identicon" }, @@ -202,6 +212,9 @@ "copiedExclamation": { "message": "คัดลอกแล้ว!" }, + "copy": { + "message": "คัดลอก" + }, "copyToClipboard": { "message": "คัดลอกไปคลิปบอร์ด" }, @@ -284,6 +297,9 @@ "directDepositEtherExplainer": { "message": "ถ้าคุณมีอีเธอร์อยู่แล้ววิธีการที่เร็วที่สุดในการเอาเงินเข้ากระเป๋าใหม่ก็คือการโอนตรงๆ" }, + "dismiss": { + "message": "ปิด" + }, "done": { "message": "เสร็จสิ้น" }, @@ -431,6 +447,10 @@ "message": "รับอีเธอร์ที่ปล่อยจาก $1", "description": "Displays network name for Ether faucet" }, + "greaterThanMin": { + "message": "ต้องมากกว่าหรือเท่ากับ $1.", + "description": "helper for inputting hex as decimal input" + }, "here": { "message": "ที่นี่", "description": "as in -click here- for more information (goes with troubleTokenBalances)" @@ -719,6 +739,9 @@ "message": "วางคีย์ส่วนตัวของคุณที่นี่:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "วางคำชีดของคุณที่นี่!" + }, "personalAddressDetected": { "message": "ตรวจพบแอดแดรสส่วนตัวแล้ว ใส่แอดแดรสสัญญาโทเค็น" }, @@ -1053,6 +1076,9 @@ "usedByClients": { "message": "ถูกใช้งานโดยหลายไคลเอนท์" }, + "validFileImport": { + "message": "คุณต้องเลือกไฟล์ที่ถูกต้องเพื่อนำเข้า" + }, "viewAccount": { "message": "ดูบัญชี" }, diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index fdb1ae927c..fe2332f2f0 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -1,7 +1,16 @@ { + "confirmClear": { + "message": "Onaylanmış web sitelerini silmek istediğinizden emin misiniz?" + }, + "clearApprovalData": { + "message": "Onay verilerini temizle" + }, "reject": { "message": "Reddetmek" }, + "providerRequestInfo": { + "message": "Aşağıda listelenen etki alanı, Ethereum API'sine erişim talep etmeye çalışmaktadır, böylece Ethereum blockchain ile etkileşime girebilir. Web3 erişimini onaylamadan önce her zaman doğru sitede olduğunuzu kontrol edin." + }, "account": { "message": "Hesap" }, @@ -49,6 +58,10 @@ "balanceIsInsufficientGas": { "message": "Toplam gas için yetersiz bakiye" }, + "betweenMinAndMax": { + "message": "$1'e eşit veya daha büyük olmalı ve $2'den küçük veya eşit olmalı", + "description": "helper for inputting hex as decimal input" + }, "blockiesIdenticon": { "message": "Blockies Identicon kullan" }, @@ -103,6 +116,9 @@ "copiedExclamation": { "message": "Kopyalandı!" }, + "copy": { + "message": "Kopyala" + }, "copyToClipboard": { "message": "Panoya kopyala" }, @@ -278,6 +294,10 @@ "learnMore": { "message": "Daha fazla bilgi." }, + "lessThanMax": { + "message": "$1'den az veya eşit olmalıdır.", + "description": "helper for inputting hex as decimal input" + }, "likeToAddTokens": { "message": "Bu jetonlara adres eklemek ister misiniz?" }, @@ -359,6 +379,9 @@ "message": "Özel anahtar dizinizi buraya yapıştırın:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Kaynak ifadenizi buraya yapıştırın!" + }, "personalAddressDetected": { "message": "Kişisel adres tespit edilidi. Jeton sözleşme adresini girin." }, @@ -471,6 +494,9 @@ "stateLogError": { "message": "Durum kayıtlarını alma hatası" }, + "submit": { + "message": "Gönder" + }, "submitted": { "message": "Gönderildi" }, @@ -527,6 +553,9 @@ "usedByClients": { "message": "Farklı istemciler tarafından kullanılmakta" }, + "validFileImport": { + "message": "Almak için geçerli bir dosya seçmelisiniz" + }, "viewAccount": { "message": "Hesabı İncele" }, diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index 6136403bae..b223616787 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -1,7 +1,16 @@ { + "confirmClear": { + "message": "Bạn có chắc chắn muốn xóa các trang web được phê duyệt không?" + }, + "clearApprovalData": { + "message": "Xóa dữ liệu phê duyệt" + }, "reject": { "message": "Từ chối" }, + "providerRequestInfo": { + "message": "Miền được liệt kê bên dưới đang cố gắng yêu cầu quyền truy cập vào API Ethereum để nó có thể tương tác với chuỗi khối Ethereum. Luôn kiểm tra kỹ xem bạn có đang ở đúng trang web trước khi phê duyệt quyền truy cập Ethereum hay không." + }, "account": { "message": "Tài khoản" }, @@ -40,6 +49,10 @@ "balanceIsInsufficientGas": { "message": "Số dư không đủ để thanh toán tổng tiền gas hiện tại" }, + "betweenMinAndMax": { + "message": "phải nhiều hơn hoặc bằng $1 và ít hơn hoặc bằng $2.", + "description": "helper for inputting hex as decimal input" + }, "buyCoinSwitch": { "message": "Mua trên CoinSwitch" }, @@ -70,6 +83,9 @@ "copiedExclamation": { "message": "Đã sao chép!" }, + "copy": { + "message": "Sao chép" + }, "copyToClipboard": { "message": "Đã sao chép vào clipboard" }, @@ -163,6 +179,10 @@ "message": "Lấy Ether từ vòi với giá $1", "description": "Displays network name for Ether faucet" }, + "greaterThanMin": { + "message": "phải nhiều hơn hoặc bằng $1", + "description": "helper for inputting hex as decimal input" + }, "here": { "message": "tại đây", "description": "as in -click here- for more information (goes with troubleTokenBalances)" @@ -203,6 +223,10 @@ "kovan": { "message": "Mạng thử nghiệm Kovan" }, + "lessThanMax": { + "message": "phải ít hơn hoặc bằng $1.", + "description": "helper for inputting hex as decimal input" + }, "loading": { "message": "Đang tải..." }, @@ -260,6 +284,9 @@ "message": "Dán dãy khóa cá nhân của bạn tại đây:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Dán Mật Khẩu Sinh Khoá (seed phrase) của bạn tại đây!" + }, "privateKey": { "message": "Khóa Bí Mật", "description": "select this type of file to use to import an account" @@ -321,6 +348,9 @@ "sigRequest": { "message": "Yêu cầu chữ ký" }, + "submit": { + "message": "Gửi đi" + }, "testFaucet": { "message": "Vòi nhận tiền ETH ảo để thử nghiệm" }, diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index c52fe7934c..fec03d71de 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -232,6 +232,10 @@ "basic": { "message": "基本" }, + "betweenMinAndMax": { + "message": "必须大于等于 $1 并且小于等于 $2 。", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "屏蔽管理器" }, @@ -407,6 +411,9 @@ "copiedExclamation": { "message": "已复制" }, + "copy": { + "message": "复制" + }, "copyAddress": { "message": "将地址复制到剪贴板" }, @@ -501,6 +508,9 @@ "directDepositEtherExplainer": { "message": "如果你已经有了一些 Ether,通过直接转入是你的新钱包获取 Ether 的最快捷方式。" }, + "dismiss": { + "message": "关闭" + }, "done": { "message": "完成" }, @@ -703,6 +713,10 @@ "getStarted": { "message": "开始使用" }, + "greaterThanMin": { + "message": "必须要大于等于 $1。", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "我们很高兴与您见面。" }, @@ -1088,6 +1102,9 @@ "message": "请粘贴你的私钥:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "请粘贴你的助记词!" + }, "pending": { "message": "待处理" }, @@ -1651,6 +1668,9 @@ "userName": { "message": "用户名" }, + "validFileImport": { + "message": "您必须选择一个有效的文件进行导入." + }, "viewAccount": { "message": "查看账户" }, diff --git a/app/_locales/zh_TW/messages.json b/app/_locales/zh_TW/messages.json index 5997e1c8e1..cb104f6cad 100644 --- a/app/_locales/zh_TW/messages.json +++ b/app/_locales/zh_TW/messages.json @@ -35,12 +35,18 @@ "contractInteraction": { "message": "合約互動" }, + "clearApprovalData": { + "message": "清除批准數據" + }, "reject": { "message": "拒絕" }, - "likeToConnect": { + "providerRequest": { "message": "$1 請求訪問帳戶權限" }, + "providerRequestInfo": { + "message": "此網站希望能讀取您的帳戶資訊。請務必確認您信任這個網站、並了解後續可能的交易行為。" + }, "about": { "message": "關於" }, @@ -199,6 +205,10 @@ "basic": { "message": "基本" }, + "betweenMinAndMax": { + "message": "必須大於等於 $1 並且小於等於 $2 。", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "封鎖 Explorer" }, @@ -296,6 +306,9 @@ "connect": { "message": "連線" }, + "connectRequest": { + "message": "連線請求" + }, "connectingTo": { "message": "連線到$1" }, @@ -356,6 +369,9 @@ "copiedExclamation": { "message": "已複製!" }, + "copy": { + "message": "複製" + }, "copyAddress": { "message": "複製到剪貼簿" }, @@ -440,6 +456,9 @@ "directDepositEtherExplainer": { "message": "如果您已經擁有乙太幣,直接存入功能是讓新錢包最快取得乙太幣的方式。" }, + "dismiss": { + "message": "關閉" + }, "done": { "message": "完成" }, @@ -620,6 +639,10 @@ "getStarted": { "message": "開始使用" }, + "greaterThanMin": { + "message": "必須要大於等於 $1。", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "我們很高興看到你。" }, @@ -961,6 +984,9 @@ "message": "請貼上您的私鑰字串:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "請貼上您的助憶詞!" + }, "pending": { "message": "等待處理" }, @@ -1281,6 +1307,9 @@ "storePhrase": { "message": "您可以用密碼管理系統例如 1Password 等軟體儲存助憶詞。" }, + "submit": { + "message": "送出" + }, "submitted": { "message": "已送出" }, @@ -1451,6 +1480,9 @@ "userName": { "message": "使用者名稱" }, + "validFileImport": { + "message": "您必須選擇一個合法的檔案來匯入." + }, "viewAccount": { "message": "查看帳戶" }, diff --git a/app/images/broken-line.svg b/app/images/broken-line.svg deleted file mode 100644 index ec4ed0d9c6..0000000000 --- a/app/images/broken-line.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/images/connect-white.svg b/app/images/connect-white.svg deleted file mode 100644 index e9063ae463..0000000000 --- a/app/images/connect-white.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/images/permissions-check.svg b/app/images/provider-approval-check.svg similarity index 100% rename from app/images/permissions-check.svg rename to app/images/provider-approval-check.svg diff --git a/app/manifest.json b/app/manifest.json index 58d4e13081..f9e25ceda6 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "7.7.0", + "version": "7.6.1", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", diff --git a/app/scripts/background.js b/app/scripts/background.js index e5c9ea6651..2639d77038 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -64,7 +64,6 @@ const isEdge = !isIE && !!window.StyleMedia let popupIsOpen = false let notificationIsOpen = false const openMetamaskTabsIDs = {} -const requestAccountTabIds = {} // state persistence const localStore = new LocalStore() @@ -76,6 +75,7 @@ initialize().catch(log.error) // setup metamask mesh testing container const { submitMeshMetricsEntry } = setupMetamaskMeshMetrics() + /** * An object representing a transaction, in whatever state it is in. * @typedef TransactionMeta @@ -248,12 +248,6 @@ function setupController (initState, initLangCode) { // platform specific api platform, encryptor: isEdge ? new EdgeEncryptor() : undefined, - getRequestAccountTabIds: () => { - return requestAccountTabIds - }, - getOpenMetamaskTabsIds: () => { - return openMetamaskTabsIDs - }, }) const provider = controller.provider @@ -266,9 +260,7 @@ function setupController (initState, initLangCode) { // report failed transactions to Sentry controller.txController.on(`tx:status-update`, (txId, status) => { - if (status !== 'failed') { - return - } + if (status !== 'failed') return const txMeta = controller.txController.txStateManager.getTx(txId) try { reportFailedTxToSentry({ sentry, txMeta }) @@ -320,7 +312,6 @@ function setupController (initState, initLangCode) { // extension.runtime.onConnect.addListener(connectRemote) extension.runtime.onConnectExternal.addListener(connectExternal) - extension.runtime.onMessage.addListener(controller.onMessage.bind(controller)) const metamaskInternalProcessHash = { [ENVIRONMENT_TYPE_POPUP]: true, @@ -393,17 +384,6 @@ function setupController (initState, initLangCode) { }) } } else { - if (remotePort.sender && remotePort.sender.tab && remotePort.sender.url) { - const tabId = remotePort.sender.tab.id - const url = new URL(remotePort.sender.url) - const origin = url.hostname - - remotePort.onMessage.addListener(msg => { - if (msg.data && msg.data.method === 'eth_requestAccounts') { - requestAccountTabIds[origin] = tabId - } - }) - } connectExternal(remotePort) } } @@ -428,7 +408,7 @@ function setupController (initState, initLangCode) { controller.messageManager.on('updateBadge', updateBadge) controller.personalMessageManager.on('updateBadge', updateBadge) controller.typedMessageManager.on('updateBadge', updateBadge) - controller.permissionsController.permissions.subscribe(updateBadge) + controller.providerApprovalController.memStore.on('update', updateBadge) /** * Updates the Web Extension's "badge" number, on the little fox in the toolbar. @@ -440,8 +420,8 @@ function setupController (initState, initLangCode) { const unapprovedMsgCount = controller.messageManager.unapprovedMsgCount const unapprovedPersonalMsgs = controller.personalMessageManager.unapprovedPersonalMsgCount const unapprovedTypedMsgs = controller.typedMessageManager.unapprovedTypedMessagesCount - const pendingPermissionRequests = Object.keys(controller.permissionsController.permissions.state.permissionsRequests).length - const count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgs + unapprovedTypedMsgs + pendingPermissionRequests + const pendingProviderRequests = controller.providerApprovalController.memStore.getState().providerRequests.length + const count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgs + unapprovedTypedMsgs + pendingProviderRequests if (count) { label = String(count) } diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index d583399aef..db4d5fd63a 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -2,8 +2,8 @@ const fs = require('fs') const path = require('path') const pump = require('pump') const log = require('loglevel') +const Dnode = require('dnode') const querystring = require('querystring') -const { Writable } = require('readable-stream') const LocalMessageDuplexStream = require('post-message-stream') const ObjectMultiplex = require('obj-multiplex') const extension = require('extensionizer') @@ -20,7 +20,7 @@ const inpageBundle = inpageContent + inpageSuffix // If we create a FireFox-only code path using that API, // MetaMask will be much faster loading and performant on Firefox. -if (shouldInjectProvider()) { +if (shouldInjectWeb3()) { injectScript(inpageBundle) start() } @@ -39,7 +39,7 @@ function injectScript (content) { container.insertBefore(scriptTag, container.children[0]) container.removeChild(scriptTag) } catch (e) { - console.error('MetaMask provider injection failed.', e) + console.error('MetaMask script injection failed', e) } } @@ -86,44 +86,6 @@ async function setupStreams () { (err) => logStreamDisconnectWarning('MetaMask Background Multiplex', err) ) - const onboardingStream = pageMux.createStream('onboarding') - const addCurrentTab = new Writable({ - objectMode: true, - write: (chunk, _, callback) => { - if (!chunk) { - return callback(new Error('Malformed onboarding message')) - } - - const handleSendMessageResponse = (error, success) => { - if (!error && !success) { - error = extension.runtime.lastError - } - if (error) { - log.error(`Failed to send ${chunk.type} message`, error) - return callback(error) - } - callback(null) - } - - try { - if (chunk.type === 'registerOnboarding') { - extension.runtime.sendMessage({ type: 'metamask:registerOnboarding', location: window.location.href }, handleSendMessageResponse) - } else { - throw new Error(`Unrecognized onboarding message type: '${chunk.type}'`) - } - } catch (error) { - log.error(error) - return callback(error) - } - }, - }) - - pump( - onboardingStream, - addCurrentTab, - error => console.error('MetaMask onboarding channel traffic failed', error), - ) - // forward communication across inpage-background for these channels only forwardTrafficBetweenMuxers('provider', pageMux, extensionMux) forwardTrafficBetweenMuxers('publicConfig', pageMux, extensionMux) @@ -131,6 +93,12 @@ async function setupStreams () { // connect "phishing" channel to warning system const phishingStream = extensionMux.createStream('phishing') phishingStream.once('data', redirectToPhishingWarning) + + // connect "publicApi" channel to submit page metadata + const publicApiStream = extensionMux.createStream('publicApi') + const background = await setupPublicApi(publicApiStream) + + return { background } } function forwardTrafficBetweenMuxers (channelName, muxA, muxB) { @@ -144,6 +112,37 @@ function forwardTrafficBetweenMuxers (channelName, muxA, muxB) { ) } +async function setupPublicApi (outStream) { + const api = { + getSiteMetadata: (cb) => cb(null, getSiteMetadata()), + } + const dnode = Dnode(api) + pump( + outStream, + dnode, + outStream, + (err) => { + // report any error + if (err) log.error(err) + } + ) + const background = await new Promise(resolve => dnode.once('remote', resolve)) + return background +} + +/** + * Gets site metadata and returns it + * + */ +function getSiteMetadata () { + // get metadata + const metadata = { + name: getSiteName(window), + icon: getSiteIcon(window), + } + return metadata +} + /** * Error handler for page to extension stream disconnections * @@ -152,18 +151,16 @@ function forwardTrafficBetweenMuxers (channelName, muxA, muxB) { */ function logStreamDisconnectWarning (remoteLabel, err) { let warningMsg = `MetamaskContentscript - lost connection to ${remoteLabel}` - if (err) { - warningMsg += '\n' + err.stack - } + if (err) warningMsg += '\n' + err.stack console.warn(warningMsg) } /** - * Determines if the provider should be injected + * Determines if Web3 should be injected * - * @returns {boolean} {@code true} if the provider should be injected + * @returns {boolean} {@code true} if Web3 should be injected */ -function shouldInjectProvider () { +function shouldInjectWeb3 () { return doctypeCheck() && suffixCheck() && documentElementCheck() && !blacklistedDomainCheck() } @@ -186,8 +183,8 @@ function doctypeCheck () { * Returns whether or not the extension (suffix) of the current document is prohibited * * This checks {@code window.location.pathname} against a set of file extensions - * that we should not inject the provider into. This check is indifferent of - * query parameters in the location. + * that should not have web3 injected into them. This check is indifferent of query parameters + * in the location. * * @returns {boolean} whether or not the extension of the current document is prohibited */ @@ -260,14 +257,52 @@ function redirectToPhishingWarning () { })}` } + +/** + * Extracts a name for the site from the DOM + */ +function getSiteName (window) { + const document = window.document + const siteName = document.querySelector('head > meta[property="og:site_name"]') + if (siteName) { + return siteName.content + } + + const metaTitle = document.querySelector('head > meta[name="title"]') + if (metaTitle) { + return metaTitle.content + } + + return document.title +} + +/** + * Extracts an icon for the site from the DOM + */ +function getSiteIcon (window) { + const document = window.document + + // Use the site's favicon if it exists + const shortcutIcon = document.querySelector('head > link[rel="shortcut icon"]') + if (shortcutIcon) { + return shortcutIcon.href + } + + // Search through available icons in no particular order + const icon = Array.from(document.querySelectorAll('head > link[rel="icon"]')).find((icon) => Boolean(icon.href)) + if (icon) { + return icon.href + } + + return null +} + /** * Returns a promise that resolves when the DOM is loaded (does not wait for images to load) */ async function domIsReady () { // already loaded - if (['interactive', 'complete'].includes(document.readyState)) { - return - } + if (['interactive', 'complete'].includes(document.readyState)) return // wait for load - return new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve, { once: true })) + await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve, { once: true })) } diff --git a/app/scripts/controllers/app-state.js b/app/scripts/controllers/app-state.js index c60a1c4f50..8d67874ad0 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.updateState({ + this.store.putState({ timeoutMinutes, }) diff --git a/app/scripts/controllers/detect-tokens.js b/app/scripts/controllers/detect-tokens.js index 923aa2d15f..e6e9930735 100644 --- a/app/scripts/controllers/detect-tokens.js +++ b/app/scripts/controllers/detect-tokens.js @@ -29,12 +29,8 @@ class DetectTokensController { * */ async detectNewTokens () { - if (!this.isActive) { - return - } - if (this._network.store.getState().provider.type !== MAINNET) { - return - } + if (!this.isActive) { return } + if (this._network.store.getState().provider.type !== MAINNET) { return } const tokensToDetect = [] this.web3.setProvider(this._network._provider) for (const contractAddress in contracts) { @@ -84,9 +80,7 @@ class DetectTokensController { * */ restartTokenDetection () { - if (!(this.isActive && this.selectedAddress)) { - return - } + if (!(this.isActive && this.selectedAddress)) { return } this.detectNewTokens() this.interval = DEFAULT_INTERVAL } @@ -96,12 +90,8 @@ class DetectTokensController { */ set interval (interval) { this._handle && clearInterval(this._handle) - if (!interval) { - return - } - this._handle = setInterval(() => { - this.detectNewTokens() - }, interval) + if (!interval) { return } + this._handle = setInterval(() => { this.detectNewTokens() }, interval) } /** @@ -109,15 +99,9 @@ class DetectTokensController { * @type {Object} */ set preferences (preferences) { - if (!preferences) { - return - } + if (!preferences) { return } this._preferences = preferences - preferences.store.subscribe(({ tokens = [] }) => { - this.tokenAddresses = tokens.map((obj) => { - return obj.address - }) - }) + preferences.store.subscribe(({ tokens = [] }) => { this.tokenAddresses = tokens.map((obj) => { return obj.address }) }) preferences.store.subscribe(({ selectedAddress }) => { if (this.selectedAddress !== selectedAddress) { this.selectedAddress = selectedAddress @@ -130,9 +114,7 @@ class DetectTokensController { * @type {Object} */ set network (network) { - if (!network) { - return - } + if (!network) { return } this._network = network this.web3 = new Web3(network._provider) } @@ -142,16 +124,12 @@ class DetectTokensController { * @type {Object} */ set keyringMemStore (keyringMemStore) { - if (!keyringMemStore) { - return - } + if (!keyringMemStore) { return } this._keyringMemStore = keyringMemStore this._keyringMemStore.subscribe(({ isUnlocked }) => { if (this.isUnlocked !== isUnlocked) { this.isUnlocked = isUnlocked - if (isUnlocked) { - this.restartTokenDetection() - } + if (isUnlocked) { this.restartTokenDetection() } } }) } diff --git a/app/scripts/controllers/incoming-transactions.js b/app/scripts/controllers/incoming-transactions.js index 10735b3f92..029bf47aa5 100644 --- a/app/scripts/controllers/incoming-transactions.js +++ b/app/scripts/controllers/incoming-transactions.js @@ -163,9 +163,7 @@ class IncomingTransactionsController { const newIncomingTransactions = { ...currentIncomingTxs, } - newTxs.forEach(tx => { - newIncomingTransactions[tx.hash] = tx - }) + newTxs.forEach(tx => { newIncomingTransactions[tx.hash] = tx }) this.store.updateState({ incomingTxLastFetchedBlocksByNetwork: { diff --git a/app/scripts/controllers/network/middleware/pending.js b/app/scripts/controllers/network/middleware/pending.js index 96a5d40be8..542d8bde6f 100644 --- a/app/scripts/controllers/network/middleware/pending.js +++ b/app/scripts/controllers/network/middleware/pending.js @@ -4,13 +4,9 @@ const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware function createPendingNonceMiddleware ({ getPendingNonce }) { return createAsyncMiddleware(async (req, res, next) => { const {method, params} = req - if (method !== 'eth_getTransactionCount') { - return next() - } + if (method !== 'eth_getTransactionCount') return next() const [param, blockRef] = params - if (blockRef !== 'pending') { - return next() - } + if (blockRef !== 'pending') return next() res.result = await getPendingNonce(param) }) } @@ -18,14 +14,10 @@ function createPendingNonceMiddleware ({ getPendingNonce }) { function createPendingTxMiddleware ({ getPendingTransactionByHash }) { return createAsyncMiddleware(async (req, res, next) => { const {method, params} = req - if (method !== 'eth_getTransactionByHash') { - return next() - } + if (method !== 'eth_getTransactionByHash') return next() const [hash] = params const txMeta = getPendingTransactionByHash(hash) - if (!txMeta) { - return next() - } + if (!txMeta) return next() res.result = formatTxMetaForRpcResult(txMeta) }) } diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js index d78f92f93d..7b5d8ef383 100644 --- a/app/scripts/controllers/network/network.js +++ b/app/scripts/controllers/network/network.js @@ -81,9 +81,7 @@ module.exports = class NetworkController extends EventEmitter { verifyNetwork () { // Check network when restoring connectivity: - if (this.isNetworkLoading()) { - this.lookupNetwork() - } + if (this.isNetworkLoading()) this.lookupNetwork() } getNetworkState () { @@ -198,7 +196,7 @@ module.exports = class NetworkController extends EventEmitter { }) this._setNetworkClient(networkClient) // setup networkConfig - const settings = { + var settings = { ticker: 'ETH', } this.networkConfig.putState(settings) @@ -221,7 +219,7 @@ module.exports = class NetworkController extends EventEmitter { nickname, } // setup networkConfig - let settings = { + var settings = { network: chainId, } settings = extend(settings, networks.networkList['rpc']) diff --git a/app/scripts/controllers/onboarding.js b/app/scripts/controllers/onboarding.js index 5d00fb7755..a29c8407a4 100644 --- a/app/scripts/controllers/onboarding.js +++ b/app/scripts/controllers/onboarding.js @@ -1,6 +1,5 @@ const ObservableStore = require('obs-store') const extend = require('xtend') -const log = require('loglevel') /** * @typedef {Object} InitState @@ -10,12 +9,11 @@ const log = require('loglevel') /** * @typedef {Object} OnboardingOptions * @property {InitState} initState The initial controller state - * @property {PreferencesController} preferencesController Controller for managing user perferences */ /** * Controller responsible for maintaining - * state related to onboarding + * a cache of account balances in local storage */ class OnboardingController { /** @@ -24,28 +22,10 @@ class OnboardingController { * @param {OnboardingOptions} [opts] Controller configuration parameters */ constructor (opts = {}) { - const initialTransientState = { - onboardingTabs: {}, - } - const initState = extend( - { - seedPhraseBackedUp: true, - }, - opts.initState, - initialTransientState, - ) + const initState = extend({ + seedPhraseBackedUp: true, + }, opts.initState) this.store = new ObservableStore(initState) - this.preferencesController = opts.preferencesController - this.completedOnboarding = this.preferencesController.store.getState().completedOnboarding - - this.preferencesController.store.subscribe(({ completedOnboarding }) => { - if (completedOnboarding !== this.completedOnboarding) { - this.completedOnboarding = completedOnboarding - if (completedOnboarding) { - this.store.updateState(initialTransientState) - } - } - }) } setSeedPhraseBackedUp (newSeedPhraseBackUpState) { @@ -58,24 +38,6 @@ class OnboardingController { return this.store.getState().seedPhraseBackedUp } - /** - * Registering a site as having initiated onboarding - * - * @param {string} location - The location of the site registering - * @param {string} tabId - The id of the tab registering - */ - async registerOnboarding (location, tabId) { - if (this.completedOnboarding) { - log.debug('Ignoring registerOnboarding; user already onboarded') - return - } - const onboardingTabs = Object.assign({}, this.store.getState().onboardingTabs) - if (!onboardingTabs[location] || onboardingTabs[location] !== tabId) { - log.debug(`Registering onboarding tab at location '${location}' with tabId '${tabId}'`) - onboardingTabs[location] = tabId - this.store.updateState({ onboardingTabs }) - } - } } module.exports = OnboardingController diff --git a/app/scripts/controllers/permissions/index.js b/app/scripts/controllers/permissions/index.js deleted file mode 100644 index 899044b13e..0000000000 --- a/app/scripts/controllers/permissions/index.js +++ /dev/null @@ -1,377 +0,0 @@ -const JsonRpcEngine = require('json-rpc-engine') -const asMiddleware = require('json-rpc-engine/src/asMiddleware') -const ObservableStore = require('obs-store') -const RpcCap = require('rpc-cap').CapabilitiesController -const { ethErrors } = require('eth-json-rpc-errors') - -const getRestrictedMethods = require('./restrictedMethods') -const createMethodMiddleware = require('./methodMiddleware') -const createLoggerMiddleware = require('./loggerMiddleware') - -// Methods that do not require any permissions to use: -const SAFE_METHODS = require('./permissions-safe-methods.json') - -// some constants -const METADATA_STORE_KEY = 'domainMetadata' -const LOG_STORE_KEY = 'permissionsLog' -const HISTORY_STORE_KEY = 'permissionsHistory' -const WALLET_METHOD_PREFIX = 'wallet_' -const CAVEAT_NAMES = { - exposedAccounts: 'exposedAccounts', -} -const ACCOUNTS_CHANGED_NOTIFICATION = 'wallet_accountsChanged' - -class PermissionsController { - - constructor ( - { - platform, notifyDomain, notifyAllDomains, keyringController, - } = {}, - restoredPermissions = {}, - restoredState = {}) { - this.store = new ObservableStore({ - [METADATA_STORE_KEY]: restoredState[METADATA_STORE_KEY] || {}, - [LOG_STORE_KEY]: restoredState[LOG_STORE_KEY] || [], - [HISTORY_STORE_KEY]: restoredState[HISTORY_STORE_KEY] || {}, - }) - this.notifyDomain = notifyDomain - this.notifyAllDomains = notifyAllDomains - this.keyringController = keyringController - this._platform = platform - this._restrictedMethods = getRestrictedMethods(this) - this._initializePermissions(restoredPermissions) - } - - createMiddleware ({ origin, extensionId }) { - - if (extensionId) { - this.store.updateState({ - [METADATA_STORE_KEY]: { - ...this.store.getState()[METADATA_STORE_KEY], - [origin]: { extensionId }, - }, - }) - } - - const engine = new JsonRpcEngine() - - engine.push(createLoggerMiddleware({ - walletPrefix: WALLET_METHOD_PREFIX, - restrictedMethods: Object.keys(this._restrictedMethods), - ignoreMethods: [ 'wallet_sendDomainMetadata' ], - store: this.store, - logStoreKey: LOG_STORE_KEY, - historyStoreKey: HISTORY_STORE_KEY, - })) - - engine.push(createMethodMiddleware({ - store: this.store, - storeKey: METADATA_STORE_KEY, - getAccounts: this.getAccounts.bind(this, origin), - requestAccountsPermission: this._requestPermissions.bind( - this, origin, { eth_accounts: {} } - ), - })) - - engine.push(this.permissions.providerMiddlewareFunction.bind( - this.permissions, { origin } - )) - return asMiddleware(engine) - } - - /** - * Returns the accounts that should be exposed for the given origin domain, - * if any. This method exists for when a trusted context needs to know - * which accounts are exposed to a given domain. - * - * @param {string} origin - The origin string. - */ - getAccounts (origin) { - return new Promise((resolve, _) => { - - const req = { method: 'eth_accounts' } - const res = {} - this.permissions.providerMiddlewareFunction( - { origin }, req, res, () => {}, _end - ) - - function _end () { - if (res.error || !Array.isArray(res.result)) { - resolve([]) - } else { - resolve(res.result) - } - } - }) - } - - /** - * Submits a permissions request to rpc-cap. Internal use only. - * - * @param {string} origin - The origin string. - * @param {IRequestedPermissions} permissions - The requested permissions. - */ - _requestPermissions (origin, permissions) { - return new Promise((resolve, reject) => { - - const req = { method: 'wallet_requestPermissions', params: [permissions] } - const res = {} - this.permissions.providerMiddlewareFunction( - { origin }, req, res, () => {}, _end - ) - - function _end (err) { - if (err || res.error) { - reject(err || res.error) - } else { - resolve(res.result) - } - } - }) - } - - /** - * User approval callback. The request can fail if the request is invalid. - * - * @param {object} approved the approved request object - */ - async approvePermissionsRequest (approved, accounts) { - - const { id } = approved.metadata - const approval = this.pendingApprovals[id] - - try { - - // attempt to finalize the request and resolve it - await this.finalizePermissionsRequest(approved.permissions, accounts) - approval.resolve(approved.permissions) - - } catch (err) { - - // if finalization fails, reject the request - approval.reject(ethErrors.rpc.invalidRequest({ - message: err.message, data: err, - })) - } - - delete this.pendingApprovals[id] - } - - /** - * User rejection callback. - * - * @param {string} id the id of the rejected request - */ - async rejectPermissionsRequest (id) { - const approval = this.pendingApprovals[id] - approval.reject(ethErrors.provider.userRejectedRequest()) - delete this.pendingApprovals[id] - } - - /** - * Grants the given origin the eth_accounts permission for the given account(s). - * This method should ONLY be called as a result of direct user action in the UI, - * with the intention of supporting legacy dapps that don't support EIP 1102. - * - * @param {string} origin - The origin to expose the account(s) to. - * @param {Array} accounts - The account(s) to expose. - */ - async legacyExposeAccounts (origin, accounts) { - - const permissions = { - eth_accounts: {}, - } - - await this.finalizePermissionsRequest(permissions, accounts) - - let error - try { - await new Promise((resolve, reject) => { - this.permissions.grantNewPermissions(origin, permissions, {}, err => err ? resolve() : reject(err)) - }) - } catch (err) { - error = err - } - - if (error) { - if (error.code === 4001) { - throw error - } else { - throw ethErrors.rpc.internal({ - message: `Failed to add 'eth_accounts' to '${origin}'.`, - data: { - originalError: error, - accounts, - }, - }) - } - } - } - - /** - * Update the accounts exposed to the given origin. - * Throws error if the update fails. - * - * @param {string} origin - The origin to change the exposed accounts for. - * @param {string[]} accounts - The new account(s) to expose. - */ - async updateExposedAccounts (origin, accounts) { - - await this.validateExposedAccounts(accounts) - - this.permissions.updateCaveatFor( - origin, 'eth_accounts', CAVEAT_NAMES.exposedAccounts, accounts - ) - - this.notifyDomain(origin, { - method: ACCOUNTS_CHANGED_NOTIFICATION, - result: accounts, - }) - } - - /** - * Finalizes a permissions request. - * Throws if request validation fails. - * - * @param {Object} requestedPermissions - The requested permissions. - * @param {string[]} accounts - The accounts to expose, if any. - */ - async finalizePermissionsRequest (requestedPermissions, accounts) { - - const { eth_accounts: ethAccounts } = requestedPermissions - - if (ethAccounts) { - - await this.validateExposedAccounts(accounts) - - if (!ethAccounts.caveats) { - ethAccounts.caveats = [] - } - - // caveat names are unique, and we will only construct this caveat here - ethAccounts.caveats = ethAccounts.caveats.filter(c => ( - c.name !== CAVEAT_NAMES.exposedAccounts - )) - - ethAccounts.caveats.push( - { - type: 'filterResponse', - value: accounts, - name: CAVEAT_NAMES.exposedAccounts, - }, - ) - } - } - - /** - * Validate an array of accounts representing accounts to be exposed - * to a domain. Throws error if validation fails. - * - * @param {string[]} accounts - An array of addresses. - */ - async validateExposedAccounts (accounts) { - - if (!Array.isArray(accounts) || accounts.length === 0) { - throw new Error('Must provide non-empty array of account(s).') - } - - // assert accounts exist - const allAccounts = await this.keyringController.getAccounts() - accounts.forEach(acc => { - if (!allAccounts.includes(acc)) { - throw new Error(`Unknown account: ${acc}`) - } - }) - } - - /** - * Removes the given permissions for the given domain. - * @param {object} domains { origin: [permissions] } - */ - removePermissionsFor (domains) { - - Object.entries(domains).forEach(([origin, perms]) => { - - this.permissions.removePermissionsFor( - origin, - perms.map(methodName => { - - if (methodName === 'eth_accounts') { - this.notifyDomain( - origin, - { method: ACCOUNTS_CHANGED_NOTIFICATION, result: [] } - ) - } - - return { parentCapability: methodName } - }) - ) - }) - } - - /** - * Removes all known domains and their related permissions. - */ - clearPermissions () { - this.permissions.clearDomains() - this.notifyAllDomains({ - method: ACCOUNTS_CHANGED_NOTIFICATION, - result: [], - }) - } - - /** - * A convenience method for retrieving a login object - * or creating a new one if needed. - * - * @param {string} origin = The origin string representing the domain. - */ - _initializePermissions (restoredState) { - - // these permission requests are almost certainly stale - const initState = { ...restoredState, permissionsRequests: [] } - - this.pendingApprovals = {} - - this.permissions = new RpcCap({ - - // Supports passthrough methods: - safeMethods: SAFE_METHODS, - - // optional prefix for internal methods - methodPrefix: WALLET_METHOD_PREFIX, - - restrictedMethods: this._restrictedMethods, - - /** - * A promise-returning callback used to determine whether to approve - * permissions requests or not. - * - * Currently only returns a boolean, but eventually should return any - * specific parameters or amendments to the permissions. - * - * @param {string} req - The internal rpc-cap user request object. - */ - requestUserApproval: async (req) => { - const { metadata: { id } } = req - - this._platform.openExtensionInBrowser('connect') - - return new Promise((resolve, reject) => { - this.pendingApprovals[id] = { resolve, reject } - }) - }, - }, initState) - } -} - -module.exports = { - PermissionsController, - addInternalMethodPrefix: prefix, - CAVEAT_NAMES, -} - - -function prefix (method) { - return WALLET_METHOD_PREFIX + method -} diff --git a/app/scripts/controllers/permissions/loggerMiddleware.js b/app/scripts/controllers/permissions/loggerMiddleware.js deleted file mode 100644 index 0e3c7f3932..0000000000 --- a/app/scripts/controllers/permissions/loggerMiddleware.js +++ /dev/null @@ -1,169 +0,0 @@ - -const clone = require('clone') -const { isValidAddress } = require('ethereumjs-util') - -const LOG_LIMIT = 100 - -/** - * Create middleware for logging requests and responses to restricted and - * permissions-related methods. - */ -module.exports = function createLoggerMiddleware ({ - walletPrefix, restrictedMethods, store, logStoreKey, historyStoreKey, ignoreMethods, -}) { - return (req, res, next, _end) => { - let activityEntry, requestedMethods - const { origin, method } = req - const isInternal = method.startsWith(walletPrefix) - if ((isInternal || restrictedMethods.includes(method)) && !ignoreMethods.includes(method)) { - activityEntry = logActivity(req, isInternal) - if (method === `${walletPrefix}requestPermissions`) { - requestedMethods = getRequestedMethods(req) - } - } else if (method === 'eth_requestAccounts') { - activityEntry = logActivity(req, isInternal) - requestedMethods = [ 'eth_accounts' ] - } else { - return next() - } - - next(cb => { - const time = Date.now() - addResponse(activityEntry, res, time) - if (!res.error && requestedMethods) { - logHistory(requestedMethods, origin, res.result, time, method === 'eth_requestAccounts') - } - cb() - }) - } - - function logActivity (request, isInternal) { - const activityEntry = { - id: request.id, - method: request.method, - methodType: isInternal ? 'internal' : 'restricted', - origin: request.origin, - request: cloneObj(request), - requestTime: Date.now(), - response: null, - responseTime: null, - success: null, - } - commitActivity(activityEntry) - return activityEntry - } - - function addResponse (activityEntry, response, time) { - if (!response) { - return - } - activityEntry.response = cloneObj(response) - activityEntry.responseTime = time - activityEntry.success = !response.error - } - - function commitActivity (entry) { - const logs = store.getState()[logStoreKey] - if (logs.length > LOG_LIMIT - 2) { - logs.pop() - } - logs.push(entry) - store.updateState({ [logStoreKey]: logs }) - } - - function getRequestedMethods (request) { - if ( - !request.params || - typeof request.params[0] !== 'object' || - Array.isArray(request.params[0]) - ) { - return null - } - return Object.keys(request.params[0]) - } - - function logHistory (requestedMethods, origin, result, time, isEthRequestAccounts) { - let accounts, entries - if (isEthRequestAccounts) { - accounts = result - const accountToTimeMap = accounts.reduce((acc, account) => ({ ...acc, [account]: time }), {}) - entries = { 'eth_accounts': { accounts: accountToTimeMap, lastApproved: time } } - } else { - entries = result - ? result - .map(perm => { - if (perm.parentCapability === 'eth_accounts') { - accounts = getAccountsFromPermission(perm) - } - return perm.parentCapability - }) - .reduce((acc, m) => { - if (requestedMethods.includes(m)) { - if (m === 'eth_accounts') { - const accountToTimeMap = accounts.reduce((acc, account) => ({ ...acc, [account]: time }), {}) - acc[m] = { lastApproved: time, accounts: accountToTimeMap } - } else { - acc[m] = { lastApproved: time } - } - } - return acc - }, {}) - : {} - } - - if (Object.keys(entries).length > 0) { - commitHistory(origin, entries) - } - } - - function commitHistory (origin, entries) { - const history = store.getState()[historyStoreKey] || {} - const newOriginHistory = { - ...history[origin], - ...entries, - } - - if (history[origin] && history[origin]['eth_accounts'] && entries['eth_accounts']) { - newOriginHistory['eth_accounts'] = { - lastApproved: entries['eth_accounts'].lastApproved, - accounts: { - ...history[origin]['eth_accounts'].accounts, - ...entries['eth_accounts'].accounts, - }, - } - } - - history[origin] = newOriginHistory - - store.updateState({ [historyStoreKey]: history }) - } -} - -// the call to clone is set to disallow circular references -// we attempt cloning at a depth of 3 and 2, then return a -// shallow copy of the object -function cloneObj (obj) { - for (let i = 3; i > 1; i--) { - try { - return clone(obj, false, i) - } catch (_) {} - } - return { ...obj } -} - -function getAccountsFromPermission (perm) { - if (perm.parentCapability !== 'eth_accounts' || !perm.caveats) { - return [] - } - const accounts = {} - for (const c of perm.caveats) { - if (c.type === 'filterResponse' && Array.isArray(c.value)) { - for (const v of c.value) { - if (isValidAddress(v)) { - accounts[v] = true - } - } - } - } - return Object.keys(accounts) -} diff --git a/app/scripts/controllers/permissions/methodMiddleware.js b/app/scripts/controllers/permissions/methodMiddleware.js deleted file mode 100644 index e0b451c9f6..0000000000 --- a/app/scripts/controllers/permissions/methodMiddleware.js +++ /dev/null @@ -1,90 +0,0 @@ - -const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware') -const { ethErrors } = require('eth-json-rpc-errors') - -/** - * Create middleware for handling certain methods and preprocessing permissions requests. - */ -module.exports = function createMethodMiddleware ({ - store, storeKey, getAccounts, requestAccountsPermission, -}) { - return createAsyncMiddleware(async (req, res, next) => { - - if (typeof req.method !== 'string') { - res.error = ethErrors.rpc.invalidRequest({ data: req}) - return - } - - switch (req.method) { - - // intercepting eth_accounts requests for backwards compatibility, - // i.e. return an empty array instead of an error - case 'eth_accounts': - - res.result = await getAccounts() - return - - case 'eth_requestAccounts': - - // first, just try to get accounts - let accounts = await getAccounts() - if (accounts.length > 0) { - res.result = accounts - return - } - - // if no accounts, request the accounts permission - try { - await requestAccountsPermission() - } catch (err) { - res.error = err - return - } - - // get the accounts again - accounts = await getAccounts() - if (accounts.length > 0) { - res.result = accounts - } else { - // this should never happen - res.error = ethErrors.rpc.internal( - 'Accounts unexpectedly unavailable. Please report this bug.' - ) - } - - return - - // custom method for getting metadata from the requesting domain - case 'wallet_sendDomainMetadata': - - const storeState = store.getState()[storeKey] - const extensionId = storeState[req.origin] - ? storeState[req.origin].extensionId - : undefined - - if ( - req.domainMetadata && - typeof req.domainMetadata.name === 'string' - ) { - - store.updateState({ - [storeKey]: { - ...storeState, - [req.origin]: { - extensionId, - ...req.domainMetadata, - }, - }, - }) - } - - res.result = true - return - - default: - break - } - - next() - }) -} diff --git a/app/scripts/controllers/permissions/permissions-safe-methods.json b/app/scripts/controllers/permissions/permissions-safe-methods.json deleted file mode 100644 index 17b46b5313..0000000000 --- a/app/scripts/controllers/permissions/permissions-safe-methods.json +++ /dev/null @@ -1,49 +0,0 @@ -[ - "web3_sha3", - "net_listening", - "net_peerCount", - "net_version", - "eth_blockNumber", - "eth_call", - "eth_chainId", - "eth_coinbase", - "eth_estimateGas", - "eth_gasPrice", - "eth_getBalance", - "eth_getBlockByHash", - "eth_getBlockByNumber", - "eth_getBlockTransactionCountByHash", - "eth_getBlockTransactionCountByNumber", - "eth_getCode", - "eth_getFilterChanges", - "eth_getFilterLogs", - "eth_getLogs", - "eth_getStorageAt", - "eth_getTransactionByBlockHashAndIndex", - "eth_getTransactionByBlockNumberAndIndex", - "eth_getTransactionByHash", - "eth_getTransactionCount", - "eth_getTransactionReceipt", - "eth_getUncleByBlockHashAndIndex", - "eth_getUncleByBlockNumberAndIndex", - "eth_getUncleCountByBlockHash", - "eth_getUncleCountByBlockNumber", - "eth_getWork", - "eth_hashrate", - "eth_mining", - "eth_newBlockFilter", - "eth_newFilter", - "eth_newPendingTransactionFilter", - "eth_protocolVersion", - "eth_sendRawTransaction", - "eth_sendTransaction", - "eth_sign", - "personal_sign", - "eth_signTypedData", - "eth_signTypedData_v1", - "eth_signTypedData_v3", - "eth_submitHashrate", - "eth_submitWork", - "eth_syncing", - "eth_uninstallFilter" -] \ No newline at end of file diff --git a/app/scripts/controllers/permissions/restrictedMethods.js b/app/scripts/controllers/permissions/restrictedMethods.js deleted file mode 100644 index cd87b2d574..0000000000 --- a/app/scripts/controllers/permissions/restrictedMethods.js +++ /dev/null @@ -1,20 +0,0 @@ - -module.exports = function getRestrictedMethods (permissionsController) { - return { - - 'eth_accounts': { - description: 'View the address of the selected account', - method: (_, res, __, end) => { - permissionsController.keyringController.getAccounts() - .then((accounts) => { - res.result = accounts - end() - }) - .catch((err) => { - res.error = err - end(err) - }) - }, - }, - } -} diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 409ce68766..1cfbb4d4c2 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -1,5 +1,4 @@ const ObservableStore = require('obs-store') -const { addInternalMethodPrefix } = require('./permissions') const normalizeAddress = require('eth-sig-util').normalize const { isValidAddress, sha3, bufferToHex } = require('ethereumjs-util') const extend = require('xtend') @@ -58,6 +57,7 @@ class PreferencesController { useNativeCurrencyAsPrimaryCurrency: true, }, completedOnboarding: false, + migratedPrivacyMode: false, metaMetricsId: null, metaMetricsSendCount: 0, }, opts.initState) @@ -187,10 +187,7 @@ class PreferencesController { * @param {Function} - end */ async requestWatchAsset (req, res, next, end) { - if ( - req.method === 'metamask_watchAsset' || - req.method === addInternalMethodPrefix('watchAsset') - ) { + if (req.method === 'metamask_watchAsset' || req.method === 'wallet_watchAsset') { const { type, options } = req.params switch (type) { case 'ERC20': @@ -306,9 +303,7 @@ class PreferencesController { const accountTokens = this.store.getState().accountTokens addresses.forEach((address) => { // skip if already exists - if (identities[address]) { - return - } + if (identities[address]) return // add missing identity const identityCount = Object.keys(identities).length @@ -340,9 +335,7 @@ class PreferencesController { if (Object.keys(newlyLost).length > 0) { // Notify our servers: - if (this.diagnostics) { - this.diagnostics.reportOrphans(newlyLost) - } + if (this.diagnostics) this.diagnostics.reportOrphans(newlyLost) // store lost accounts for (const key in newlyLost) { @@ -470,9 +463,7 @@ class PreferencesController { * @return {Promise} */ setAccountLabel (account, label) { - if (!account) { - throw new Error('setAccountLabel requires a valid address, got ' + String(account)) - } + if (!account) throw new Error('setAccountLabel requires a valid address, got ' + String(account)) const address = normalizeAddress(account) const {identities} = this.store.getState() identities[address] = identities[address] || {} @@ -509,9 +500,7 @@ class PreferencesController { updateRpc (newRpcDetails) { const rpcList = this.getFrequentRpcListDetail() - const index = rpcList.findIndex((element) => { - return element.rpcUrl === newRpcDetails.rpcUrl - }) + const index = rpcList.findIndex((element) => { return element.rpcUrl === newRpcDetails.rpcUrl }) if (index > -1) { const rpcDetail = rpcList[index] const updatedRpc = extend(rpcDetail, newRpcDetails) @@ -535,9 +524,7 @@ class PreferencesController { */ addToFrequentRpcList (url, chainId, ticker = 'ETH', nickname = '', rpcPrefs = {}) { const rpcList = this.getFrequentRpcListDetail() - const index = rpcList.findIndex((element) => { - return element.rpcUrl === url - }) + const index = rpcList.findIndex((element) => { return element.rpcUrl === url }) if (index !== -1) { rpcList.splice(index, 1) } @@ -561,9 +548,7 @@ class PreferencesController { */ removeFromFrequentRpcList (url) { const rpcList = this.getFrequentRpcListDetail() - const index = rpcList.findIndex((element) => { - return element.rpcUrl === url - }) + const index = rpcList.findIndex((element) => { return element.rpcUrl === url }) if (index !== -1) { rpcList.splice(index, 1) } @@ -647,6 +632,13 @@ class PreferencesController { return Promise.resolve(true) } + unsetMigratedPrivacyMode () { + this.store.updateState({ + migratedPrivacyMode: false, + }) + return Promise.resolve() + } + // // PRIVATE METHODS // @@ -695,16 +687,10 @@ class PreferencesController { */ _getTokenRelatedStates (selectedAddress) { const accountTokens = this.store.getState().accountTokens - if (!selectedAddress) { - selectedAddress = this.store.getState().selectedAddress - } + if (!selectedAddress) selectedAddress = this.store.getState().selectedAddress const providerType = this.network.providerStore.getState().type - if (!(selectedAddress in accountTokens)) { - accountTokens[selectedAddress] = {} - } - if (!(providerType in accountTokens[selectedAddress])) { - accountTokens[selectedAddress][providerType] = [] - } + if (!(selectedAddress in accountTokens)) accountTokens[selectedAddress] = {} + if (!(providerType in accountTokens[selectedAddress])) accountTokens[selectedAddress][providerType] = [] const tokens = accountTokens[selectedAddress][providerType] return { tokens, accountTokens, providerType, selectedAddress } } @@ -741,19 +727,13 @@ class PreferencesController { */ _validateERC20AssetParams (opts) { const { rawAddress, symbol, decimals } = opts - if (!rawAddress || !symbol || typeof decimals === 'undefined') { - throw new Error(`Cannot suggest token without address, symbol, and decimals`) - } - if (!(symbol.length < 7)) { - throw new Error(`Invalid symbol ${symbol} more than six characters`) - } + if (!rawAddress || !symbol || typeof decimals === 'undefined') throw new Error(`Cannot suggest token without address, symbol, and decimals`) + if (!(symbol.length < 7)) throw new Error(`Invalid symbol ${symbol} more than six characters`) const numDecimals = parseInt(decimals, 10) if (isNaN(numDecimals) || numDecimals > 36 || numDecimals < 0) { throw new Error(`Invalid decimals ${decimals} must be at least 0, and not over 36`) } - if (!isValidAddress(rawAddress)) { - throw new Error(`Invalid address ${rawAddress}`) - } + if (!isValidAddress(rawAddress)) throw new Error(`Invalid address ${rawAddress}`) } } diff --git a/app/scripts/controllers/provider-approval.js b/app/scripts/controllers/provider-approval.js new file mode 100644 index 0000000000..00ff626f71 --- /dev/null +++ b/app/scripts/controllers/provider-approval.js @@ -0,0 +1,175 @@ +const ObservableStore = require('obs-store') +const SafeEventEmitter = require('safe-event-emitter') +const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware') +const { errors: rpcErrors } = require('eth-json-rpc-errors') + +/** + * A controller that services user-approved requests for a full Ethereum provider API + */ +class ProviderApprovalController extends SafeEventEmitter { + /** + * Creates a ProviderApprovalController + * + * @param {Object} [config] - Options to configure controller + */ + constructor ({ closePopup, initState, keyringController, openPopup, preferencesController } = {}) { + super() + this.closePopup = closePopup + this.keyringController = keyringController + this.openPopup = openPopup + this.preferencesController = preferencesController + this.memStore = new ObservableStore({ + providerRequests: [], + }) + + const defaultState = { approvedOrigins: {} } + this.store = new ObservableStore(Object.assign(defaultState, initState)) + } + + /** + * Called when a user approves access to a full Ethereum provider API + * + * @param {object} opts - opts for the middleware contains the origin for the middleware + */ + createMiddleware ({ senderUrl, extensionId, getSiteMetadata }) { + return createAsyncMiddleware(async (req, res, next) => { + // only handle requestAccounts + if (req.method !== 'eth_requestAccounts') return next() + // if already approved or privacy mode disabled, return early + const isUnlocked = this.keyringController.memStore.getState().isUnlocked + const origin = senderUrl.hostname + if (this.shouldExposeAccounts(origin) && isUnlocked) { + res.result = [this.preferencesController.getSelectedAddress()] + return + } + // register the provider request + const metadata = { hostname: senderUrl.hostname, origin } + if (extensionId) { + metadata.extensionId = extensionId + } else { + const siteMetadata = await getSiteMetadata(origin) + Object.assign(metadata, { siteTitle: siteMetadata.name, siteImage: siteMetadata.icon}) + } + this._handleProviderRequest(metadata) + // wait for resolution of request + const approved = await new Promise(resolve => this.once(`resolvedRequest:${origin}`, ({ approved }) => resolve(approved))) + if (approved) { + res.result = [this.preferencesController.getSelectedAddress()] + } else { + throw rpcErrors.eth.userRejectedRequest('User denied account authorization') + } + }) + } + + /** + * @typedef {Object} SiteMetadata + * @param {string} hostname - The hostname of the site + * @param {string} origin - The origin of the site + * @param {string} [siteTitle] - The title of the site + * @param {string} [siteImage] - The icon for the site + * @param {string} [extensionId] - The extension ID of the extension + */ + /** + * Called when a tab requests access to a full Ethereum provider API + * + * @param {SiteMetadata} siteMetadata - The metadata for the site requesting full provider access + */ + _handleProviderRequest (siteMetadata) { + const { providerRequests } = this.memStore.getState() + const origin = siteMetadata.origin + this.memStore.updateState({ + providerRequests: [ + ...providerRequests, + siteMetadata, + ], + }) + const isUnlocked = this.keyringController.memStore.getState().isUnlocked + const { approvedOrigins } = this.store.getState() + const originAlreadyHandled = approvedOrigins[origin] + if (originAlreadyHandled && isUnlocked) { + return + } + this.openPopup && this.openPopup() + } + + /** + * Called when a user approves access to a full Ethereum provider API + * + * @param {string} origin - origin of the domain that had provider access approved + */ + approveProviderRequestByOrigin (origin) { + if (this.closePopup) { + this.closePopup() + } + + const { approvedOrigins } = this.store.getState() + const { providerRequests } = this.memStore.getState() + const providerRequest = providerRequests.find((request) => request.origin === origin) + const remainingProviderRequests = providerRequests.filter(request => request.origin !== origin) + this.store.updateState({ + approvedOrigins: { + ...approvedOrigins, + [origin]: { + siteTitle: providerRequest ? providerRequest.siteTitle : null, + siteImage: providerRequest ? providerRequest.siteImage : null, + hostname: providerRequest ? providerRequest.hostname : null, + }, + }, + }) + this.memStore.updateState({ providerRequests: remainingProviderRequests }) + this.emit(`resolvedRequest:${origin}`, { approved: true }) + } + + /** + * Called when a tab rejects access to a full Ethereum provider API + * + * @param {string} origin - origin of the domain that had provider access approved + */ + rejectProviderRequestByOrigin (origin) { + if (this.closePopup) { + this.closePopup() + } + + const { approvedOrigins } = this.store.getState() + const { providerRequests } = this.memStore.getState() + const remainingProviderRequests = providerRequests.filter(request => request.origin !== origin) + + // We're cloning and deleting keys here because we don't want to keep unneeded keys + const _approvedOrigins = Object.assign({}, approvedOrigins) + delete _approvedOrigins[origin] + + this.store.putState({ approvedOrigins: _approvedOrigins }) + this.memStore.putState({ providerRequests: remainingProviderRequests }) + this.emit(`resolvedRequest:${origin}`, { approved: false }) + } + + /** + * Clears any approvals for user-approved origins + */ + clearApprovedOrigins () { + this.store.updateState({ + approvedOrigins: {}, + }) + } + + /** + * Determines if a given origin should have accounts exposed + * + * @param {string} origin - Domain origin to check for approval status + * @returns {boolean} - True if the origin has been approved + */ + shouldExposeAccounts (origin) { + return Boolean(this.store.getState().approvedOrigins[origin]) + } + + /** + * Returns a merged state representation + * @return {object} + * @private + */ + _getMergedState () { + return Object.assign({}, this.memStore.getState(), this.store.getState()) + } +} + +module.exports = ProviderApprovalController diff --git a/app/scripts/controllers/recent-blocks.js b/app/scripts/controllers/recent-blocks.js index 9e5a384a9f..a2b5d1bae6 100644 --- a/app/scripts/controllers/recent-blocks.js +++ b/app/scripts/controllers/recent-blocks.js @@ -90,9 +90,7 @@ class RecentBlocksController { async processBlock (newBlockNumberHex) { const newBlockNumber = Number.parseInt(newBlockNumberHex, 16) const newBlock = await this.getBlockByNumber(newBlockNumber, true) - if (!newBlock) { - return - } + if (!newBlock) return const block = this.mapTransactionsToPrices(newBlock) @@ -164,9 +162,7 @@ class RecentBlocksController { await Promise.all(targetBlockNumbers.map(async (targetBlockNumber) => { try { const newBlock = await this.getBlockByNumber(targetBlockNumber, true) - if (!newBlock) { - return - } + if (!newBlock) return this.backfillBlock(newBlock) } catch (e) { diff --git a/app/scripts/controllers/threebox.js b/app/scripts/controllers/threebox.js index 8226fb6b1f..5bcab29ed4 100644 --- a/app/scripts/controllers/threebox.js +++ b/app/scripts/controllers/threebox.js @@ -28,9 +28,7 @@ class ThreeBoxController { this.provider = this._createProvider({ version, getAccounts: async ({ origin }) => { - if (origin !== '3Box') { - return [] - } + if (origin !== '3Box') { return [] } const isUnlocked = getKeyringControllerState().isUnlocked const accounts = await this.keyringController.getAccounts() diff --git a/app/scripts/controllers/token-rates.js b/app/scripts/controllers/token-rates.js index 7c8526c343..9b86a9ebf0 100644 --- a/app/scripts/controllers/token-rates.js +++ b/app/scripts/controllers/token-rates.js @@ -28,9 +28,7 @@ class TokenRatesController { * Updates exchange rates for all tokens */ async updateExchangeRates () { - if (!this.isActive) { - return - } + if (!this.isActive) { return } const contractExchangeRates = {} const nativeCurrency = this.currency ? this.currency.state.nativeCurrency.toLowerCase() : 'eth' const pairs = this._tokens.map(token => token.address).join(',') @@ -55,12 +53,8 @@ class TokenRatesController { */ set interval (interval) { this._handle && clearInterval(this._handle) - if (!interval) { - return - } - this._handle = setInterval(() => { - this.updateExchangeRates() - }, interval) + if (!interval) { return } + this._handle = setInterval(() => { this.updateExchangeRates() }, interval) } /** @@ -68,14 +62,10 @@ class TokenRatesController { */ set preferences (preferences) { this._preferences && this._preferences.unsubscribe() - if (!preferences) { - return - } + if (!preferences) { return } this._preferences = preferences this.tokens = preferences.getState().tokens - preferences.subscribe(({ tokens = [] }) => { - this.tokens = tokens - }) + preferences.subscribe(({ tokens = [] }) => { this.tokens = tokens }) } /** diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 85733aa382..df9fb65023 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -3,7 +3,7 @@ const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') const Transaction = require('ethereumjs-tx') const EthQuery = require('ethjs-query') -const { ethErrors } = require('eth-json-rpc-errors') +const { errors: rpcErrors } = require('eth-json-rpc-errors') const abi = require('human-standard-token-abi') const abiDecoder = require('abi-decoder') abiDecoder.addABI(abi) @@ -54,7 +54,6 @@ const { hexToBn, bnToHex, BnMultiplyByFraction } = require('../../lib/util') @param {Object} opts.blockTracker - An instance of eth-blocktracker @param {Object} opts.provider - A network provider. @param {Function} opts.signTransaction - function the signs an ethereumjs-tx - @param {object} opts.getPermittedAccounts - get accounts that an origin has permissions for @param {Function} [opts.getGasPrice] - optional gas price calculator @param {Function} opts.signTransaction - ethTx signer that returns a rawTx @param {Number} [opts.txHistoryLimit] - number *optional* for limiting how many transactions are in state @@ -67,7 +66,6 @@ class TransactionController extends EventEmitter { this.networkStore = opts.networkStore || new ObservableStore({}) this.preferencesStore = opts.preferencesStore || new ObservableStore({}) this.provider = opts.provider - this.getPermittedAccounts = opts.getPermittedAccounts this.blockTracker = opts.blockTracker this.signEthTx = opts.signTransaction this.getGasPrice = opts.getGasPrice @@ -135,7 +133,7 @@ class TransactionController extends EventEmitter { /** Adds a tx to the txlist @emits ${txMeta.id}:unapproved - */ +*/ addTx (txMeta) { this.txStateManager.addTx(txMeta) this.emit(`${txMeta.id}:unapproved`, txMeta) @@ -150,18 +148,18 @@ class TransactionController extends EventEmitter { } /** - * Add a new unapproved transaction to the pipeline - * - * @returns {Promise} the hash of the transaction after being submitted to the network - * @param txParams {object} - txParams for the transaction - * @param opts {object} - with the key origin to put the origin on the txMeta + add a new unapproved transaction to the pipeline + + @returns {Promise} the hash of the transaction after being submitted to the network + @param txParams {object} - txParams for the transaction + @param opts {object} - with the key origin to put the origin on the txMeta */ - async newUnapprovedTransaction (txParams, opts = {}) { + async newUnapprovedTransaction (txParams, opts = {}) { log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`) - - const initialTxMeta = await this.addUnapprovedTransaction(txParams, opts.origin) - + const initialTxMeta = await this.addUnapprovedTransaction(txParams) + initialTxMeta.origin = opts.origin + this.txStateManager.updateTx(initialTxMeta, '#newUnapprovedTransaction - adding the origin') // listen for tx completion (success, fail) return new Promise((resolve, reject) => { this.txStateManager.once(`${initialTxMeta.id}:finished`, (finishedTxMeta) => { @@ -169,64 +167,38 @@ class TransactionController extends EventEmitter { case 'submitted': return resolve(finishedTxMeta.hash) case 'rejected': - return reject(cleanErrorStack(ethErrors.provider.userRejectedRequest('MetaMask Tx Signature: User denied transaction signature.'))) + return reject(cleanErrorStack(rpcErrors.eth.userRejectedRequest('MetaMask Tx Signature: User denied transaction signature.'))) case 'failed': - return reject(cleanErrorStack(ethErrors.rpc.internal(finishedTxMeta.err.message))) + return reject(cleanErrorStack(rpcErrors.internal(finishedTxMeta.err.message))) default: - return reject(cleanErrorStack(ethErrors.rpc.internal(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`))) + return reject(cleanErrorStack(rpcErrors.internal(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`))) } }) }) } /** - * Validates and generates a txMeta with defaults and puts it in txStateManager - * store. - * - * @returns {txMeta} - */ - async addUnapprovedTransaction (txParams, origin) { + Validates and generates a txMeta with defaults and puts it in txStateManager + store + @returns {txMeta} + */ + + async addUnapprovedTransaction (txParams) { // validate const normalizedTxParams = txUtils.normalizeTxParams(txParams) - + // Assert the from address is the selected address + if (normalizedTxParams.from !== this.getSelectedAddress()) { + throw new Error(`Transaction from address isn't valid for this account`) + } txUtils.validateTxParams(normalizedTxParams) - /** - `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`. - */ + // construct txMeta + const { transactionCategory, getCodeResponse } = await this._determineTransactionCategory(txParams) let txMeta = this.txStateManager.generateTxMeta({ txParams: normalizedTxParams, type: TRANSACTION_TYPE_STANDARD, + transactionCategory, }) - - if (origin === 'metamask') { - // Assert the from address is the selected address - if (normalizedTxParams.from !== this.getSelectedAddress()) { - throw ethErrors.rpc.internal({ - message: `Internally initiated transaction is using invalid account.`, - data: { - origin, - fromAddress: normalizedTxParams.from, - selectedAddress: this.getSelectedAddress(), - }, - }) - } - } else { - // Assert that the origin has permissions to initiate transactions from - // the specified address - const permittedAddresses = await this.getPermittedAccounts(origin) - if (!permittedAddresses.includes(normalizedTxParams.from)) { - throw ethErrors.provider.unauthorized({ data: { origin }}) - } - } - - txMeta['origin'] = origin - - const { transactionCategory, getCodeResponse } = await this._determineTransactionCategory(txParams) - txMeta.transactionCategory = transactionCategory this.addTx(txMeta) this.emit('newUnapprovedTx', txMeta) @@ -245,16 +217,15 @@ class TransactionController extends EventEmitter { txMeta.loadingDefaults = false // save txMeta - this.txStateManager.updateTx(txMeta, 'Added new unapproved transaction.') + this.txStateManager.updateTx(txMeta) return txMeta } - /** - * Adds the tx gas defaults: gas && gasPrice - * @param txMeta {Object} - the txMeta object - * @returns {Promise} resolves with txMeta - */ + adds the tx gas defaults: gas && gasPrice + @param txMeta {Object} - the txMeta object + @returns {Promise} resolves with txMeta +*/ async addTxGasDefaults (txMeta, getCodeResponse) { const txParams = txMeta.txParams // ensure value @@ -431,16 +402,13 @@ class TransactionController extends EventEmitter { log.error(err) } // must set transaction to submitted/failed before releasing lock - if (nonceLock) { - nonceLock.releaseLock() - } + if (nonceLock) nonceLock.releaseLock() // continue with error chain throw err } finally { this.inProcessOfSigning.delete(txId) } } - /** adds the chain id and signs the transaction and set the status to signed @param txId {number} - the tx's Id @@ -635,9 +603,7 @@ class TransactionController extends EventEmitter { } }) this.pendingTxTracker.on('tx:retry', (txMeta) => { - if (!('retryCount' in txMeta)) { - txMeta.retryCount = 0 - } + if (!('retryCount' in txMeta)) txMeta.retryCount = 0 txMeta.retryCount++ this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:retry') }) @@ -691,14 +657,10 @@ class TransactionController extends EventEmitter { const txMeta = this.txStateManager.getTx(txId) const { nonce, from } = txMeta.txParams const sameNonceTxs = this.txStateManager.getFilteredTxList({nonce, from}) - if (!sameNonceTxs.length) { - return - } + if (!sameNonceTxs.length) return // mark all same nonce transactions as dropped and give i a replacedBy hash sameNonceTxs.forEach((otherTxMeta) => { - if (otherTxMeta.id === txId) { - return - } + if (otherTxMeta.id === txId) return otherTxMeta.replacedBy = txMeta.hash this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:confirmed reference to confirmed txHash with same nonce') this.txStateManager.setTxStatusDropped(otherTxMeta.id) diff --git a/app/scripts/controllers/transactions/lib/tx-state-history-helper.js b/app/scripts/controllers/transactions/lib/tx-state-history-helper.js index 3cc76e6179..76fc5c35b3 100644 --- a/app/scripts/controllers/transactions/lib/tx-state-history-helper.js +++ b/app/scripts/controllers/transactions/lib/tx-state-history-helper.js @@ -18,9 +18,7 @@ function migrateFromSnapshotsToDiffs (longHistory) { longHistory // convert non-initial history entries into diffs .map((entry, index) => { - if (index === 0) { - return entry - } + if (index === 0) return entry return generateHistoryEntry(longHistory[index - 1], entry) }) ) @@ -42,9 +40,7 @@ function generateHistoryEntry (previousState, newState, note) { const entry = jsonDiffer.compare(previousState, newState) // Add a note to the first op, since it breaks if we append it to the entry if (entry[0]) { - if (note) { - entry[0].note = note - } + if (note) entry[0].note = note entry[0].timestamp = Date.now() } diff --git a/app/scripts/controllers/transactions/lib/util.js b/app/scripts/controllers/transactions/lib/util.js index 86924e7faf..0d2ddddef9 100644 --- a/app/scripts/controllers/transactions/lib/util.js +++ b/app/scripts/controllers/transactions/lib/util.js @@ -35,9 +35,7 @@ function normalizeTxParams (txParams, LowerCase) { // apply only keys in the normalizers const normalizedTxParams = {} for (const key in normalizers) { - if (txParams[key]) { - normalizedTxParams[key] = normalizers[key](txParams[key], LowerCase) - } + if (txParams[key]) normalizedTxParams[key] = normalizers[key](txParams[key], LowerCase) } return normalizedTxParams } @@ -66,12 +64,8 @@ function validateTxParams (txParams) { @param txParams {object} */ function validateFrom (txParams) { - if (!(typeof txParams.from === 'string')) { - throw new Error(`Invalid from address ${txParams.from} not a string`) - } - if (!isValidAddress(txParams.from)) { - throw new Error('Invalid from address') - } + if (!(typeof txParams.from === 'string')) throw new Error(`Invalid from address ${txParams.from} not a string`) + if (!isValidAddress(txParams.from)) throw new Error('Invalid from address') } /** diff --git a/app/scripts/controllers/transactions/pending-tx-tracker.js b/app/scripts/controllers/transactions/pending-tx-tracker.js index 4e2db5ead7..8f4076f45c 100644 --- a/app/scripts/controllers/transactions/pending-tx-tracker.js +++ b/app/scripts/controllers/transactions/pending-tx-tracker.js @@ -56,9 +56,7 @@ class PendingTransactionTracker extends EventEmitter { resubmitPendingTxs (blockNumber) { const pending = this.getPendingTransactions() // only try resubmitting if their are transactions to resubmit - if (!pending.length) { - return - } + if (!pending.length) return pending.forEach((txMeta) => this._resubmitTx(txMeta, blockNumber).catch((err) => { /* Dont marked as failed if the error is a "known" transaction warning @@ -81,9 +79,7 @@ class PendingTransactionTracker extends EventEmitter { errorMessage.includes('nonce too low') ) // ignore resubmit warnings, return early - if (isKnownTx) { - return - } + if (isKnownTx) return // encountered real error - transition to error state txMeta.warning = { error: errorMessage, @@ -111,14 +107,10 @@ class PendingTransactionTracker extends EventEmitter { const retryCount = txMeta.retryCount || 0 // Exponential backoff to limit retries at publishing - if (txBlockDistance <= Math.pow(2, retryCount) - 1) { - return - } + if (txBlockDistance <= Math.pow(2, retryCount) - 1) return // Only auto-submit already-signed txs: - if (!('rawTx' in txMeta)) { - return this.approveTransaction(txMeta.id) - } + if (!('rawTx' in txMeta)) return this.approveTransaction(txMeta.id) const rawTx = txMeta.rawTx const txHash = await this.publishTransaction(rawTx) @@ -140,9 +132,7 @@ class PendingTransactionTracker extends EventEmitter { const txId = txMeta.id // Only check submitted txs - if (txMeta.status !== 'submitted') { - return - } + if (txMeta.status !== 'submitted') return // extra check in case there was an uncaught error during the // signature and submission process diff --git a/app/scripts/controllers/transactions/tx-gas-utils.js b/app/scripts/controllers/transactions/tx-gas-utils.js index 517137f865..287fb6f44c 100644 --- a/app/scripts/controllers/transactions/tx-gas-utils.js +++ b/app/scripts/controllers/transactions/tx-gas-utils.js @@ -142,13 +142,9 @@ class TxGasUtil { const bufferedGasLimitBn = initialGasLimitBn.muln(1.5) // if initialGasLimit is above blockGasLimit, dont modify it - if (initialGasLimitBn.gt(upperGasLimitBn)) { - return bnToHex(initialGasLimitBn) - } + if (initialGasLimitBn.gt(upperGasLimitBn)) return bnToHex(initialGasLimitBn) // if bufferedGasLimit is below blockGasLimit, use bufferedGasLimit - if (bufferedGasLimitBn.lt(upperGasLimitBn)) { - return bnToHex(bufferedGasLimitBn) - } + if (bufferedGasLimitBn.lt(upperGasLimitBn)) return bnToHex(bufferedGasLimitBn) // otherwise use blockGasLimit return bnToHex(upperGasLimitBn) } diff --git a/app/scripts/controllers/transactions/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js index ffbe6f0107..6a92c06011 100644 --- a/app/scripts/controllers/transactions/tx-state-manager.js +++ b/app/scripts/controllers/transactions/tx-state-manager.js @@ -45,9 +45,7 @@ class TransactionStateManager extends EventEmitter { */ generateTxMeta (opts) { const netId = this.getNetwork() - if (netId === 'loading') { - throw new Error('MetaMask is having trouble connecting to the network') - } + if (netId === 'loading') throw new Error('MetaMask is having trouble connecting to the network') return extend({ id: createId(), time: (new Date()).getTime(), @@ -91,9 +89,7 @@ class TransactionStateManager extends EventEmitter { */ getApprovedTransactions (address) { const opts = { status: 'approved' } - if (address) { - opts.from = address - } + if (address) opts.from = address return this.getFilteredTxList(opts) } @@ -104,9 +100,7 @@ class TransactionStateManager extends EventEmitter { */ getPendingTransactions (address) { const opts = { status: 'submitted' } - if (address) { - opts.from = address - } + if (address) opts.from = address return this.getFilteredTxList(opts) } @@ -117,9 +111,7 @@ class TransactionStateManager extends EventEmitter { */ getConfirmedTransactions (address) { const opts = { status: 'confirmed' } - if (address) { - opts.from = address - } + if (address) opts.from = address return this.getFilteredTxList(opts) } @@ -167,12 +159,7 @@ class TransactionStateManager extends EventEmitter { transactions.splice(index, 1) } } - const newTxIndex = transactions - .findIndex((currentTxMeta) => currentTxMeta.time > txMeta.time) - - newTxIndex === -1 - ? transactions.push(txMeta) - : transactions.splice(newTxIndex, 0, txMeta) + transactions.push(txMeta) this._saveTxList(transactions) return txMeta } @@ -249,14 +236,10 @@ class TransactionStateManager extends EventEmitter { // validate types switch (key) { case 'chainId': - if (typeof value !== 'number' && typeof value !== 'string') { - throw new Error(`${key} in txParams is not a Number or hex string. got: (${value})`) - } + if (typeof value !== 'number' && typeof value !== 'string') throw new Error(`${key} in txParams is not a Number or hex string. got: (${value})`) break default: - if (typeof value !== 'string') { - throw new Error(`${key} in txParams is not a string. got: (${value})`) - } + if (typeof value !== 'string') throw new Error(`${key} in txParams is not a string. got: (${value})`) break } }) diff --git a/app/scripts/createStandardProvider.js b/app/scripts/createStandardProvider.js new file mode 100644 index 0000000000..2059b9b3a7 --- /dev/null +++ b/app/scripts/createStandardProvider.js @@ -0,0 +1,73 @@ +class StandardProvider { + _isConnected + _provider + + constructor (provider) { + this._provider = provider + this._subscribe() + // indicate that we've connected, mostly just for standard compliance + setTimeout(() => { + this._onConnect() + }) + } + + _onClose () { + if (this._isConnected === undefined || this._isConnected) { + this._provider.emit('close', { + code: 1011, + reason: 'Network connection error', + }) + } + this._isConnected = false + } + + _onConnect () { + !this._isConnected && this._provider.emit('connect') + this._isConnected = true + } + + _subscribe () { + this._provider.on('data', (error, { method, params }) => { + if (!error && method === 'eth_subscription') { + this._provider.emit('notification', params.result) + } + }) + } + + /** + * Initiate an RPC method call + * + * @param {string} method - RPC method name to call + * @param {string[]} params - Array of RPC method parameters + * @returns {Promise<*>} Promise resolving to the result if successful + */ + send (method, params = []) { + return new Promise((resolve, reject) => { + try { + this._provider.sendAsync({ id: 1, jsonrpc: '2.0', method, params }, (error, response) => { + error = error || response.error + error ? reject(error) : resolve(response) + }) + } catch (error) { + reject(error) + } + }) + } +} + +/** + * Converts a legacy provider into an EIP-1193-compliant standard provider + * @param {Object} provider - Legacy provider to convert + * @returns {Object} Standard provider + */ +export default function createStandardProvider (provider) { + const standardProvider = new StandardProvider(provider) + const sendLegacy = provider.send + provider.send = (methodOrPayload, callbackOrArgs) => { + if (typeof methodOrPayload === 'string' && !callbackOrArgs || Array.isArray(callbackOrArgs)) { + return standardProvider.send(methodOrPayload, callbackOrArgs) + } + return sendLegacy.call(provider, methodOrPayload, callbackOrArgs) + } + return provider +} diff --git a/app/scripts/edge-encryptor.js b/app/scripts/edge-encryptor.js index d086a854de..012672ed23 100644 --- a/app/scripts/edge-encryptor.js +++ b/app/scripts/edge-encryptor.js @@ -14,17 +14,17 @@ class EdgeEncryptor { * @returns {Promise} Promise resolving to an object with ciphertext */ encrypt (password, dataObject) { - const salt = this._generateSalt() + var salt = this._generateSalt() return this._keyFromPassword(password, salt) .then(function (key) { - const data = JSON.stringify(dataObject) - const dataBuffer = Unibabel.utf8ToBuffer(data) - const vector = global.crypto.getRandomValues(new Uint8Array(16)) - const resultbuffer = asmcrypto.AES_GCM.encrypt(dataBuffer, key, vector) + var data = JSON.stringify(dataObject) + var dataBuffer = Unibabel.utf8ToBuffer(data) + var vector = global.crypto.getRandomValues(new Uint8Array(16)) + var resultbuffer = asmcrypto.AES_GCM.encrypt(dataBuffer, key, vector) - const buffer = new Uint8Array(resultbuffer) - const vectorStr = Unibabel.bufferToBase64(vector) - const vaultStr = Unibabel.bufferToBase64(buffer) + var buffer = new Uint8Array(resultbuffer) + var vectorStr = Unibabel.bufferToBase64(vector) + var vaultStr = Unibabel.bufferToBase64(buffer) return JSON.stringify({ data: vaultStr, iv: vectorStr, @@ -48,7 +48,7 @@ class EdgeEncryptor { const encryptedData = Unibabel.base64ToBuffer(payload.data) const vector = Unibabel.base64ToBuffer(payload.iv) return new Promise((resolve, reject) => { - let result + var result try { result = asmcrypto.AES_GCM.decrypt(encryptedData, key, vector) } catch (err) { @@ -72,12 +72,12 @@ class EdgeEncryptor { */ _keyFromPassword (password, salt) { - const passBuffer = Unibabel.utf8ToBuffer(password) - const saltBuffer = Unibabel.base64ToBuffer(salt) + var passBuffer = Unibabel.utf8ToBuffer(password) + var saltBuffer = Unibabel.base64ToBuffer(salt) const iterations = 10000 const length = 32 // SHA256 hash size return new Promise((resolve) => { - const key = asmcrypto.Pbkdf2HmacSha256(passBuffer, saltBuffer, iterations, length) + var key = asmcrypto.Pbkdf2HmacSha256(passBuffer, saltBuffer, iterations, length) resolve(key) }) } @@ -89,9 +89,9 @@ class EdgeEncryptor { * @returns {string} Randomized base64 encoded data */ _generateSalt (byteCount = 32) { - const view = new Uint8Array(byteCount) + var view = new Uint8Array(byteCount) global.crypto.getRandomValues(view) - const b64encoded = btoa(String.fromCharCode.apply(null, view)) + var b64encoded = btoa(String.fromCharCode.apply(null, view)) return b64encoded } } diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index d81d8587b9..ec88243a4e 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -1,5 +1,6 @@ /*global Web3*/ + // need to make sure we aren't affected by overlapping namespaces // and that we dont affect the app with our namespace // mostly a fix for web3's BigNumber if AMD's "define" is defined... @@ -31,14 +32,14 @@ const restoreContextAfterImports = () => { } cleanContextForImports() - +require('web3/dist/web3.min.js') const log = require('loglevel') const LocalMessageDuplexStream = require('post-message-stream') +const setupDappAutoReload = require('./lib/auto-reload.js') const MetamaskInpageProvider = require('metamask-inpage-provider') +const createStandardProvider = require('./createStandardProvider').default -// TODO:deprecate:2020-01-13 -require('web3/dist/web3.min.js') -const setupDappAutoReload = require('./lib/auto-reload.js') +let warned = false restoreContextAfterImports() @@ -60,6 +61,89 @@ const inpageProvider = new MetamaskInpageProvider(metamaskStream) // set a high max listener count to avoid unnecesary warnings inpageProvider.setMaxListeners(100) +let warnedOfAutoRefreshDeprecation = false +// augment the provider with its enable method +inpageProvider.enable = function ({ force } = {}) { + if ( + !warnedOfAutoRefreshDeprecation && + inpageProvider.autoRefreshOnNetworkChange + ) { + console.warn(`MetaMask: MetaMask will soon stop reloading pages on network change. +If you rely upon this behavior, add a 'networkChanged' event handler to trigger the reload manually: https://metamask.github.io/metamask-docs/API_Reference/Ethereum_Provider#ethereum.on(eventname%2C-callback) +Set 'ethereum.autoRefreshOnNetworkChange' to 'false' to silence this warning: https://metamask.github.io/metamask-docs/API_Reference/Ethereum_Provider#ethereum.autorefreshonnetworkchange' +`) + warnedOfAutoRefreshDeprecation = true + } + return new Promise((resolve, reject) => { + inpageProvider.sendAsync({ method: 'eth_requestAccounts', params: [force] }, (error, response) => { + if (error || response.error) { + reject(error || response.error) + } else { + resolve(response.result) + } + }) + }) +} + +// give the dapps control of a refresh they can toggle this off on the window.ethereum +// this will be default true so it does not break any old apps. +inpageProvider.autoRefreshOnNetworkChange = true + + +// publicConfig isn't populated until we get a message from background. +// Using this getter will ensure the state is available +const getPublicConfigWhenReady = async () => { + const store = inpageProvider.publicConfigStore + let state = store.getState() + // if state is missing, wait for first update + if (!state.networkVersion) { + state = await new Promise(resolve => store.once('update', resolve)) + console.log('new state', state) + } + return state +} + +// add metamask-specific convenience methods +inpageProvider._metamask = new Proxy({ + /** + * Synchronously determines if this domain is currently enabled, with a potential false negative if called to soon + * + * @returns {boolean} - returns true if this domain is currently enabled + */ + isEnabled: function () { + const { isEnabled } = inpageProvider.publicConfigStore.getState() + return Boolean(isEnabled) + }, + + /** + * Asynchronously determines if this domain is currently enabled + * + * @returns {Promise} - Promise resolving to true if this domain is currently enabled + */ + isApproved: async function () { + const { isEnabled } = await getPublicConfigWhenReady() + return Boolean(isEnabled) + }, + + /** + * Determines if MetaMask is unlocked by the user + * + * @returns {Promise} - Promise resolving to true if MetaMask is currently unlocked + */ + isUnlocked: async function () { + const { isUnlocked } = await getPublicConfigWhenReady() + return Boolean(isUnlocked) + }, +}, { + get: function (obj, prop) { + !warned && console.warn('Heads up! ethereum._metamask exposes methods that have ' + + 'not been standardized yet. This means that these methods may not be implemented ' + + 'in other dapp browsers and may be removed from MetaMask in the future.') + warned = true + return obj[prop] + }, +}) + // Work around for web3@1.0 deleting the bound `sendAsync` but not the unbound // `sendAsync` method on the prototype, causing `this` reference issues const proxiedInpageProvider = new Proxy(inpageProvider, { @@ -68,11 +152,11 @@ const proxiedInpageProvider = new Proxy(inpageProvider, { deleteProperty: () => true, }) -// -// TODO:deprecate:2020-01-13 -// +window.ethereum = createStandardProvider(proxiedInpageProvider) +// // setup web3 +// if (typeof window.web3 !== 'undefined') { throw new Error(`MetaMask detected another web3. @@ -88,13 +172,15 @@ web3.setProvider = function () { } log.debug('MetaMask - injected web3') -proxiedInpageProvider._web3Ref = web3.eth +setupDappAutoReload(web3, inpageProvider.publicConfigStore) -// setup dapp auto reload AND proxy web3 -setupDappAutoReload(web3, inpageProvider._publicConfigStore) - -// -// end deprecate:2020-01-13 -// +// set web3 defaultAccount +inpageProvider.publicConfigStore.subscribe(function (state) { + web3.eth.defaultAccount = state.selectedAddress +}) -window.ethereum = proxiedInpageProvider +inpageProvider.publicConfigStore.subscribe(function (state) { + if (state.onboardingcomplete) { + window.postMessage('onboardingcomplete', '*') + } +}) diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js index 8fef712fe6..3137ea8a36 100644 --- a/app/scripts/lib/account-tracker.js +++ b/app/scripts/lib/account-tracker.js @@ -124,9 +124,7 @@ class AccountTracker { // save accounts state this.store.updateState({ accounts }) // fetch balances for the accounts if there is block number ready - if (!this._currentBlockNumber) { - return - } + if (!this._currentBlockNumber) return this._updateAccounts() } @@ -160,9 +158,7 @@ class AccountTracker { // block gasLimit polling shouldn't be in account-tracker shouldn't be here... const currentBlock = await this._query.getBlockByNumber(blockNumber, false) - if (!currentBlock) { - return - } + if (!currentBlock) return const currentBlockGasLimit = currentBlock.gasLimit this.store.updateState({ currentBlockGasLimit }) @@ -222,9 +218,7 @@ class AccountTracker { // update accounts state const { accounts } = this.store.getState() // only populate if the entry is still present - if (!accounts[address]) { - return - } + if (!accounts[address]) return accounts[address] = result this.store.updateState({ accounts }) } diff --git a/app/scripts/lib/auto-reload.js b/app/scripts/lib/auto-reload.js index e2b9c17bd3..fd209c230a 100644 --- a/app/scripts/lib/auto-reload.js +++ b/app/scripts/lib/auto-reload.js @@ -1,6 +1,3 @@ - -// TODO:deprecate:2020-01-13 - module.exports = setupDappAutoReload function setupDappAutoReload (web3, observable) { @@ -16,7 +13,7 @@ function setupDappAutoReload (web3, observable) { lastTimeUsed = Date.now() // show warning once on web3 access if (!hasBeenWarned && key !== 'currentProvider') { - console.warn(`MetaMask: On 2020-01-13, MetaMask will no longer inject web3. For more information, see: https://medium.com/metamask/no-longer-injecting-web3-js-4a899ad6e59e`) + console.warn('MetaMask: web3 will be deprecated in the near future in favor of the ethereumProvider\nhttps://medium.com/metamask/4a899ad6e59e') hasBeenWarned = true } // return value normally @@ -31,14 +28,10 @@ function setupDappAutoReload (web3, observable) { observable.subscribe(function (state) { // if the auto refresh on network change is false do not // do anything - if (!window.ethereum.autoRefreshOnNetworkChange) { - return - } + if (!window.ethereum.autoRefreshOnNetworkChange) return // if reload in progress, no need to check reload logic - if (reloadInProgress) { - return - } + if (reloadInProgress) return const currentNetwork = state.networkVersion @@ -49,14 +42,10 @@ function setupDappAutoReload (web3, observable) { } // skip reload logic if web3 not used - if (!lastTimeUsed) { - return - } + if (!lastTimeUsed) return // if network did not change, exit - if (currentNetwork === lastSeenNetwork) { - return - } + if (currentNetwork === lastSeenNetwork) return // initiate page reload reloadInProgress = true diff --git a/app/scripts/lib/buy-eth-url.js b/app/scripts/lib/buy-eth-url.js index 6892364ca0..5cae83a9f4 100644 --- a/app/scripts/lib/buy-eth-url.js +++ b/app/scripts/lib/buy-eth-url.js @@ -13,13 +13,11 @@ module.exports = getBuyEthUrl */ function getBuyEthUrl ({ network, amount, address, service }) { // default service by network if not specified - if (!service) { - service = getDefaultServiceForNetwork(network) - } + if (!service) service = getDefaultServiceForNetwork(network) switch (service) { case 'wyre': - return `https://pay.sendwyre.com/?dest=ethereum:${address}&destCurrency=ETH&accountId=AC-7AG3W4XH4N2` + return `https://dash.sendwyre.com/sign-up` case 'coinswitch': return `https://metamask.coinswitch.co/?address=${address}&to=eth` case 'coinbase': @@ -32,9 +30,8 @@ function getBuyEthUrl ({ network, amount, address, service }) { return 'https://github.com/kovan-testnet/faucet' case 'goerli-faucet': return 'https://goerli-faucet.slock.it/' - default: - throw new Error(`Unknown cryptocurrency exchange or faucet: "${service}"`) } + throw new Error(`Unknown cryptocurrency exchange or faucet: "${service}"`) } function getDefaultServiceForNetwork (network) { @@ -49,7 +46,6 @@ function getDefaultServiceForNetwork (network) { return 'kovan-faucet' case '5': return 'goerli-faucet' - default: - throw new Error(`No default cryptocurrency exchange or faucet for networkId: "${network}"`) } + throw new Error(`No default cryptocurrency exchange or faucet for networkId: "${network}"`) } diff --git a/app/scripts/lib/cleanErrorStack.js b/app/scripts/lib/cleanErrorStack.js index 58f7ccfdf1..8adf55db70 100644 --- a/app/scripts/lib/cleanErrorStack.js +++ b/app/scripts/lib/cleanErrorStack.js @@ -4,10 +4,10 @@ * @returns {Error} Error with clean stack trace. */ function cleanErrorStack (err) { - let name = err.name + var name = err.name name = (name === undefined) ? 'Error' : String(name) - let msg = err.message + var msg = err.message msg = (msg === undefined) ? '' : String(msg) if (name === '') { diff --git a/app/scripts/lib/createDnodeRemoteGetter.js b/app/scripts/lib/createDnodeRemoteGetter.js index 60a7753831..b70d218f32 100644 --- a/app/scripts/lib/createDnodeRemoteGetter.js +++ b/app/scripts/lib/createDnodeRemoteGetter.js @@ -8,9 +8,7 @@ function createDnodeRemoteGetter (dnode) { }) async function getRemote () { - if (remote) { - return remote - } + if (remote) return remote return await new Promise(resolve => dnode.once('remote', resolve)) } diff --git a/app/scripts/lib/createLoggerMiddleware.js b/app/scripts/lib/createLoggerMiddleware.js index d95cdb465f..996c3477c6 100644 --- a/app/scripts/lib/createLoggerMiddleware.js +++ b/app/scripts/lib/createLoggerMiddleware.js @@ -13,9 +13,7 @@ function createLoggerMiddleware (opts) { if (res.error) { log.error('Error in RPC response:\n', res) } - if (req.isMetamaskInternal) { - return - } + if (req.isMetamaskInternal) return log.info(`RPC (${opts.origin}):`, req, '->', res) cb() }) diff --git a/app/scripts/lib/createOriginMiddleware.js b/app/scripts/lib/createOriginMiddleware.js index 4ff9a33862..98bb0e3b39 100644 --- a/app/scripts/lib/createOriginMiddleware.js +++ b/app/scripts/lib/createOriginMiddleware.js @@ -1,4 +1,3 @@ - module.exports = createOriginMiddleware /** diff --git a/app/scripts/lib/ens-ipfs/resolver.js b/app/scripts/lib/ens-ipfs/resolver.js index 34b1304c17..a0af263bc7 100644 --- a/app/scripts/lib/ens-ipfs/resolver.js +++ b/app/scripts/lib/ens-ipfs/resolver.js @@ -66,7 +66,5 @@ function getRegistryForChainId (chainId) { // goerli case 5: return '0x112234455c3a32fd11230c42e7bccd4a84e02010' - default: - return null } } diff --git a/app/scripts/lib/ens-ipfs/setup.js b/app/scripts/lib/ens-ipfs/setup.js index f12a22238b..a3711c5f94 100644 --- a/app/scripts/lib/ens-ipfs/setup.js +++ b/app/scripts/lib/ens-ipfs/setup.js @@ -23,18 +23,14 @@ function setupEnsIpfsResolver ({ provider }) { async function webRequestDidFail (details) { const { tabId, url } = details // ignore requests that are not associated with tabs - if (tabId === -1) { - return - } + if (tabId === -1) return // parse ens name const urlData = urlUtil.parse(url) const { hostname: name, path, search } = urlData const domainParts = name.split('.') const topLevelDomain = domainParts[domainParts.length - 1] // if unsupported TLD, abort - if (!supportedTopLevelDomains.includes(topLevelDomain)) { - return - } + if (!supportedTopLevelDomains.includes(topLevelDomain)) return // otherwise attempt resolve attemptResolve({ tabId, name, path, search }) } @@ -49,9 +45,7 @@ function setupEnsIpfsResolver ({ provider }) { try { // check if ipfs gateway has result const response = await fetch(resolvedUrl, { method: 'HEAD' }) - if (response.status === 200) { - url = resolvedUrl - } + if (response.status === 200) url = resolvedUrl } catch (err) { console.warn(err) } diff --git a/app/scripts/lib/local-store.js b/app/scripts/lib/local-store.js index b7212c9805..8fde2e9111 100644 --- a/app/scripts/lib/local-store.js +++ b/app/scripts/lib/local-store.js @@ -1,6 +1,5 @@ const extension = require('extensionizer') const log = require('loglevel') -const { checkForError } = require('./util') /** * A wrapper around the extension's storage local API @@ -21,9 +20,7 @@ module.exports = class ExtensionStore { * @return {Promise<*>} */ async get () { - if (!this.isSupported) { - return undefined - } + if (!this.isSupported) return undefined const result = await this._get() // extension.storage.local always returns an obj // if the object is empty, treat it as undefined @@ -91,3 +88,17 @@ module.exports = class ExtensionStore { function isEmpty (obj) { return Object.keys(obj).length === 0 } + +/** + * Returns an Error if extension.runtime.lastError is present + * this is a workaround for the non-standard error object thats used + * @returns {Error} + */ +function checkForError () { + const lastError = extension.runtime.lastError + if (!lastError) return + // if it quacks like an Error, its an Error + if (lastError.stack && lastError.message) return lastError + // repair incomplete error object (eg chromium v77) + return new Error(lastError.message) +} diff --git a/app/scripts/lib/message-manager.js b/app/scripts/lib/message-manager.js index a947188e53..8e1ff34b75 100644 --- a/app/scripts/lib/message-manager.js +++ b/app/scripts/lib/message-manager.js @@ -1,7 +1,7 @@ const EventEmitter = require('events') const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') -const { ethErrors } = require('eth-json-rpc-errors') +const { errors: rpcErrors } = require('eth-json-rpc-errors') const createId = require('./random-id') /** @@ -62,9 +62,7 @@ module.exports = class MessageManager extends EventEmitter { */ getUnapprovedMsgs () { return this.messages.filter(msg => msg.status === 'unapproved') - .reduce((result, msg) => { - result[msg.id] = msg; return result - }, {}) + .reduce((result, msg) => { result[msg.id] = msg; return result }, {}) } /** @@ -85,7 +83,7 @@ module.exports = class MessageManager extends EventEmitter { case 'signed': return resolve(data.rawSig) case 'rejected': - return reject(ethErrors.provider.userRejectedRequest('MetaMask Message Signature: User denied message signature.')) + return reject(rpcErrors.eth.userRejectedRequest('MetaMask Message Signature: User denied message signature.')) default: return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) } @@ -104,14 +102,12 @@ module.exports = class MessageManager extends EventEmitter { */ addUnapprovedMessage (msgParams, req) { // add origin from request - if (req) { - msgParams.origin = req.origin - } + if (req) msgParams.origin = req.origin msgParams.data = normalizeMsgData(msgParams.data) // create txData obj with parameters and meta data - const time = (new Date()).getTime() - const msgId = createId() - const msgData = { + var time = (new Date()).getTime() + var msgId = createId() + var msgData = { id: msgId, msgParams: msgParams, time: time, @@ -223,9 +219,7 @@ module.exports = class MessageManager extends EventEmitter { */ _setMsgStatus (msgId, status) { const msg = this.getMsg(msgId) - if (!msg) { - throw new Error('MessageManager - Message not found for id: "${msgId}".') - } + if (!msg) throw new Error('MessageManager - Message not found for id: "${msgId}".') msg.status = status this._updateMsg(msg) this.emit(`${msgId}:${status}`, msg) diff --git a/app/scripts/lib/migrator/index.js b/app/scripts/lib/migrator/index.js index c1c225fb39..345ca80016 100644 --- a/app/scripts/lib/migrator/index.js +++ b/app/scripts/lib/migrator/index.js @@ -40,12 +40,8 @@ class Migrator extends EventEmitter { try { // attempt migration and validate const migratedData = await migration.migrate(versionedData) - if (!migratedData.data) { - throw new Error('Migrator - migration returned empty data') - } - if (migratedData.version !== undefined && migratedData.meta.version !== migration.version) { - throw new Error('Migrator - Migration did not update version number correctly') - } + if (!migratedData.data) throw new Error('Migrator - migration returned empty data') + if (migratedData.version !== undefined && migratedData.meta.version !== migration.version) throw new Error('Migrator - Migration did not update version number correctly') // accept the migration as good versionedData = migratedData } catch (err) { diff --git a/app/scripts/lib/nodeify.js b/app/scripts/lib/nodeify.js index 99b96b3566..a813ae679c 100644 --- a/app/scripts/lib/nodeify.js +++ b/app/scripts/lib/nodeify.js @@ -1,9 +1,5 @@ const promiseToCallback = require('promise-to-callback') -const callbackNoop = function (err) { - if (err) { - throw err - } -} +const callbackNoop = function (err) { if (err) throw err } /** * A generator that returns a function which, when passed a promise, can treat that promise as a node style callback. diff --git a/app/scripts/lib/notification-manager.js b/app/scripts/lib/notification-manager.js index 85177cceb9..721d109a1a 100644 --- a/app/scripts/lib/notification-manager.js +++ b/app/scripts/lib/notification-manager.js @@ -18,9 +18,7 @@ class NotificationManager { */ showPopup () { this._getPopup((err, popup) => { - if (err) { - throw err - } + if (err) throw err // Bring focus to chrome popup if (popup) { @@ -30,9 +28,7 @@ class NotificationManager { const {screenX, screenY, outerWidth, outerHeight} = window const notificationTop = Math.round(screenY + (outerHeight / 2) - (NOTIFICATION_HEIGHT / 2)) const notificationLeft = Math.round(screenX + (outerWidth / 2) - (NOTIFICATION_WIDTH / 2)) - const cb = (currentPopup) => { - this._popupId = currentPopup.id - } + const cb = (currentPopup) => { this._popupId = currentPopup.id } // create new notification popup const creation = extension.windows.create({ url: 'notification.html', @@ -54,12 +50,8 @@ class NotificationManager { closePopup () { // closes notification popup this._getPopup((err, popup) => { - if (err) { - throw err - } - if (!popup) { - return - } + if (err) throw err + if (!popup) return extension.windows.remove(popup.id, console.error) }) } @@ -74,9 +66,7 @@ class NotificationManager { */ _getPopup (cb) { this._getWindows((err, windows) => { - if (err) { - throw err - } + if (err) throw err cb(null, this._getPopupIn(windows)) }) } diff --git a/app/scripts/lib/pending-balance-calculator.js b/app/scripts/lib/pending-balance-calculator.js index dd7fa6de46..0f1dc19a90 100644 --- a/app/scripts/lib/pending-balance-calculator.js +++ b/app/scripts/lib/pending-balance-calculator.js @@ -32,9 +32,7 @@ class PendingBalanceCalculator { ]) const [ balance, pending ] = results - if (!balance) { - return undefined - } + if (!balance) return undefined const pendingValue = pending.reduce((total, tx) => { return total.add(this.calculateMaxCost(tx)) diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js index fb30cc03ce..2a2ab481ab 100644 --- a/app/scripts/lib/personal-message-manager.js +++ b/app/scripts/lib/personal-message-manager.js @@ -1,7 +1,7 @@ const EventEmitter = require('events') const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') -const { ethErrors } = require('eth-json-rpc-errors') +const { errors: rpcErrors } = require('eth-json-rpc-errors') const createId = require('./random-id') const hexRe = /^[0-9A-Fa-f]+$/g const log = require('loglevel') @@ -65,9 +65,7 @@ module.exports = class PersonalMessageManager extends EventEmitter { */ getUnapprovedMsgs () { return this.messages.filter(msg => msg.status === 'unapproved') - .reduce((result, msg) => { - result[msg.id] = msg; return result - }, {}) + .reduce((result, msg) => { result[msg.id] = msg; return result }, {}) } /** @@ -91,7 +89,7 @@ module.exports = class PersonalMessageManager extends EventEmitter { case 'signed': return resolve(data.rawSig) case 'rejected': - return reject(ethErrors.provider.userRejectedRequest('MetaMask Message Signature: User denied message signature.')) + return reject(rpcErrors.eth.userRejectedRequest('MetaMask Message Signature: User denied message signature.')) default: return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) } @@ -112,14 +110,12 @@ module.exports = class PersonalMessageManager extends EventEmitter { addUnapprovedMessage (msgParams, req) { log.debug(`PersonalMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`) // add origin from request - if (req) { - msgParams.origin = req.origin - } + if (req) msgParams.origin = req.origin msgParams.data = this.normalizeMsgData(msgParams.data) // create txData obj with parameters and meta data - const time = (new Date()).getTime() - const msgId = createId() - const msgData = { + var time = (new Date()).getTime() + var msgId = createId() + var msgData = { id: msgId, msgParams: msgParams, time: time, @@ -233,9 +229,7 @@ module.exports = class PersonalMessageManager extends EventEmitter { */ _setMsgStatus (msgId, status) { const msg = this.getMsg(msgId) - if (!msg) { - throw new Error(`PersonalMessageManager - Message not found for id: "${msgId}".`) - } + if (!msg) throw new Error(`PersonalMessageManager - Message not found for id: "${msgId}".`) msg.status = status this._updateMsg(msg) this.emit(`${msgId}:${status}`, msg) diff --git a/app/scripts/lib/select-chain-id.js b/app/scripts/lib/select-chain-id.js index d94b35898f..3171c98402 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: { chainId } } = metamaskState - return standardNetworkId[network] || `0x${parseInt(chainId, 10).toString(16)}` + const { network, provider: { chaindId } } = metamaskState + return standardNetworkId[network] || `0x${parseInt(chaindId, 10).toString(16)}` } module.exports = selectChainId diff --git a/app/scripts/lib/setupFetchDebugging.js b/app/scripts/lib/setupFetchDebugging.js index d5c01b1f23..431340e2be 100644 --- a/app/scripts/lib/setupFetchDebugging.js +++ b/app/scripts/lib/setupFetchDebugging.js @@ -7,9 +7,7 @@ module.exports = setupFetchDebugging // function setupFetchDebugging () { - if (!global.fetch) { - return - } + if (!global.fetch) return const originalFetch = global.fetch global.fetch = wrappedFetch diff --git a/app/scripts/lib/setupMetamaskMeshMetrics.js b/app/scripts/lib/setupMetamaskMeshMetrics.js index b520ceaa7f..6f0d86b284 100644 --- a/app/scripts/lib/setupMetamaskMeshMetrics.js +++ b/app/scripts/lib/setupMetamaskMeshMetrics.js @@ -22,9 +22,7 @@ function setupMetamaskMeshMetrics () { function submitMeshMetricsEntry (message) { // ignore if we haven't loaded yet - if (!didLoad) { - return - } + if (!didLoad) return // submit the message testingContainer.contentWindow.postMessage(message, targetOrigin) } diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index 096bd34541..ba0e17df0d 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -67,15 +67,11 @@ function simplifyErrorMessages (report) { function rewriteErrorMessages (report, rewriteFn) { // rewrite top level message - if (typeof report.message === 'string') { - report.message = rewriteFn(report.message) - } + if (typeof report.message === 'string') report.message = rewriteFn(report.message) // rewrite each exception message if (report.exception && report.exception.values) { report.exception.values.forEach(item => { - if (typeof item.value === 'string') { - item.value = rewriteFn(item.value) - } + if (typeof item.value === 'string') item.value = rewriteFn(item.value) }) } } @@ -95,9 +91,7 @@ function rewriteReportUrls (report) { function toMetamaskUrl (origUrl) { const filePath = origUrl.split(location.origin)[1] - if (!filePath) { - return origUrl - } + if (!filePath) return origUrl const metamaskUrl = `metamask${filePath}` return metamaskUrl } diff --git a/app/scripts/lib/stream-utils.js b/app/scripts/lib/stream-utils.js index 66b3107f4e..3dbc064b5e 100644 --- a/app/scripts/lib/stream-utils.js +++ b/app/scripts/lib/stream-utils.js @@ -43,9 +43,7 @@ function setupMultiplex (connectionStream) { mux, connectionStream, (err) => { - if (err) { - console.error(err) - } + if (err) console.error(err) } ) return mux diff --git a/app/scripts/lib/typed-message-manager.js b/app/scripts/lib/typed-message-manager.js index 5e3c480387..e4d3e842bb 100644 --- a/app/scripts/lib/typed-message-manager.js +++ b/app/scripts/lib/typed-message-manager.js @@ -2,7 +2,7 @@ const EventEmitter = require('events') const ObservableStore = require('obs-store') const createId = require('./random-id') const assert = require('assert') -const { ethErrors } = require('eth-json-rpc-errors') +const { errors: rpcErrors } = require('eth-json-rpc-errors') const sigUtil = require('eth-sig-util') const log = require('loglevel') const jsonschema = require('jsonschema') @@ -58,9 +58,7 @@ module.exports = class TypedMessageManager extends EventEmitter { */ getUnapprovedMsgs () { return this.messages.filter(msg => msg.status === 'unapproved') - .reduce((result, msg) => { - result[msg.id] = msg; return result - }, {}) + .reduce((result, msg) => { result[msg.id] = msg; return result }, {}) } /** @@ -81,7 +79,7 @@ module.exports = class TypedMessageManager extends EventEmitter { case 'signed': return resolve(data.rawSig) case 'rejected': - return reject(ethErrors.provider.userRejectedRequest('MetaMask Message Signature: User denied message signature.')) + return reject(rpcErrors.eth.userRejectedRequest('MetaMask Message Signature: User denied message signature.')) case 'errored': return reject(new Error(`MetaMask Message Signature: ${data.error}`)) default: @@ -105,15 +103,13 @@ module.exports = class TypedMessageManager extends EventEmitter { msgParams.version = version this.validateParams(msgParams) // add origin from request - if (req) { - msgParams.origin = req.origin - } + if (req) msgParams.origin = req.origin log.debug(`TypedMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`) // create txData obj with parameters and meta data - const time = (new Date()).getTime() - const msgId = createId() - const msgData = { + var time = (new Date()).getTime() + var msgId = createId() + var msgData = { id: msgId, msgParams: msgParams, time: time, @@ -153,9 +149,7 @@ module.exports = class TypedMessageManager extends EventEmitter { 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.') + assert.doesNotThrow(() => { data = JSON.parse(params.data) }, 'Data must be passed as 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.') @@ -163,8 +157,6 @@ module.exports = class TypedMessageManager extends EventEmitter { const activeChainId = parseInt(this.networkController.getNetworkState()) chainId && assert.equal(chainId, activeChainId, `Provided chainId (${chainId}) must match the active chainId (${activeChainId})`) break - default: - assert.fail(`Unknown params.version ${params.version}`) } } @@ -286,9 +278,7 @@ module.exports = class TypedMessageManager extends EventEmitter { */ _setMsgStatus (msgId, status) { const msg = this.getMsg(msgId) - if (!msg) { - throw new Error('TypedMessageManager - Message not found for id: "${msgId}".') - } + if (!msg) throw new Error('TypedMessageManager - Message not found for id: "${msgId}".') msg.status = status this._updateMsg(msg) this.emit(`${msgId}:${status}`, msg) diff --git a/app/scripts/lib/util.js b/app/scripts/lib/util.js index 36b836eb11..114203d7f8 100644 --- a/app/scripts/lib/util.js +++ b/app/scripts/lib/util.js @@ -1,4 +1,3 @@ -const extension = require('extensionizer') const ethUtil = require('ethereumjs-util') const assert = require('assert') const BN = require('bn.js') @@ -149,32 +148,6 @@ function getRandomArrayItem (array) { return array[Math.floor((Math.random() * array.length))] } -function mapObjectValues (object, cb) { - const mappedObject = {} - Object.keys(object).forEach(key => { - mappedObject[key] = cb(key, object[key]) - }) - return mappedObject -} - -/** - * Returns an Error if extension.runtime.lastError is present - * this is a workaround for the non-standard error object thats used - * @returns {Error} - */ -function checkForError () { - const lastError = extension.runtime.lastError - if (!lastError) { - return - } - // if it quacks like an Error, its an Error - if (lastError.stack && lastError.message) { - return lastError - } - // repair incomplete error object (eg chromium v77) - return new Error(lastError.message) -} - module.exports = { removeListeners, applyListeners, @@ -186,6 +159,4 @@ module.exports = { bnToHex, BnMultiplyByFraction, getRandomArrayItem, - mapObjectValues, - checkForError, } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 246ae511d3..14caf07069 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -4,13 +4,13 @@ * @license MIT */ -const assert = require('assert').strict const EventEmitter = require('events') const pump = require('pump') const Dnode = require('dnode') -const extension = require('extensionizer') +const pify = require('pify') const ObservableStore = require('obs-store') const ComposableObservableStore = require('./lib/ComposableObservableStore') +const createDnodeRemoteGetter = require('./lib/createDnodeRemoteGetter') const asStream = require('obs-store/lib/asStream') const AccountTracker = require('./lib/account-tracker') const RpcEngine = require('json-rpc-engine') @@ -18,8 +18,8 @@ const debounce = require('debounce') const createEngineStream = require('json-rpc-middleware-stream/engineStream') const createFilterMiddleware = require('eth-json-rpc-filters') const createSubscriptionManager = require('eth-json-rpc-filters/subscriptionManager') -const createLoggerMiddleware = require('./lib/createLoggerMiddleware') const createOriginMiddleware = require('./lib/createOriginMiddleware') +const createLoggerMiddleware = require('./lib/createLoggerMiddleware') const providerAsMiddleware = require('eth-json-rpc-middleware/providerAsMiddleware') const {setupMultiplex} = require('./lib/stream-utils.js') const KeyringController = require('eth-keyring-controller') @@ -39,8 +39,8 @@ const TypedMessageManager = require('./lib/typed-message-manager') const TransactionController = require('./controllers/transactions') const TokenRatesController = require('./controllers/token-rates') const DetectTokensController = require('./controllers/detect-tokens') +const ProviderApprovalController = require('./controllers/provider-approval') const ABTestController = require('./controllers/ab-test') -const { PermissionsController } = require('./controllers/permissions/') const nodeify = require('./lib/nodeify') const accountImporter = require('./account-import-strategies') const getBuyEthUrl = require('./lib/buy-eth-url') @@ -56,7 +56,6 @@ const TrezorKeyring = require('eth-trezor-keyring') const LedgerBridgeKeyring = require('eth-ledger-bridge-keyring') const EthQuery = require('eth-query') const ethUtil = require('ethereumjs-util') -const nanoid = require('nanoid') const contractMap = require('eth-contract-metadata') const { AddressBookController, @@ -89,24 +88,16 @@ module.exports = class MetamaskController extends EventEmitter { // platform-specific api this.platform = opts.platform - this.getRequestAccountTabIds = opts.getRequestAccountTabIds - this.getOpenMetamaskTabsIds = opts.getOpenMetamaskTabsIds - // observable state store this.store = new ComposableObservableStore(initState) - // external connections by origin - // Do not modify directly. Use the associated methods. - this.connections = {} - // lock to ensure only one vault created at once this.createVaultMutex = new Mutex() - // next, we will initialize the controllers - // controller initializaiton order matters - + // network store this.networkController = new NetworkController(initState.NetworkController) + // preferences controller this.preferencesController = new PreferencesController({ initState: initState.PreferencesController, initLangCode: opts.initLangCode, @@ -114,14 +105,16 @@ module.exports = class MetamaskController extends EventEmitter { network: this.networkController, }) + // app-state controller this.appStateController = new AppStateController({ preferencesStore: this.preferencesController.store, onInactiveTimeout: () => this.setLocked(), - initState: initState.AppStateController, }) + // currency controller this.currencyRateController = new CurrencyRateController(undefined, initState.CurrencyController) + // infura controller this.infuraController = new InfuraController({ initState: initState.InfuraController, }) @@ -129,7 +122,7 @@ module.exports = class MetamaskController extends EventEmitter { this.phishingController = new PhishingController() - // now we can initialize the RPC provider, which other controllers require + // rpc provider this.initializeProvider() this.provider = this.networkController.getProviderAndBlockTracker().provider this.blockTracker = this.networkController.getProviderAndBlockTracker().blockTracker @@ -158,7 +151,7 @@ module.exports = class MetamaskController extends EventEmitter { initState: initState.IncomingTransactionsController, }) - // account tracker watches balances, nonces, and any code at their address + // account tracker watches balances, nonces, and any code at their address. this.accountTracker = new AccountTracker({ provider: this.provider, blockTracker: this.blockTracker, @@ -184,7 +177,6 @@ module.exports = class MetamaskController extends EventEmitter { this.onboardingController = new OnboardingController({ initState: initState.OnboardingController, - preferencesController: this.preferencesController, }) // ensure accountTracker updates balances after network change @@ -192,6 +184,7 @@ module.exports = class MetamaskController extends EventEmitter { this.accountTracker._updateAccounts() }) + // key mgmt const additionalKeyrings = [TrezorKeyring, LedgerBridgeKeyring] this.keyringController = new KeyringController({ keyringTypes: additionalKeyrings, @@ -199,25 +192,16 @@ module.exports = class MetamaskController extends EventEmitter { getNetwork: this.networkController.getNetworkState.bind(this.networkController), encryptor: opts.encryptor || undefined, }) - this.keyringController.memStore.subscribe((s) => this._onKeyringControllerUpdate(s)) - this.permissionsController = new PermissionsController({ - keyringController: this.keyringController, - platform: opts.platform, - notifyDomain: this.notifyConnections.bind(this), - notifyAllDomains: this.notifyAllConnections.bind(this), - }, initState.PermissionsController, initState.PermissionsMetadata) + this.keyringController.memStore.subscribe((s) => this._onKeyringControllerUpdate(s)) + // detect tokens controller this.detectTokensController = new DetectTokensController({ preferences: this.preferencesController, network: this.networkController, keyringMemStore: this.keyringController.memStore, }) - this.abTestController = new ABTestController({ - initState: initState.ABTestController, - }) - this.addressBookController = new AddressBookController(undefined, initState.AddressBookController) this.threeBoxController = new ThreeBoxController({ @@ -229,9 +213,9 @@ module.exports = class MetamaskController extends EventEmitter { version, }) + // tx mgmt this.txController = new TransactionController({ initState: initState.TransactionController || initState.TransactionManager, - getPermittedAccounts: this.permissionsController.getAccounts.bind(this.permissionsController), networkStore: this.networkController.networkStore, preferencesStore: this.preferencesController.store, txHistoryLimit: 40, @@ -282,6 +266,18 @@ module.exports = class MetamaskController extends EventEmitter { this.isClientOpenAndUnlocked = memState.isUnlocked && this._isClientOpen }) + this.providerApprovalController = new ProviderApprovalController({ + closePopup: opts.closePopup, + initState: initState.ProviderApprovalController, + keyringController: this.keyringController, + openPopup: opts.openPopup, + preferencesController: this.preferencesController, + }) + + this.abTestController = new ABTestController({ + initState: initState.ABTestController, + }) + this.store.updateStructure({ AppStateController: this.appStateController.store, TransactionController: this.txController.store, @@ -294,11 +290,10 @@ module.exports = class MetamaskController extends EventEmitter { InfuraController: this.infuraController.store, CachedBalancesController: this.cachedBalancesController.store, OnboardingController: this.onboardingController.store, + ProviderApprovalController: this.providerApprovalController.store, IncomingTransactionsController: this.incomingTransactionsController.store, - ABTestController: this.abTestController.store, - PermissionsController: this.permissionsController.permissions, - PermissionsMetadata: this.permissionsController.store, ThreeBoxController: this.threeBoxController.store, + ABTestController: this.abTestController.store, }) this.memStore = new ComposableObservableStore(null, { @@ -319,9 +314,11 @@ module.exports = class MetamaskController extends EventEmitter { ShapeshiftController: this.shapeshiftController, InfuraController: this.infuraController.store, OnboardingController: this.onboardingController.store, + // ProviderApprovalController + ProviderApprovalController: this.providerApprovalController.store, + ProviderApprovalControllerMemStore: this.providerApprovalController.memStore, IncomingTransactionsController: this.incomingTransactionsController.store, - PermissionsController: this.permissionsController.permissions, - PermissionsMetadata: this.permissionsController.store, + // ThreeBoxController ThreeBoxController: this.threeBoxController.store, ABTestController: this.abTestController.store, // ENS Controller @@ -342,15 +339,18 @@ module.exports = class MetamaskController extends EventEmitter { version, // account mgmt getAccounts: async ({ origin }) => { - if (origin === 'metamask') { - const selectedAddress = this.preferencesController.getSelectedAddress() - return selectedAddress ? [selectedAddress] : [] - } else if ( - this.keyringController.memStore.getState().isUnlocked - ) { - return await this.permissionsController.getAccounts(origin) + // Expose no accounts if this origin has not been approved, preventing + // account-requring RPC methods from completing successfully + const exposeAccounts = this.providerApprovalController.shouldExposeAccounts(origin) + if (origin !== 'metamask' && !exposeAccounts) { return [] } + const isUnlocked = this.keyringController.memStore.getState().isUnlocked + const selectedAddress = this.preferencesController.getSelectedAddress() + // only show address if account is unlocked + if (isUnlocked && selectedAddress) { + return [selectedAddress] + } else { + return [] } - return [] // changing this is a breaking change }, // tx signing processTransaction: this.newUnapprovedTransaction.bind(this), @@ -371,7 +371,7 @@ module.exports = class MetamaskController extends EventEmitter { * Constructor helper: initialize a public config store. * This store is used to make some config info available to Dapps synchronously. */ - createPublicConfigStore () { + createPublicConfigStore ({ checkIsEnabled }) { // subset of state for metamask inpage provider const publicConfigStore = new ObservableStore() @@ -384,16 +384,24 @@ module.exports = class MetamaskController extends EventEmitter { } function updatePublicConfigStore (memState) { - publicConfigStore.putState(selectPublicState(memState)) + const publicState = selectPublicState(memState) + publicConfigStore.putState(publicState) } - function selectPublicState ({ isUnlocked, network, provider }) { - return { + function selectPublicState ({ isUnlocked, selectedAddress, network, completedOnboarding, provider }) { + const isEnabled = checkIsEnabled() + const isReady = isUnlocked && isEnabled + const result = { isUnlocked, + isEnabled, + selectedAddress: isReady ? selectedAddress : null, networkVersion: network, + onboardingcomplete: completedOnboarding, chainId: selectChainId({ network, provider }), } + return result } + return publicConfigStore } @@ -425,13 +433,13 @@ module.exports = class MetamaskController extends EventEmitter { */ getApi () { const keyringController = this.keyringController + const preferencesController = this.preferencesController + const txController = this.txController const networkController = this.networkController + const providerApprovalController = this.providerApprovalController const onboardingController = this.onboardingController - const permissionsController = this.permissionsController - const preferencesController = this.preferencesController const threeBoxController = this.threeBoxController const abTestController = this.abTestController - const txController = this.txController return { // etc @@ -489,6 +497,7 @@ module.exports = class MetamaskController extends EventEmitter { setPreference: nodeify(preferencesController.setPreference, preferencesController), completeOnboarding: nodeify(preferencesController.completeOnboarding, preferencesController), addKnownMethodData: nodeify(preferencesController.addKnownMethodData, preferencesController), + unsetMigratedPrivacyMode: nodeify(preferencesController.unsetMigratedPrivacyMode, preferencesController), // BlacklistController whitelistPhishingDomain: this.whitelistPhishingDomain.bind(this), @@ -536,6 +545,11 @@ module.exports = class MetamaskController extends EventEmitter { signTypedMessage: nodeify(this.signTypedMessage, this), cancelTypedMessage: this.cancelTypedMessage.bind(this), + // provider approval + approveProviderRequestByOrigin: providerApprovalController.approveProviderRequestByOrigin.bind(providerApprovalController), + rejectProviderRequestByOrigin: providerApprovalController.rejectProviderRequestByOrigin.bind(providerApprovalController), + clearApprovedOrigins: providerApprovalController.clearApprovedOrigins.bind(providerApprovalController), + // onboarding controller setSeedPhraseBackedUp: nodeify(onboardingController.setSeedPhraseBackedUp, onboardingController), @@ -549,21 +563,10 @@ module.exports = class MetamaskController extends EventEmitter { // a/b test controller getAssignedABTestGroupName: nodeify(abTestController.getAssignedABTestGroupName, abTestController), - - // permissions - approvePermissionsRequest: nodeify(permissionsController.approvePermissionsRequest, permissionsController), - clearPermissions: permissionsController.clearPermissions.bind(permissionsController), - getApprovedAccounts: nodeify(permissionsController.getAccounts.bind(permissionsController)), - rejectPermissionsRequest: nodeify(permissionsController.rejectPermissionsRequest, permissionsController), - removePermissionsFor: permissionsController.removePermissionsFor.bind(permissionsController), - updateExposedAccounts: nodeify(permissionsController.updateExposedAccounts, permissionsController), - legacyExposeAccounts: nodeify(permissionsController.legacyExposeAccounts, permissionsController), - - getRequestAccountTabIds: (cb) => cb(null, this.getRequestAccountTabIds()), - getOpenMetamaskTabsIds: (cb) => cb(null, this.getOpenMetamaskTabsIds()), } } + //============================================================================= // VAULT / KEYRING RELATED METHODS //============================================================================= @@ -1343,10 +1346,10 @@ module.exports = class MetamaskController extends EventEmitter { // setup multiplexing const mux = setupMultiplex(connectionStream) - - // messages between inpage and background - this.setupProviderConnection(mux.createStream('provider'), senderUrl, extensionId) - this.setupPublicConfig(mux.createStream('publicConfig')) + // connect features + const publicApi = this.setupPublicApi(mux.createStream('publicApi')) + this.setupProviderConnection(mux.createStream('provider'), senderUrl, extensionId, publicApi) + this.setupPublicConfig(mux.createStream('publicConfig'), senderUrl) } /** @@ -1402,9 +1405,7 @@ module.exports = class MetamaskController extends EventEmitter { this.activeControllerConnections-- this.emit('controllerConnectionChanged', this.activeControllerConnections) // report any error - if (err) { - log.error(err) - } + if (err) log.error(err) } ) dnode.on('remote', (remote) => { @@ -1424,15 +1425,13 @@ module.exports = class MetamaskController extends EventEmitter { * resource is an extension. * @param {object} publicApi - The public API */ - setupProviderConnection (outStream, senderUrl, extensionId) { - const origin = senderUrl.hostname - const engine = this.setupProviderEngine(senderUrl, extensionId) + setupProviderConnection (outStream, senderUrl, extensionId, publicApi) { + const getSiteMetadata = publicApi && publicApi.getSiteMetadata + const engine = this.setupProviderEngine(senderUrl, extensionId, getSiteMetadata) // setup connection const providerStream = createEngineStream({ engine }) - const connectionId = this.addConnection(origin, { engine }) - pump( outStream, providerStream, @@ -1444,10 +1443,7 @@ module.exports = class MetamaskController extends EventEmitter { mid.destroy() } }) - connectionId && this.removeConnection(origin, connectionId) - if (err) { - log.error(err) - } + if (err) log.error(err) } ) } @@ -1455,8 +1451,7 @@ module.exports = class MetamaskController extends EventEmitter { /** * A method for creating a provider that is safely restricted for the requesting domain. **/ - setupProviderEngine (senderUrl, extensionId) { - + setupProviderEngine (senderUrl, extensionId, getSiteMetadata) { const origin = senderUrl.hostname // setup json rpc engine stack const engine = new RpcEngine() @@ -1470,17 +1465,20 @@ module.exports = class MetamaskController extends EventEmitter { const subscriptionManager = createSubscriptionManager({ provider, blockTracker }) subscriptionManager.events.on('notification', (message) => engine.emit('notification', message)) - // append origin to each request + // metadata engine.push(createOriginMiddleware({ origin })) - // logging engine.push(createLoggerMiddleware({ origin })) // filter and subscription polyfills engine.push(filterMiddleware) engine.push(subscriptionManager.middleware) - // permissions - engine.push(this.permissionsController.createMiddleware({ origin, extensionId })) // watch asset engine.push(this.preferencesController.requestWatchAsset.bind(this.preferencesController)) + // requestAccounts + engine.push(this.providerApprovalController.createMiddleware({ + senderUrl, + extensionId, + getSiteMetadata, + })) // forward to metamask primary provider engine.push(providerAsMiddleware(provider)) return engine @@ -1495,9 +1493,13 @@ module.exports = class MetamaskController extends EventEmitter { * this is a good candidate for deprecation. * * @param {*} outStream - The stream to provide public config over. + * @param {URL} senderUrl - The URL of requesting resource */ - setupPublicConfig (outStream) { - const configStore = this.createPublicConfigStore() + setupPublicConfig (outStream, senderUrl) { + const configStore = this.createPublicConfigStore({ + // check the providerApprovalController's approvedOrigins + checkIsEnabled: () => this.providerApprovalController.shouldExposeAccounts(senderUrl.hostname), + }) const configStream = asStream(configStore) pump( @@ -1506,145 +1508,43 @@ module.exports = class MetamaskController extends EventEmitter { (err) => { configStore.destroy() configStream.destroy() - if (err) { - log.error(err) - } + if (err) log.error(err) } ) } - // manage external connections - - onMessage (message, sender, sendResponse) { - if (!message || !message.type) { - log.debug(`Ignoring invalid message: '${JSON.stringify(message)}'`) - return - } - - let handleMessage - - try { - if (message.type === 'metamask:registerOnboarding') { - assert(sender.tab, 'Missing tab from sender') - assert(sender.tab.id && sender.tab.id !== extension.tabs.TAB_ID_NONE, 'Missing tab ID from sender') - assert(message.location, 'Missing location from message') - - handleMessage = this.onboardingController.registerOnboarding(message.location, sender.tab.id) - } else { - throw new Error(`Unrecognized message type: '${message.type}'`) - } - } catch (error) { - console.error(error) - sendResponse(error) - return true - } - - if (handleMessage) { - handleMessage - .then(() => { - sendResponse(null, true) - }) - .catch((error) => { - console.error(error) - sendResponse(error) - }) - return true - } - } - - /** - * Adds a reference to a connection by origin. Ignores the 'metamask' origin. - * Caller must ensure that the returned id is stored such that the reference - * can be deleted later. - * - * @param {string} origin - The connection's origin string. - * @param {Object} options - Data associated with the connection - * @param {Object} options.engine - The connection's JSON Rpc Engine - * @returns {string} - The connection's id (so that it can be deleted later) - */ - addConnection (origin, { engine }) { - - if (origin === 'metamask') { - return null - } - - if (!this.connections[origin]) { - this.connections[origin] = {} - } - - const id = nanoid() - this.connections[origin][id] = { - engine, - } - - return id - } - - /** - * Deletes a reference to a connection, by origin and id. - * Ignores unknown origins. - * - * @param {string} origin - The connection's origin string. - * @param {string} id - The connection's id, as returned from addConnection. - */ - removeConnection (origin, id) { - - const connections = this.connections[origin] - if (!connections) { - return - } - - delete connections[id] - - if (Object.keys(connections.length === 0)) { - delete this.connections[origin] - } - } - /** - * Causes the RPC engines associated with the connections to the given origin - * to emit a notification event with the given payload. - * Does nothing if the extension is locked or the origin is unknown. + * A method for providing our public api over a stream. + * This includes a method for setting site metadata like title and image * - * @param {string} origin - The connection's origin string. - * @param {any} payload - The event payload. + * @param {*} outStream - The stream to provide the api over. */ - notifyConnections (origin, payload) { - - const { isUnlocked } = this.getState() - const connections = this.connections[origin] - if (!isUnlocked || !connections) { - return - } - - Object.values(connections).forEach(conn => { - conn.engine && conn.engine.emit('notification', payload) - }) - } + setupPublicApi (outStream) { + const dnode = Dnode() + // connect dnode api to remote connection + pump( + outStream, + dnode, + outStream, + (err) => { + // report any error + if (err) log.error(err) + } + ) - /** - * Causes the RPC engines associated with all connections to emit a - * notification event with the given payload. - * Does nothing if the extension is locked. - * - * @param {any} payload - The event payload. - */ - notifyAllConnections (payload) { + const getRemote = createDnodeRemoteGetter(dnode) - const { isUnlocked } = this.getState() - if (!isUnlocked) { - return + const publicApi = { + // wrap with an await remote + getSiteMetadata: async () => { + const remote = await getRemote() + return await pify(remote.getSiteMetadata)() + }, } - Object.values(this.connections).forEach(origin => { - Object.values(origin).forEach(conn => { - conn.engine && conn.engine.emit('notification', payload) - }) - }) + return publicApi } - // handlers - /** * Handle a KeyringController update * @param {object} state the KC state @@ -1673,8 +1573,6 @@ module.exports = class MetamaskController extends EventEmitter { } } - // misc - /** * A method for emitting the full MetaMask state to all registered listeners. * @private @@ -1683,10 +1581,6 @@ module.exports = class MetamaskController extends EventEmitter { this.emit('update', this.getState()) } - //============================================================================= - // MISCELLANEOUS - //============================================================================= - /** * A method for estimating a good gas price at recent prices. * Returns the lowest price that would have been included in @@ -1783,14 +1677,10 @@ module.exports = class MetamaskController extends EventEmitter { * @param {string} amount - The amount of ether desired, as a base 10 string. */ buyEth (address, amount) { - if (!amount) { - amount = '5' - } + if (!amount) amount = '5' const network = this.networkController.getNetworkState() const url = getBuyEthUrl({ network, address, amount }) - if (url) { - this.platform.openWindow({ url }) - } + if (url) this.platform.openWindow({ url }) } /** diff --git a/app/scripts/migrations/004.js b/app/scripts/migrations/004.js index a1ae823cde..cd558300c3 100644 --- a/app/scripts/migrations/004.js +++ b/app/scripts/migrations/004.js @@ -9,9 +9,7 @@ module.exports = { const safeVersionedData = clone(versionedData) safeVersionedData.meta.version = version try { - if (safeVersionedData.data.config.provider.type !== 'rpc') { - return Promise.resolve(safeVersionedData) - } + if (safeVersionedData.data.config.provider.type !== 'rpc') return Promise.resolve(safeVersionedData) switch (safeVersionedData.data.config.provider.rpcTarget) { case 'https://testrpc.metamask.io/': safeVersionedData.data.config.provider = { @@ -23,7 +21,6 @@ module.exports = { type: 'mainnet', } break - // No default } } catch (_) {} return Promise.resolve(safeVersionedData) diff --git a/app/scripts/migrations/015.js b/app/scripts/migrations/015.js index 3d20b58db5..5e2f9e63bd 100644 --- a/app/scripts/migrations/015.js +++ b/app/scripts/migrations/015.js @@ -32,11 +32,8 @@ function transformState (state) { if (TransactionController && TransactionController.transactions) { const transactions = TransactionController.transactions newState.TransactionController.transactions = transactions.map((txMeta) => { - if (!txMeta.err) { - return txMeta - } else if (txMeta.err.message === 'Gave up submitting tx.') { - txMeta.status = 'failed' - } + if (!txMeta.err) return txMeta + else if (txMeta.err.message === 'Gave up submitting tx.') txMeta.status = 'failed' return txMeta }) } diff --git a/app/scripts/migrations/016.js b/app/scripts/migrations/016.js index 76a106ca7e..048c7a40ec 100644 --- a/app/scripts/migrations/016.js +++ b/app/scripts/migrations/016.js @@ -33,9 +33,7 @@ function transformState (state) { const transactions = newState.TransactionController.transactions newState.TransactionController.transactions = transactions.map((txMeta) => { - if (!txMeta.err) { - return txMeta - } + if (!txMeta.err) return txMeta if (txMeta.err === 'transaction with the same hash was already imported.') { txMeta.status = 'submitted' delete txMeta.err diff --git a/app/scripts/migrations/017.js b/app/scripts/migrations/017.js index 918df4ade1..5f6d906d67 100644 --- a/app/scripts/migrations/017.js +++ b/app/scripts/migrations/017.js @@ -31,9 +31,7 @@ function transformState (state) { if (TransactionController && TransactionController.transactions) { const transactions = newState.TransactionController.transactions newState.TransactionController.transactions = transactions.map((txMeta) => { - if (!txMeta.status === 'failed') { - return txMeta - } + if (!txMeta.status === 'failed') return txMeta if (txMeta.retryCount > 0 && txMeta.retryCount < 2) { txMeta.status = 'submitted' delete txMeta.err diff --git a/app/scripts/migrations/019.js b/app/scripts/migrations/019.js index f560f5579d..7b726c3e85 100644 --- a/app/scripts/migrations/019.js +++ b/app/scripts/migrations/019.js @@ -35,9 +35,7 @@ function transformState (state) { const transactions = newState.TransactionController.transactions newState.TransactionController.transactions = transactions.map((txMeta, _, txList) => { - if (txMeta.status !== 'submitted') { - return txMeta - } + if (txMeta.status !== 'submitted') return txMeta const confirmedTxs = txList.filter((tx) => tx.status === 'confirmed') .filter((tx) => tx.txParams.from === txMeta.txParams.from) diff --git a/app/scripts/migrations/022.js b/app/scripts/migrations/022.js index eeb142b7a9..1fbe241e6e 100644 --- a/app/scripts/migrations/022.js +++ b/app/scripts/migrations/022.js @@ -33,9 +33,7 @@ function transformState (state) { const transactions = newState.TransactionController.transactions newState.TransactionController.transactions = transactions.map((txMeta) => { - if (txMeta.status !== 'submitted' || txMeta.submittedTime) { - return txMeta - } + if (txMeta.status !== 'submitted' || txMeta.submittedTime) return txMeta txMeta.submittedTime = (new Date()).getTime() return txMeta }) diff --git a/app/scripts/migrations/023.js b/app/scripts/migrations/023.js index 84532537ea..18493a7893 100644 --- a/app/scripts/migrations/023.js +++ b/app/scripts/migrations/023.js @@ -33,9 +33,7 @@ function transformState (state) { if (TransactionController && TransactionController.transactions) { const transactions = newState.TransactionController.transactions - if (transactions.length <= 40) { - return newState - } + if (transactions.length <= 40) return newState const reverseTxList = transactions.reverse() let stripping = true @@ -46,11 +44,8 @@ function transformState (state) { txMeta.status === 'confirmed' || txMeta.status === 'dropped') }) - if (txIndex < 0) { - stripping = false - } else { - reverseTxList.splice(txIndex, 1) - } + if (txIndex < 0) stripping = false + else reverseTxList.splice(txIndex, 1) } newState.TransactionController.transactions = reverseTxList.reverse() diff --git a/app/scripts/migrations/024.js b/app/scripts/migrations/024.js index fda463e1a1..5ffaea377e 100644 --- a/app/scripts/migrations/024.js +++ b/app/scripts/migrations/024.js @@ -25,9 +25,7 @@ module.exports = { function transformState (state) { const newState = state - if (!newState.TransactionController) { - return newState - } + if (!newState.TransactionController) return newState const transactions = newState.TransactionController.transactions newState.TransactionController.transactions = transactions.map((txMeta, _) => { if ( diff --git a/app/scripts/migrations/025.js b/app/scripts/migrations/025.js index 48bff962de..fd4faa782c 100644 --- a/app/scripts/migrations/025.js +++ b/app/scripts/migrations/025.js @@ -29,9 +29,7 @@ function transformState (state) { if (newState.TransactionController.transactions) { const transactions = newState.TransactionController.transactions newState.TransactionController.transactions = transactions.map((txMeta) => { - if (txMeta.status !== 'unapproved') { - return txMeta - } + if (txMeta.status !== 'unapproved') return txMeta txMeta.txParams = normalizeTxParams(txMeta.txParams) return txMeta }) @@ -56,9 +54,7 @@ function normalizeTxParams (txParams) { // apply only keys in the whiteList const normalizedTxParams = {} Object.keys(whiteList).forEach((key) => { - if (txParams[key]) { - normalizedTxParams[key] = whiteList[key](txParams[key]) - } + if (txParams[key]) normalizedTxParams[key] = whiteList[key](txParams[key]) }) return normalizedTxParams diff --git a/app/scripts/migrations/029.js b/app/scripts/migrations/029.js index 59724c1545..e17479ccc2 100644 --- a/app/scripts/migrations/029.js +++ b/app/scripts/migrations/029.js @@ -24,3 +24,4 @@ module.exports = { return isApproved && now - createdTime > unacceptableDelay }), } + diff --git a/app/scripts/migrations/031.js b/app/scripts/migrations/031.js index 927de98c43..9c8cbeb09b 100644 --- a/app/scripts/migrations/031.js +++ b/app/scripts/migrations/031.js @@ -3,7 +3,7 @@ const version = 31 const clone = require('clone') /* - * The purpose of this migration is to properly set the completedOnboarding flag based on the state + * The purpose of this migration is to properly set the completedOnboarding flag baesd on the state * of the KeyringController. */ module.exports = { diff --git a/app/scripts/migrations/040.js b/app/scripts/migrations/040.js deleted file mode 100644 index 042f499b2c..0000000000 --- a/app/scripts/migrations/040.js +++ /dev/null @@ -1,23 +0,0 @@ -const version = 40 -const clone = require('clone') - -/** - * Site connections are now managed by the PermissionsController, and the - * ProviderApprovalController is removed. This migration deletes all - * ProviderApprovalController state. - */ -module.exports = { - version, - migrate: async function (originalVersionedData) { - const versionedData = clone(originalVersionedData) - versionedData.meta.version = version - const state = versionedData.data - versionedData.data = transformState(state) - return versionedData - }, -} - -function transformState (state) { - delete state.ProviderApprovalController - return state -} diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js index 5ae05d2304..d54a8a7b32 100644 --- a/app/scripts/platforms/extension.js +++ b/app/scripts/platforms/extension.js @@ -1,7 +1,7 @@ const extension = require('extensionizer') const {createExplorerLink: explorerLink} = require('etherscan-link') -const { getEnvironmentType, checkForError } = require('../lib/util') +const {getEnvironmentType} = require('../lib/util') const {ENVIRONMENT_TYPE_BACKGROUND} = require('../lib/enums') class ExtensionPlatform { @@ -66,58 +66,6 @@ class ExtensionPlatform { } } - queryTabs () { - return new Promise((resolve, reject) => { - extension.tabs.query({}, tabs => { - const err = checkForError() - if (err) { - reject(err) - } else { - resolve(tabs) - } - }) - }) - } - - currentTab () { - return new Promise((resolve, reject) => { - extension.tabs.getCurrent(tab => { - const err = checkForError() - if (err) { - reject(err) - } else { - resolve(tab) - } - }) - }) - } - - switchToTab (tabId) { - return new Promise((resolve, reject) => { - extension.tabs.update(tabId, {highlighted: true}, (tab) => { - const err = checkForError() - if (err) { - reject(err) - } else { - resolve(tab) - } - }) - }) - } - - closeTab (tabId) { - return new Promise((resolve, reject) => { - extension.tabs.remove(tabId, () => { - const err = checkForError() - if (err) { - reject(err) - } else { - resolve() - } - }) - }) - } - _showConfirmedTransaction (txMeta) { this._subscribeToNotificationClicked() diff --git a/development/announcer.js b/development/announcer.js index 8b0c1de272..ea1bfdd366 100644 --- a/development/announcer.js +++ b/development/announcer.js @@ -1,11 +1,11 @@ -const manifest = require('../app/manifest.json') -const version = manifest.version +var manifest = require('../app/manifest.json') +var version = manifest.version -const fs = require('fs') -const path = require('path') -const changelog = fs.readFileSync(path.join(__dirname, '..', 'CHANGELOG.md')).toString() +var fs = require('fs') +var path = require('path') +var changelog = fs.readFileSync(path.join(__dirname, '..', 'CHANGELOG.md')).toString() -const log = changelog.split(version)[1].split('##')[0].trim() +var log = changelog.split(version)[1].split('##')[0].trim() const msg = `*MetaMask ${version}* now published! It should auto-update soon!\n${log}` diff --git a/development/auto-changelog.sh b/development/auto-changelog.sh index 26ab8e93f7..3ed059b3d7 100755 --- a/development/auto-changelog.sh +++ b/development/auto-changelog.sh @@ -10,7 +10,7 @@ git fetch --tags most_recent_tag="$(git describe --tags "$(git rev-list --tags --max-count=1)")" -git rev-list "${most_recent_tag}"..HEAD | while read -r commit +git rev-list "${most_recent_tag}"..HEAD | while read commit do subject="$(git show -s --format="%s" "$commit")" diff --git a/development/mock-3box.js b/development/mock-3box.js index a26e02a820..53f228f216 100644 --- a/development/mock-3box.js +++ b/development/mock-3box.js @@ -28,9 +28,7 @@ class Mock3Box { static openBox (address) { this.address = address return Promise.resolve({ - onSyncDone: cb => { - setTimeout(cb, 200) - }, + onSyncDone: cb => { setTimeout(cb, 200) }, openSpace: async (spaceName, config) => { const { onSyncDone } = config this.spaceName = spaceName diff --git a/development/mock-dev.js b/development/mock-dev.js index 51a8cde463..8da6251493 100644 --- a/development/mock-dev.js +++ b/development/mock-dev.js @@ -10,13 +10,13 @@ * without having to re-open the plugin or even re-build it. */ -import React from 'react' const render = require('react-dom').render +const h = require('react-hyperscript') const Root = require('../ui/app/pages') const configureStore = require('../ui/app/store/store') const actions = require('../ui/app/store/actions') const backGroundConnectionModifiers = require('./backGroundConnectionModifiers') -import Selector from './selector' +const Selector = require('./selector') const MetamaskController = require('../app/scripts/metamask-controller') const firstTimeState = require('../app/scripts/first-time-state') const ExtensionPlatform = require('../app/scripts/platforms/extension') @@ -94,7 +94,7 @@ function modifyBackgroundConnection (backgroundConnectionModifier) { } // parse opts -const store = configureStore(firstState) +var store = configureStore(firstState) // start app startApp() @@ -106,38 +106,40 @@ function startApp () { body.appendChild(container) render( -
- - -
- -
-
, - container, - ) + }, + }, [ + h(Root, { + store: store, + }), + ]), + + ] + ), container) } diff --git a/development/rollback.sh b/development/rollback.sh index a3040e6f11..639d72a675 100755 --- a/development/rollback.sh +++ b/development/rollback.sh @@ -4,20 +4,20 @@ echo "Rolling back to version $1" # Checkout branch to increment version -git checkout -b "version-increment-$1" +git checkout -b version-increment-$1 yarn version:bump patch # Store the new version name -NEW_VERSION=$(jq -r .version < app/manifest.json) +NEW_VERSION=$(cat app/manifest.json | jq -r .version) # Make sure origin tags are loaded git fetch origin # check out the rollback branch -git checkout "origin/v$1" +git checkout origin/v$1 # Create the rollback branch. -git checkout -b "Version-$NEW_VERSION-Rollback-to-$1" +git checkout -b Version-$NEW_VERSION-Rollback-to-$1 # Set the version files to the next one. git checkout master CHANGELOG.md @@ -28,8 +28,8 @@ git commit -m "Version $NEW_VERSION (Rollback to $1)" git push -u origin HEAD # Create tag and push that up too -git tag "v${NEW_VERSION}" -git push origin "v${NEW_VERSION}" +git tag v${NEW_VERSION} +git push origin v${NEW_VERSION} # Cleanup version branch -git branch -D "version-increment-$1" +git branch -D version-increment-$1 diff --git a/development/selector.js b/development/selector.js index 01011c7107..2673d0fe3c 100644 --- a/development/selector.js +++ b/development/selector.js @@ -1,49 +1,42 @@ -import PropTypes from 'prop-types' -import React, {Component} from 'react' +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits -export default class Selector extends Component { - state = {} +module.exports = NewComponent - render () { - const { - states, - selectedKey, - actions, - store, - modifyBackgroundConnection, - backGroundConnectionModifiers, - } = this.props - const selected = this.state.selected || selectedKey - - return ( - - ) - } +inherits(NewComponent, Component) +function NewComponent () { + Component.call(this) } -Selector.propTypes = { - states: PropTypes.object.isRequired, - selectedKey: PropTypes.string.isRequired, - actions: PropTypes.object.isRequired, - store: PropTypes.object.isRequired, - modifyBackgroundConnection: PropTypes.func.isRequired, - backGroundConnectionModifiers: PropTypes.object.isRequired, +NewComponent.prototype.render = function () { + const props = this.props + const { + states, + selectedKey, + actions, + store, + modifyBackgroundConnection, + backGroundConnectionModifiers, + } = props + + const state = this.state || {} + const selected = state.selected || selectedKey + + return h('select', { + style: { + margin: '20px 20px 0px', + }, + value: selected, + onChange: (event) => { + const selectedKey = event.target.value + const backgroundConnectionModifier = backGroundConnectionModifiers[selectedKey] + modifyBackgroundConnection(backgroundConnectionModifier || {}) + store.dispatch(actions.update(selectedKey)) + this.setState({ selected: selectedKey }) + }, + }, Object.keys(states).map((stateName) => { + return h('option', { value: stateName }, stateName) + })) + } diff --git a/development/show-deps-install-scripts.js b/development/show-deps-install-scripts.js index 419c9d25f2..03a9bb859b 100644 --- a/development/show-deps-install-scripts.js +++ b/development/show-deps-install-scripts.js @@ -5,9 +5,7 @@ const readInstalled = require('read-installed') const installScripts = ['preinstall', 'install', 'postinstall'] readInstalled('./', { dev: true }, function (err, data) { - if (err) { - throw err - } + if (err) throw err const deps = data.dependencies Object.entries(deps).forEach(([packageName, packageData]) => { @@ -15,20 +13,12 @@ readInstalled('./', { dev: true }, function (err, data) { const scriptKeys = Reflect.ownKeys(packageScripts) const hasInstallScript = installScripts.some(installKey => scriptKeys.includes(installKey)) - if (!hasInstallScript) { - return - } + if (!hasInstallScript) return const matchingScripts = {} - if (packageScripts.preinstall) { - matchingScripts.preinstall = packageScripts.preinstall - } - if (packageScripts.install) { - matchingScripts.install = packageScripts.install - } - if (packageScripts.postinstall) { - matchingScripts.postinstall = packageScripts.postinstall - } + if (packageScripts.preinstall) matchingScripts.preinstall = packageScripts.preinstall + if (packageScripts.install) matchingScripts.install = packageScripts.install + if (packageScripts.postinstall) matchingScripts.postinstall = packageScripts.postinstall const scriptNames = Reflect.ownKeys(matchingScripts) const relativePath = path.relative(process.cwd(), packageData.path) diff --git a/development/sourcemap-validator.js b/development/sourcemap-validator.js index 22c448862a..546745f166 100644 --- a/development/sourcemap-validator.js +++ b/development/sourcemap-validator.js @@ -52,9 +52,7 @@ async function validateSourcemapForFile ({ buildName }) { const consumer = await new SourceMapConsumer(rawSourceMap) const hasContentsOfAllSources = consumer.hasContentsOfAllSources() - if (!hasContentsOfAllSources) { - console.warn('SourcemapValidator - missing content of some sources...') - } + if (!hasContentsOfAllSources) console.warn('SourcemapValidator - missing content of some sources...') console.log(` sampling from ${consumer.sources.length} files`) let sampleCount = 0 @@ -96,10 +94,8 @@ async function validateSourcemapForFile ({ buildName }) { } function indicesOf (substring, string) { - const a = [] - let i = -1 - while ((i = string.indexOf(substring, i + 1)) >= 0) { - a.push(i) - } + var a = [] + var i = -1 + while ((i = string.indexOf(substring, i + 1)) >= 0) a.push(i) return a } diff --git a/development/static-server.js b/development/static-server.js deleted file mode 100644 index d8f22cabea..0000000000 --- a/development/static-server.js +++ /dev/null @@ -1,92 +0,0 @@ -const fs = require('fs') -const http = require('http') -const path = require('path') - -const chalk = require('chalk') -const pify = require('pify') -const serveHandler = require('serve-handler') - -const fsStat = pify(fs.stat) -const DEFAULT_PORT = 9080 - -const onResponse = (request, response) => { - if (response.statusCode >= 400) { - console.log(chalk`{gray '-->'} {red ${response.statusCode}} ${request.url}`) - } else if (response.statusCode >= 200 && response.statusCode < 300) { - console.log(chalk`{gray '-->'} {green ${response.statusCode}} ${request.url}`) - } else { - console.log(chalk`{gray '-->'} {green.dim ${response.statusCode}} ${request.url}`) - } -} -const onRequest = (request, response) => { - console.log(chalk`{gray '<--'} {blue [${request.method}]} ${request.url}`) - response.on('finish', () => onResponse(request, response)) -} - -const startServer = ({ port, rootDirectory }) => { - const server = http.createServer((request, response) => { - if (request.url.startsWith('/node_modules/')) { - request.url = request.url.substr(14) - return serveHandler(request, response, { - directoryListing: false, - public: path.resolve('./node_modules'), - }) - } - return serveHandler(request, response, { - directoryListing: false, - public: rootDirectory, - }) - }) - - server.on('request', onRequest) - - server.listen(port, () => { - console.log(`Running at http://localhost:${port}`) - }) -} - -const parsePort = (portString) => { - const port = Number(portString) - if (!Number.isInteger(port)) { - throw new Error(`Port '${portString}' is invalid; must be an integer`) - } else if (port < 0 || port > 65535) { - throw new Error(`Port '${portString}' is out of range; must be between 0 and 65535 inclusive`) - } - return port -} - -const parseDirectoryArgument = async (pathString) => { - const resolvedPath = path.resolve(pathString) - const directoryStats = await fsStat(resolvedPath) - if (!directoryStats.isDirectory()) { - throw new Error(`Invalid path '${pathString}'; must be a directory`) - } - return resolvedPath -} - -const main = async () => { - const args = process.argv.slice(2) - - const options = { - port: process.env.port || DEFAULT_PORT, - rootDirectory: path.resolve('.'), - } - - while (args.length) { - if (/^(--port|-p)$/i.test(args[0])) { - if (args[1] === undefined) { - throw new Error('Missing port argument') - } - options.port = parsePort(args[1]) - args.splice(0, 2) - } else { - options.rootDirectory = await parseDirectoryArgument(args[0]) - args.splice(0, 1) - } - } - - startServer(options) -} - -main() - .catch(console.error) diff --git a/development/verify-locale-strings.js b/development/verify-locale-strings.js index c6875b36c9..aa0c4503fd 100644 --- a/development/verify-locale-strings.js +++ b/development/verify-locale-strings.js @@ -161,11 +161,11 @@ async function verifyEnglishLocale (fix = false) { const englishLocale = await getLocale('en') const javascriptFiles = await findJavascriptFiles(path.resolve(__dirname, '..', 'ui')) - const regex = /'(\w+)'|"(\w+)"/g + const regex = /'(\w+)'/g const usedMessages = new Set() for await (const fileContents of getFileContents(javascriptFiles)) { for (const match of matchAll.call(fileContents, regex)) { - usedMessages.add(match[1] || match[2]) + usedMessages.add(match[1]) } } diff --git a/development/version-bump.js b/development/version-bump.js index 6ae21b782a..66b6baffee 100644 --- a/development/version-bump.js +++ b/development/version-bump.js @@ -40,8 +40,6 @@ function newVersionFrom (manifest, bumpType) { case 'patch': segments[2] += 1 break - default: - throw new Error(`invalid bumpType ${bumpType}`) } return segments.map(String).join('.') diff --git a/docs/porting_to_new_environment.md b/docs/porting_to_new_environment.md index 92f06f37db..9cb8e9c2dc 100644 --- a/docs/porting_to_new_environment.md +++ b/docs/porting_to_new_environment.md @@ -18,8 +18,8 @@ const providerFromEngine = require('eth-json-rpc-middleware/providerFromEngine') /** * returns a provider restricted to the requesting domain **/ -function incomingConnection (domain) { - const engine = metamaskController.setupProviderEngine(domain) +function incomingConnection (domain, getSiteMetadata) { + const engine = metamaskController.setupProviderEngine(domain, getSiteMetadata) const provider = providerFromEngine(engine) return provider } @@ -32,6 +32,15 @@ const filterMiddleware = engine._middleware.filter(mid => mid.name === 'filterMi filterMiddleware.destroy() ``` +### getSiteMetadata() + +This method is used to enhance our confirmation screens with images and text representing the requesting domain. + +It should return a promise that resolves with an object with the following properties: + +- `name`: The requesting site's name. +- `icon`: A URI representing the site's logo. + ### Using the Streams Interface Only use this if you intend to construct the [metamask-inpage-provider](https://github.com/MetaMask/metamask-inpage-provider) over a stream! diff --git a/gulpfile.js b/gulpfile.js index d5d74333af..3e4a3b7ff2 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -428,9 +428,7 @@ function createTasksForBuildJs ({ rootDir, taskPrefix, bundleTaskOpts, destinati // compose into larger task const subtasks = [] subtasks.push(gulp.parallel(buildPhase1.map(file => `${taskPrefix}:${file}`))) - if (buildPhase2.length) { - subtasks.push(gulp.parallel(buildPhase2.map(file => `${taskPrefix}:${file}`))) - } + if (buildPhase2.length) subtasks.push(gulp.parallel(buildPhase2.map(file => `${taskPrefix}:${file}`))) gulp.task(taskPrefix, gulp.series(subtasks)) } diff --git a/package.json b/package.json index 000a66d161..86acdd5071 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "dapp": "static-server test/e2e/contract-test --port 8080", "dapp-chain": "GANACHE_ARGS='-b 2' concurrently -k -n ganache,dapp -p '[{time}][{name}]' 'yarn ganache:start' 'sleep 5 && static-server test/e2e/contract-test --port 8080'", "watch:test:unit": "nodemon --exec \"yarn test:unit\" ./test ./app ./ui", - "sendwithprivatedapp": "node development/static-server.js test/e2e/send-eth-with-private-key-test --port 8080", + "sendwithprivatedapp": "static-server test/e2e/send-eth-with-private-key-test --port 8080", "test:unit": "cross-env METAMASK_ENV=test mocha --exit --require test/setup.js --recursive \"test/unit/**/*.js\" \"ui/app/**/*.test.js\"", "test:unit:global": "mocha test/unit-global/*", "test:single": "cross-env METAMASK_ENV=test mocha --require test/helper.js", diff --git a/test/e2e/contract-test/contract.js b/test/e2e/contract-test/contract.js index 6c3941b9de..ebfea34ecd 100644 --- a/test/e2e/contract-test/contract.js +++ b/test/e2e/contract-test/contract.js @@ -1,4 +1,4 @@ -/*global ethereum, MetamaskOnboarding */ +/*global ethereum*/ /* The `piggybankContract` is compiled from: @@ -30,14 +30,8 @@ The `piggybankContract` is compiled from: } */ -const forwarderOrigin = 'http://localhost:9010' - -const isMetaMaskInstalled = () => { - return Boolean(window.ethereum && window.ethereum.isMetaMask) -} - -const initialize = () => { - const onboardButton = document.getElementById('connectButton') +web3.currentProvider.enable().then(() => { + var piggybankContract = web3.eth.contract([{'constant': false, 'inputs': [{'name': 'withdrawAmount', 'type': 'uint256'}], 'name': 'withdraw', 'outputs': [{'name': 'remainingBal', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'owner', 'outputs': [{'name': '', 'type': 'address'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [], 'name': 'deposit', 'outputs': [{'name': '', 'type': 'uint256'}], 'payable': true, 'stateMutability': 'payable', 'type': 'function'}, {'inputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'constructor'}]) const deployButton = document.getElementById('deployButton') const depositButton = document.getElementById('depositButton') const withdrawButton = document.getElementById('withdrawButton') @@ -48,307 +42,200 @@ const initialize = () => { const transferTokensWithoutGas = document.getElementById('transferTokensWithoutGas') const approveTokensWithoutGas = document.getElementById('approveTokensWithoutGas') const signTypedData = document.getElementById('signTypedData') - const signTypedDataResults = document.getElementById('signTypedDataResult') - const getAccountsButton = document.getElementById('getAccounts') - const getAccountsResults = document.getElementById('getAccountsResult') - - const contractStatus = document.getElementById('contractStatus') - const tokenAddress = document.getElementById('tokenAddress') - const networkDiv = document.getElementById('network') - const chainIdDiv = document.getElementById('chainId') - const accountsDiv = document.getElementById('accounts') - - let onboarding - try { - onboarding = new MetamaskOnboarding({ forwarderOrigin }) - } catch (error) { - console.error(error) - } - let accounts - let piggybankContract - const accountButtons = [ - deployButton, - depositButton, - withdrawButton, - sendButton, - createToken, - transferTokens, - approveTokens, - transferTokensWithoutGas, - approveTokensWithoutGas, - ] - - for (const button of accountButtons) { - button.disabled = true - } - - const isMetaMaskConnected = () => accounts && accounts.length > 0 - - const onClickInstall = () => { - onboardButton.innerText = 'Onboarding in progress' - onboardButton.disabled = true - onboarding.startOnboarding() - } - - const onClickConnect = async () => { - await window.ethereum.enable() - } - - const updateButtons = () => { - const accountButtonsDisabled = !isMetaMaskInstalled() || !isMetaMaskConnected() - if (accountButtonsDisabled) { - for (const button of accountButtons) { - button.disabled = true - } - } else { - deployButton.disabled = false - sendButton.disabled = false - createToken.disabled = false - } - - if (!isMetaMaskInstalled()) { - onboardButton.innerText = 'Click here to install MetaMask!' - onboardButton.onclick = onClickInstall - } else if (isMetaMaskConnected()) { - onboardButton.innerText = 'Connected' - onboardButton.disabled = true - if (onboarding) { - onboarding.stopOnboarding() - } - } else { - onboardButton.innerText = 'Connect' - onboardButton.onclick = onClickConnect - } - } - - const initializeAccountButtons = () => { - piggybankContract = web3.eth.contract([{'constant': false, 'inputs': [{'name': 'withdrawAmount', 'type': 'uint256'}], 'name': 'withdraw', 'outputs': [{'name': 'remainingBal', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'owner', 'outputs': [{'name': '', 'type': 'address'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [], 'name': 'deposit', 'outputs': [{'name': '', 'type': 'uint256'}], 'payable': true, 'stateMutability': 'payable', 'type': 'function'}, {'inputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'constructor'}]) - deployButton.onclick = async () => { - contractStatus.innerHTML = 'Deploying' + deployButton.addEventListener('click', async function () { + document.getElementById('contractStatus').innerHTML = 'Deploying' + + var piggybank = await piggybankContract.new( + { + from: web3.eth.accounts[0], + data: '0x608060405234801561001057600080fd5b5033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000808190555061023b806100686000396000f300608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632e1a7d4d1461005c5780638da5cb5b1461009d578063d0e30db0146100f4575b600080fd5b34801561006857600080fd5b5061008760048036038101908080359060200190929190505050610112565b6040518082815260200191505060405180910390f35b3480156100a957600080fd5b506100b26101d0565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100fc6101f6565b6040518082815260200191505060405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561017057600080fd5b8160008082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f193505050501580156101c5573d6000803e3d6000fd5b506000549050919050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60003460008082825401925050819055506000549050905600a165627a7a72305820f237db3ec816a52589d82512117bc85bc08d3537683ffeff9059108caf3e5d400029', + gas: '4700000', + }, function (e, contract) { + if (e) { + throw e + } + if (typeof contract.address !== 'undefined') { + console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash) - const piggybank = await piggybankContract.new( - { - from: accounts[0], - data: '0x608060405234801561001057600080fd5b5033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000808190555061023b806100686000396000f300608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632e1a7d4d1461005c5780638da5cb5b1461009d578063d0e30db0146100f4575b600080fd5b34801561006857600080fd5b5061008760048036038101908080359060200190929190505050610112565b6040518082815260200191505060405180910390f35b3480156100a957600080fd5b506100b26101d0565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100fc6101f6565b6040518082815260200191505060405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561017057600080fd5b8160008082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f193505050501580156101c5573d6000803e3d6000fd5b506000549050919050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60003460008082825401925050819055506000549050905600a165627a7a72305820f237db3ec816a52589d82512117bc85bc08d3537683ffeff9059108caf3e5d400029', - gas: '4700000', - }, (error, contract) => { - if (error) { - contractStatus.innerHTML = 'Deployment Failed' - throw error - } else if (contract.address === undefined) { - return - } + document.getElementById('contractStatus').innerHTML = 'Deployed' - console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash) - contractStatus.innerHTML = 'Deployed' - depositButton.disabled = false - withdrawButton.disabled = false + depositButton.addEventListener('click', function () { + document.getElementById('contractStatus').innerHTML = 'Deposit initiated' + contract.deposit({ from: web3.eth.accounts[0], value: '0x3782dace9d900000' }, function (result) { + console.log(result) + document.getElementById('contractStatus').innerHTML = 'Deposit completed' + }) + }) - depositButton.onclick = () => { - contractStatus.innerHTML = 'Deposit initiated' - contract.deposit( - { - from: accounts[0], - value: '0x3782dace9d900000', - }, - (result) => { - console.log(result) - contractStatus.innerHTML = 'Deposit completed' - } - ) - } - withdrawButton.onclick = () => { - contract.withdraw( - '0xde0b6b3a7640000', - { from: accounts[0] }, - (result) => { - console.log(result) - contractStatus.innerHTML = 'Withdrawn' - } - ) - } + withdrawButton.addEventListener('click', function () { + contract.withdraw('0xde0b6b3a7640000', { from: web3.eth.accounts[0] }, function (result) { + console.log(result) + document.getElementById('contractStatus').innerHTML = 'Withdrawn' + }) + }) } - ) - console.log(piggybank) - } - - sendButton.onclick = () => { - web3.eth.sendTransaction({ - from: accounts[0], - to: '0x2f318C334780961FB129D2a6c30D0763d9a5C970', - value: '0x29a2241af62c0000', - gas: 21000, - gasPrice: 20000000000, - }, (result) => { - console.log(result) }) - } - - createToken.onclick = async () => { - const _initialAmount = 100 - const _tokenName = 'TST' - const _decimalUnits = 0 - const _tokenSymbol = 'TST' - const humanstandardtokenContract = web3.eth.contract([{'constant': true, 'inputs': [], 'name': 'name', 'outputs': [{'name': '', 'type': 'string'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_spender', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}], 'name': 'approve', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'totalSupply', 'outputs': [{'name': '', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_from', 'type': 'address'}, {'name': '_to', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}], 'name': 'transferFrom', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'decimals', 'outputs': [{'name': '', 'type': 'uint8'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'version', 'outputs': [{'name': '', 'type': 'string'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [{'name': '_owner', 'type': 'address'}], 'name': 'balanceOf', 'outputs': [{'name': 'balance', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'symbol', 'outputs': [{'name': '', 'type': 'string'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_to', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}], 'name': 'transfer', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_spender', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}, {'name': '_extraData', 'type': 'bytes'}], 'name': 'approveAndCall', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [{'name': '_owner', 'type': 'address'}, {'name': '_spender', 'type': 'address'}], 'name': 'allowance', 'outputs': [{'name': 'remaining', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'name': '_initialAmount', 'type': 'uint256'}, {'name': '_tokenName', 'type': 'string'}, {'name': '_decimalUnits', 'type': 'uint8'}, {'name': '_tokenSymbol', 'type': 'string'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'constructor'}, {'payable': false, 'stateMutability': 'nonpayable', 'type': 'fallback'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': '_from', 'type': 'address'}, {'indexed': true, 'name': '_to', 'type': 'address'}, {'indexed': false, 'name': '_value', 'type': 'uint256'}], 'name': 'Transfer', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': '_owner', 'type': 'address'}, {'indexed': true, 'name': '_spender', 'type': 'address'}, {'indexed': false, 'name': '_value', 'type': 'uint256'}], 'name': 'Approval', 'type': 'event'}]) - - return humanstandardtokenContract.new( - _initialAmount, - _tokenName, - _decimalUnits, - _tokenSymbol, - { - from: accounts[0], - data: '0x60806040523480156200001157600080fd5b506040516200156638038062001566833981018060405260808110156200003757600080fd5b8101908080516401000000008111156200005057600080fd5b828101905060208101848111156200006757600080fd5b81518560018202830111640100000000821117156200008557600080fd5b50509291906020018051640100000000811115620000a257600080fd5b82810190506020810184811115620000b957600080fd5b8151856001820283011164010000000082111715620000d757600080fd5b5050929190602001805190602001909291908051906020019092919050505083838382600390805190602001906200011192919062000305565b5081600490805190602001906200012a92919062000305565b5080600560006101000a81548160ff021916908360ff1602179055505050506200016433826200016e640100000000026401000000009004565b50505050620003b4565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614151515620001ab57600080fd5b620001d081600254620002e36401000000000262001155179091906401000000009004565b60028190555062000237816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054620002e36401000000000262001155179091906401000000009004565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a35050565b6000808284019050838110151515620002fb57600080fd5b8091505092915050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106200034857805160ff191683800117855562000379565b8280016001018555821562000379579182015b82811115620003785782518255916020019190600101906200035b565b5b5090506200038891906200038c565b5090565b620003b191905b80821115620003ad57600081600090555060010162000393565b5090565b90565b6111a280620003c46000396000f3fe6080604052600436106100af576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100b4578063095ea7b31461014457806318160ddd146101b757806323b872dd146101e2578063313ce5671461027557806339509351146102a657806370a082311461031957806395d89b411461037e578063a457c2d71461040e578063a9059cbb14610481578063dd62ed3e146104f4575b600080fd5b3480156100c057600080fd5b506100c9610579565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101095780820151818401526020810190506100ee565b50505050905090810190601f1680156101365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561015057600080fd5b5061019d6004803603604081101561016757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061061b565b604051808215151515815260200191505060405180910390f35b3480156101c357600080fd5b506101cc610748565b6040518082815260200191505060405180910390f35b3480156101ee57600080fd5b5061025b6004803603606081101561020557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610752565b604051808215151515815260200191505060405180910390f35b34801561028157600080fd5b5061028a61095a565b604051808260ff1660ff16815260200191505060405180910390f35b3480156102b257600080fd5b506102ff600480360360408110156102c957600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610971565b604051808215151515815260200191505060405180910390f35b34801561032557600080fd5b506103686004803603602081101561033c57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610ba8565b6040518082815260200191505060405180910390f35b34801561038a57600080fd5b50610393610bf0565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156103d35780820151818401526020810190506103b8565b50505050905090810190601f1680156104005780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561041a57600080fd5b506104676004803603604081101561043157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610c92565b604051808215151515815260200191505060405180910390f35b34801561048d57600080fd5b506104da600480360360408110156104a457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610ec9565b604051808215151515815260200191505060405180910390f35b34801561050057600080fd5b506105636004803603604081101561051757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610ee0565b6040518082815260200191505060405180910390f35b606060038054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156106115780601f106105e657610100808354040283529160200191610611565b820191906000526020600020905b8154815290600101906020018083116105f457829003601f168201915b5050505050905090565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415151561065857600080fd5b81600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b6000600254905090565b60006107e382600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610f6790919063ffffffff16565b600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555061086e848484610f89565b3373ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546040518082815260200191505060405180910390a3600190509392505050565b6000600560009054906101000a900460ff16905090565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141515156109ae57600080fd5b610a3d82600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461115590919063ffffffff16565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546040518082815260200191505060405180910390a36001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b606060048054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610c885780601f10610c5d57610100808354040283529160200191610c88565b820191906000526020600020905b815481529060010190602001808311610c6b57829003601f168201915b5050505050905090565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614151515610ccf57600080fd5b610d5e82600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610f6790919063ffffffff16565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546040518082815260200191505060405180910390a36001905092915050565b6000610ed6338484610f89565b6001905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b6000828211151515610f7857600080fd5b600082840390508091505092915050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614151515610fc557600080fd5b611016816000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610f6790919063ffffffff16565b6000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506110a9816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461115590919063ffffffff16565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a3505050565b600080828401905083811015151561116c57600080fd5b809150509291505056fea165627a7a723058205fcdfea06f4d97b442bc9f444b1e92524bc66398eb4f37ed5a99f2093a8842640029000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000186a00000000000000000000000000000000000000000000000000000000000000003545354000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035453540000000000000000000000000000000000000000000000000000000000', - gas: '4700000', - gasPrice: '20000000000', - }, (error, contract) => { - if (error) { - tokenAddress.innerHTML = 'Creation Failed' - throw error - } else if (contract.address === undefined) { - return - } + console.log(piggybank) + }) + + sendButton.addEventListener('click', function () { + web3.eth.sendTransaction({ + from: web3.eth.accounts[0], + to: '0x2f318C334780961FB129D2a6c30D0763d9a5C970', + value: '0x29a2241af62c0000', + gas: 21000, + gasPrice: 20000000000, + }, (result) => { + console.log(result) + }) + }) + + + createToken.addEventListener('click', async function () { + var _initialAmount = 100 + var _tokenName = 'TST' + var _decimalUnits = 0 + var _tokenSymbol = 'TST' + var humanstandardtokenContract = web3.eth.contract([{'constant': true, 'inputs': [], 'name': 'name', 'outputs': [{'name': '', 'type': 'string'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_spender', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}], 'name': 'approve', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'totalSupply', 'outputs': [{'name': '', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_from', 'type': 'address'}, {'name': '_to', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}], 'name': 'transferFrom', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'decimals', 'outputs': [{'name': '', 'type': 'uint8'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'version', 'outputs': [{'name': '', 'type': 'string'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [{'name': '_owner', 'type': 'address'}], 'name': 'balanceOf', 'outputs': [{'name': 'balance', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'symbol', 'outputs': [{'name': '', 'type': 'string'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_to', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}], 'name': 'transfer', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_spender', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}, {'name': '_extraData', 'type': 'bytes'}], 'name': 'approveAndCall', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [{'name': '_owner', 'type': 'address'}, {'name': '_spender', 'type': 'address'}], 'name': 'allowance', 'outputs': [{'name': 'remaining', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'name': '_initialAmount', 'type': 'uint256'}, {'name': '_tokenName', 'type': 'string'}, {'name': '_decimalUnits', 'type': 'uint8'}, {'name': '_tokenSymbol', 'type': 'string'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'constructor'}, {'payable': false, 'stateMutability': 'nonpayable', 'type': 'fallback'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': '_from', 'type': 'address'}, {'indexed': true, 'name': '_to', 'type': 'address'}, {'indexed': false, 'name': '_value', 'type': 'uint256'}], 'name': 'Transfer', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': '_owner', 'type': 'address'}, {'indexed': true, 'name': '_spender', 'type': 'address'}, {'indexed': false, 'name': '_value', 'type': 'uint256'}], 'name': 'Approval', 'type': 'event'}]) + return humanstandardtokenContract.new( + _initialAmount, + _tokenName, + _decimalUnits, + _tokenSymbol, + { + from: web3.eth.accounts[0], + data: '0x60806040523480156200001157600080fd5b506040516200156638038062001566833981018060405260808110156200003757600080fd5b8101908080516401000000008111156200005057600080fd5b828101905060208101848111156200006757600080fd5b81518560018202830111640100000000821117156200008557600080fd5b50509291906020018051640100000000811115620000a257600080fd5b82810190506020810184811115620000b957600080fd5b8151856001820283011164010000000082111715620000d757600080fd5b5050929190602001805190602001909291908051906020019092919050505083838382600390805190602001906200011192919062000305565b5081600490805190602001906200012a92919062000305565b5080600560006101000a81548160ff021916908360ff1602179055505050506200016433826200016e640100000000026401000000009004565b50505050620003b4565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614151515620001ab57600080fd5b620001d081600254620002e36401000000000262001155179091906401000000009004565b60028190555062000237816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054620002e36401000000000262001155179091906401000000009004565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a35050565b6000808284019050838110151515620002fb57600080fd5b8091505092915050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106200034857805160ff191683800117855562000379565b8280016001018555821562000379579182015b82811115620003785782518255916020019190600101906200035b565b5b5090506200038891906200038c565b5090565b620003b191905b80821115620003ad57600081600090555060010162000393565b5090565b90565b6111a280620003c46000396000f3fe6080604052600436106100af576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100b4578063095ea7b31461014457806318160ddd146101b757806323b872dd146101e2578063313ce5671461027557806339509351146102a657806370a082311461031957806395d89b411461037e578063a457c2d71461040e578063a9059cbb14610481578063dd62ed3e146104f4575b600080fd5b3480156100c057600080fd5b506100c9610579565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101095780820151818401526020810190506100ee565b50505050905090810190601f1680156101365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561015057600080fd5b5061019d6004803603604081101561016757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061061b565b604051808215151515815260200191505060405180910390f35b3480156101c357600080fd5b506101cc610748565b6040518082815260200191505060405180910390f35b3480156101ee57600080fd5b5061025b6004803603606081101561020557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610752565b604051808215151515815260200191505060405180910390f35b34801561028157600080fd5b5061028a61095a565b604051808260ff1660ff16815260200191505060405180910390f35b3480156102b257600080fd5b506102ff600480360360408110156102c957600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610971565b604051808215151515815260200191505060405180910390f35b34801561032557600080fd5b506103686004803603602081101561033c57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610ba8565b6040518082815260200191505060405180910390f35b34801561038a57600080fd5b50610393610bf0565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156103d35780820151818401526020810190506103b8565b50505050905090810190601f1680156104005780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561041a57600080fd5b506104676004803603604081101561043157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610c92565b604051808215151515815260200191505060405180910390f35b34801561048d57600080fd5b506104da600480360360408110156104a457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610ec9565b604051808215151515815260200191505060405180910390f35b34801561050057600080fd5b506105636004803603604081101561051757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610ee0565b6040518082815260200191505060405180910390f35b606060038054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156106115780601f106105e657610100808354040283529160200191610611565b820191906000526020600020905b8154815290600101906020018083116105f457829003601f168201915b5050505050905090565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415151561065857600080fd5b81600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b6000600254905090565b60006107e382600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610f6790919063ffffffff16565b600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555061086e848484610f89565b3373ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546040518082815260200191505060405180910390a3600190509392505050565b6000600560009054906101000a900460ff16905090565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141515156109ae57600080fd5b610a3d82600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461115590919063ffffffff16565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546040518082815260200191505060405180910390a36001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b606060048054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610c885780601f10610c5d57610100808354040283529160200191610c88565b820191906000526020600020905b815481529060010190602001808311610c6b57829003601f168201915b5050505050905090565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614151515610ccf57600080fd5b610d5e82600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610f6790919063ffffffff16565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546040518082815260200191505060405180910390a36001905092915050565b6000610ed6338484610f89565b6001905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b6000828211151515610f7857600080fd5b600082840390508091505092915050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614151515610fc557600080fd5b611016816000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610f6790919063ffffffff16565b6000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506110a9816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461115590919063ffffffff16565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a3505050565b600080828401905083811015151561116c57600080fd5b809150509291505056fea165627a7a723058205fcdfea06f4d97b442bc9f444b1e92524bc66398eb4f37ed5a99f2093a8842640029000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000186a00000000000000000000000000000000000000000000000000000000000000003545354000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035453540000000000000000000000000000000000000000000000000000000000', + gas: '4700000', + gasPrice: '20000000000', + }, function (e, contract) { + console.log(e, contract) + if (typeof contract.address !== 'undefined') { console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash) - tokenAddress.innerHTML = contract.address - transferTokens.disabled = false - approveTokens.disabled = false - transferTokensWithoutGas.disabled = false - approveTokensWithoutGas.disabled = false - transferTokens.onclick = (event) => { + document.getElementById('tokenAddress').innerHTML = contract.address + + transferTokens.addEventListener('click', function (event) { console.log(`event`, event) contract.transfer('0x2f318C334780961FB129D2a6c30D0763d9a5C970', '15000', { - from: accounts[0], + from: web3.eth.accounts[0], to: contract.address, data: '0xa9059cbb0000000000000000000000002f318c334780961fb129d2a6c30d0763d9a5c9700000000000000000000000000000000000000000000000000000000000003a98', gas: 60000, gasPrice: '20000000000', - }, (result) => { + }, function (result) { console.log('result', result) }) - } + }) - approveTokens.onclick = () => { + approveTokens.addEventListener('click', function () { contract.approve('0x9bc5baF874d2DA8D216aE9f137804184EE5AfEF4', '70000', { - from: accounts[0], + from: web3.eth.accounts[0], to: contract.address, data: '0x095ea7b30000000000000000000000009bc5baF874d2DA8D216aE9f137804184EE5AfEF40000000000000000000000000000000000000000000000000000000000000005', gas: 60000, gasPrice: '20000000000', - }, (result) => { + }, function (result) { console.log(result) }) - } + }) - transferTokensWithoutGas.onclick = (event) => { + transferTokensWithoutGas.addEventListener('click', function (event) { console.log(`event`, event) contract.transfer('0x2f318C334780961FB129D2a6c30D0763d9a5C970', '15000', { - from: accounts[0], + from: web3.eth.accounts[0], to: contract.address, data: '0xa9059cbb0000000000000000000000002f318c334780961fb129d2a6c30d0763d9a5c9700000000000000000000000000000000000000000000000000000000000003a98', gasPrice: '20000000000', - }, (result) => { + }, function (result) { console.log('result', result) }) - } + }) - approveTokensWithoutGas.onclick = () => { + approveTokensWithoutGas.addEventListener('click', function () { contract.approve('0x2f318C334780961FB129D2a6c30D0763d9a5C970', '70000', { - from: accounts[0], + from: web3.eth.accounts[0], to: contract.address, data: '0x095ea7b30000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C9700000000000000000000000000000000000000000000000000000000000000005', gasPrice: '20000000000', - }, (result) => { + }, function (result) { console.log(result) }) - } - } - ) - } - - signTypedData.addEventListener('click', () => { - const typedData = { - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - Person: [ - { name: 'name', type: 'string' }, - { name: 'wallet', type: 'address' }, - ], - Mail: [ - { name: 'from', type: 'Person' }, - { name: 'to', type: 'Person' }, - { name: 'contents', type: 'string' }, - ], - }, - primaryType: 'Mail', - domain: { - name: 'Ether Mail', - version: '1', - chainId: 3, - verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', - }, - message: { - sender: { - name: 'Cow', - wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', - }, - recipient: { - name: 'Bob', - wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', - }, - contents: 'Hello, Bob!', - }, - } - web3.currentProvider.sendAsync({ - method: 'eth_signTypedData_v3', - params: [ethereum.selectedAddress, JSON.stringify(typedData)], - from: ethereum.selectedAddress, - }, (err, result) => { - if (err) { - console.log(err) - } else { - signTypedDataResults.innerHTML = result + }) } }) - }) - getAccountsButton.addEventListener('click', async () => { - try { - const accounts = await ethereum.send({ method: 'eth_accounts' }) - getAccountsResults.innerHTML = accounts[0] || 'Not able to get accounts' - } catch (error) { - console.error(error) - getAccountsResults.innerHTML = `Error: ${error}` - } - }) + }) - } + ethereum.autoRefreshOnNetworkChange = false - updateButtons() - if (isMetaMaskInstalled()) { - ethereum.autoRefreshOnNetworkChange = false - ethereum.on('networkChanged', (networkId) => { - networkDiv.innerHTML = networkId - }) - ethereum.on('chainIdChanged', (chainId) => { - chainIdDiv.innerHTML = chainId - }) - ethereum.on('accountsChanged', (newAccounts) => { - const connecting = Boolean((!accounts || !accounts.length) && newAccounts && newAccounts.length) - accounts = newAccounts - accountsDiv.innerHTML = accounts - if (connecting) { - initializeAccountButtons() + const networkDiv = document.getElementById('network') + const chainIdDiv = document.getElementById('chainId') + const accountsDiv = document.getElementById('accounts') + + ethereum.on('networkChanged', (networkId) => { + networkDiv.innerHTML = networkId + }) + + ethereum.on('chainIdChanged', (chainId) => { + chainIdDiv.innerHTML = chainId + }) + + ethereum.on('accountsChanged', (accounts) => { + accountsDiv.innerHTML = accounts + }) + + const signTypedDataResultsDiv = document.getElementById('signTypedDataResult') + signTypedData.addEventListener('click', function () { + + const typedData = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], + }, + primaryType: 'Mail', + domain: { + name: 'Ether Mail', + version: '1', + chainId: 3, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + }, + message: { + sender: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + recipient: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + } + web3.currentProvider.sendAsync({ + method: 'eth_signTypedData_v3', + params: [ethereum.selectedAddress, JSON.stringify(typedData)], + from: ethereum.selectedAddress, + }, function (err, result) { + if (err) { + console.log(err) + } else { + signTypedDataResultsDiv.innerHTML = result } - updateButtons() }) - } -} -window.addEventListener('DOMContentLoaded', initialize) + }) +}) diff --git a/test/e2e/contract-test/index.html b/test/e2e/contract-test/index.html index 7655b87866..9689654ee0 100644 --- a/test/e2e/contract-test/index.html +++ b/test/e2e/contract-test/index.html @@ -1,69 +1,51 @@ - E2E Test Dapp - - -
-

E2E Test Dapp

-
-
-
-

Connect

- -
-
-

Contract

-
- - - -
-
- Contract Status: Not clicked -
-
-
-

Send Eth

+
+
Contract
+
+ + + +
+
+ Not clicked +
+
+
+
Send eth
+
-
-
-

Send Tokens

-
- Token: -
-
- - - - - -
-
-
-

Get Accounts

- -
-
-
-

Status

-
- Network: -
-
- ChainId: -
-
- Accounts: -
-
-
-

Sign Typed Data

+ + +
+
Send tokens
+
+
+ + + + + +
+
+
+
Network:
+
ChainId:
+
Accounts:
+
+
+
+
Sign Typed Data
+
-
Sign Typed Data Result:
-
-
+
Sign Typed Data Result:
+ + + + - + + \ No newline at end of file diff --git a/test/e2e/ethereum-on.spec.js b/test/e2e/ethereum-on.spec.js index c50615521d..9c6cd4ceb6 100644 --- a/test/e2e/ethereum-on.spec.js +++ b/test/e2e/ethereum-on.spec.js @@ -113,42 +113,28 @@ describe('MetaMask', function () { let extension let popup let dapp - - it('connects to the dapp', async () => { + it('switches to a dapp', async () => { await openNewPage(driver, 'http://127.0.0.1:8080/') await delay(regularDelayMs) - const connectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) - await connectButton.click() - - await delay(regularDelayMs) - await waitUntilXWindowHandles(driver, 3) const windowHandles = await driver.getAllWindowHandles() extension = windowHandles[0] - dapp = await switchToWindowWithTitle(driver, 'E2E Test Dapp', windowHandles) - popup = windowHandles.find(handle => handle !== extension && handle !== dapp) - - await driver.switchTo().window(popup) + popup = await switchToWindowWithTitle(driver, 'MetaMask Notification', windowHandles) + dapp = windowHandles.find(handle => handle !== extension && handle !== popup) await delay(regularDelayMs) + const approveButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) + await approveButton.click() - const accountButton = await findElement(driver, By.css('.permissions-connect-choose-account__account')) - await accountButton.click() - - const submitButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Submit')]`)) - await submitButton.click() - - await waitUntilXWindowHandles(driver, 2) await driver.switchTo().window(dapp) await delay(regularDelayMs) }) - it('has the ganache network id within the dapp', async () => { + it('has not set the network within the dapp', async () => { const networkDiv = await findElement(driver, By.css('#network')) - await delay(regularDelayMs) - assert.equal(await networkDiv.getText(), '5777') + assert.equal(await networkDiv.getText(), '') }) it('changes the network', async () => { diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js index 27de882eb7..9e12ba8da0 100644 --- a/test/e2e/helpers.js +++ b/test/e2e/helpers.js @@ -123,8 +123,6 @@ async function loadExtension (driver, extensionId) { await driver.get(`moz-extension://${extensionId}/home.html`) break } - default: - throw new Error('Unrecognized SELENIUM_BROWSER value') } } diff --git a/test/e2e/metamask-responsive-ui.spec.js b/test/e2e/metamask-responsive-ui.spec.js index 143e759ee8..90cf357108 100644 --- a/test/e2e/metamask-responsive-ui.spec.js +++ b/test/e2e/metamask-responsive-ui.spec.js @@ -134,7 +134,7 @@ describe('MetaMask', function () { it('show account details dropdown menu', async () => { await driver.findElement(By.css('div.menu-bar__open-in-browser')).click() const options = await driver.findElements(By.css('div.menu.account-details-dropdown div.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 await delay(regularDelayMs) }) }) diff --git a/test/e2e/metamask-ui.spec.js b/test/e2e/metamask-ui.spec.js index a22c0c1ca1..a0efe56f96 100644 --- a/test/e2e/metamask-ui.spec.js +++ b/test/e2e/metamask-ui.spec.js @@ -432,35 +432,23 @@ describe('MetaMask', function () { await delay(largeDelayMs) }) - it('connects the dapp', async () => { + it('starts a send transaction inside the dapp', async () => { await openNewPage(driver, 'http://127.0.0.1:8080/') await delay(regularDelayMs) - const connectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) - await connectButton.click() - - await delay(regularDelayMs) - await waitUntilXWindowHandles(driver, 3) windowHandles = await driver.getAllWindowHandles() extension = windowHandles[0] - dapp = await switchToWindowWithTitle(driver, 'E2E Test Dapp', windowHandles) - popup = windowHandles.find(handle => handle !== extension && handle !== dapp) - - await driver.switchTo().window(popup) + popup = await switchToWindowWithTitle(driver, 'MetaMask Notification', windowHandles) + dapp = windowHandles.find(handle => handle !== extension && handle !== popup) await delay(regularDelayMs) + const approveButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) + await approveButton.click() - const accountButton = await findElement(driver, By.css('.permissions-connect-choose-account__account')) - await accountButton.click() - - const submitButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Submit')]`)) - await submitButton.click() - - await waitUntilXWindowHandles(driver, 2) await driver.switchTo().window(dapp) - await delay(regularDelayMs) + await delay(2000) }) it('initiates a send from the dapp', async () => { diff --git a/test/e2e/permissions.spec.js b/test/e2e/permissions.spec.js deleted file mode 100644 index b7147d7a27..0000000000 --- a/test/e2e/permissions.spec.js +++ /dev/null @@ -1,201 +0,0 @@ -const assert = require('assert') -const webdriver = require('selenium-webdriver') -const { By, until } = webdriver -const { - delay, -} = require('./func') -const { - checkBrowserForConsoleErrors, - findElement, - findElements, - openNewPage, - verboseReportOnFailure, - waitUntilXWindowHandles, - switchToWindowWithTitle, - setupFetchMocking, - prepareExtensionForTesting, -} = require('./helpers') -const enLocaleMessages = require('../../app/_locales/en/messages.json') - -describe('MetaMask', function () { - let driver - let publicAddress - - const tinyDelayMs = 200 - const regularDelayMs = tinyDelayMs * 2 - const largeDelayMs = regularDelayMs * 2 - - this.timeout(0) - this.bail(true) - - before(async function () { - const result = await prepareExtensionForTesting() - driver = result.driver - await setupFetchMocking(driver) - }) - - afterEach(async function () { - if (process.env.SELENIUM_BROWSER === 'chrome') { - const errors = await checkBrowserForConsoleErrors(driver) - if (errors.length) { - const errorReports = errors.map(err => err.message) - const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` - console.error(new Error(errorMessage)) - } - } - if (this.currentTest.state === 'failed') { - await verboseReportOnFailure(driver, this.currentTest) - } - }) - - after(async function () { - await driver.quit() - }) - - describe('Going through the first time flow, but skipping the seed phrase challenge', () => { - it('clicks the continue button on the welcome screen', async () => { - await findElement(driver, By.css('.welcome-page__header')) - const welcomeScreenBtn = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`)) - welcomeScreenBtn.click() - await delay(largeDelayMs) - }) - - it('clicks the "Create New Wallet" option', async () => { - const customRpcButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Create a Wallet')]`)) - customRpcButton.click() - await delay(largeDelayMs) - }) - - it('clicks the "No thanks" option on the metametrics opt-in screen', async () => { - const optOutButton = await findElement(driver, By.css('.btn-default')) - optOutButton.click() - await delay(largeDelayMs) - }) - - it('accepts a secure password', async () => { - const passwordBox = await findElement(driver, By.css('.first-time-flow__form #create-password')) - const passwordBoxConfirm = await findElement(driver, By.css('.first-time-flow__form #confirm-password')) - const button = await findElement(driver, By.css('.first-time-flow__form button')) - - await passwordBox.sendKeys('correct horse battery staple') - await passwordBoxConfirm.sendKeys('correct horse battery staple') - - const tosCheckBox = await findElement(driver, By.css('.first-time-flow__checkbox')) - await tosCheckBox.click() - - await button.click() - await delay(largeDelayMs) - }) - - it('skips the seed phrase challenge', async () => { - const button = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.remindMeLater.message}')]`)) - await button.click() - await delay(regularDelayMs) - - const detailsButton = await findElement(driver, By.css('.account-details__details-button')) - await detailsButton.click() - await delay(regularDelayMs) - }) - - it('gets the current accounts address', async () => { - const addressInput = await findElement(driver, By.css('.qr-ellip-address')) - publicAddress = await addressInput.getAttribute('value') - const accountModal = await driver.findElement(By.css('span .modal')) - - await driver.executeScript("document.querySelector('.account-modal-close').click()") - - await driver.wait(until.stalenessOf(accountModal)) - await delay(regularDelayMs) - }) - }) - - describe('sets permissions', () => { - let extension - let popup - let dapp - - it('connects to the dapp', async () => { - await openNewPage(driver, 'http://127.0.0.1:8080/') - await delay(regularDelayMs) - - const connectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) - await connectButton.click() - - await waitUntilXWindowHandles(driver, 3) - const windowHandles = await driver.getAllWindowHandles() - - extension = windowHandles[0] - dapp = await switchToWindowWithTitle(driver, 'E2E Test Dapp', windowHandles) - popup = windowHandles.find(handle => handle !== extension && handle !== dapp) - - await driver.switchTo().window(popup) - - await delay(regularDelayMs) - - const accountButton = await findElement(driver, By.css('.permissions-connect-choose-account__account')) - await accountButton.click() - - const submitButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Submit')]`)) - await submitButton.click() - - await waitUntilXWindowHandles(driver, 2) - await driver.switchTo().window(extension) - await delay(regularDelayMs) - }) - - it('shows connected sites', async () => { - const connectedSites = await findElement(driver, By.xpath(`//button[contains(text(), 'Connected Sites')]`)) - await connectedSites.click() - - await findElement(driver, By.css('.connected-sites__title')) - - const domains = await findElements(driver, By.css('.connected-sites-list__domain')) - assert.equal(domains.length, 1) - - const domainName = await findElement(driver, By.css('.connected-sites-list__domain-name')) - assert.equal(await domainName.getText(), 'E2E Test Dapp') - - await domains[0].click() - - const permissionDescription = await findElement(driver, By.css('.connected-sites-list__permission-description')) - assert.equal(await permissionDescription.getText(), 'View the address of the selected account') - }) - - it('can get accounts within the dapp', async () => { - await driver.switchTo().window(dapp) - await delay(regularDelayMs) - - const getAccountsButton = await findElement(driver, By.xpath(`//button[contains(text(), 'eth_accounts')]`)) - await getAccountsButton.click() - - const getAccountsResult = await findElement(driver, By.css('#getAccountsResult')) - assert.equal((await getAccountsResult.getText()).toLowerCase(), publicAddress.toLowerCase()) - }) - - it('can disconnect all accounts', async () => { - await driver.switchTo().window(extension) - - const disconnectAllButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Disconnect All')]`)) - await disconnectAllButton.click() - - const disconnectModal = await driver.findElement(By.css('span .modal')) - - const disconnectAllModalButton = await findElement(driver, By.css('.disconnect-all-modal .btn-danger')) - await disconnectAllModalButton.click() - - await driver.wait(until.stalenessOf(disconnectModal)) - await delay(regularDelayMs) - }) - - it('can no longer get accounts within the dapp', async () => { - await driver.switchTo().window(dapp) - await delay(regularDelayMs) - - const getAccountsButton = await findElement(driver, By.xpath(`//button[contains(text(), 'eth_accounts')]`)) - await getAccountsButton.click() - - const getAccountsResult = await findElement(driver, By.css('#getAccountsResult')) - assert.equal(await getAccountsResult.getText(), 'Not able to get accounts') - }) - }) -}) diff --git a/test/e2e/run-all.sh b/test/e2e/run-all.sh index 55a06fc888..33c2428dab 100755 --- a/test/e2e/run-all.sh +++ b/test/e2e/run-all.sh @@ -60,14 +60,6 @@ concurrently --kill-others \ 'yarn dapp' \ 'sleep 5 && mocha test/e2e/ethereum-on.spec' -concurrently --kill-others \ - --names 'ganache,dapp,e2e' \ - --prefix '[{time}][{name}]' \ - --success first \ - 'yarn ganache:start' \ - 'yarn dapp' \ - 'sleep 5 && mocha test/e2e/permissions.spec' - export GANACHE_ARGS="${BASE_GANACHE_ARGS} --deterministic --account=0x250F458997A364988956409A164BA4E16F0F99F916ACDD73ADCD3A1DE30CF8D1,0 --account=0x53CB0AB5226EEBF4D872113D98332C1555DC304443BEE1CF759D15798D3C55A9,25000000000000000000" concurrently --kill-others \ --names 'ganache,sendwithprivatedapp,e2e' \ diff --git a/test/e2e/run-web3.sh b/test/e2e/run-web3.sh index 729333b840..174370683a 100755 --- a/test/e2e/run-web3.sh +++ b/test/e2e/run-web3.sh @@ -9,5 +9,5 @@ export PATH="$PATH:./node_modules/.bin" concurrently --kill-others \ --names 'dapp,e2e' \ --prefix '[{time}][{name}]' \ - 'node development/static-server.js test/web3 --port 8080' \ + 'static-server test/web3 --port 8080' \ 'sleep 5 && mocha test/e2e/web3.spec' diff --git a/test/e2e/signature-request.spec.js b/test/e2e/signature-request.spec.js index f36b6ac513..e9490e08dc 100644 --- a/test/e2e/signature-request.spec.js +++ b/test/e2e/signature-request.spec.js @@ -119,40 +119,28 @@ describe('MetaMask', function () { }) }) - describe('successfuly signs typed data', () => { + describe('provider listening for events', () => { let extension let popup let dapp let windowHandles - - it('connects to the dapp', async () => { + it('switches to a dapp', async () => { await openNewPage(driver, 'http://127.0.0.1:8080/') await delay(regularDelayMs) - const connectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) - await connectButton.click() - - await delay(regularDelayMs) - await waitUntilXWindowHandles(driver, 3) - const windowHandles = await driver.getAllWindowHandles() + windowHandles = await driver.getAllWindowHandles() extension = windowHandles[0] - dapp = await switchToWindowWithTitle(driver, 'E2E Test Dapp', windowHandles) - popup = windowHandles.find(handle => handle !== extension && handle !== dapp) - - await driver.switchTo().window(popup) + popup = await switchToWindowWithTitle(driver, 'MetaMask Notification', windowHandles) + dapp = windowHandles.find(handle => handle !== extension && handle !== popup) await delay(regularDelayMs) + const approveButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) + await approveButton.click() - const accountButton = await findElement(driver, By.css('.permissions-connect-choose-account__account')) - await accountButton.click() - - const submitButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Submit')]`)) - await submitButton.click() - - await waitUntilXWindowHandles(driver, 2) await driver.switchTo().window(dapp) + await delay(regularDelayMs) }) it('creates a sign typed data signature request', async () => { @@ -160,7 +148,6 @@ describe('MetaMask', function () { await signTypedMessage.click() await delay(largeDelayMs) - await delay(regularDelayMs) windowHandles = await driver.getAllWindowHandles() await switchToWindowWithTitle(driver, 'MetaMask Notification', windowHandles) await delay(regularDelayMs) diff --git a/test/e2e/web3.spec.js b/test/e2e/web3.spec.js index 8c0853755e..6dd0813223 100644 --- a/test/e2e/web3.spec.js +++ b/test/e2e/web3.spec.js @@ -30,7 +30,7 @@ describe('Using MetaMask with an existing account', function () { await delay(largeDelayMs) const [results] = await findElements(driver, By.css('#results')) const resulttext = await results.getText() - const parsedData = JSON.parse(resulttext) + var parsedData = JSON.parse(resulttext) return (parsedData) @@ -126,11 +126,6 @@ describe('Using MetaMask with an existing account', function () { await openNewPage(driver, 'http://127.0.0.1:8080/') await delay(regularDelayMs) - const connectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) - await connectButton.click() - - await delay(regularDelayMs) - await waitUntilXWindowHandles(driver, 3) const windowHandles = await driver.getAllWindowHandles() @@ -153,14 +148,14 @@ describe('Using MetaMask with an existing account', function () { it('testing hexa methods', async () => { - const List = await driver.findElements(By.className('hexaNumberMethods')) + var List = await driver.findElements(By.className('hexaNumberMethods')) for (let i = 0; i < List.length; i++) { try { - const parsedData = await button(List[i]) + var parsedData = await button(List[i]) console.log(parsedData) - const result = parseInt(parsedData.result, 16) + var result = parseInt(parsedData.result, 16) assert.equal((typeof result === 'number'), true) await delay(regularDelayMs) @@ -174,14 +169,14 @@ describe('Using MetaMask with an existing account', function () { it('testing booleanMethods', async () => { - const List = await driver.findElements(By.className('booleanMethods')) + var List = await driver.findElements(By.className('booleanMethods')) for (let i = 0; i < List.length; i++) { try { - const parsedData = await button(List[i]) + var parsedData = await button(List[i]) console.log(parsedData) - const result = parsedData.result + var result = parsedData.result assert.equal(result, false) await delay(regularDelayMs) @@ -197,16 +192,16 @@ describe('Using MetaMask with an existing account', function () { it('testing transactionMethods', async () => { - const List = await driver.findElements(By.className('transactionMethods')) + var List = await driver.findElements(By.className('transactionMethods')) for (let i = 0; i < List.length; i++) { try { - const parsedData = await button(List[i]) + var parsedData = await button(List[i]) console.log(parsedData.result.blockHash) - const result = [] + var result = [] result.push(parseInt(parsedData.result.blockHash, 16)) result.push(parseInt(parsedData.result.blockNumber, 16)) result.push(parseInt(parsedData.result.gas, 16)) @@ -239,17 +234,17 @@ describe('Using MetaMask with an existing account', function () { it('testing blockMethods', async () => { - const List = await driver.findElements(By.className('blockMethods')) + var List = await driver.findElements(By.className('blockMethods')) for (let i = 0; i < List.length; i++) { try { - const parsedData = await button(List[i]) + var parsedData = await button(List[i]) console.log(JSON.stringify(parsedData) + i) console.log(parsedData.result.parentHash) - const result = parseInt(parsedData.result.parentHash, 16) + var result = parseInt(parsedData.result.parentHash, 16) assert.equal((typeof result === 'number'), true) await delay(regularDelayMs) @@ -265,9 +260,9 @@ describe('Using MetaMask with an existing account', function () { it('testing methods', async () => { - const List = await driver.findElements(By.className('methods')) - let parsedData - let result + var List = await driver.findElements(By.className('methods')) + var parsedData + var result for (let i = 0; i < List.length; i++) { try { diff --git a/test/helper.js b/test/helper.js index bea0b326c0..ddc2aba408 100644 --- a/test/helper.js +++ b/test/helper.js @@ -17,7 +17,7 @@ server.listen(8545, () => { }) // logging util -const log = require('loglevel') +var log = require('loglevel') log.setDefaultLevel(5) global.log = log @@ -36,12 +36,8 @@ require('jsdom-global')() window.localStorage = {} // crypto.getRandomValues -if (!window.crypto) { - window.crypto = {} -} -if (!window.crypto.getRandomValues) { - window.crypto.getRandomValues = require('polyfill-crypto.getrandomvalues') -} +if (!window.crypto) window.crypto = {} +if (!window.crypto.getRandomValues) window.crypto.getRandomValues = require('polyfill-crypto.getrandomvalues') function enableFailureOnUnhandledPromiseRejection () { // overwrite node's promise with the stricter Bluebird promise @@ -62,11 +58,9 @@ function enableFailureOnUnhandledPromiseRejection () { throw evt.detail.reason }) } else { - const oldOHR = window.onunhandledrejection + var oldOHR = window.onunhandledrejection window.onunhandledrejection = function (evt) { - if (typeof oldOHR === 'function') { - oldOHR.apply(this, arguments) - } + if (typeof oldOHR === 'function') oldOHR.apply(this, arguments) throw evt.detail.reason } } diff --git a/test/integration/index.js b/test/integration/index.js index 1c4523e1e3..b266ddf37c 100644 --- a/test/integration/index.js +++ b/test/integration/index.js @@ -19,9 +19,7 @@ pump( b.bundle(), writeStream, (err) => { - if (err) { - throw err - } + if (err) throw err console.log(`Integration test build completed: "${bundlePath}"`) process.exit(0) } diff --git a/test/lib/mock-encryptor.js b/test/lib/mock-encryptor.js index 0f70b5b3c2..23ab2404fe 100644 --- a/test/lib/mock-encryptor.js +++ b/test/lib/mock-encryptor.js @@ -1,5 +1,5 @@ -const mockHex = '0xabcdef0123456789' -const mockKey = Buffer.alloc(32) +var mockHex = '0xabcdef0123456789' +var mockKey = Buffer.alloc(32) let cacheVal module.exports = { diff --git a/test/lib/mock-simple-keychain.js b/test/lib/mock-simple-keychain.js index 74e6fd8a26..d3addc3e8a 100644 --- a/test/lib/mock-simple-keychain.js +++ b/test/lib/mock-simple-keychain.js @@ -1,4 +1,4 @@ -const fakeWallet = { +var fakeWallet = { privKey: '0x123456788890abcdef', address: '0xfedcba0987654321', } @@ -6,9 +6,7 @@ const type = 'Simple Key Pair' module.exports = class MockSimpleKeychain { - static type () { - return type - } + static type () { return type } constructor (opts) { this.type = type @@ -28,7 +26,7 @@ module.exports = class MockSimpleKeychain { } addAccounts (n = 1) { - for (let i = 0; i < n; i++) { + for (var i = 0; i < n; i++) { this.wallets.push(fakeWallet) } } diff --git a/test/lib/react-trigger-change.js b/test/lib/react-trigger-change.js index 52fe779c1f..d169dd614c 100644 --- a/test/lib/react-trigger-change.js +++ b/test/lib/react-trigger-change.js @@ -18,7 +18,7 @@ // Constants and functions are declared inside the closure. // In this way, reactTriggerChange can be passed directly to executeScript in Selenium. module.exports = function reactTriggerChange (node) { - const supportedInputTypes = { + var supportedInputTypes = { color: true, date: true, datetime: true, @@ -35,27 +35,27 @@ module.exports = function reactTriggerChange (node) { url: true, week: true, } - const nodeName = node.nodeName.toLowerCase() - const type = node.type - let event - let descriptor - let initialValue - let initialChecked - let initialCheckedRadio + var nodeName = node.nodeName.toLowerCase() + var type = node.type + var event + var descriptor + var initialValue + var initialChecked + var initialCheckedRadio // Do not try to delete non-configurable properties. // Value and checked properties on DOM elements are non-configurable in PhantomJS. function deletePropertySafe (elem, prop) { - const desc = Object.getOwnPropertyDescriptor(elem, prop) + var desc = Object.getOwnPropertyDescriptor(elem, prop) if (desc && desc.configurable) { delete elem[prop] } } function getCheckedRadio (radio) { - const name = radio.name - let radios - let i + var name = radio.name + var radios + var i if (name) { radios = document.querySelectorAll('input[type="radio"][name="' + name + '"]') for (i = 0; i < radios.length; i += 1) { diff --git a/test/lib/util.js b/test/lib/util.js index be7e240a4c..4c5d789d16 100644 --- a/test/lib/util.js +++ b/test/lib/util.js @@ -15,9 +15,7 @@ async function findAsync (container, selector, opts) { try { return await pollUntilTruthy(() => { const result = container.find(selector) - if (result.length > 0) { - return result - } + if (result.length > 0) return result }, opts) } catch (err) { throw new Error(`Failed to find element within interval: "${selector}"`) @@ -28,9 +26,7 @@ async function queryAsync (jQuery, selector, opts) { try { return await pollUntilTruthy(() => { const result = jQuery(selector) - if (result.length > 0) { - return result - } + if (result.length > 0) return result }, opts) } catch (err) { throw new Error(`Failed to find element within interval: "${selector}"`) diff --git a/test/setup.js b/test/setup.js index 5aa6e59dd9..bccb4d58e5 100644 --- a/test/setup.js +++ b/test/setup.js @@ -5,10 +5,6 @@ require('@babel/register')({ require('./helper') window.SVGPathElement = window.SVGPathElement || { prototype: {} } -window.fetch = window.fetch || function fetch () { - return Promise.resolve() -} +window.fetch = window.fetch || function fetch () { return Promise.resolve() } global.indexedDB = {} -global.fetch = global.fetch || function fetch () { - return Promise.resolve() -} +global.fetch = global.fetch || function fetch () { return Promise.resolve() } diff --git a/test/unit/actions/config_test.js b/test/unit/actions/config_test.js index 39224a9b17..9127474a8b 100644 --- a/test/unit/actions/config_test.js +++ b/test/unit/actions/config_test.js @@ -1,13 +1,13 @@ // var jsdom = require('mocha-jsdom') -const assert = require('assert') -const freeze = require('deep-freeze-strict') -const path = require('path') +var assert = require('assert') +var freeze = require('deep-freeze-strict') +var path = require('path') -const actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) -const reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) +var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) describe('config view actions', function () { - const initialState = { + var initialState = { metamask: { rpcTarget: 'foo', frequentRpcList: [], @@ -22,7 +22,7 @@ describe('config view actions', function () { describe('SHOW_CONFIG_PAGE', function () { it('should set appState.currentView.name to config', function () { - const result = reducers(initialState, actions.showConfigPage()) + var result = reducers(initialState, actions.showConfigPage()) assert.equal(result.appState.currentView.name, 'config') }) }) @@ -34,7 +34,7 @@ describe('config view actions', function () { value: 'foo', } - const result = reducers(initialState, action) + var result = reducers(initialState, action) assert.equal(result.metamask.provider.type, 'rpc') assert.equal(result.metamask.provider.rpcTarget, 'foo') }) diff --git a/test/unit/actions/set_selected_account_test.js b/test/unit/actions/set_selected_account_test.js index 0488c844e9..36d312d7bb 100644 --- a/test/unit/actions/set_selected_account_test.js +++ b/test/unit/actions/set_selected_account_test.js @@ -1,14 +1,14 @@ // var jsdom = require('mocha-jsdom') -const assert = require('assert') -const freeze = require('deep-freeze-strict') -const path = require('path') +var assert = require('assert') +var freeze = require('deep-freeze-strict') +var path = require('path') -const actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) -const reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) +var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) describe('SET_SELECTED_ACCOUNT', function () { it('sets the state.appState.activeAddress property of the state to the action.value', function () { - const initialState = { + var initialState = { appState: { activeAddress: 'foo', }, @@ -21,14 +21,14 @@ describe('SET_SELECTED_ACCOUNT', function () { } freeze(action) - const resultingState = reducers(initialState, action) + var resultingState = reducers(initialState, action) assert.equal(resultingState.appState.activeAddress, action.value) }) }) describe('SHOW_ACCOUNT_DETAIL', function () { it('updates metamask state', function () { - const initialState = { + var initialState = { metamask: { selectedAddress: 'foo', }, @@ -41,7 +41,7 @@ describe('SHOW_ACCOUNT_DETAIL', function () { } freeze(action) - const resultingState = reducers(initialState, action) + var resultingState = reducers(initialState, action) assert.equal(resultingState.metamask.selectedAddress, action.value) }) }) diff --git a/test/unit/actions/tx_test.js b/test/unit/actions/tx_test.js index 0b2da16aad..66378b5944 100644 --- a/test/unit/actions/tx_test.js +++ b/test/unit/actions/tx_test.js @@ -1,5 +1,5 @@ -const assert = require('assert') -const path = require('path') +var assert = require('assert') +var path = require('path') import configureMockStore from 'redux-mock-store' import thunk from 'redux-thunk' @@ -33,15 +33,9 @@ describe('tx confirmation screen', function () { describe('cancelTx', function () { before(function (done) { actions._setBackgroundConnection({ - approveTransaction (_, cb) { - cb('An error!') - }, - cancelTransaction (_, cb) { - cb() - }, - getState (cb) { - cb() - }, + approveTransaction (_, cb) { cb('An error!') }, + cancelTransaction (_, cb) { cb() }, + getState (cb) { cb() }, }) done() }) diff --git a/test/unit/actions/view_info_test.js b/test/unit/actions/view_info_test.js index 4792af7278..5785a368c4 100644 --- a/test/unit/actions/view_info_test.js +++ b/test/unit/actions/view_info_test.js @@ -1,14 +1,14 @@ // var jsdom = require('mocha-jsdom') -const assert = require('assert') -const freeze = require('deep-freeze-strict') -const path = require('path') +var assert = require('assert') +var freeze = require('deep-freeze-strict') +var path = require('path') -const actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) -const reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) +var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) describe('SHOW_INFO_PAGE', function () { it('sets the state.appState.currentView.name property to info', function () { - const initialState = { + var initialState = { appState: { activeAddress: 'foo', }, @@ -16,7 +16,7 @@ describe('SHOW_INFO_PAGE', function () { freeze(initialState) const action = actions.showInfoPage() - const resultingState = reducers(initialState, action) + var resultingState = reducers(initialState, action) assert.equal(resultingState.appState.currentView.name, 'info') }) }) diff --git a/test/unit/actions/warning_test.js b/test/unit/actions/warning_test.js index ac639f6001..e57374cda5 100644 --- a/test/unit/actions/warning_test.js +++ b/test/unit/actions/warning_test.js @@ -1,14 +1,14 @@ // var jsdom = require('mocha-jsdom') -const assert = require('assert') -const freeze = require('deep-freeze-strict') -const path = require('path') +var assert = require('assert') +var freeze = require('deep-freeze-strict') +var path = require('path') -const actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) -const reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) +var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) describe('action DISPLAY_WARNING', function () { it('sets appState.warning to provided value', function () { - const initialState = { + var initialState = { appState: {}, } freeze(initialState) diff --git a/test/unit/app/buy-eth-url.spec.js b/test/unit/app/buy-eth-url.spec.js index 96814c59d7..7db9922440 100644 --- a/test/unit/app/buy-eth-url.spec.js +++ b/test/unit/app/buy-eth-url.spec.js @@ -17,10 +17,10 @@ describe('buy-eth-url', function () { network: '42', } - it('returns wyre url with address for network 1', function () { + it('returns coinbase url with amount and address for network 1', function () { const wyreUrl = getBuyEthUrl(mainnet) - assert.equal(wyreUrl, 'https://pay.sendwyre.com/?dest=ethereum:0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc&destCurrency=ETH&accountId=AC-7AG3W4XH4N2') + assert.equal(wyreUrl, 'https://dash.sendwyre.com/sign-up') }) diff --git a/test/unit/app/controllers/detect-tokens-test.js b/test/unit/app/controllers/detect-tokens-test.js index c65ee97aba..8f18406f4b 100644 --- a/test/unit/app/controllers/detect-tokens-test.js +++ b/test/unit/app/controllers/detect-tokens-test.js @@ -54,7 +54,7 @@ describe('DetectTokensController', () => { controller.isOpen = true controller.isUnlocked = true - const stub = sandbox.stub(controller, 'detectNewTokens') + var stub = sandbox.stub(controller, 'detectNewTokens') clock.tick(1) sandbox.assert.notCalled(stub) @@ -70,7 +70,7 @@ describe('DetectTokensController', () => { controller.isOpen = true controller.isUnlocked = true - const stub = sandbox.stub(controller, 'detectTokenBalance') + var stub = sandbox.stub(controller, 'detectTokenBalance') .withArgs('0x0D262e5dC4A06a0F1c90cE79C7a60C09DfC884E4').returns(true) .withArgs('0xBC86727E770de68B1060C91f6BB6945c73e10388').returns(true) @@ -114,7 +114,7 @@ describe('DetectTokensController', () => { it('should trigger detect new tokens when change address', async () => { controller.isOpen = true controller.isUnlocked = true - const stub = sandbox.stub(controller, 'detectNewTokens') + var stub = sandbox.stub(controller, 'detectNewTokens') await preferences.setSelectedAddress('0xbc86727e770de68b1060c91f6bb6945c73e10388') sandbox.assert.called(stub) }) @@ -122,7 +122,7 @@ describe('DetectTokensController', () => { it('should trigger detect new tokens when submit password', async () => { controller.isOpen = true controller.selectedAddress = '0x0' - const stub = sandbox.stub(controller, 'detectNewTokens') + var stub = sandbox.stub(controller, 'detectNewTokens') await controller._keyringMemStore.updateState({ isUnlocked: true }) sandbox.assert.called(stub) }) @@ -130,7 +130,7 @@ describe('DetectTokensController', () => { it('should not trigger detect new tokens when not open or not unlocked', async () => { controller.isOpen = true controller.isUnlocked = false - const stub = sandbox.stub(controller, 'detectTokenBalance') + var stub = sandbox.stub(controller, 'detectTokenBalance') clock.tick(180000) sandbox.assert.notCalled(stub) controller.isOpen = false diff --git a/test/unit/app/controllers/metamask-controller-test.js b/test/unit/app/controllers/metamask-controller-test.js index a904dec089..7eac91a9b3 100644 --- a/test/unit/app/controllers/metamask-controller-test.js +++ b/test/unit/app/controllers/metamask-controller-test.js @@ -227,9 +227,7 @@ describe('MetaMaskController', function () { it('should be able to call newVaultAndRestore despite a mistake.', async function () { const password = 'what-what-what' sandbox.stub(metamaskController, 'getBalance') - metamaskController.getBalance.callsFake(() => { - return Promise.resolve('0x0') - }) + metamaskController.getBalance.callsFake(() => { return Promise.resolve('0x0') }) await metamaskController.createNewVaultAndRestore(password, TEST_SEED.slice(0, -1)).catch(() => null) await metamaskController.createNewVaultAndRestore(password, TEST_SEED) @@ -239,9 +237,7 @@ describe('MetaMaskController', function () { it('should clear previous identities after vault restoration', async () => { sandbox.stub(metamaskController, 'getBalance') - metamaskController.getBalance.callsFake(() => { - return Promise.resolve('0x0') - }) + metamaskController.getBalance.callsFake(() => { return Promise.resolve('0x0') }) await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED) assert.deepEqual(metamaskController.getState().identities, { @@ -680,9 +676,7 @@ describe('MetaMaskController', function () { beforeEach(async () => { sandbox.stub(metamaskController, 'getBalance') - metamaskController.getBalance.callsFake(() => { - return Promise.resolve('0x0') - }) + metamaskController.getBalance.callsFake(() => { return Promise.resolve('0x0') }) await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT) @@ -740,9 +734,7 @@ describe('MetaMaskController', function () { beforeEach(async function () { sandbox.stub(metamaskController, 'getBalance') - metamaskController.getBalance.callsFake(() => { - return Promise.resolve('0x0') - }) + metamaskController.getBalance.callsFake(() => { return Promise.resolve('0x0') }) await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT) @@ -817,9 +809,7 @@ describe('MetaMaskController', function () { const { promise, resolve } = deferredPromise() streamTest = createThoughStream((chunk, _, cb) => { - if (chunk.name !== 'phishing') { - return cb() - } + if (chunk.name !== 'phishing') return cb() assert.equal(chunk.data.hostname, phishingUrl.hostname) resolve() cb() @@ -939,8 +929,6 @@ describe('MetaMaskController', function () { function deferredPromise () { let resolve - const promise = new Promise(_resolve => { - resolve = _resolve - }) + const promise = new Promise(_resolve => { resolve = _resolve }) return { promise, resolve } } diff --git a/test/unit/app/controllers/network/pending-middleware-test.js b/test/unit/app/controllers/network/pending-middleware-test.js index ac6f8ad9a6..838395b0bf 100644 --- a/test/unit/app/controllers/network/pending-middleware-test.js +++ b/test/unit/app/controllers/network/pending-middleware-test.js @@ -19,9 +19,7 @@ describe('#createPendingNonceMiddleware', function () { it('should fill the result with a the "pending" nonce', (done) => { const req = { method: 'eth_getTransactionCount', params: [address, 'pending'] } const res = {} - pendingNonceMiddleware(req, res, () => { - done(new Error('should not have called next')) - }, () => { + pendingNonceMiddleware(req, res, () => { done(new Error('should not have called next')) }, () => { assert(res.result === '0x2') done() }) @@ -65,9 +63,7 @@ describe('#createPendingTxMiddleware', function () { returnUndefined = false const req = { method: 'eth_getTransactionByHash', params: [address, 'pending'] } const res = {} - pendingTxMiddleware(req, res, () => { - done(new Error('should not have called next')) - }, () => { + pendingTxMiddleware(req, res, () => { done(new Error('should not have called next')) }, () => { /* // uncomment this section for debugging help with non matching keys const coppy = {...res.result} diff --git a/test/unit/app/controllers/preferences-controller-test.js b/test/unit/app/controllers/preferences-controller-test.js index 7aea39a369..7e047a2a81 100644 --- a/test/unit/app/controllers/preferences-controller-test.js +++ b/test/unit/app/controllers/preferences-controller-test.js @@ -1,7 +1,6 @@ const assert = require('assert') const ObservableStore = require('obs-store') const PreferencesController = require('../../../../app/scripts/controllers/preferences') -const { addInternalMethodPrefix } = require('../../../../app/scripts/controllers/permissions') const sinon = require('sinon') describe('preferences controller', function () { @@ -343,7 +342,7 @@ describe('preferences controller', function () { }) describe('on watchAsset', function () { - let stubNext, stubEnd, stubHandleWatchAssetERC20, asy, req, res + var stubNext, stubEnd, stubHandleWatchAssetERC20, asy, req, res const sandbox = sinon.createSandbox() beforeEach(() => { @@ -360,8 +359,8 @@ describe('preferences controller', function () { it('shouldn not do anything if method not corresponds', async function () { const asy = {next: () => {}, end: () => {}} - const stubNext = sandbox.stub(asy, 'next') - const stubEnd = sandbox.stub(asy, 'end').returns(0) + var stubNext = sandbox.stub(asy, 'next') + var stubEnd = sandbox.stub(asy, 'end').returns(0) req.method = 'metamask' await preferencesController.requestWatchAsset(req, res, asy.next, asy.end) sandbox.assert.notCalled(stubEnd) @@ -369,14 +368,14 @@ describe('preferences controller', function () { }) it('should do something if method is supported', async function () { const asy = {next: () => {}, end: () => {}} - const stubNext = sandbox.stub(asy, 'next') - const stubEnd = sandbox.stub(asy, 'end').returns(0) + var stubNext = sandbox.stub(asy, 'next') + var stubEnd = sandbox.stub(asy, 'end').returns(0) req.method = 'metamask_watchAsset' req.params.type = 'someasset' await preferencesController.requestWatchAsset(req, res, asy.next, asy.end) sandbox.assert.called(stubEnd) sandbox.assert.notCalled(stubNext) - req.method = addInternalMethodPrefix('watchAsset') + req.method = 'wallet_watchAsset' req.params.type = 'someasset' await preferencesController.requestWatchAsset(req, res, asy.next, asy.end) sandbox.assert.calledTwice(stubEnd) @@ -401,7 +400,7 @@ describe('preferences controller', function () { }) describe('on watchAsset of type ERC20', function () { - let req + var req const sandbox = sinon.createSandbox() beforeEach(() => { @@ -456,44 +455,28 @@ describe('preferences controller', function () { }) it('should validate ERC20 asset correctly', async function () { const validateSpy = sandbox.spy(preferencesController._validateERC20AssetParams) - try { - validateSpy({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABC', decimals: 0}) - } catch (e) {} + try { validateSpy({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABC', decimals: 0}) } catch (e) {} assert.equal(validateSpy.threw(), false, 'correct options object') const validateSpyAddress = sandbox.spy(preferencesController._validateERC20AssetParams) - try { - validateSpyAddress({symbol: 'ABC', decimals: 0}) - } catch (e) {} + try { validateSpyAddress({symbol: 'ABC', decimals: 0}) } catch (e) {} assert.equal(validateSpyAddress.threw(), true, 'options object with no address') const validateSpySymbol = sandbox.spy(preferencesController._validateERC20AssetParams) - try { - validateSpySymbol({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', decimals: 0}) - } catch (e) {} + try { validateSpySymbol({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', decimals: 0}) } catch (e) {} assert.equal(validateSpySymbol.threw(), true, 'options object with no symbol') const validateSpyDecimals = sandbox.spy(preferencesController._validateERC20AssetParams) - try { - validateSpyDecimals({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABC'}) - } catch (e) {} + try { validateSpyDecimals({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABC'}) } catch (e) {} assert.equal(validateSpyDecimals.threw(), true, 'options object with no decimals') const validateSpyInvalidSymbol = sandbox.spy(preferencesController._validateERC20AssetParams) - try { - validateSpyInvalidSymbol({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABCDEFGHI', decimals: 0}) - } catch (e) {} + try { validateSpyInvalidSymbol({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABCDEFGHI', decimals: 0}) } catch (e) {} assert.equal(validateSpyInvalidSymbol.threw(), true, 'options object with invalid symbol') const validateSpyInvalidDecimals1 = sandbox.spy(preferencesController._validateERC20AssetParams) - try { - validateSpyInvalidDecimals1({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABCDEFGHI', decimals: -1}) - } catch (e) {} + try { validateSpyInvalidDecimals1({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABCDEFGHI', decimals: -1}) } catch (e) {} assert.equal(validateSpyInvalidDecimals1.threw(), true, 'options object with decimals less than zero') const validateSpyInvalidDecimals2 = sandbox.spy(preferencesController._validateERC20AssetParams) - try { - validateSpyInvalidDecimals2({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABCDEFGHI', decimals: 38}) - } catch (e) {} + try { validateSpyInvalidDecimals2({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABCDEFGHI', decimals: 38}) } catch (e) {} assert.equal(validateSpyInvalidDecimals2.threw(), true, 'options object with decimals more than 36') const validateSpyInvalidAddress = sandbox.spy(preferencesController._validateERC20AssetParams) - try { - validateSpyInvalidAddress({rawAddress: '0x123', symbol: 'ABC', decimals: 0}) - } catch (e) {} + try { validateSpyInvalidAddress({rawAddress: '0x123', symbol: 'ABC', decimals: 0}) } catch (e) {} assert.equal(validateSpyInvalidAddress.threw(), true, 'options object with address invalid') }) }) diff --git a/test/unit/app/controllers/provider-approval-test.js b/test/unit/app/controllers/provider-approval-test.js new file mode 100644 index 0000000000..eeb9e813b2 --- /dev/null +++ b/test/unit/app/controllers/provider-approval-test.js @@ -0,0 +1,330 @@ +const assert = require('assert') +const sinon = require('sinon') +const ProviderApprovalController = require('../../../../app/scripts/controllers/provider-approval') + +const mockLockedKeyringController = { + memStore: { + getState: () => ({ + isUnlocked: false, + }), + }, +} + +const mockUnlockedKeyringController = { + memStore: { + getState: () => ({ + isUnlocked: true, + }), + }, +} + +describe('ProviderApprovalController', () => { + describe('#_handleProviderRequest', () => { + it('should add a pending provider request when unlocked', () => { + const controller = new ProviderApprovalController({ + keyringController: mockUnlockedKeyringController, + }) + + const metadata = { + hostname: 'https://example.com', + origin: 'example.com', + siteTitle: 'Example', + siteImage: 'https://example.com/logo.svg', + } + + controller._handleProviderRequest(metadata) + assert.deepEqual(controller._getMergedState(), { + approvedOrigins: {}, + providerRequests: [metadata], + }) + }) + + it('should add a pending provider request when locked', () => { + const controller = new ProviderApprovalController({ + keyringController: mockLockedKeyringController, + }) + + const metadata = { + hostname: 'https://example.com', + origin: 'example.com', + siteTitle: 'Example', + siteImage: 'https://example.com/logo.svg', + } + controller._handleProviderRequest(metadata) + assert.deepEqual(controller._getMergedState(), { + approvedOrigins: {}, + providerRequests: [metadata], + }) + }) + + it('should add a 2nd pending provider request when unlocked', () => { + const controller = new ProviderApprovalController({ + keyringController: mockUnlockedKeyringController, + }) + + const metadata = [{ + hostname: 'https://example1.com', + origin: 'example1.com', + siteTitle: 'Example 1', + siteImage: 'https://example1.com/logo.svg', + }, { + hostname: 'https://example2.com', + origin: 'example2.com', + siteTitle: 'Example 2', + siteImage: 'https://example2.com/logo.svg', + }] + + controller._handleProviderRequest(metadata[0]) + controller._handleProviderRequest(metadata[1]) + assert.deepEqual(controller._getMergedState(), { + approvedOrigins: {}, + providerRequests: metadata, + }) + }) + + it('should add a 2nd pending provider request when locked', () => { + const controller = new ProviderApprovalController({ + keyringController: mockLockedKeyringController, + }) + + const metadata = [{ + hostname: 'https://example1.com', + origin: 'example1.com', + siteTitle: 'Example 1', + siteImage: 'https://example1.com/logo.svg', + }, { + hostname: 'https://example2.com', + origin: 'example2.com', + siteTitle: 'Example 2', + siteImage: 'https://example2.com/logo.svg', + }] + + controller._handleProviderRequest(metadata[0]) + controller._handleProviderRequest(metadata[1]) + assert.deepEqual(controller._getMergedState(), { + approvedOrigins: {}, + providerRequests: metadata, + }) + }) + + it('should call openPopup when unlocked and when given', () => { + const openPopup = sinon.spy() + const controller = new ProviderApprovalController({ + openPopup, + keyringController: mockUnlockedKeyringController, + }) + + const metadata = { + hostname: 'https://example.com', + origin: 'example.com', + siteTitle: 'Example', + siteImage: 'https://example.com/logo.svg', + } + controller._handleProviderRequest(metadata) + assert.ok(openPopup.calledOnce) + }) + + it('should call openPopup when locked and when given', () => { + const openPopup = sinon.spy() + const controller = new ProviderApprovalController({ + openPopup, + keyringController: mockLockedKeyringController, + }) + + const metadata = { + hostname: 'https://example.com', + origin: 'example.com', + siteTitle: 'Example', + siteImage: 'https://example.com/logo.svg', + } + controller._handleProviderRequest(metadata) + assert.ok(openPopup.calledOnce) + }) + + it('should NOT call openPopup when unlocked and when the domain has already been approved', () => { + const openPopup = sinon.spy() + const controller = new ProviderApprovalController({ + openPopup, + keyringController: mockUnlockedKeyringController, + }) + + controller.store.updateState({ + approvedOrigins: { + 'example.com': { + siteTitle: 'Example', + siteImage: 'https://example.com/logo.svg', + }, + }, + }) + const metadata = { + hostname: 'https://example.com', + origin: 'example.com', + siteTitle: 'Example', + siteImage: 'https://example.com/logo.svg', + } + controller._handleProviderRequest(metadata) + assert.ok(openPopup.notCalled) + }) + }) + + describe('#approveProviderRequestByOrigin', () => { + it('should mark the origin as approved and remove the provider request', () => { + const controller = new ProviderApprovalController({ + keyringController: mockUnlockedKeyringController, + }) + + const metadata = { + hostname: 'https://example.com', + origin: 'example.com', + siteTitle: 'Example', + siteImage: 'https://example.com/logo.svg', + } + controller._handleProviderRequest(metadata) + controller.approveProviderRequestByOrigin('example.com') + assert.deepEqual(controller._getMergedState(), { + providerRequests: [], + approvedOrigins: { + 'example.com': { + hostname: 'https://example.com', + siteTitle: 'Example', + siteImage: 'https://example.com/logo.svg', + }, + }, + }) + }) + + it('should mark the origin as approved and multiple requests for the same domain', () => { + const controller = new ProviderApprovalController({ + keyringController: mockUnlockedKeyringController, + }) + + const metadata = { + hostname: 'https://example.com', + origin: 'example.com', + siteTitle: 'Example', + siteImage: 'https://example.com/logo.svg', + } + controller._handleProviderRequest(metadata) + controller._handleProviderRequest(metadata) + controller.approveProviderRequestByOrigin('example.com') + assert.deepEqual(controller._getMergedState(), { + providerRequests: [], + approvedOrigins: { + 'example.com': { + hostname: 'https://example.com', + siteTitle: 'Example', + siteImage: 'https://example.com/logo.svg', + }, + }, + }) + }) + + it('should mark the origin as approved without a provider request', () => { + const controller = new ProviderApprovalController({ + keyringController: mockUnlockedKeyringController, + }) + + controller.approveProviderRequestByOrigin('example.com') + assert.deepEqual(controller._getMergedState(), { + providerRequests: [], + approvedOrigins: { + 'example.com': { + hostname: null, + siteTitle: null, + siteImage: null, + }, + }, + }) + }) + }) + + describe('#rejectProviderRequestByOrigin', () => { + it('should remove the origin from approved', () => { + const controller = new ProviderApprovalController({ + keyringController: mockUnlockedKeyringController, + }) + + const metadata = { + hostname: 'https://example.com', + origin: 'example.com', + siteTitle: 'Example', + siteImage: 'https://example.com/logo.svg', + } + controller._handleProviderRequest(metadata) + controller.approveProviderRequestByOrigin('example.com') + controller.rejectProviderRequestByOrigin('example.com') + assert.deepEqual(controller._getMergedState(), { + providerRequests: [], + approvedOrigins: {}, + }) + }) + + it('should reject the origin even without a pending request', () => { + const controller = new ProviderApprovalController({ + keyringController: mockUnlockedKeyringController, + }) + + controller.rejectProviderRequestByOrigin('example.com') + assert.deepEqual(controller._getMergedState(), { + providerRequests: [], + approvedOrigins: {}, + }) + }) + }) + + describe('#clearApprovedOrigins', () => { + it('should clear the approved origins', () => { + const controller = new ProviderApprovalController({ + keyringController: mockUnlockedKeyringController, + }) + + const metadata = { + hostname: 'https://example.com', + origin: 'example.com', + siteTitle: 'Example', + siteImage: 'https://example.com/logo.svg', + } + controller._handleProviderRequest(metadata) + controller.approveProviderRequestByOrigin('example.com') + controller.clearApprovedOrigins() + assert.deepEqual(controller._getMergedState(), { + providerRequests: [], + approvedOrigins: {}, + }) + }) + }) + + describe('#shouldExposeAccounts', () => { + it('should return true for an approved origin', () => { + const controller = new ProviderApprovalController({ + keyringController: mockUnlockedKeyringController, + }) + + const metadata = { + hostname: 'https://example.com', + origin: 'example.com', + siteTitle: 'Example', + siteImage: 'https://example.com/logo.svg', + } + controller._handleProviderRequest(metadata) + controller.approveProviderRequestByOrigin('example.com') + assert.ok(controller.shouldExposeAccounts('example.com')) + }) + + it('should return false for an origin not yet approved', () => { + const controller = new ProviderApprovalController({ + keyringController: mockUnlockedKeyringController, + }) + + const metadata = { + hostname: 'https://example.com', + origin: 'example.com', + siteTitle: 'Example', + siteImage: 'https://example.com/logo.svg', + } + controller._handleProviderRequest(metadata) + controller.approveProviderRequestByOrigin('example.com') + assert.ok(!controller.shouldExposeAccounts('bad.website')) + }) + }) +}) diff --git a/test/unit/app/controllers/transactions/pending-tx-test.js b/test/unit/app/controllers/transactions/pending-tx-test.js index 622a53b184..e1de5731b4 100644 --- a/test/unit/app/controllers/transactions/pending-tx-test.js +++ b/test/unit/app/controllers/transactions/pending-tx-test.js @@ -40,19 +40,13 @@ describe('PendingTransactionTracker', function () { return { releaseLock: () => {} } }, }, - getPendingTransactions: () => { - return [] - }, - getCompletedTransactions: () => { - return [] - }, + getPendingTransactions: () => { return [] }, + getCompletedTransactions: () => { return [] }, publishTransaction: () => {}, confirmTransaction: () => {}, }) - pendingTxTracker._getBlock = (blockNumber) => { - return {number: blockNumber, transactions: []} - } + pendingTxTracker._getBlock = (blockNumber) => { return {number: blockNumber, transactions: []} } }) describe('_checkPendingTx state management', function () { @@ -156,18 +150,14 @@ describe('PendingTransactionTracker', function () { txMeta2.id = 2 txMeta3.id = 3 txList = [txMeta, txMeta2, txMeta3].map((tx) => { - tx.processed = new Promise((resolve) => { - tx.resolve = resolve - }) + tx.processed = new Promise((resolve) => { tx.resolve = resolve }) return tx }) }) it('should warp all txMeta\'s in #updatePendingTxs', function (done) { pendingTxTracker.getPendingTransactions = () => txList - pendingTxTracker._checkPendingTx = (tx) => { - tx.resolve(tx) - } + pendingTxTracker._checkPendingTx = (tx) => { tx.resolve(tx) } Promise.all(txList.map((tx) => tx.processed)) .then(() => done()) .catch(done) @@ -181,9 +171,7 @@ describe('PendingTransactionTracker', function () { beforeEach(function () { const txMeta2 = txMeta3 = txMeta txList = [txMeta, txMeta2, txMeta3].map((tx) => { - tx.processed = new Promise((resolve) => { - tx.resolve = resolve - }) + tx.processed = new Promise((resolve) => { tx.resolve = resolve }) return tx }) }) @@ -193,9 +181,7 @@ describe('PendingTransactionTracker', function () { }) it('should call #_resubmitTx for all pending tx\'s', function (done) { pendingTxTracker.getPendingTransactions = () => txList - pendingTxTracker._resubmitTx = async (tx) => { - tx.resolve(tx) - } + pendingTxTracker._resubmitTx = async (tx) => { tx.resolve(tx) } Promise.all(txList.map((tx) => tx.processed)) .then(() => done()) .catch(done) @@ -239,9 +225,7 @@ describe('PendingTransactionTracker', function () { }) pendingTxTracker.getPendingTransactions = () => txList - pendingTxTracker._resubmitTx = async () => { - throw new TypeError('im some real error') - } + pendingTxTracker._resubmitTx = async () => { throw new TypeError('im some real error') } Promise.all(txList.map((tx) => tx.processed)) .then(() => done()) .catch(done) @@ -385,9 +369,7 @@ describe('PendingTransactionTracker', function () { rawTx: '0xf86c808504a817c800827b0d940c62bb85faa3311a998d3aba8098c1235c564966880de0b6b3a7640000802aa08ff665feb887a25d4099e40e11f0fef93ee9608f404bd3f853dd9e84ed3317a6a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d', }] pendingTxTracker.getCompletedTransactions = (address) => { - if (!address) { - throw new Error('unless behavior has changed #_checkIfNonceIsTaken needs a filtered list of transactions to see if the nonce is taken') - } + if (!address) throw new Error('unless behavior has changed #_checkIfNonceIsTaken needs a filtered list of transactions to see if the nonce is taken') return confirmedTxList } }) diff --git a/test/unit/app/controllers/transactions/tx-controller-test.js b/test/unit/app/controllers/transactions/tx-controller-test.js index d398c7e04f..76b8e50254 100644 --- a/test/unit/app/controllers/transactions/tx-controller-test.js +++ b/test/unit/app/controllers/transactions/tx-controller-test.js @@ -38,9 +38,7 @@ describe('Transaction Controller', function () { blockTrackerStub.getLatestBlock = noop txController = new TransactionController({ provider, - getGasPrice: function () { - return '0xee6b2800' - }, + getGasPrice: function () { return '0xee6b2800' }, networkStore: netStore, txHistoryLimit: 10, blockTracker: blockTrackerStub, @@ -48,7 +46,6 @@ describe('Transaction Controller', function () { ethTx.sign(fromAccount.key) resolve() }), - getPermittedAccounts: () => {}, }) txController.nonceTracker.getNonceLock = () => Promise.resolve({ nextNonce: 0, releaseLock: noop }) }) @@ -165,11 +162,8 @@ describe('Transaction Controller', function () { txController.newUnapprovedTransaction(txParams) .catch((err) => { - if (err.message === 'MetaMask Tx Signature: User denied transaction signature.') { - done() - } else { - done(err) - } + if (err.message === 'MetaMask Tx Signature: User denied transaction signature.') done() + else done(err) }) }) }) @@ -177,15 +171,13 @@ describe('Transaction Controller', function () { describe('#addUnapprovedTransaction', function () { const selectedAddress = '0x1678a085c290ebd122dc42cba69373b5953b831d' - let getSelectedAddress, getPermittedAccounts + let getSelectedAddress beforeEach(function () { getSelectedAddress = sinon.stub(txController, 'getSelectedAddress').returns(selectedAddress) - getPermittedAccounts = sinon.stub(txController, 'getPermittedAccounts').returns([selectedAddress]) }) afterEach(function () { getSelectedAddress.restore() - getPermittedAccounts.restore() }) it('should add an unapproved transaction and return a valid txMeta', function (done) { @@ -217,11 +209,8 @@ describe('Transaction Controller', function () { txController.networkStore = new ObservableStore(1) txController.addUnapprovedTransaction({ from: selectedAddress, to: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2' }) .catch((err) => { - if (err.message === 'Recipient is a public account') { - done() - } else { - done(err) - } + if (err.message === 'Recipient is a public account') done() + else done(err) }) }) @@ -250,11 +239,8 @@ describe('Transaction Controller', function () { txController.networkStore = new ObservableStore('loading') txController.addUnapprovedTransaction({ from: selectedAddress, to: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2' }) .catch((err) => { - if (err.message === 'MetaMask is having trouble connecting to the network') { - done() - } else { - done(err) - } + if (err.message === 'MetaMask is having trouble connecting to the network') done() + else done(err) }) }) }) @@ -417,9 +403,7 @@ describe('Transaction Controller', function () { assert.equal(status, 'rejected', 'status should e rejected') assert.equal(txId, 0, 'id should e 0') done() - } catch (e) { - done(e) - } + } catch (e) { done(e) } }) txController.cancelTransaction(0) @@ -514,9 +498,7 @@ describe('Transaction Controller', function () { }) it('should ignore the error "Transaction Failed: known transaction" and be as usual', async function () { - providerResultStub['eth_sendRawTransaction'] = async (_, __, ___, end) => { - end('Transaction Failed: known transaction') - } + providerResultStub['eth_sendRawTransaction'] = async (_, __, ___, end) => { end('Transaction Failed: known transaction') } const rawTx = '0xf86204831e848082520894f231d46dd78806e1dd93442cf33c7671f853874880802ca05f973e540f2d3c2f06d3725a626b75247593cb36477187ae07ecfe0a4db3cf57a00259b52ee8c58baaa385fb05c3f96116e58de89bcc165cb3bfdfc708672fed8a' txController.txStateManager.addTx(txMeta) await txController.publishTransaction(txMeta.id, rawTx) @@ -635,9 +617,7 @@ describe('Transaction Controller', function () { _blockTrackerStub.getLatestBlock = noop const _txController = new TransactionController({ provider: _provider, - getGasPrice: function () { - return '0xee6b2800' - }, + getGasPrice: function () { return '0xee6b2800' }, networkStore: new ObservableStore(currentNetworkId), txHistoryLimit: 10, blockTracker: _blockTrackerStub, @@ -667,9 +647,7 @@ describe('Transaction Controller', function () { _blockTrackerStub.getLatestBlock = noop const _txController = new TransactionController({ provider: _provider, - getGasPrice: function () { - return '0xee6b2800' - }, + getGasPrice: function () { return '0xee6b2800' }, networkStore: new ObservableStore(currentNetworkId), txHistoryLimit: 10, blockTracker: _blockTrackerStub, diff --git a/test/unit/app/controllers/transactions/tx-state-history-helper-test.js b/test/unit/app/controllers/transactions/tx-state-history-helper-test.js index 6136de66e5..328c2ac6f6 100644 --- a/test/unit/app/controllers/transactions/tx-state-history-helper-test.js +++ b/test/unit/app/controllers/transactions/tx-state-history-helper-test.js @@ -106,9 +106,7 @@ describe('Transaction state history helper', function () { assert.equal(result[0].path, expectedEntry1.path) assert.equal(result[0].value, expectedEntry1.value) assert.equal(result[0].value, expectedEntry1.value) - if (note) { - assert.equal(result[0].note, note) - } + if (note) { assert.equal(result[0].note, note) } assert.ok(result[0].timestamp >= before && result[0].timestamp <= after) diff --git a/test/unit/app/controllers/transactions/tx-utils-test.js b/test/unit/app/controllers/transactions/tx-utils-test.js index e8bc8a5b78..65c8d35b05 100644 --- a/test/unit/app/controllers/transactions/tx-utils-test.js +++ b/test/unit/app/controllers/transactions/tx-utils-test.js @@ -5,7 +5,7 @@ const txUtils = require('../../../../../app/scripts/controllers/transactions/lib describe('txUtils', function () { describe('#validateTxParams', function () { it('does not throw for positive values', function () { - const sample = { + var sample = { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', value: '0x01', } @@ -13,7 +13,7 @@ describe('txUtils', function () { }) it('returns error for negative values', function () { - const sample = { + var sample = { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', value: '-0x01', } @@ -66,9 +66,7 @@ describe('txUtils', function () { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', to: '0x', } - assert.throws(() => { - txUtils.validateRecipient(zeroRecipientTxParams) - }, Error, 'Invalid recipient address') + assert.throws(() => { txUtils.validateRecipient(zeroRecipientTxParams) }, Error, 'Invalid recipient address') }) }) @@ -78,27 +76,19 @@ describe('txUtils', function () { // where from is undefined const txParams = {} - assert.throws(() => { - txUtils.validateFrom(txParams) - }, Error, `Invalid from address ${txParams.from} not a string`) + assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`) // where from is array txParams.from = [] - assert.throws(() => { - txUtils.validateFrom(txParams) - }, Error, `Invalid from address ${txParams.from} not a string`) + assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`) // where from is a object txParams.from = {} - assert.throws(() => { - txUtils.validateFrom(txParams) - }, Error, `Invalid from address ${txParams.from} not a string`) + assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`) // where from is a invalid address txParams.from = 'im going to fail' - assert.throws(() => { - txUtils.validateFrom(txParams) - }, Error, `Invalid from address`) + assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address`) // should run txParams.from = '0x1678a085c290ebd122dc42cba69373b5953b831d' diff --git a/test/unit/app/edge-encryptor-test.js b/test/unit/app/edge-encryptor-test.js index 3f2e8830a3..ad873e3511 100644 --- a/test/unit/app/edge-encryptor-test.js +++ b/test/unit/app/edge-encryptor-test.js @@ -2,8 +2,8 @@ const assert = require('assert') const EdgeEncryptor = require('../../../app/scripts/edge-encryptor') -const password = 'passw0rd1' -const data = 'some random data' +var password = 'passw0rd1' +var data = 'some random data' global.crypto = global.crypto || { getRandomValues: function (array) { diff --git a/test/unit/app/message-manager-test.js b/test/unit/app/message-manager-test.js index b824acfe7a..36ef6c29f2 100644 --- a/test/unit/app/message-manager-test.js +++ b/test/unit/app/message-manager-test.js @@ -10,7 +10,7 @@ describe('Message Manager', function () { describe('#getMsgList', function () { it('when new should return empty array', function () { - const result = messageManager.messages + var result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 0) }) @@ -21,9 +21,9 @@ describe('Message Manager', function () { describe('#addMsg', function () { it('adds a Msg returned in getMsgList', function () { - const Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' } + var Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' } messageManager.addMsg(Msg) - const result = messageManager.messages + var result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 1) assert.equal(result[0].id, 1) @@ -32,10 +32,10 @@ describe('Message Manager', function () { describe('#setMsgStatusApproved', function () { it('sets the Msg status to approved', function () { - const Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } + var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } messageManager.addMsg(Msg) messageManager.setMsgStatusApproved(1) - const result = messageManager.messages + var result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 1) assert.equal(result[0].status, 'approved') @@ -44,10 +44,10 @@ describe('Message Manager', function () { describe('#rejectMsg', function () { it('sets the Msg status to rejected', function () { - const Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } + var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } messageManager.addMsg(Msg) messageManager.rejectMsg(1) - const result = messageManager.messages + var result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 1) assert.equal(result[0].status, 'rejected') @@ -59,7 +59,7 @@ describe('Message Manager', function () { messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) messageManager._updateMsg({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: 'unit test' }) - const result = messageManager.getMsg('1') + var result = messageManager.getMsg('1') assert.equal(result.hash, 'foo') }) }) diff --git a/test/unit/app/nodeify-test.js b/test/unit/app/nodeify-test.js index fc06803dbc..fa5e49fb22 100644 --- a/test/unit/app/nodeify-test.js +++ b/test/unit/app/nodeify-test.js @@ -33,9 +33,7 @@ describe('nodeify', function () { }) it('no callback - should asyncly throw an error if underlying function does', function (done) { - const nodified = nodeify(async () => { - throw new Error('boom!') - }, obj) + const nodified = nodeify(async () => { throw new Error('boom!') }, obj) process.prependOnceListener('uncaughtException', function (err) { assert.ok(err, 'got expected error') assert.ok(err.message.includes('boom!'), 'got expected error message') @@ -52,9 +50,7 @@ describe('nodeify', function () { const nodified = nodeify(() => 42) try { nodified((err, result) => { - if (err) { - return done(new Error(`should not have thrown any error: ${err.message}`)) - } + if (err) return done(new Error(`should not have thrown any error: ${err.message}`)) assert.equal(42, result, 'got expected result') }) done() @@ -64,14 +60,10 @@ describe('nodeify', function () { }) it('sync functions - handles errors', function (done) { - const nodified = nodeify(() => { - throw new Error('boom!') - }) + const nodified = nodeify(() => { throw new Error('boom!') }) try { nodified((err, result) => { - if (result) { - return done(new Error('should not have returned any result')) - } + if (result) return done(new Error('should not have returned any result')) assert.ok(err, 'got expected error') assert.ok(err.message.includes('boom!'), 'got expected error message') }) diff --git a/test/unit/app/personal-message-manager-test.js b/test/unit/app/personal-message-manager-test.js index 52cbdb75cb..b07167bff9 100644 --- a/test/unit/app/personal-message-manager-test.js +++ b/test/unit/app/personal-message-manager-test.js @@ -11,7 +11,7 @@ describe('Personal Message Manager', function () { describe('#getMsgList', function () { it('when new should return empty array', function () { - const result = messageManager.messages + var result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 0) }) @@ -22,9 +22,9 @@ describe('Personal Message Manager', function () { describe('#addMsg', function () { it('adds a Msg returned in getMsgList', function () { - const Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' } + var Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' } messageManager.addMsg(Msg) - const result = messageManager.messages + var result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 1) assert.equal(result[0].id, 1) @@ -33,10 +33,10 @@ describe('Personal Message Manager', function () { describe('#setMsgStatusApproved', function () { it('sets the Msg status to approved', function () { - const Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } + var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } messageManager.addMsg(Msg) messageManager.setMsgStatusApproved(1) - const result = messageManager.messages + var result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 1) assert.equal(result[0].status, 'approved') @@ -45,10 +45,10 @@ describe('Personal Message Manager', function () { describe('#rejectMsg', function () { it('sets the Msg status to rejected', function () { - const Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } + var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } messageManager.addMsg(Msg) messageManager.rejectMsg(1) - const result = messageManager.messages + var result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 1) assert.equal(result[0].status, 'rejected') @@ -60,7 +60,7 @@ describe('Personal Message Manager', function () { messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) messageManager._updateMsg({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: 'unit test' }) - const result = messageManager.getMsg('1') + var result = messageManager.getMsg('1') assert.equal(result.hash, 'foo') }) }) @@ -87,20 +87,20 @@ describe('Personal Message Manager', function () { describe('#normalizeMsgData', function () { it('converts text to a utf8 hex string', function () { - const input = 'hello' - const output = messageManager.normalizeMsgData(input) + var input = 'hello' + var output = messageManager.normalizeMsgData(input) assert.equal(output, '0x68656c6c6f', 'predictably hex encoded') }) it('tolerates a hex prefix', function () { - const input = '0x12' - const output = messageManager.normalizeMsgData(input) + var input = '0x12' + var output = messageManager.normalizeMsgData(input) assert.equal(output, '0x12', 'un modified') }) it('tolerates normal hex', function () { - const input = '12' - const output = messageManager.normalizeMsgData(input) + var input = '12' + var output = messageManager.normalizeMsgData(input) assert.equal(output, '0x12', 'adds prefix') }) }) diff --git a/test/unit/app/typed-message-manager.spec.js b/test/unit/app/typed-message-manager.spec.js index 959662b916..3db92d4a38 100644 --- a/test/unit/app/typed-message-manager.spec.js +++ b/test/unit/app/typed-message-manager.spec.js @@ -30,12 +30,12 @@ describe('Typed Message Manager', () => { 'EIP712Domain': [ {'name': 'name', 'type': 'string' }, {'name': 'version', 'type': 'string' }, - {'name': 'chainId', 'type': 'uint256' }, - {'name': 'verifyingContract', 'type': 'address' }, + {'name': 'chainId', ' type': 'uint256' }, + {'name': 'verifyingContract', ' type': 'address' }, ], 'Person': [ {'name': 'name', 'type': 'string' }, - {'name': 'wallet', 'type': 'address' }, + {'name': 'wallet', ' type': 'address' }, ], 'Mail': [ {'name': 'from', 'type': 'Person' }, @@ -64,7 +64,7 @@ describe('Typed Message Manager', () => { }), } - typedMessageManager.addUnapprovedMessage(msgParamsV3, null, 'V3') + typedMessageManager.addUnapprovedMessage(msgParamsV3, 'V3') typedMsgs = typedMessageManager.getUnapprovedMsgs() messages = typedMessageManager.messages msgId = Object.keys(typedMsgs)[0] @@ -73,7 +73,7 @@ describe('Typed Message Manager', () => { }) it('supports version 1 of signedTypedData', () => { - typedMessageManager.addUnapprovedMessage(msgParamsV1, null, 'V1') + typedMessageManager.addUnapprovedMessage(msgParamsV1, 'V1') assert.equal(messages[messages.length - 1].msgParams.data, msgParamsV1.data) }) @@ -87,7 +87,7 @@ describe('Typed Message Manager', () => { it('validates params', function () { assert.doesNotThrow(() => { - typedMessageManager.validateParams(messages[0].msgParams) + typedMessageManager.validateParams(messages[0]) }, 'Does not throw with valid parameters') }) diff --git a/test/unit/migrations/023-test.js b/test/unit/migrations/023-test.js index 7c93feefec..1b47dea92e 100644 --- a/test/unit/migrations/023-test.js +++ b/test/unit/migrations/023-test.js @@ -37,9 +37,7 @@ let nonDeletableCount = 0 let status while (transactions.length <= 100) { status = txStates[Math.floor(Math.random() * Math.floor(txStates.length - 1))] - if (!deletableTxStates.find((s) => s === status)) { - nonDeletableCount++ - } + if (!deletableTxStates.find((s) => s === status)) nonDeletableCount++ transactions.push({status}) } diff --git a/test/unit/migrations/024-test.js b/test/unit/migrations/024-test.js index 9a6a6661ac..671c7f8328 100644 --- a/test/unit/migrations/024-test.js +++ b/test/unit/migrations/024-test.js @@ -31,11 +31,8 @@ describe('storage is migrated successfully and the txParams.from are lowercase', .then((migratedData) => { const migratedTransactions = migratedData.data.TransactionController.transactions migratedTransactions.forEach((tx) => { - if (tx.status === 'unapproved') { - assert.equal(tx.txParams.from, '0x8acce2391c0d510a6c5e5d8f819a678f79b7e675') - } else { - assert.equal(tx.txParams.from, '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675') - } + if (tx.status === 'unapproved') assert.equal(tx.txParams.from, '0x8acce2391c0d510a6c5e5d8f819a678f79b7e675') + else assert.equal(tx.txParams.from, '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675') }) done() }).catch(done) diff --git a/test/unit/migrations/025-test.js b/test/unit/migrations/025-test.js index cabaecd410..fa89bc54fd 100644 --- a/test/unit/migrations/025-test.js +++ b/test/unit/migrations/025-test.js @@ -32,12 +32,8 @@ describe('storage is migrated successfully and the txParams.from are lowercase', .then((migratedData) => { const migratedTransactions = migratedData.data.TransactionController.transactions migratedTransactions.forEach((tx) => { - if (tx.status === 'unapproved') { - assert(!tx.txParams.random) - } - if (tx.status === 'unapproved') { - assert(!tx.txParams.chainId) - } + if (tx.status === 'unapproved') assert(!tx.txParams.random) + if (tx.status === 'unapproved') assert(!tx.txParams.chainId) }) done() }).catch(done) diff --git a/test/unit/migrations/027-test.js b/test/unit/migrations/027-test.js index 767243a25e..3ec9f0c0e5 100644 --- a/test/unit/migrations/027-test.js +++ b/test/unit/migrations/027-test.js @@ -30,9 +30,7 @@ describe('migration #27', () => { const newTransactions = newStorage.data.TransactionController.transactions assert.equal(newTransactions.length, 6, 'transactions is expected to have the length of 6') newTransactions.forEach((txMeta) => { - if (txMeta.status === 'rejected') { - done(new Error('transaction was found with a status of rejected')) - } + if (txMeta.status === 'rejected') done(new Error('transaction was found with a status of rejected')) }) done() }) diff --git a/test/unit/migrations/029-test.js b/test/unit/migrations/029-test.js index ca0996389b..7f9b8a005f 100644 --- a/test/unit/migrations/029-test.js +++ b/test/unit/migrations/029-test.js @@ -28,9 +28,7 @@ describe('storage is migrated successfully where transactions that are submitted assert(txMeta1.err.message.includes('too long'), 'error message assigned') txs.forEach((tx) => { - if (tx.id === 1) { - return - } + if (tx.id === 1) return assert.notEqual(tx.status, 'failed', 'other tx is not auto failed') }) diff --git a/test/unit/migrations/migrator-test.js b/test/unit/migrations/migrator-test.js index 9a949def45..3dcc5aff79 100644 --- a/test/unit/migrations/migrator-test.js +++ b/test/unit/migrations/migrator-test.js @@ -58,9 +58,7 @@ describe('Migrator', () => { it('should emit an error', function (done) { this.timeout(15000) - const migrator = new Migrator({ migrations: [{ version: 1, migrate: async () => { - throw new Error('test') - } } ] }) + const migrator = new Migrator({ migrations: [{ version: 1, migrate: async () => { throw new Error('test') } } ] }) migrator.on('error', () => done()) migrator.migrateData({ meta: {version: 0} }) .then(() => { diff --git a/test/unit/reducers/unlock_vault_test.js b/test/unit/reducers/unlock_vault_test.js index bb992ab40c..d66891a636 100644 --- a/test/unit/reducers/unlock_vault_test.js +++ b/test/unit/reducers/unlock_vault_test.js @@ -1,11 +1,11 @@ // var jsdom = require('mocha-jsdom') -const assert = require('assert') +var assert = require('assert') // var freeze = require('deep-freeze-strict') -const path = require('path') -const sinon = require('sinon') +var path = require('path') +var sinon = require('sinon') -const actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) -const reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) +var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) describe('#unlockMetamask(selectedAccount)', function () { beforeEach(function () { diff --git a/test/unit/responsive/components/dropdown-test.js b/test/unit/responsive/components/dropdown-test.js index 2b9a5457ba..1fadbfb601 100644 --- a/test/unit/responsive/components/dropdown-test.js +++ b/test/unit/responsive/components/dropdown-test.js @@ -1,6 +1,6 @@ -const React = require('react') const assert = require('assert') +const h = require('react-hyperscript') const sinon = require('sinon') const path = require('path') const Dropdown = require(path.join(__dirname, '..', '..', '..', '..', 'ui', 'app', 'components', 'app', 'dropdowns', 'index.js')).Dropdown @@ -39,19 +39,23 @@ describe('Dropdown components', function () { onClick = sinon.spy() store = createMockStore(mockState) - component = mountWithStore(( - - -
  • Item 1
  • -
  • Item 2
  • -
    + component = mountWithStore(h( + Dropdown, + dropdownComponentProps, + [ + h('style', ` + .drop-menu-item:hover { background:rgb(235, 235, 235); } + .drop-menu-item i { margin: 11px; } + `), + h('li', { + closeMenu, + onClick, + }, 'Item 1'), + h('li', { + closeMenu, + onClick, + }, 'Item 2'), + ] ), store) dropdownComponent = component }) diff --git a/test/unit/test-utils.js b/test/unit/test-utils.js index de508c5046..7d0ae4d91a 100644 --- a/test/unit/test-utils.js +++ b/test/unit/test-utils.js @@ -10,9 +10,7 @@ async function assertRejects (asyncFn, regExp) { try { await asyncFn() } catch (error) { - f = () => { - throw error - } + f = () => { throw error } } finally { assert.throws(f, regExp) } diff --git a/test/unit/ui/app/actions.spec.js b/test/unit/ui/app/actions.spec.js index fffc14ab8c..9ed21e729c 100644 --- a/test/unit/ui/app/actions.spec.js +++ b/test/unit/ui/app/actions.spec.js @@ -755,6 +755,7 @@ describe('Actions', () => { 'domain': { 'name': 'Ether Mainl', 'version': '1', + 'chainId': 1, 'verifyingContract': '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', }, 'message': { @@ -772,12 +773,11 @@ describe('Actions', () => { } beforeEach(() => { - metamaskController.newUnsignedTypedMessage(msgParamsV3, null, 'V3') + metamaskController.newUnsignedTypedMessage(msgParamsV3, 'V3') messages = metamaskController.typedMessageManager.getUnapprovedMsgs() typedMessages = metamaskController.typedMessageManager.messages msgId = Object.keys(messages)[0] typedMessages[0].msgParams.metamaskId = parseInt(msgId) - signTypedMsgSpy = sinon.stub(background, 'signTypedMessage') }) afterEach(() => { @@ -786,6 +786,7 @@ describe('Actions', () => { it('calls signTypedMsg in background with no error', () => { const store = mockStore() + signTypedMsgSpy = sinon.stub(background, 'signTypedMessage') store.dispatch(actions.signTypedMsg(msgParamsV3)) assert(signTypedMsgSpy.calledOnce) @@ -800,6 +801,8 @@ describe('Actions', () => { { type: 'DISPLAY_WARNING', value: 'error' }, ] + signTypedMsgSpy = sinon.stub(background, 'signTypedMessage') + signTypedMsgSpy.callsFake((_, callback) => { callback(new Error('error')) }) diff --git a/test/unit/util_test.js b/test/unit/util_test.js index ffb2202765..87f57b218e 100644 --- a/test/unit/util_test.js +++ b/test/unit/util_test.js @@ -1,15 +1,13 @@ -const assert = require('assert') -const sinon = require('sinon') +var assert = require('assert') +var sinon = require('sinon') const ethUtil = require('ethereumjs-util') -const path = require('path') -const util = require(path.join(__dirname, '..', '..', 'ui', 'app', 'helpers', 'utils', 'util.js')) +var path = require('path') +var util = require(path.join(__dirname, '..', '..', 'ui', 'app', 'helpers', 'utils', 'util.js')) describe('util', function () { - let ethInWei = '1' - for (let i = 0; i < 18; i++) { - ethInWei += '0' - } + var ethInWei = '1' + for (var i = 0; i < 18; i++) { ethInWei += '0' } beforeEach(function () { this.sinon = sinon.createSandbox() @@ -47,52 +45,52 @@ describe('util', function () { describe('#addressSummary', function () { it('should add case-sensitive checksum', function () { - const address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825' - const result = util.addressSummary(address) + var address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825' + var result = util.addressSummary(address) assert.equal(result, '0xFDEa65C8...b825') }) it('should accept arguments for firstseg, lastseg, and keepPrefix', function () { - const address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825' - const result = util.addressSummary(address, 4, 4, false) + var address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825' + var result = util.addressSummary(address, 4, 4, false) assert.equal(result, 'FDEa...b825') }) }) describe('#isValidAddress', function () { it('should allow 40-char non-prefixed hex', function () { - const address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b825' - const result = util.isValidAddress(address) + var address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b825' + var result = util.isValidAddress(address) assert.ok(result) }) it('should allow 42-char non-prefixed hex', function () { - const address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825' - const result = util.isValidAddress(address) + var address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825' + var result = util.isValidAddress(address) assert.ok(result) }) it('should not allow less non hex-prefixed', function () { - const address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b85' - const result = util.isValidAddress(address) + var address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b85' + var result = util.isValidAddress(address) assert.ok(!result) }) it('should not allow less hex-prefixed', function () { - const address = '0xfdea65ce26263f6d9a1b5de9555d2931a33b85' - const result = util.isValidAddress(address) + var address = '0xfdea65ce26263f6d9a1b5de9555d2931a33b85' + var result = util.isValidAddress(address) assert.ok(!result) }) it('should recognize correct capitalized checksum', function () { - const address = '0xFDEa65C8e26263F6d9A1B5de9555D2931A33b825' - const result = util.isValidAddress(address) + var address = '0xFDEa65C8e26263F6d9A1B5de9555D2931A33b825' + var result = util.isValidAddress(address) assert.ok(result) }) it('should recognize incorrect capitalized checksum', function () { - const address = '0xFDea65C8e26263F6d9A1B5de9555D2931A33b825' - const result = util.isValidAddress(address) + var address = '0xFDea65C8e26263F6d9A1B5de9555D2931A33b825' + var result = util.isValidAddress(address) assert.ok(!result) }) @@ -107,58 +105,58 @@ describe('util', function () { describe('#numericBalance', function () { it('should return a BN 0 if given nothing', function () { - const result = util.numericBalance() + var result = util.numericBalance() assert.equal(result.toString(10), 0) }) it('should work with hex prefix', function () { - const result = util.numericBalance('0x012') + var result = util.numericBalance('0x012') assert.equal(result.toString(10), '18') }) it('should work with no hex prefix', function () { - const result = util.numericBalance('012') + var result = util.numericBalance('012') assert.equal(result.toString(10), '18') }) }) describe('#formatBalance', function () { it('when given nothing', function () { - const result = util.formatBalance() + var result = util.formatBalance() assert.equal(result, 'None', 'should return "None"') }) it('should return eth as string followed by ETH', function () { - const input = new ethUtil.BN(ethInWei, 10).toJSON() - const result = util.formatBalance(input, 4) + var input = new ethUtil.BN(ethInWei, 10).toJSON() + var result = util.formatBalance(input, 4) assert.equal(result, '1.0000 ETH') }) it('should return eth as string followed by ETH', function () { - const input = new ethUtil.BN(ethInWei, 10).div(new ethUtil.BN('2', 10)).toJSON() - const result = util.formatBalance(input, 3) + var input = new ethUtil.BN(ethInWei, 10).div(new ethUtil.BN('2', 10)).toJSON() + var result = util.formatBalance(input, 3) assert.equal(result, '0.500 ETH') }) it('should display specified decimal points', function () { - const input = '0x128dfa6a90b28000' - const result = util.formatBalance(input, 2) + var input = '0x128dfa6a90b28000' + var result = util.formatBalance(input, 2) assert.equal(result, '1.33 ETH') }) it('should default to 3 decimal points', function () { - const input = '0x128dfa6a90b28000' - const result = util.formatBalance(input) + var input = '0x128dfa6a90b28000' + var result = util.formatBalance(input) assert.equal(result, '1.337 ETH') }) it('should show 2 significant digits for tiny balances', function () { - const input = '0x1230fa6a90b28' - const result = util.formatBalance(input) + var input = '0x1230fa6a90b28' + var result = util.formatBalance(input) assert.equal(result, '0.00032 ETH') }) it('should not parse the balance and return value with 2 decimal points with ETH at the end', function () { - const value = '1.2456789' - const needsParse = false - const result = util.formatBalance(value, 2, needsParse) + var value = '1.2456789' + var needsParse = false + var result = util.formatBalance(value, 2, needsParse) assert.equal(result, '1.24 ETH') }) }) @@ -166,7 +164,7 @@ describe('util', function () { describe('normalizing values', function () { describe('#normalizeToWei', function () { it('should convert an eth to the appropriate equivalent values', function () { - const valueTable = { + var valueTable = { wei: '1000000000000000000', kwei: '1000000000000000', mwei: '1000000000000', @@ -181,11 +179,11 @@ describe('util', function () { // gether:'0.000000001', // tether:'0.000000000001', } - const oneEthBn = new ethUtil.BN(ethInWei, 10) + var oneEthBn = new ethUtil.BN(ethInWei, 10) - for (const currency in valueTable) { - const value = new ethUtil.BN(valueTable[currency], 10) - const output = util.normalizeToWei(value, currency) + for (var currency in valueTable) { + var value = new ethUtil.BN(valueTable[currency], 10) + var output = util.normalizeToWei(value, currency) assert.equal(output.toString(10), valueTable.wei, `value of ${output.toString(10)} ${currency} should convert to ${oneEthBn}`) } }) @@ -193,66 +191,66 @@ describe('util', function () { describe('#normalizeEthStringToWei', function () { it('should convert decimal eth to pure wei BN', function () { - const input = '1.23456789' - const output = util.normalizeEthStringToWei(input) + var input = '1.23456789' + var output = util.normalizeEthStringToWei(input) assert.equal(output.toString(10), '1234567890000000000') }) it('should convert 1 to expected wei', function () { - const input = '1' - const output = util.normalizeEthStringToWei(input) + var input = '1' + var output = util.normalizeEthStringToWei(input) assert.equal(output.toString(10), ethInWei) }) it('should account for overflow numbers gracefully by dropping extra precision.', function () { - const input = '1.11111111111111111111' - const output = util.normalizeEthStringToWei(input) + var input = '1.11111111111111111111' + var output = util.normalizeEthStringToWei(input) assert.equal(output.toString(10), '1111111111111111111') }) it('should not truncate very exact wei values that do not have extra precision.', function () { - const input = '1.100000000000000001' - const output = util.normalizeEthStringToWei(input) + var input = '1.100000000000000001' + var output = util.normalizeEthStringToWei(input) assert.equal(output.toString(10), '1100000000000000001') }) }) describe('#normalizeNumberToWei', function () { it('should handle a simple use case', function () { - const input = 0.0002 - const output = util.normalizeNumberToWei(input, 'ether') - const str = output.toString(10) + var input = 0.0002 + var output = util.normalizeNumberToWei(input, 'ether') + var str = output.toString(10) assert.equal(str, '200000000000000') }) it('should convert a kwei number to the appropriate equivalent wei', function () { - const result = util.normalizeNumberToWei(1.111, 'kwei') + var result = util.normalizeNumberToWei(1.111, 'kwei') assert.equal(result.toString(10), '1111', 'accepts decimals') }) it('should convert a ether number to the appropriate equivalent wei', function () { - const result = util.normalizeNumberToWei(1.111, 'ether') + var result = util.normalizeNumberToWei(1.111, 'ether') assert.equal(result.toString(10), '1111000000000000000', 'accepts decimals') }) }) describe('#isHex', function () { it('should return true when given a hex string', function () { - const result = util.isHex('c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2') + var result = util.isHex('c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2') assert(result) }) it('should return false when given a non-hex string', function () { - const result = util.isHex('c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714imnotreal') + var result = util.isHex('c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714imnotreal') assert(!result) }) it('should return false when given a string containing a non letter/number character', function () { - const result = util.isHex('c3ab8ff13720!8ad9047dd39466b3c%8974e592c2fa383d4a396071imnotreal') + var result = util.isHex('c3ab8ff13720!8ad9047dd39466b3c%8974e592c2fa383d4a396071imnotreal') assert(!result) }) it('should return true when given a hex string with hex-prefix', function () { - const result = util.isHex('0xc3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2') + var result = util.isHex('0xc3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2') assert(result) }) }) diff --git a/test/web3/schema.js b/test/web3/schema.js index 9dc528a574..3360604315 100644 --- a/test/web3/schema.js +++ b/test/web3/schema.js @@ -1,6 +1,6 @@ /* eslint no-unused-vars: 0 */ -const params = { +var params = { // diffrent params used in the methods param: [], blockHashParams: '0xb3b20624f8f0f86eb50dd04688409e5cea4bd02d700bf6e79e9384d47d6a5a35', @@ -93,7 +93,7 @@ const params = { }, } -const methods = { +var methods = { hexaNumberMethods: { // these are the methods which have output in the form of hexa decimal numbers eth_blockNumber: ['eth_blockNumber', params.param, 'Q'], diff --git a/test/web3/web3.js b/test/web3/web3.js index 3431ee8ba7..8f319f38ec 100644 --- a/test/web3/web3.js +++ b/test/web3/web3.js @@ -1,6 +1,6 @@ /* eslint no-undef: 0 */ -const json = methods +var json = methods web3.currentProvider.enable().then(() => { diff --git a/ui/app/components/app/account-details/account-details.component.js b/ui/app/components/app/account-details/account-details.component.js index e75e777a6d..55078cee0d 100644 --- a/ui/app/components/app/account-details/account-details.component.js +++ b/ui/app/components/app/account-details/account-details.component.js @@ -19,11 +19,9 @@ export default class AccountDetails extends Component { static propTypes = { hideSidebar: PropTypes.func, showAccountDetailModal: PropTypes.func, - showConnectedSites: PropTypes.func.isRequired, label: PropTypes.string.isRequired, checksummedAddress: PropTypes.string.isRequired, name: PropTypes.string.isRequired, - history: PropTypes.object.isRequired, } state = { @@ -50,7 +48,6 @@ export default class AccountDetails extends Component { const { hideSidebar, showAccountDetailModal, - showConnectedSites, label, checksummedAddress, name, @@ -68,19 +65,14 @@ export default class AccountDetails extends Component {
    {label}
    -
    - +
    + {name} -
    - - -
    +
    list.concat(keyring.accounts), []) @@ -76,8 +71,6 @@ export default class AccountMenu extends PureComponent { const keyring = keyrings.find(kr => { return kr.accounts.includes(simpleAddress) || kr.accounts.includes(identity.address) }) - const addressDomains = addressConnectedDomainMap[identity.address] || {} - const iconAndNameForOpenDomain = addressDomains[originOfCurrentTab] return (
    - { iconAndNameForOpenDomain - ? ( -
    - -
    - ) - : null - } { this.renderKeyringType(keyring) } { this.renderRemoveAccount(keyring, identity) } @@ -171,11 +156,9 @@ export default class AccountMenu extends PureComponent { case 'Simple Key Pair': label = t('imported') break - default: - return null } - return ( + return label && (
    { label }
    @@ -268,12 +251,12 @@ export default class AccountMenu extends PureComponent { }) history.push(NEW_ACCOUNT_ROUTE) }} - icon={( + icon={ - )} + } text={t('createAccount')} /> - )} + } text={t('importAccount')} /> - )} + } text={t('connectHardwareWallet')} /> @@ -343,12 +326,12 @@ export default class AccountMenu extends PureComponent { }, }) }} - icon={( + icon={ - )} + } text={t('settings')} /> diff --git a/ui/app/components/app/account-menu/account-menu.container.js b/ui/app/components/app/account-menu/account-menu.container.js index 00a0666ec3..ae2e28e769 100644 --- a/ui/app/components/app/account-menu/account-menu.container.js +++ b/ui/app/components/app/account-menu/account-menu.container.js @@ -11,12 +11,7 @@ import { showInfoPage, showModal, } from '../../../store/actions' -import { - getAddressConnectedDomainMap, - getMetaMaskAccounts, - getOriginOfCurrentTab, -} from '../../../selectors/selectors' - +import { getMetaMaskAccounts } from '../../../selectors/selectors' import AccountMenu from './account-menu.component' function mapStateToProps (state) { @@ -28,8 +23,6 @@ function mapStateToProps (state) { keyrings, identities, accounts: getMetaMaskAccounts(state), - addressConnectedDomainMap: getAddressConnectedDomainMap(state), - originOfCurrentTab: getOriginOfCurrentTab(state), } } diff --git a/ui/app/components/app/account-menu/index.scss b/ui/app/components/app/account-menu/index.scss index 93b9766d35..614e191041 100644 --- a/ui/app/components/app/account-menu/index.scss +++ b/ui/app/components/app/account-menu/index.scss @@ -175,8 +175,4 @@ opacity: 1; } } - - &__icon-list { - display: flex; - } } diff --git a/ui/app/components/app/account-panel.js b/ui/app/components/app/account-panel.js index 3ffd978877..e61cb8ad67 100644 --- a/ui/app/components/app/account-panel.js +++ b/ui/app/components/app/account-panel.js @@ -1,5 +1,6 @@ -import React, { Component } from 'react' const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') import Identicon from '../ui/identicon' const formatBalance = require('../../helpers/utils/util').formatBalance const addressSummary = require('../../helpers/utils/util').addressSummary @@ -13,12 +14,12 @@ function AccountPanel () { } AccountPanel.prototype.render = function () { - const state = this.props - const identity = state.identity || {} - const account = state.account || {} - const isFauceting = state.isFauceting + var state = this.props + var identity = state.identity || {} + var account = state.account || {} + var isFauceting = state.isFauceting - const panelState = { + var panelState = { key: `accountPanel${identity.address}`, identiconKey: identity.address, identiconLabel: identity.name || '', @@ -32,24 +33,39 @@ AccountPanel.prototype.render = function () { } return ( -
    -
    - - {panelState.identiconLabel.substring(0, 7) + '...'} -
    -
    - {panelState.attributes.map((attr) => ( -
    - - {attr.value} -
    - ))} -
    -
    + + h('.identity-panel.flex-row.flex-space-between', { + style: { + flex: '1 0 auto', + cursor: panelState.onClick ? 'pointer' : undefined, + }, + onClick: panelState.onClick, + }, [ + + // account identicon + h('.identicon-wrapper.flex-column.select-none', [ + h(Identicon, { + address: panelState.identiconKey, + imageify: state.imageifyIdenticons, + }), + h('span.font-small', panelState.identiconLabel.substring(0, 7) + '...'), + ]), + + // account address, balance + h('.identity-data.flex-column.flex-justify-center.flex-grow.select-none', [ + + panelState.attributes.map((attr) => { + return h('.flex-row.flex-space-between', { + key: '' + Math.round(Math.random() * 1000000), + }, [ + h('label.font-small.no-select', attr.key), + h('span.font-small', attr.value), + ]) + }), + ]), + + ]) + ) } diff --git a/ui/app/components/app/app-header/app-header.component.js b/ui/app/components/app/app-header/app-header.component.js index bd01558c5e..e1bc0cf24e 100644 --- a/ui/app/components/app/app-header/app-header.component.js +++ b/ui/app/components/app/app-header/app-header.component.js @@ -89,8 +89,7 @@ export default class AppHeader extends PureComponent { return (
    + className={classnames('app-header', { 'app-header--back-drop': isUnlocked })}>
    { + this.updateValidity(event) + }, + onChange: (event) => { + this.updateValidity(event) + const value = (event.target.value === '') ? '' : event.target.value + + + const scaledNumber = this.upsize(value, scale, precision) + const precisionBN = new BN(scaledNumber, 10) + onChange(precisionBN, event.target.checkValidity()) + }, + onInvalid: (event) => { + const msg = this.constructWarning() + if (msg === state.invalid) { + return + } + this.setState({ invalid: msg }) + event.preventDefault() + return false + }, + }), + h('div', { + style: { + color: ' #AEAEAE', + fontSize: '12px', + marginLeft: '5px', + marginRight: '6px', + width: '20px', + }, + }, suffix), + ]), + + state.invalid ? h('span.error', { + style: { + position: 'absolute', + right: '0px', + textAlign: 'right', + transform: 'translateY(26px)', + padding: '3px', + background: 'rgba(255,255,255,0.85)', + zIndex: '1', + textTransform: 'capitalize', + border: '2px solid #E20202', + }, + }, state.invalid) : null, + ]) + ) +} + +BnAsDecimalInput.prototype.setValid = function () { + this.setState({ invalid: null }) +} + +BnAsDecimalInput.prototype.updateValidity = function (event) { + const target = event.target + const value = this.props.value + const newValue = target.value + + if (value === newValue) { + return + } + + const valid = target.checkValidity() + + if (valid) { + this.setState({ invalid: null }) + } +} + +BnAsDecimalInput.prototype.constructWarning = function () { + const { name, min, max, scale, suffix } = this.props + const newMin = min && this.downsize(min.toString(10), scale) + const newMax = max && this.downsize(max.toString(10), scale) + let message = name ? name + ' ' : '' + + if (min && max) { + message += this.context.t('betweenMinAndMax', [`${newMin} ${suffix}`, `${newMax} ${suffix}`]) + } else if (min) { + message += this.context.t('greaterThanMin', [`${newMin} ${suffix}`]) + } else if (max) { + message += this.context.t('lessThanMax', [`${newMax} ${suffix}`]) + } else { + message += this.context.t('invalidInput') + } + + return message +} + + +BnAsDecimalInput.prototype.downsize = function (number, scale) { + // if there is no scaling, simply return the number + if (scale === 0) { + return Number(number) + } else { + // if the scale is the same as the precision, account for this edge case. + var adjustedNumber = number + while (adjustedNumber.length < scale) { + adjustedNumber = '0' + adjustedNumber + } + return Number(adjustedNumber.slice(0, -scale) + '.' + adjustedNumber.slice(-scale)) + } +} + +BnAsDecimalInput.prototype.upsize = function (number, scale, precision) { + var stringArray = number.toString().split('.') + var decimalLength = stringArray[1] ? stringArray[1].length : 0 + var newString = stringArray[0] + + // If there is scaling and decimal parts exist, integrate them in. + if ((scale !== 0) && (decimalLength !== 0)) { + newString += stringArray[1].slice(0, precision) + } + + // Add 0s to account for the upscaling. + for (var i = decimalLength; i < scale; i++) { + newString += '0' + } + return newString +} diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js index b973ad84db..0cc4d8262b 100644 --- a/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js +++ b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js @@ -47,11 +47,9 @@ const ConfirmPageContainerSummary = props => {
    { - hideSubtitle || ( -
    - { subtitleComponent || subtitle } -
    - ) + hideSubtitle ||
    + { subtitleComponent || subtitle } +
    } ) diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js index 4b91b4ddb2..898d590689 100644 --- a/ui/app/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js +++ b/ui/app/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js @@ -34,40 +34,36 @@ export default class ConfirmPageContainerHeader extends Component { return (
    { !showAccountInHeader - ? ( -
    + + onEdit()} > - - onEdit()} - > - { this.context.t('edit') } - -
    - ) + { this.context.t('edit') } + +
    : null } { showAccountInHeader - ? ( -
    -
    - -
    -
    - { addressSlicer(accountAddress) } -
    + ?
    +
    + +
    +
    + { addressSlicer(accountAddress) }
    - ) +
    : null } { !isFullScreen && } diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js index dd1fd2da48..c24d24b170 100755 --- a/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js +++ b/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js @@ -5,28 +5,23 @@ const ConfirmPageContainerNavigation = props => { const { onNextTx, totalTx, positionOfCurrentTx, nextTxId, prevTxId, showNavigation, firstTx, lastTx, ofText, requestsWaitingText } = props return ( -
    -
    + }}>
    onNextTx(firstTx)} - > + onClick={() => onNextTx(firstTx)}>
    onNextTx(prevTxId)} - > + onClick={() => onNextTx(prevTxId)}>
    @@ -42,18 +37,15 @@ const ConfirmPageContainerNavigation = props => { className="confirm-page-container-navigation__container" style={{ visibility: nextTxId ? 'initial' : 'hidden', - }} - > + }}>
    onNextTx(nextTxId)} - > + onClick={() => onNextTx(nextTxId)}>
    onNextTx(lastTx)} - > + onClick={() => onNextTx(lastTx)}>
    diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container.component.js index 9ebae3d95d..86ce2bff71 100644 --- a/ui/app/components/app/confirm-page-container/confirm-page-container.component.js +++ b/ui/app/components/app/confirm-page-container/confirm-page-container.component.js @@ -133,17 +133,15 @@ export default class ConfirmPageContainer extends Component { > { hideSenderToRecipient ? null - : ( - - ) + : } { diff --git a/ui/app/components/app/connected-sites-list/connected-sites-list.component.js b/ui/app/components/app/connected-sites-list/connected-sites-list.component.js deleted file mode 100644 index a04f87486c..0000000000 --- a/ui/app/components/app/connected-sites-list/connected-sites-list.component.js +++ /dev/null @@ -1,157 +0,0 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' -import classnames from 'classnames' -import Button from '../../ui/button' -import IconWithFallBack from '../../ui/icon-with-fallback' - -export default class ConnectedSitesList extends Component { - static contextTypes = { - t: PropTypes.func, - } - - static propTypes = { - renderableDomains: PropTypes.arrayOf(PropTypes.shape({ - name: PropTypes.string, - icon: PropTypes.string, - key: PropTypes.string, - lastConnectedTime: PropTypes.string, - permissionDescriptions: PropTypes.array, - })).isRequired, - domains: PropTypes.object, - showDisconnectAccountModal: PropTypes.func.isRequired, - showDisconnectAllModal: PropTypes.func.isRequired, - tabToConnect: PropTypes.object, - legacyExposeAccounts: PropTypes.func.isRequired, - selectedAddress: PropTypes.string.isRequired, - getOpenMetamaskTabsIds: PropTypes.func.isRequired, - } - - state = { - expandedDomain: '', - } - - componentWillMount () { - const { getOpenMetamaskTabsIds } = this.props - getOpenMetamaskTabsIds() - } - - handleDomainItemClick (domainKey) { - if (this.state.expandedDomain === domainKey) { - this.setState({ expandedDomain: '' }) - } else { - this.setState({ expandedDomain: domainKey }) - } - } - - render () { - const { - renderableDomains, - domains, - showDisconnectAccountModal, - showDisconnectAllModal, - tabToConnect, - legacyExposeAccounts, - selectedAddress, - } = this.props - const { expandedDomain } = this.state - const { t } = this.context - - return ( -
    - { - renderableDomains.map((domain, domainIndex) => { - const domainIsExpanded = expandedDomain === domain.key - return ( -
    -
    this.handleDomainItemClick(domain.key) }> -
    - - -
    -
    -
    - { domain.extensionId ? t('externalExtension') : domain.name } -
    -
    - { domain.lastConnectedTime - ? ( -
    - { t('domainLastConnect', [domain.lastConnectedTime]) } -
    - ) - : null - } - {domainIsExpanded - ? ( -
    - { domain.extensionId ? t('extensionId', [domain.extensionId]) : domain.secondaryName } -
    - ) - : null - } -
    -
    -
    - { domainIsExpanded ? : } -
    -
    - { domainIsExpanded - ? ( -
    -
    - { - domain.permissionDescriptions.map((description, pdIndex) => { - return ( -
    - -
    - { description } -
    -
    - ) - }) - } -
    -
    showDisconnectAccountModal(domain.key, domains[domain.key]) } - > - { t('disconnectAccount') } -
    -
    - ) - : null - } -
    - ) - }) - } -
    -
    - -
    - { tabToConnect - ? ( -
    - -
    - ) - : null - } -
    -
    - ) - } -} diff --git a/ui/app/components/app/connected-sites-list/connected-sites-list.container.js b/ui/app/components/app/connected-sites-list/connected-sites-list.container.js deleted file mode 100644 index de45831a9d..0000000000 --- a/ui/app/components/app/connected-sites-list/connected-sites-list.container.js +++ /dev/null @@ -1,57 +0,0 @@ -import { connect } from 'react-redux' -import { compose } from 'recompose' - -import ConnectedSitesList from './connected-sites-list.component' -import { - showModal, - legacyExposeAccounts, - getOpenMetamaskTabsIds, -} from '../../../store/actions' -import { - getRenderablePermissionsDomains, - getPermissionsDomains, - getAddressConnectedToCurrentTab, - getSelectedAddress, -} from '../../../selectors/selectors' -import { getOriginFromUrl } from '../../../helpers/utils/util' - -const mapStateToProps = state => { - const addressConnectedToCurrentTab = getAddressConnectedToCurrentTab(state) - const { openMetaMaskTabs } = state.appState - const { title, url, id } = state.activeTab - - let tabToConnect - - if (!addressConnectedToCurrentTab && url && !openMetaMaskTabs[id]) { - tabToConnect = { - title, - origin: getOriginFromUrl(url), - } - } - - return { - domains: getPermissionsDomains(state), - renderableDomains: getRenderablePermissionsDomains(state), - tabToConnect, - selectedAddress: getSelectedAddress(state), - } -} - -const mapDispatchToProps = dispatch => { - return { - showDisconnectAccountModal: (domainKey, domain) => { - dispatch(showModal({ name: 'DISCONNECT_ACCOUNT', domainKey, domain })) - }, - showDisconnectAllModal: () => { - dispatch(showModal({ name: 'DISCONNECT_ALL' })) - }, - legacyExposeAccounts: (origin, account) => { - dispatch(legacyExposeAccounts(origin, [account])) - }, - getOpenMetamaskTabsIds: () => dispatch(getOpenMetamaskTabsIds()), - } -} - -export default compose( - connect(mapStateToProps, mapDispatchToProps) -)(ConnectedSitesList) diff --git a/ui/app/components/app/connected-sites-list/index.js b/ui/app/components/app/connected-sites-list/index.js deleted file mode 100644 index 431a3a8eb6..0000000000 --- a/ui/app/components/app/connected-sites-list/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './connected-sites-list.container' diff --git a/ui/app/components/app/connected-sites-list/index.scss b/ui/app/components/app/connected-sites-list/index.scss deleted file mode 100644 index c2d98d2aa0..0000000000 --- a/ui/app/components/app/connected-sites-list/index.scss +++ /dev/null @@ -1,125 +0,0 @@ -.connected-sites-list { - font-family: Roboto; - font-style: normal; - font-weight: normal; - - &__domain, &__domain--expanded { - border-bottom: 1px solid #c4c4c4; - } - - &__domain { - cursor: pointer; - } - - &__domain--expanded { - background: #FAFBFC; - cursor: initial; - } - - &__domain-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 16px; - } - - &__domain-item-info-container { - display: flex; - } - - &__identicon-container { - position: relative; - display: flex; - justify-content: center; - align-items: center; - height: 32px; - width: 32px; - } - - &__identicon-border { - height: 32px; - width: 32px; - border-radius: 50%; - border: 1px solid #F2F3F4; - position: absolute; - background: #FFFFFF; - } - - &__identicon { - width: 24px; - height: 24px; - z-index: 1; - - &--default { - z-index: 1; - color: black; - } - } - - &__domain-info { - display: flex; - flex-direction: column; - margin-left: 16px; - } - - &__domain-names { - display: flex; - align-items: center; - } - - &__domain-name { - font-size: 18px; - color: #24292E; - } - - &__domain-origin, &__domain-last-connected { - font-size: 12px; - color: #6A737D; - } - - &__domain-last-connected { - margin-top: 2px; - } - - &__expand-arrow { - align-self: flex-start; - margin-top: 6px; - } - - &__permissions { - padding-left: 16px; - padding-bottom: 24px; - } - - &__permission { - display: flex; - align-items: center; - color: #6A737D; - margin-left: 16px; - } - - &__permission-description { - margin-left: 18px; - } - - &__disconnect { - margin-top: 24px; - color: #D73A49; - cursor: pointer; - } - - &__bottom-buttons { - display: flex; - align-items: center; - } - - &__disconnect-all { - padding: 10px; - width: 50%; - } - - &__connect-to { - padding: 10px; - width: 50%; - } -} \ No newline at end of file diff --git a/ui/app/components/app/contact-list/recipient-group/recipient-group.component.js b/ui/app/components/app/contact-list/recipient-group/recipient-group.component.js index 20e2b34946..a2248326ec 100644 --- a/ui/app/components/app/contact-list/recipient-group/recipient-group.component.js +++ b/ui/app/components/app/contact-list/recipient-group/recipient-group.component.js @@ -15,11 +15,9 @@ export default function RecipientGroup ({ label, items, onSelect, selectedAddres return (
    - {label && ( -
    - {label} -
    - )} + {label &&
    + {label} +
    } { items.map(({ address, name }) => (
    { + event.preventDefault() + event.stopPropagation() + copyToClipboard(value) + this.debounceRestore() + }, + }, children)) +} + +Copyable.prototype.debounceRestore = function () { + this.setState({ copied: true }) + clearTimeout(this.timeout) + this.timeout = setTimeout(() => { + this.setState({ copied: false }) + }, 850) +} diff --git a/ui/app/components/app/customize-gas-modal/gas-modal-card.js b/ui/app/components/app/customize-gas-modal/gas-modal-card.js index 5ba96dd824..23754d819b 100644 --- a/ui/app/components/app/customize-gas-modal/gas-modal-card.js +++ b/ui/app/components/app/customize-gas-modal/gas-modal-card.js @@ -1,6 +1,8 @@ -import React, {Component} from 'react' +const Component = require('react').Component +const h = require('react-hyperscript') const inherits = require('util').inherits const InputNumber = require('../input-number.js') +// const GasSlider = require('./gas-slider.js') module.exports = GasModalCard @@ -9,30 +11,44 @@ function GasModalCard () { Component.call(this) } -GasModalCard.prototype.render = function GasModalCard () { +GasModalCard.prototype.render = function () { const { + // memo, onChange, unitLabel, value, min, + // max, step, title, copy, } = this.props - return ( -
    -
    {title}
    -
    {copy}
    - -
    - ) + return h('div.send-v2__gas-modal-card', [ + + h('div.send-v2__gas-modal-card__title', {}, title), + + h('div.send-v2__gas-modal-card__copy', {}, copy), + + h(InputNumber, { + unitLabel, + step, + // max, + min, + placeholder: '0', + value, + onChange, + }), + + // h(GasSlider, { + // value, + // step, + // max, + // min, + // onChange, + // }), + + ]) + } diff --git a/ui/app/components/app/customize-gas-modal/gas-slider.js b/ui/app/components/app/customize-gas-modal/gas-slider.js new file mode 100644 index 0000000000..69fd6f9856 --- /dev/null +++ b/ui/app/components/app/customize-gas-modal/gas-slider.js @@ -0,0 +1,50 @@ +// const Component = require('react').Component +// const h = require('react-hyperscript') +// const inherits = require('util').inherits + +// module.exports = GasSlider + +// inherits(GasSlider, Component) +// function GasSlider () { +// Component.call(this) +// } + +// GasSlider.prototype.render = function () { +// const { +// memo, +// identities, +// onChange, +// unitLabel, +// value, +// id, +// step, +// max, +// min, +// } = this.props + +// return h('div.gas-slider', [ + +// h('input.gas-slider__input', { +// type: 'range', +// step, +// max, +// min, +// value, +// id: 'gasSlider', +// onChange: event => onChange(event.target.value), +// }, []), + +// h('div.gas-slider__bar', [ + +// h('div.gas-slider__low'), + +// h('div.gas-slider__mid'), + +// h('div.gas-slider__high'), + +// ]), + +// ]) + +// } + diff --git a/ui/app/components/app/customize-gas-modal/index.js b/ui/app/components/app/customize-gas-modal/index.js index 7bbe725b22..1f94368107 100644 --- a/ui/app/components/app/customize-gas-modal/index.js +++ b/ui/app/components/app/customize-gas-modal/index.js @@ -1,5 +1,6 @@ -import React, { Component } from 'react' +const Component = require('react').Component const PropTypes = require('prop-types') +const h = require('react-hyperscript') const inherits = require('util').inherits const connect = require('react-redux').connect const BigNumber = require('bignumber.js') @@ -327,72 +328,69 @@ CustomizeGasModal.prototype.render = function () { toNumericBase: 'dec', }) - if (gasIsLoading) { - return null - } - - const { t } = this.context - - return ( -
    -
    -
    -
    - {this.context.t('customGas')} -
    -
    -
    -
    - this.convertAndSetGasPrice(value)} - title={t('gasPrice')} - copy={t('gasPriceCalculation')} - gasIsLoading={gasIsLoading} - /> - this.convertAndSetGasLimit(value)} - title={t('gasLimit')} - copy={t('gasLimitCalculation')} - gasIsLoading={gasIsLoading} - /> -
    -
    - {error && ( -
    - {error} -
    - )} -
    this.revert()}> - {t('revert')} -
    -
    - - -
    -
    -
    -
    - ) + return !gasIsLoading && h('div.send-v2__customize-gas', {}, [ + h('div.send-v2__customize-gas__content', { + }, [ + h('div.send-v2__customize-gas__header', {}, [ + + h('div.send-v2__customize-gas__title', this.context.t('customGas')), + + h('div.send-v2__customize-gas__close', { + onClick: hideModal, + }), + + ]), + + h('div.send-v2__customize-gas__body', {}, [ + + h(GasModalCard, { + value: convertedGasPrice, + min: forceGasMin || MIN_GAS_PRICE_GWEI, + step: 1, + onChange: value => this.convertAndSetGasPrice(value), + title: this.context.t('gasPrice'), + copy: this.context.t('gasPriceCalculation'), + gasIsLoading, + }), + + h(GasModalCard, { + value: convertedGasLimit, + min: 1, + step: 1, + onChange: value => this.convertAndSetGasLimit(value), + title: this.context.t('gasLimit'), + copy: this.context.t('gasLimitCalculation'), + gasIsLoading, + }), + + ]), + + h('div.send-v2__customize-gas__footer', {}, [ + + error && h('div.send-v2__customize-gas__error-message', [ + error, + ]), + + h('div.send-v2__customize-gas__revert', { + onClick: () => this.revert(), + }, [this.context.t('revert')]), + + h('div.send-v2__customize-gas__buttons', [ + h(Button, { + type: 'default', + className: 'send-v2__customize-gas__cancel', + onClick: this.props.hideModal, + }, [this.context.t('cancel')]), + h(Button, { + type: 'secondary', + className: 'send-v2__customize-gas__save', + onClick: () => !error && this.save(newGasPrice, gasLimit, gasTotal), + disabled: error, + }, [this.context.t('save')]), + ]), + + ]), + + ]), + ]) } diff --git a/ui/app/components/app/dropdowns/account-details-dropdown.js b/ui/app/components/app/dropdowns/account-details-dropdown.js index dc09b8c643..cf2aa8ae82 100644 --- a/ui/app/components/app/dropdowns/account-details-dropdown.js +++ b/ui/app/components/app/dropdowns/account-details-dropdown.js @@ -1,12 +1,10 @@ -import React, { Component } from 'react' +const Component = require('react').Component const PropTypes = require('prop-types') -const { compose } = require('recompose') -const { withRouter } = require('react-router-dom') +const h = require('react-hyperscript') const inherits = require('util').inherits const connect = require('react-redux').connect const actions = require('../../../store/actions') const { getSelectedIdentity, getRpcPrefsForCurrentProvider } = require('../../../selectors/selectors') -const { CONNECTED_ROUTE } = require('../../../helpers/constants/routes') const genAccountLink = require('../../../../lib/account-link.js') const { Menu, Item, CloseArea } = require('./components/menu') @@ -15,7 +13,7 @@ AccountDetailsDropdown.contextTypes = { metricsEvent: PropTypes.func, } -module.exports = compose(withRouter, connect(mapStateToProps, mapDispatchToProps))(AccountDetailsDropdown) +module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountDetailsDropdown) function mapStateToProps (state) { return { @@ -52,7 +50,7 @@ AccountDetailsDropdown.prototype.onClose = function (e) { this.props.onClose() } -AccountDetailsDropdown.prototype.render = function AccountDetailsDropdown () { +AccountDetailsDropdown.prototype.render = function () { const { selectedIdentity, network, @@ -61,7 +59,6 @@ AccountDetailsDropdown.prototype.render = function AccountDetailsDropdown () { viewOnEtherscan, showRemoveAccountConfirmationModal, rpcPrefs, - history, } = this.props const address = selectedIdentity.address @@ -72,104 +69,71 @@ AccountDetailsDropdown.prototype.render = function AccountDetailsDropdown () { const isRemovable = keyring.type !== 'HD Key Tree' - return ( - - - { - e.stopPropagation() - this.context.metricsEvent({ - eventOpts: { - category: 'Navigation', - action: 'Account Options', - name: 'Clicked Expand View', - }, - }) - global.platform.openExtensionInBrowser() - this.props.onClose() - }} - text={this.context.t('expandView')} - icon={( - - )} - /> - { - e.stopPropagation() - showAccountDetailModal() - this.context.metricsEvent({ - eventOpts: { - category: 'Navigation', - action: 'Account Options', - name: 'Viewed Account Details', - }, - }) - this.props.onClose() - }} - text={this.context.t('accountDetails')} - icon={( - - )} - /> - { - e.stopPropagation() - this.context.metricsEvent({ - eventOpts: { - category: 'Navigation', - action: 'Account Options', - name: 'Clicked View on Etherscan', - }, - }) - viewOnEtherscan(address, network, rpcPrefs) - this.props.onClose() - }} - text={ - rpcPrefs.blockExplorerUrl - ? this.context.t('viewinExplorer') - : this.context.t('viewOnEtherscan') - } - subText={ - rpcPrefs.blockExplorerUrl - ? rpcPrefs.blockExplorerUrl.match(/^https?:\/\/(.+)/)[1] - : null - } - icon={( - - )} - /> - { - e.stopPropagation() - this.context.metricsEvent({ - eventOpts: { - category: 'Navigation', - action: 'Account Options', - name: 'Opened Connected Sites', - }, - }) - history.push(CONNECTED_ROUTE) - }} - text={this.context.t('connectedSites')} - icon={( - - )} - /> - { - isRemovable - ? ( - { - e.stopPropagation() - showRemoveAccountConfirmationModal(selectedIdentity) - this.props.onClose() - }} - text={this.context.t('removeAccount')} - icon={} - /> - ) - : null - } - - ) + return h(Menu, { className: 'account-details-dropdown', isShowing: true }, [ + h(CloseArea, { + onClick: this.onClose, + }), + h(Item, { + onClick: (e) => { + e.stopPropagation() + this.context.metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Account Options', + name: 'Clicked Expand View', + }, + }) + global.platform.openExtensionInBrowser() + this.props.onClose() + }, + text: this.context.t('expandView'), + icon: h(`img`, { src: 'images/expand.svg', style: { height: '15px' } }), + }), + h(Item, { + onClick: (e) => { + e.stopPropagation() + showAccountDetailModal() + this.context.metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Account Options', + name: 'Viewed Account Details', + }, + }) + this.props.onClose() + }, + text: this.context.t('accountDetails'), + icon: h(`img`, { src: 'images/info.svg', style: { height: '15px' } }), + }), + h(Item, { + onClick: (e) => { + e.stopPropagation() + this.context.metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Account Options', + name: 'Clicked View on Etherscan', + }, + }) + viewOnEtherscan(address, network, rpcPrefs) + this.props.onClose() + }, + text: (rpcPrefs.blockExplorerUrl + ? this.context.t('viewinExplorer') + : this.context.t('viewOnEtherscan')), + subText: (rpcPrefs.blockExplorerUrl + ? rpcPrefs.blockExplorerUrl.match(/^https?:\/\/(.+)/)[1] + : null), + icon: h(`img`, { src: 'images/open-etherscan.svg', style: { height: '15px' } }), + }), + isRemovable ? h(Item, { + onClick: (e) => { + e.stopPropagation() + showRemoveAccountConfirmationModal(selectedIdentity) + this.props.onClose() + }, + text: this.context.t('removeAccount'), + icon: h(`img`, { src: 'images/hide.svg', style: { height: '15px' } }), + }) : null, + ]) } diff --git a/ui/app/components/app/dropdowns/components/dropdown.js b/ui/app/components/app/dropdowns/components/dropdown.js index ad6c1c5140..cc966ffa5b 100644 --- a/ui/app/components/app/dropdowns/components/dropdown.js +++ b/ui/app/components/app/dropdowns/components/dropdown.js @@ -1,5 +1,6 @@ -import React, { Component } from 'react' +const Component = require('react').Component const PropTypes = require('prop-types') +const h = require('react-hyperscript') const MenuDroppo = require('../../menu-droppo') const extend = require('xtend') @@ -22,28 +23,31 @@ class Dropdown extends Component { boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', }, innerStyle) - return ( - - - { children } - + return h( + MenuDroppo, + { + containerClassName, + useCssTransition, + isOpen, + zIndex: 55, + onClickOutside, + style, + innerStyle: innerStyleDefaults, + }, + [ + h( + 'style', + ` + li.dropdown-menu-item:hover { + color:rgb(225, 225, 225); + background-color: rgba(255, 255, 255, 0.05); + border-radius: 4px; + } + li.dropdown-menu-item { color: rgb(185, 185, 185); } + ` + ), + ...children, + ] ) } } @@ -54,6 +58,7 @@ Dropdown.defaultProps = { Dropdown.propTypes = { isOpen: PropTypes.bool.isRequired, + onClick: PropTypes.func.isRequired, children: PropTypes.node, style: PropTypes.object.isRequired, onClickOutside: PropTypes.func, @@ -66,14 +71,14 @@ class DropdownMenuItem extends Component { render () { const { onClick, closeMenu, children, style } = this.props - return ( -
  • { + return h( + 'li.dropdown-menu-item', + { + onClick: () => { onClick() closeMenu() - }} - style={Object.assign({ + }, + style: Object.assign({ listStyle: 'none', padding: '8px 0px', fontSize: '18px', @@ -83,10 +88,9 @@ class DropdownMenuItem extends Component { justifyContent: 'flex-start', alignItems: 'center', color: 'white', - }, style)} - > - {children} -
  • + }, style), + }, + children ) } } diff --git a/ui/app/components/app/dropdowns/components/menu.js b/ui/app/components/app/dropdowns/components/menu.js index 669a50c63b..63501eaa96 100644 --- a/ui/app/components/app/dropdowns/components/menu.js +++ b/ui/app/components/app/dropdowns/components/menu.js @@ -1,85 +1,53 @@ -import PropTypes from 'prop-types' -import React from 'react' -import classnames from 'classnames' +const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') -/** - * Menu component - * @return {Component|null} - */ -function Menu (props) { - const { className, children, isShowing } = props - return isShowing - ?
    {children}
    - : null -} +inherits(Menu, Component) +function Menu () { Component.call(this) } -Menu.defaultProps = { - className: '', - isShowing: false, - children: null, +Menu.prototype.render = function () { + const { className = '', children, isShowing } = this.props + return isShowing + ? h('div', { className: `menu ${className}` }, children) + : h('noscript') } -Menu.propTypes = { - className: PropTypes.string, - children: PropTypes.node, - isShowing: PropTypes.bool, -} +inherits(Item, Component) +function Item () { Component.call(this) } -function Item (props) { +Item.prototype.render = function () { const { icon, children, text, subText, - className, + className = '', onClick, - } = props + } = this.props + const itemClassName = `menu__item ${className} ${onClick ? 'menu__item--clickable' : ''}` + const iconComponent = icon ? h('div.menu__item__icon', [icon]) : null + const textComponent = text ? h('div.menu__item__text', text) : null + const subTextComponent = subText ? h('div.menu__item__subtext', subText) : null - const itemClassName = classnames('menu__item', className, { - 'menu__item--clickable': Boolean(onClick), - }) return children - ?
    {children}
    - : ( -
    - {icon ?
    {icon}
    : null} - {text ?
    {text}
    : null} - {subText ?
    {subText}
    : null} -
    + ? h('div', { className: itemClassName, onClick }, children) + : h('div.menu__item', { className: itemClassName, onClick }, [ iconComponent, textComponent, subTextComponent ] + .filter(d => Boolean(d)) ) } -Item.defaultProps = { - children: null, - icon: null, - text: null, - subText: null, - className: '', - onClick: null, -} - -Item.propTypes = { - icon: PropTypes.node, - children: PropTypes.node, - text: PropTypes.node, - subText: PropTypes.node, - className: PropTypes.string, - onClick: PropTypes.func, -} +inherits(Divider, Component) +function Divider () { Component.call(this) } -function Divider () { - return
    +Divider.prototype.render = function () { + return h('div.menu__divider') } -function CloseArea ({ onClick }) { - return
    -} +inherits(CloseArea, Component) +function CloseArea () { Component.call(this) } -CloseArea.propTypes = { - onClick: PropTypes.func.isRequired, +CloseArea.prototype.render = function () { + return h('div.menu__close-area', { onClick: this.props.onClick }) } module.exports = { Menu, Item, Divider, CloseArea } diff --git a/ui/app/components/app/dropdowns/components/network-dropdown-icon.js b/ui/app/components/app/dropdowns/components/network-dropdown-icon.js index ca318c9e56..d4a2c2ff73 100644 --- a/ui/app/components/app/dropdowns/components/network-dropdown-icon.js +++ b/ui/app/components/app/dropdowns/components/network-dropdown-icon.js @@ -1,56 +1,47 @@ -import PropTypes from 'prop-types' -import React from 'react' +const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') -function NetworkDropdownIcon (props) { + +inherits(NetworkDropdownIcon, Component) +module.exports = NetworkDropdownIcon + +function NetworkDropdownIcon () { + Component.call(this) +} + +NetworkDropdownIcon.prototype.render = function () { const { backgroundColor, isSelected, - innerBorder, - diameter, + innerBorder = 'none', + diameter = '12', loading, - } = props + } = this.props return loading - ? ( - - - - ) - : ( -
    -
    -
    + ? h('span.pointer.network-indicator', { + style: { + display: 'flex', + alignItems: 'center', + flexDirection: 'row', + }, + }, [ + h('img', { + style: { + width: '27px', + }, + src: 'images/loading.svg', + }), + ]) + : h(`.menu-icon-circle${isSelected ? '--active' : ''}`, {}, + h('div', { + style: { + background: backgroundColor, + border: innerBorder, + height: `${diameter}px`, + width: `${diameter}px`, + }, + }) ) } - -NetworkDropdownIcon.defaultProps = { - backgroundColor: undefined, - loading: false, - innerBorder: 'none', - diameter: '12', - isSelected: false, -} - -NetworkDropdownIcon.propTypes = { - backgroundColor: PropTypes.string, - loading: PropTypes.bool, - innerBorder: PropTypes.string, - diameter: PropTypes.string, - isSelected: PropTypes.bool, -} - -module.exports = NetworkDropdownIcon diff --git a/ui/app/components/app/dropdowns/network-dropdown.js b/ui/app/components/app/dropdowns/network-dropdown.js index cf56919c4e..e6a24ef116 100644 --- a/ui/app/components/app/dropdowns/network-dropdown.js +++ b/ui/app/components/app/dropdowns/network-dropdown.js @@ -1,5 +1,6 @@ -import PropTypes from 'prop-types' -import React, {Component} from 'react' +const Component = require('react').Component +const PropTypes = require('prop-types') +const h = require('react-hyperscript') const inherits = require('util').inherits const connect = require('react-redux').connect const { withRouter } = require('react-router-dom') @@ -66,9 +67,10 @@ module.exports = compose( // TODO: specify default props and proptypes -NetworkDropdown.prototype.render = function NetworkDropdown () { - const { provider: { type: providerType, rpcTarget: activeNetwork }, setNetworksTabAddMode } = this.props - const rpcListDetail = this.props.frequentRpcListDetail +NetworkDropdown.prototype.render = function () { + const props = this.props + const { provider: { type: providerType, rpcTarget: activeNetwork }, setNetworksTabAddMode } = props + const rpcListDetail = props.frequentRpcListDetail const isOpen = this.props.networkDropdownOpen const dropdownMenuItemStyle = { fontSize: '16px', @@ -76,206 +78,201 @@ NetworkDropdown.prototype.render = function NetworkDropdown () { padding: '12px 0', } - return ( - { - const { classList } = event.target - const isInClassList = className => classList.contains(className) - const notToggleElementIndex = R.findIndex(isInClassList)(notToggleElementClassnames) - - if (notToggleElementIndex === -1) { - this.props.hideNetworkDropdown() - } - }} - containerClassName="network-droppo" - zIndex={55} - style={{ - position: 'absolute', - top: '58px', - width: '309px', - zIndex: '55px', - }} - innerStyle={{ - padding: '18px 8px', - }} - > -
    -
    - {this.context.t('networks')} -
    -
    -
    - {this.context.t('defaultNetwork')} -
    -
    - this.props.hideNetworkDropdown()} - onClick={() => this.handleClick('mainnet')} - style={{ ...dropdownMenuItemStyle, borderColor: '#038789' }} - > - { - providerType === 'mainnet' - ? - :
    - } - - - {this.context.t('mainnet')} - -
    - this.props.hideNetworkDropdown()} - onClick={() => this.handleClick('ropsten')} - style={dropdownMenuItemStyle} - > - { - providerType === 'ropsten' - ? - :
    - } - - - {this.context.t('ropsten')} - -
    - this.props.hideNetworkDropdown()} - onClick={() => this.handleClick('kovan')} - style={dropdownMenuItemStyle} - > - { - providerType === 'kovan' - ? - :
    - } - - - {this.context.t('kovan')} - -
    - this.props.hideNetworkDropdown()} - onClick={() => this.handleClick('rinkeby')} - style={dropdownMenuItemStyle} - > - { - providerType === 'rinkeby' - ? - :
    - } - - - {this.context.t('rinkeby')} - -
    - this.props.hideNetworkDropdown()} - onClick={() => this.handleClick('goerli')} - style={dropdownMenuItemStyle} - > - { - providerType === 'goerli' - ? - :
    - } - - - {this.context.t('goerli')} - -
    - this.props.hideNetworkDropdown()} - onClick={() => this.handleClick('localhost')} - style={dropdownMenuItemStyle} - > - { - providerType === 'localhost' - ? - :
    - } - - - {this.context.t('localhost')} - -
    - {this.renderCustomOption(this.props.provider)} - {this.renderCommonRpc(rpcListDetail, this.props.provider)} - this.props.hideNetworkDropdown()} - onClick={() => { + return h(Dropdown, { + isOpen, + onClickOutside: (event) => { + const { classList } = event.target + const isInClassList = className => classList.contains(className) + const notToggleElementIndex = R.findIndex(isInClassList)(notToggleElementClassnames) + + if (notToggleElementIndex === -1) { + this.props.hideNetworkDropdown() + } + }, + containerClassName: 'network-droppo', + zIndex: 55, + style: { + position: 'absolute', + top: '58px', + width: '309px', + zIndex: '55px', + }, + innerStyle: { + padding: '18px 8px', + }, + }, [ + + h('div.network-dropdown-header', {}, [ + h('div.network-dropdown-title', {}, this.context.t('networks')), + + h('div.network-dropdown-divider'), + + h('div.network-dropdown-content', + {}, + this.context.t('defaultNetwork') + ), + ]), + + h( + DropdownMenuItem, + { + key: 'main', + closeMenu: () => this.props.hideNetworkDropdown(), + onClick: () => this.handleClick('mainnet'), + style: { ...dropdownMenuItemStyle, borderColor: '#038789' }, + }, + [ + providerType === 'mainnet' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'), + h(NetworkDropdownIcon, { + backgroundColor: '#29B6AF', // $java + isSelected: providerType === 'mainnet', + }), + h('span.network-name-item', { + style: { + color: providerType === 'mainnet' ? '#ffffff' : '#9b9b9b', + }, + }, this.context.t('mainnet')), + ] + ), + + h( + DropdownMenuItem, + { + key: 'ropsten', + closeMenu: () => this.props.hideNetworkDropdown(), + onClick: () => this.handleClick('ropsten'), + style: dropdownMenuItemStyle, + }, + [ + providerType === 'ropsten' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'), + h(NetworkDropdownIcon, { + backgroundColor: '#ff4a8d', // $wild-strawberry + isSelected: providerType === 'ropsten', + }), + h('span.network-name-item', { + style: { + color: providerType === 'ropsten' ? '#ffffff' : '#9b9b9b', + }, + }, this.context.t('ropsten')), + ] + ), + + h( + DropdownMenuItem, + { + key: 'kovan', + closeMenu: () => this.props.hideNetworkDropdown(), + onClick: () => this.handleClick('kovan'), + style: dropdownMenuItemStyle, + }, + [ + providerType === 'kovan' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'), + h(NetworkDropdownIcon, { + backgroundColor: '#7057ff', // $cornflower-blue + isSelected: providerType === 'kovan', + }), + h('span.network-name-item', { + style: { + color: providerType === 'kovan' ? '#ffffff' : '#9b9b9b', + }, + }, this.context.t('kovan')), + ] + ), + + h( + DropdownMenuItem, + { + key: 'rinkeby', + closeMenu: () => this.props.hideNetworkDropdown(), + onClick: () => this.handleClick('rinkeby'), + style: dropdownMenuItemStyle, + }, + [ + providerType === 'rinkeby' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'), + h(NetworkDropdownIcon, { + backgroundColor: '#f6c343', // $saffron + isSelected: providerType === 'rinkeby', + }), + h('span.network-name-item', { + style: { + color: providerType === 'rinkeby' ? '#ffffff' : '#9b9b9b', + }, + }, this.context.t('rinkeby')), + ] + ), + + h( + DropdownMenuItem, + { + key: 'goerli', + closeMenu: () => this.props.hideNetworkDropdown(), + onClick: () => this.handleClick('goerli'), + style: dropdownMenuItemStyle, + }, + [ + providerType === 'goerli' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'), + h(NetworkDropdownIcon, { + backgroundColor: '#3099f2', // $dodger-blue + isSelected: providerType === 'goerli', + }), + h('span.network-name-item', { + style: { + color: providerType === 'goerli' ? '#ffffff' : '#9b9b9b', + }, + }, this.context.t('goerli')), + ] + ), + + h( + DropdownMenuItem, + { + key: 'default', + closeMenu: () => this.props.hideNetworkDropdown(), + onClick: () => this.handleClick('localhost'), + style: dropdownMenuItemStyle, + }, + [ + providerType === 'localhost' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'), + h(NetworkDropdownIcon, { + isSelected: providerType === 'localhost', + innerBorder: '1px solid #9b9b9b', + }), + h('span.network-name-item', { + style: { + color: providerType === 'localhost' ? '#ffffff' : '#9b9b9b', + }, + }, this.context.t('localhost')), + ] + ), + + this.renderCustomOption(props.provider), + this.renderCommonRpc(rpcListDetail, props.provider), + + h( + DropdownMenuItem, + { + closeMenu: () => this.props.hideNetworkDropdown(), + onClick: () => { setNetworksTabAddMode(true) this.props.history.push(NETWORKS_ROUTE) - }} - style={dropdownMenuItemStyle} - > - { - activeNetwork === 'custom' - ? - :
    - } - - - {this.context.t('customRPC')} - -
    - - ) + }, + style: dropdownMenuItemStyle, + }, + [ + activeNetwork === 'custom' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'), + h(NetworkDropdownIcon, { + isSelected: activeNetwork === 'custom', + innerBorder: '1px solid #9b9b9b', + }), + h('span.network-name-item', { + style: { + color: activeNetwork === 'custom' ? '#ffffff' : '#9b9b9b', + }, + }, this.context.t('customRPC')), + ] + ), + + ]) } NetworkDropdown.prototype.handleClick = function (newProviderType) { @@ -335,56 +332,45 @@ NetworkDropdown.prototype.renderCommonRpc = function (rpcListDetail, provider) { return null } else { const chainId = entry.chainId - return ( - this.props.hideNetworkDropdown()} - onClick={() => props.setRpcTarget(rpc, chainId, ticker, nickname)} - style={{ + return h( + DropdownMenuItem, + { + key: `common${rpc}`, + closeMenu: () => this.props.hideNetworkDropdown(), + onClick: () => props.setRpcTarget(rpc, chainId, ticker, nickname), + style: { fontSize: '16px', lineHeight: '20px', padding: '12px 0', - }} - > - { - currentRpcTarget - ? - :
    - } - - - {nickname || rpc} - - { - e.stopPropagation() - props.delRpcTarget(rpc) - }} - /> -
    + }, + }, + [ + currentRpcTarget ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'), + h('i.fa.fa-question-circle.fa-med.menu-icon-circle'), + h('span.network-name-item', { + style: { + color: currentRpcTarget ? '#ffffff' : '#9b9b9b', + }, + }, nickname || rpc), + h('i.fa.fa-times.delete', + { + onClick: (e) => { + e.stopPropagation() + props.delRpcTarget(rpc) + }, + }), + ] ) } }) } -/** - * @return {Component|null} - */ -NetworkDropdown.prototype.renderCustomOption = function NetworkDropdown (provider) { +NetworkDropdown.prototype.renderCustomOption = function (provider) { const { rpcTarget, type, ticker, nickname } = provider - const network = this.props.network + const props = this.props + const network = props.network - if (type !== 'rpc') { - return null - } + if (type !== 'rpc') return null switch (rpcTarget) { @@ -392,28 +378,27 @@ NetworkDropdown.prototype.renderCustomOption = function NetworkDropdown (provide return null default: - return ( - this.props.setRpcTarget(rpcTarget, network, ticker, nickname)} - closeMenu={() => this.props.hideNetworkDropdown()} - style={{ + return h( + DropdownMenuItem, + { + key: rpcTarget, + onClick: () => props.setRpcTarget(rpcTarget, network, ticker, nickname), + closeMenu: () => this.props.hideNetworkDropdown(), + style: { fontSize: '16px', lineHeight: '20px', padding: '12px 0', - }} - > - - - - {nickname || rpcTarget} - - + }, + }, nickname || rpcTarget), + ] ) } } diff --git a/ui/app/components/app/dropdowns/simple-dropdown.js b/ui/app/components/app/dropdowns/simple-dropdown.js index 081674397f..bba088ed1d 100644 --- a/ui/app/components/app/dropdowns/simple-dropdown.js +++ b/ui/app/components/app/dropdowns/simple-dropdown.js @@ -1,18 +1,15 @@ -import classnames from 'classnames' -import PropTypes from 'prop-types' -import React, { Component } from 'react' -import R from 'ramda' +const { Component } = require('react') +const PropTypes = require('prop-types') +const h = require('react-hyperscript') +const classnames = require('classnames') +const R = require('ramda') class SimpleDropdown extends Component { - static propTypes = { - options: PropTypes.array.isRequired, - placeholder: PropTypes.string, - onSelect: PropTypes.func, - selectedOption: PropTypes.string, - } - - state = { - isOpen: false, + constructor (props) { + super(props) + this.state = { + isOpen: false, + } } getDisplayValue () { @@ -29,58 +26,67 @@ class SimpleDropdown extends Component { } toggleOpen () { - this.setState((prevState) => ({ - isOpen: !prevState.isOpen, - })) + const { isOpen } = this.state + this.setState({ isOpen: !isOpen }) } renderOptions () { const { options, onSelect, selectedOption } = this.props - return ( -
    -
    { - event.stopPropagation() - this.handleClose() - }} - /> -
    - {options.map((option) => ( -
    { + event.stopPropagation() + this.handleClose() + }, + }), + h('div.simple-dropdown__options', [ + ...options.map(option => { + return h( + 'div.simple-dropdown__option', + { + className: classnames({ 'simple-dropdown__option--selected': option.value === selectedOption, - })} - key={option.value} - onClick={() => { + }), + key: option.value, + onClick: () => { if (option.value !== selectedOption) { onSelect(option.value) } this.handleClose() - }} - > - {option.displayValue || option.value} -
    - ))} -
    -
    - ) + }, + }, + option.displayValue || option.value, + ) + }), + ]), + ]) } render () { const { placeholder } = this.props const { isOpen } = this.state - return ( -
    this.toggleOpen()}> -
    {this.getDisplayValue() || placeholder || 'Select'}
    - - {isOpen && this.renderOptions()} -
    + return h( + 'div.simple-dropdown', + { + onClick: () => this.toggleOpen(), + }, + [ + h('div.simple-dropdown__selected', this.getDisplayValue() || placeholder || 'Select'), + h('i.fa.fa-caret-down.fa-lg.simple-dropdown__caret'), + isOpen && this.renderOptions(), + ] ) } } +SimpleDropdown.propTypes = { + options: PropTypes.array.isRequired, + placeholder: PropTypes.string, + onSelect: PropTypes.func, + selectedOption: PropTypes.string, +} + module.exports = SimpleDropdown diff --git a/ui/app/components/app/dropdowns/tests/menu.test.js b/ui/app/components/app/dropdowns/tests/menu.test.js index b3bc34fa99..6413c0c2c7 100644 --- a/ui/app/components/app/dropdowns/tests/menu.test.js +++ b/ui/app/components/app/dropdowns/tests/menu.test.js @@ -11,7 +11,7 @@ describe('Dropdown Menu Components', () => { beforeEach(() => { wrapper = shallow( - + ) }) @@ -31,14 +31,14 @@ describe('Dropdown Menu Components', () => { ) }) it('add className based on props', () => { - assert.equal(wrapper.find('.menu__item').prop('className'), 'menu__item test foo1 menu__item--clickable') + assert.equal(wrapper.find('.menu__item').prop('className'), 'menu__item menu__item test className menu__item--clickable') }) it('simulates onClick called', () => { @@ -73,11 +73,9 @@ describe('Dropdown Menu Components', () => { const onClickSpy = sinon.spy() beforeEach(() => { - wrapper = shallow(( - - )) + wrapper = shallow() }) it('simulates click', () => { diff --git a/ui/app/components/app/dropdowns/tests/network-dropdown-icon.test.js b/ui/app/components/app/dropdowns/tests/network-dropdown-icon.test.js index ce3853a637..ed7b7e253b 100644 --- a/ui/app/components/app/dropdowns/tests/network-dropdown-icon.test.js +++ b/ui/app/components/app/dropdowns/tests/network-dropdown-icon.test.js @@ -7,14 +7,12 @@ describe('Network Dropdown Icon', () => { let wrapper beforeEach(() => { - wrapper = shallow(( - - )) + wrapper = shallow() }) it('adds style props based on props', () => { diff --git a/ui/app/components/app/dropdowns/tests/network-dropdown.test.js b/ui/app/components/app/dropdowns/tests/network-dropdown.test.js index 98cba1d2e2..4a81b973fd 100644 --- a/ui/app/components/app/dropdowns/tests/network-dropdown.test.js +++ b/ui/app/components/app/dropdowns/tests/network-dropdown.test.js @@ -57,7 +57,7 @@ describe('Network Dropdown', () => { beforeEach(() => { wrapper = mountWithRouter( - , + , ) }) diff --git a/ui/app/components/app/dropdowns/token-menu-dropdown.js b/ui/app/components/app/dropdowns/token-menu-dropdown.js index 65c559f709..e2730aea21 100644 --- a/ui/app/components/app/dropdowns/token-menu-dropdown.js +++ b/ui/app/components/app/dropdowns/token-menu-dropdown.js @@ -1,5 +1,6 @@ -import PropTypes from 'prop-types' -import React, { Component } from 'react' +const Component = require('react').Component +const PropTypes = require('prop-types') +const h = require('react-hyperscript') const inherits = require('util').inherits const connect = require('react-redux').connect const actions = require('../../../store/actions') @@ -39,29 +40,29 @@ TokenMenuDropdown.prototype.onClose = function (e) { this.props.onClose() } -TokenMenuDropdown.prototype.render = function TokenMenuDropdown () { +TokenMenuDropdown.prototype.render = function () { const { showHideTokenConfirmationModal } = this.props - return ( - - - { - e.stopPropagation() - showHideTokenConfirmationModal(this.props.token) - this.props.onClose() - }} - text={this.context.t('hideToken')} - /> - { - e.stopPropagation() - const url = genAccountLink(this.props.token.address, this.props.network) - global.platform.openWindow({ url }) - this.props.onClose() - }} - text={this.context.t('viewOnEtherscan')} - /> - - ) + return h(Menu, { className: 'token-menu-dropdown', isShowing: true }, [ + h(CloseArea, { + onClick: this.onClose, + }), + h(Item, { + onClick: (e) => { + e.stopPropagation() + showHideTokenConfirmationModal(this.props.token) + this.props.onClose() + }, + text: this.context.t('hideToken'), + }), + h(Item, { + onClick: (e) => { + e.stopPropagation() + const url = genAccountLink(this.props.token.address, this.props.network) + global.platform.openWindow({ url }) + this.props.onClose() + }, + text: this.context.t('viewOnEtherscan'), + }), + ]) } diff --git a/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js index b881db6e5a..7fb5aa6f40 100644 --- a/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js +++ b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js @@ -119,12 +119,10 @@ export default class AdvancedGasInputs extends Component { value={value} onChange={onChange} /> -
    +
    onChange({ target: { value: value + 1 } })} @@ -161,21 +159,21 @@ export default class AdvancedGasInputs extends Component { errorText: gasPriceErrorText, errorType: gasPriceErrorType, } = this.gasPriceError({ insufficientBalance, customPriceIsSafe, isSpeedUp, gasPrice }) - const gasPriceErrorComponent = gasPriceErrorType ? ( + const gasPriceErrorComponent = gasPriceErrorType ?
    { gasPriceErrorText } -
    - ) : null +
    : + null const { errorText: gasLimitErrorText, errorType: gasLimitErrorType, } = this.gasLimitError({ insufficientBalance, gasLimit }) - const gasLimitErrorComponent = gasLimitErrorType ? ( + const gasLimitErrorComponent = gasLimitErrorType ?
    { gasLimitErrorText } -
    - ) : null +
    : + null return (
    diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js index 19e3aa7764..306dd03a08 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js @@ -83,19 +83,17 @@ export default class AdvancedTabContent extends Component { />
    { isEthereumNetwork - ? ( -
    -
    { t('liveGasPricePredictions') }
    - {!gasEstimatesLoading - ? - : - } -
    - { t('slower') } - { t('faster') } -
    + ?
    +
    { t('liveGasPricePredictions') }
    + {!gasEstimatesLoading + ? + : + } +
    + { t('slower') } + { t('faster') }
    - ) +
    :
    { t('chartOnlyAvailableEth') }
    }
    diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js index 16b15df412..05075f3ba1 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js @@ -18,20 +18,18 @@ describe('AdvancedTabContent Component', function () { let wrapper beforeEach(() => { - wrapper = shallow(( - - )) + wrapper = shallow() }) afterEach(() => { diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js index 94d22ee4a1..c804abe3a7 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js @@ -21,13 +21,11 @@ export default class BasicTabContent extends Component {
    { t('estimatedProcessingTimes') }
    { t('selectAHigherGasFee') }
    {!gasPriceButtonGroupProps.loading - ? ( - - ) + ? : }
    { t('acceleratingATransaction') }
    diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/tests/basic-tab-content-component.test.js b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/tests/basic-tab-content-component.test.js index 80fbffe05b..0989ac6770 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/tests/basic-tab-content-component.test.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/tests/basic-tab-content-component.test.js @@ -38,11 +38,9 @@ describe('BasicTabContent Component', function () { let wrapper beforeEach(() => { - wrapper = shallow(( - - )) + wrapper = shallow() }) describe('render', () => { diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js index daf91eae3e..f405cb7b9d 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js @@ -160,14 +160,13 @@ export default class GasModalPageContainer extends Component { return ( - {tabsToRender.map(({ name, content }, i) => ( - -
    - { content } - { this.renderInfoRows(newTotalFiat, newTotalEth, sendAmount, transactionFee) } -
    -
    - ))} + {tabsToRender.map(({ name, content }, i) => +
    + { content } + { this.renderInfoRows(newTotalFiat, newTotalEth, sendAmount, transactionFee) } +
    +
    + )}
    ) } diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-component.test.js b/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-component.test.js index 50efb46f0a..3e416db492 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-component.test.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-component.test.js @@ -63,25 +63,23 @@ describe('GasModalPageContainer Component', function () { let wrapper beforeEach(() => { - wrapper = shallow(( - 'mockupdateCustomGasPrice'} - updateCustomGasLimit={() => 'mockupdateCustomGasLimit'} - customGasPrice={21} - customGasLimit={54321} - gasPriceButtonGroupProps={mockGasPriceButtonGroupProps} - infoRowProps={mockInfoRowProps} - currentTimeEstimate="1 min 31 sec" - customGasPriceInHex="mockCustomGasPriceInHex" - customGasLimitInHex="mockCustomGasLimitInHex" - insufficientBalance={false} - disableSave={false} - /> - )) + wrapper = shallow( 'mockupdateCustomGasPrice'} + updateCustomGasLimit={() => 'mockupdateCustomGasLimit'} + customGasPrice={21} + customGasLimit={54321} + gasPriceButtonGroupProps={mockGasPriceButtonGroupProps} + infoRowProps={mockInfoRowProps} + currentTimeEstimate="1 min 31 sec" + customGasPriceInHex="mockCustomGasPriceInHex" + customGasLimitInHex="mockCustomGasLimitInHex" + insufficientBalance={false} + disableSave={false} + />) }) afterEach(() => { @@ -136,12 +134,10 @@ describe('GasModalPageContainer Component', function () { it('should pass the correct renderTabs property to PageContainer', () => { sinon.stub(GP, 'renderTabs').returns('mockTabs') - const renderTabsWrapperTester = shallow(( - - ), { context: { t: (str1, str2) => str2 ? str1 + str2 : str1 } }) + const renderTabsWrapperTester = shallow(, { context: { t: (str1, str2) => str2 ? str1 + str2 : str1 } }) const { tabsComponent } = renderTabsWrapperTester.find(PageContainer).props() assert.equal(tabsComponent, 'mockTabs') GasModalPageContainer.prototype.renderTabs.restore() @@ -188,26 +184,24 @@ describe('GasModalPageContainer Component', function () { }) it('should not render the basic tab if hideBasic is true', () => { - wrapper = shallow(( - 'mockupdateCustomGasPrice'} - updateCustomGasLimit={() => 'mockupdateCustomGasLimit'} - customGasPrice={21} - customGasLimit={54321} - gasPriceButtonGroupProps={mockGasPriceButtonGroupProps} - infoRowProps={mockInfoRowProps} - currentTimeEstimate="1 min 31 sec" - customGasPriceInHex="mockCustomGasPriceInHex" - customGasLimitInHex="mockCustomGasLimitInHex" - insufficientBalance={false} - disableSave={false} - hideBasic - /> - )) + wrapper = shallow( 'mockupdateCustomGasPrice'} + updateCustomGasLimit={() => 'mockupdateCustomGasLimit'} + customGasPrice={21} + customGasLimit={54321} + gasPriceButtonGroupProps={mockGasPriceButtonGroupProps} + infoRowProps={mockInfoRowProps} + currentTimeEstimate="1 min 31 sec" + customGasPriceInHex="mockCustomGasPriceInHex" + customGasLimitInHex="mockCustomGasLimitInHex" + insufficientBalance={false} + disableSave={false} + hideBasic + />) const renderTabsResult = wrapper.instance().renderTabs() const renderedTabs = shallow(renderTabsResult) diff --git a/ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js b/ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js index b578ff1ed4..0412b3381d 100644 --- a/ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js +++ b/ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js @@ -49,15 +49,13 @@ export default class GasPriceButtonGroup extends Component { className, showCheck, }) { - return ( -
    - { gasEstimateType &&
    { this.gasEstimateTypeLabel(gasEstimateType) }
    } - { timeEstimate &&
    { timeEstimate }
    } - { feeInPrimaryCurrency &&
    { feeInPrimaryCurrency }
    } - { feeInSecondaryCurrency &&
    { feeInSecondaryCurrency }
    } - { showCheck &&
    } -
    - ) + return (
    + { gasEstimateType &&
    { this.gasEstimateTypeLabel(gasEstimateType) }
    } + { timeEstimate &&
    { timeEstimate }
    } + { feeInPrimaryCurrency &&
    { feeInPrimaryCurrency }
    } + { feeInSecondaryCurrency &&
    { feeInSecondaryCurrency }
    } + { showCheck &&
    } +
    ) } renderButton ({ @@ -90,16 +88,14 @@ export default class GasPriceButtonGroup extends Component { return ( !buttonDataLoading - ? ( - - { gasButtonInfo.map((obj, index) => this.renderButton(obj, buttonPropsAndFlags, index)) } - - ) + ? + { gasButtonInfo.map((obj, index) => this.renderButton(obj, buttonPropsAndFlags, index)) } + :
    { this.context.t('loading') }
    ) } diff --git a/ui/app/components/app/gas-customization/gas-price-button-group/tests/gas-price-button-group-component.test.js b/ui/app/components/app/gas-customization/gas-price-button-group/tests/gas-price-button-group-component.test.js index 1d924f850a..85f53d08e9 100644 --- a/ui/app/components/app/gas-customization/gas-price-button-group/tests/gas-price-button-group-component.test.js +++ b/ui/app/components/app/gas-customization/gas-price-button-group/tests/gas-price-button-group-component.test.js @@ -48,11 +48,9 @@ describe('GasPriceButtonGroup Component', function () { let wrapper beforeEach(() => { - wrapper = shallow(( - - )) + wrapper = shallow() }) afterEach(() => { diff --git a/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js b/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js index 452544abe8..419cae0cdb 100644 --- a/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js +++ b/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js @@ -214,9 +214,7 @@ export function generateChart (gasPrices, estimatedTimes, gasPricesMax, estimate tick: { values: [Math.floor(gasPrices[0]), Math.ceil(gasPricesMax)], outer: false, - format: function (val) { - return val + ' GWEI' - }, + format: function (val) { return val + ' GWEI' }, }, padding: {left: gasPricesMax / 50, right: gasPricesMax / 50}, label: { diff --git a/ui/app/components/app/gas-customization/gas-slider/gas-slider.component.js b/ui/app/components/app/gas-customization/gas-slider/gas-slider.component.js index 629d81f0ec..8233dedb5a 100644 --- a/ui/app/components/app/gas-customization/gas-slider/gas-slider.component.js +++ b/ui/app/components/app/gas-customization/gas-slider/gas-slider.component.js @@ -36,7 +36,7 @@ export default class GasSlider extends Component { onChange={event => onChange(event.target.value)} />
    -
    +
    {lowLabel} diff --git a/ui/app/components/app/index.scss b/ui/app/components/app/index.scss index 7578aa204a..1afbebd004 100644 --- a/ui/app/components/app/index.scss +++ b/ui/app/components/app/index.scss @@ -38,7 +38,7 @@ @import '../../pages/index'; -@import 'permission-page-container/index'; +@import 'provider-page-container/index'; @import 'selected-account/index'; @@ -64,12 +64,18 @@ @import 'transaction-status/index'; +@import 'app-header/index'; + @import 'sidebars/index'; @import '../ui/unit-input/index'; @import 'gas-customization/gas-modal-page-container/index'; +@import 'gas-customization/gas-modal-page-container/index'; + +@import 'gas-customization/gas-modal-page-container/index'; + @import 'gas-customization/index'; @import 'gas-customization/gas-price-button-group/index'; @@ -81,7 +87,3 @@ @import 'multiple-notifications/index'; @import 'signature-request/index'; - -@import 'connected-sites-list/index'; - -@import '../ui/icon-with-fallback/index'; diff --git a/ui/app/components/app/input-number.js b/ui/app/components/app/input-number.js index 7506808b38..8a6ec725c3 100644 --- a/ui/app/components/app/input-number.js +++ b/ui/app/components/app/input-number.js @@ -1,5 +1,5 @@ -import React from 'react' const Component = require('react').Component +const h = require('react-hyperscript') const inherits = require('util').inherits const { addCurrencies, @@ -28,9 +28,7 @@ function removeLeadingZeroes (str) { InputNumber.prototype.setValue = function (newValue) { newValue = removeLeadingZeroes(newValue) - if (newValue && !isValidInput(newValue)) { - return - } + if (newValue && !isValidInput(newValue)) return const { fixed, min = -1, max = Infinity, onChange } = this.props newValue = fixed ? newValue.toFixed(4) : newValue @@ -52,36 +50,32 @@ InputNumber.prototype.setValue = function (newValue) { } } -InputNumber.prototype.render = function InputNumber () { +InputNumber.prototype.render = function () { const { unitLabel, step = 1, placeholder, value } = this.props - return ( -
    - { - this.setValue(e.target.value) - }} - min={0} - /> - {unitLabel} -
    -
    this.setValue(addCurrencies(value, step, { toNumericBase: 'dec' }))} - > - -
    -
    this.setValue(subtractCurrencies(value, step, { toNumericBase: 'dec' }))} - > - -
    -
    -
    - ) + return h('div.customize-gas-input-wrapper', {}, [ + h('input', { + className: 'customize-gas-input', + value, + placeholder, + type: 'number', + onChange: e => { + this.setValue(e.target.value) + }, + min: 0, + }), + h('span.gas-tooltip-input-detail', {}, [unitLabel]), + h('div.gas-tooltip-input-arrows', {}, [ + h('div.gas-tooltip-input-arrow-wrapper', { + onClick: () => this.setValue(addCurrencies(value, step, { toNumericBase: 'dec' })), + }, [ + h('i.fa.fa-angle-up'), + ]), + h('div.gas-tooltip-input-arrow-wrapper', { + onClick: () => this.setValue(subtractCurrencies(value, step, { toNumericBase: 'dec' })), + }, [ + h('i.fa.fa-angle-down'), + ]), + ]), + ]) } diff --git a/ui/app/components/app/loading-network-screen/loading-network-screen.component.js b/ui/app/components/app/loading-network-screen/loading-network-screen.component.js index 22419aabee..97b16d08fb 100644 --- a/ui/app/components/app/loading-network-screen/loading-network-screen.component.js +++ b/ui/app/components/app/loading-network-screen/loading-network-screen.component.js @@ -61,46 +61,42 @@ export default class LoadingNetworkScreen extends PureComponent { } renderLoadingScreenContent = () => { - return ( -
    - - {this.renderMessage()} -
    - ) + return
    + + {this.renderMessage()} +
    } renderErrorScreenContent = () => { const { showNetworkDropdown, setProviderArgs, setProviderType } = this.props - return ( -
    - 😞 - { this.context.t('somethingWentWrong') } -
    - - - -
    + return
    + 😞 + { this.context.t('somethingWentWrong') } +
    + + +
    - ) +
    } cancelCall = () => { diff --git a/ui/app/components/app/menu-droppo.js b/ui/app/components/app/menu-droppo.js index 57a0df2fec..a88cad4b41 100644 --- a/ui/app/components/app/menu-droppo.js +++ b/ui/app/components/app/menu-droppo.js @@ -1,4 +1,5 @@ -import React, { Component } from 'react' +const Component = require('react').Component +const h = require('react-hyperscript') const inherits = require('util').inherits const findDOMNode = require('react-dom').findDOMNode const ReactCSSTransitionGroup = require('react-transition-group/CSSTransitionGroup') @@ -26,44 +27,41 @@ MenuDroppoComponent.prototype.render = function () { style.zIndex = zIndex return ( -
    - - { - useCssTransition - ? ( - - {this.renderPrimary()} - - ) - : this.renderPrimary() - } -
    + h('div', { + style, + className: `.menu-droppo-container ${containerClassName}`, + }, [ + h('style', ` + .menu-droppo-enter { + transition: transform ${speed} ease-in-out; + transform: translateY(-200%); + } + + .menu-droppo-enter.menu-droppo-enter-active { + transition: transform ${speed} ease-in-out; + transform: translateY(0%); + } + + .menu-droppo-leave { + transition: transform ${speed} ease-in-out; + transform: translateY(0%); + } + + .menu-droppo-leave.menu-droppo-leave-active { + transition: transform ${speed} ease-in-out; + transform: translateY(-200%); + } + `), + + useCssTransition + ? h(ReactCSSTransitionGroup, { + className: 'css-transition-group', + transitionName: 'menu-droppo', + transitionEnterTimeout: parseInt(speed), + transitionLeaveTimeout: parseInt(speed), + }, this.renderPrimary()) + : this.renderPrimary(), + ]) ) } @@ -76,9 +74,11 @@ MenuDroppoComponent.prototype.renderPrimary = function () { const innerStyle = this.props.innerStyle || {} return ( -
    - {this.props.children} -
    + h('.menu-droppo', { + key: 'menu-droppo-drawer', + style: innerStyle, + }, + [ this.props.children ]) ) } @@ -98,7 +98,7 @@ MenuDroppoComponent.prototype.componentDidMount = function () { this.globalClickHandler = this.globalClickOccurred.bind(this) document.body.addEventListener('click', this.globalClickHandler) // eslint-disable-next-line react/no-find-dom-node - const container = findDOMNode(this) + var container = findDOMNode(this) this.container = container } } @@ -122,7 +122,7 @@ MenuDroppoComponent.prototype.globalClickOccurred = function (event) { } function isDescendant (parent, child) { - let node = child.parentNode + var node = child.parentNode while (node !== null) { if (node === parent) { return true diff --git a/ui/app/components/app/modal/modal.component.js b/ui/app/components/app/modal/modal.component.js index 6c45160fdc..f0fdd3bd52 100644 --- a/ui/app/components/app/modal/modal.component.js +++ b/ui/app/components/app/modal/modal.component.js @@ -16,7 +16,6 @@ export default class Modal extends PureComponent { submitType: PropTypes.string, submitText: PropTypes.string, submitDisabled: PropTypes.bool, - hideFooter: PropTypes.bool, // Cancel button (left button) onCancel: PropTypes.func, cancelType: PropTypes.string, @@ -42,7 +41,6 @@ export default class Modal extends PureComponent { cancelText, contentClass, containerClass, - hideFooter, } = this.props return ( @@ -63,32 +61,27 @@ export default class Modal extends PureComponent {
    { children }
    - { !hideFooter - ? ( -
    - { - onCancel && ( - - ) - } +
    + { + onCancel && ( -
    - ) - : null - } + ) + } + +
    ) } diff --git a/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js b/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js index b2ed1a8cc4..1b9a6a7185 100644 --- a/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js +++ b/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js @@ -56,7 +56,7 @@ export default class AccountDetailsModal extends Component { }} /> -
    +
    - ) + ? : null } diff --git a/ui/app/components/app/modals/account-modal-container.js b/ui/app/components/app/modals/account-modal-container.js new file mode 100644 index 0000000000..b7ae0b5b87 --- /dev/null +++ b/ui/app/components/app/modals/account-modal-container.js @@ -0,0 +1,80 @@ +const Component = require('react').Component +const PropTypes = require('prop-types') +const h = require('react-hyperscript') +const inherits = require('util').inherits +const connect = require('react-redux').connect +const actions = require('../../../store/actions') +const { getSelectedIdentity } = require('../../../selectors/selectors') +import Identicon from '../../ui/identicon' + +function mapStateToProps (state, ownProps) { + return { + selectedIdentity: ownProps.selectedIdentity || getSelectedIdentity(state), + } +} + +function mapDispatchToProps (dispatch) { + return { + hideModal: () => { + dispatch(actions.hideModal()) + }, + } +} + +inherits(AccountModalContainer, Component) +function AccountModalContainer () { + Component.call(this) +} + +AccountModalContainer.contextTypes = { + t: PropTypes.func, +} + +module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountModalContainer) + + +AccountModalContainer.prototype.render = function () { + const { + selectedIdentity, + showBackButton = false, + backButtonAction, + } = this.props + let { children } = this.props + + if (children.constructor !== Array) { + children = [children] + } + + return h('div', { style: { borderRadius: '4px' }}, [ + h('div.account-modal-container', [ + + h('div', [ + + // Needs a border; requires changes to svg + h(Identicon, { + address: selectedIdentity.address, + diameter: 64, + style: {}, + }), + + ]), + + showBackButton && h('div.account-modal-back', { + onClick: backButtonAction, + }, [ + + h('i.fa.fa-angle-left.fa-lg'), + + h('span.account-modal-back__text', ' ' + this.context.t('back')), + + ]), + + h('div.account-modal-close', { + onClick: this.props.hideModal, + }), + + ...children, + + ]), + ]) +} diff --git a/ui/app/components/app/modals/account-modal-container/account-modal-container.component.js b/ui/app/components/app/modals/account-modal-container/account-modal-container.component.js deleted file mode 100644 index ed28f5fd12..0000000000 --- a/ui/app/components/app/modals/account-modal-container/account-modal-container.component.js +++ /dev/null @@ -1,52 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import Identicon from '../../../ui/identicon' - -export default function AccountModalContainer (props, context) { - const { - selectedIdentity, - showBackButton, - backButtonAction, - hideModal, - children, - } = props - - return ( -
    -
    -
    - -
    - {showBackButton && ( -
    - - {' ' + context.t('back')} -
    - )} -
    - {children} -
    -
    - ) -} - -AccountModalContainer.contextTypes = { - t: PropTypes.func, -} - -AccountModalContainer.defaultProps = { - showBackButton: false, - children: null, - backButtonAction: undefined, -} - -AccountModalContainer.propTypes = { - selectedIdentity: PropTypes.object.isRequired, - showBackButton: PropTypes.bool, - backButtonAction: PropTypes.func, - hideModal: PropTypes.func.isRequired, - children: PropTypes.node, -} diff --git a/ui/app/components/app/modals/account-modal-container/account-modal-container.container.js b/ui/app/components/app/modals/account-modal-container/account-modal-container.container.js deleted file mode 100644 index ad118f92d5..0000000000 --- a/ui/app/components/app/modals/account-modal-container/account-modal-container.container.js +++ /dev/null @@ -1,20 +0,0 @@ -import { connect } from 'react-redux' -import { hideModal } from '../../../../store/actions' -import { getSelectedIdentity } from '../../../../selectors/selectors' -import AccountModalContainer from './account-modal-container.component' - -function mapStateToProps (state, ownProps) { - return { - selectedIdentity: ownProps.selectedIdentity || getSelectedIdentity(state), - } -} - -function mapDispatchToProps (dispatch) { - return { - hideModal: () => { - dispatch(hideModal()) - }, - } -} - -export default connect(mapStateToProps, mapDispatchToProps)(AccountModalContainer) diff --git a/ui/app/components/app/modals/account-modal-container/index.js b/ui/app/components/app/modals/account-modal-container/index.js deleted file mode 100644 index e37684b963..0000000000 --- a/ui/app/components/app/modals/account-modal-container/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './account-modal-container.container' diff --git a/ui/app/components/app/modals/clear-approved-origins/clear-approved-origins.component.js b/ui/app/components/app/modals/clear-approved-origins/clear-approved-origins.component.js new file mode 100644 index 0000000000..ceaa20a951 --- /dev/null +++ b/ui/app/components/app/modals/clear-approved-origins/clear-approved-origins.component.js @@ -0,0 +1,39 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import Modal, { ModalContent } from '../../modal' + +export default class ClearApprovedOrigins extends PureComponent { + static propTypes = { + hideModal: PropTypes.func.isRequired, + clearApprovedOrigins: PropTypes.func.isRequired, + } + + static contextTypes = { + t: PropTypes.func, + } + + handleClear = () => { + const { clearApprovedOrigins, hideModal } = this.props + clearApprovedOrigins() + hideModal() + } + + render () { + const { t } = this.context + + return ( + this.props.hideModal()} + submitText={t('ok')} + cancelText={t('nevermind')} + submitType="secondary" + > + + + ) + } +} diff --git a/ui/app/components/app/modals/disconnect-all/disconnect-all.container.js b/ui/app/components/app/modals/clear-approved-origins/clear-approved-origins.container.js similarity index 53% rename from ui/app/components/app/modals/disconnect-all/disconnect-all.container.js rename to ui/app/components/app/modals/clear-approved-origins/clear-approved-origins.container.js index 2415c3fa9f..2276bc7e78 100644 --- a/ui/app/components/app/modals/disconnect-all/disconnect-all.container.js +++ b/ui/app/components/app/modals/clear-approved-origins/clear-approved-origins.container.js @@ -1,20 +1,16 @@ import { connect } from 'react-redux' import { compose } from 'recompose' -import { withRouter } from 'react-router-dom' import withModalProps from '../../../../helpers/higher-order-components/with-modal-props' -import DisconnectAll from './disconnect-all.component' -import { clearPermissions } from '../../../../store/actions' +import ClearApprovedOriginsComponent from './clear-approved-origins.component' +import { clearApprovedOrigins } from '../../../../store/actions' const mapDispatchToProps = dispatch => { return { - disconnectAll: () => { - dispatch(clearPermissions()) - }, + clearApprovedOrigins: () => dispatch(clearApprovedOrigins()), } } export default compose( withModalProps, - withRouter, connect(null, mapDispatchToProps) -)(DisconnectAll) +)(ClearApprovedOriginsComponent) diff --git a/ui/app/components/app/modals/clear-approved-origins/index.js b/ui/app/components/app/modals/clear-approved-origins/index.js new file mode 100644 index 0000000000..b3e3219951 --- /dev/null +++ b/ui/app/components/app/modals/clear-approved-origins/index.js @@ -0,0 +1 @@ +export { default } from './clear-approved-origins.container' diff --git a/ui/app/components/app/modals/confirm-remove-account/confirm-remove-account.component.js b/ui/app/components/app/modals/confirm-remove-account/confirm-remove-account.component.js index 7bea983b4f..7fe79be5b8 100644 --- a/ui/app/components/app/modals/confirm-remove-account/confirm-remove-account.component.js +++ b/ui/app/components/app/modals/confirm-remove-account/confirm-remove-account.component.js @@ -78,9 +78,7 @@ export default class ConfirmRemoveAccount extends Component { + target="_blank" href="https://metamask.zendesk.com/hc/en-us/articles/360015289932"> { t('learnMore') }
    diff --git a/ui/app/components/app/modals/deposit-ether-modal.js b/ui/app/components/app/modals/deposit-ether-modal.js new file mode 100644 index 0000000000..f71e0619e8 --- /dev/null +++ b/ui/app/components/app/modals/deposit-ether-modal.js @@ -0,0 +1,213 @@ +const Component = require('react').Component +const PropTypes = require('prop-types') +const h = require('react-hyperscript') +const inherits = require('util').inherits +const connect = require('react-redux').connect +const actions = require('../../../store/actions') +const { getNetworkDisplayName } = require('../../../../../app/scripts/controllers/network/util') + +import Button from '../../ui/button' + +let DIRECT_DEPOSIT_ROW_TITLE +let DIRECT_DEPOSIT_ROW_TEXT +let WYRE_ROW_TITLE +let WYRE_ROW_TEXT +let FAUCET_ROW_TITLE +let COINSWITCH_ROW_TITLE +let COINSWITCH_ROW_TEXT + +function mapStateToProps (state) { + return { + network: state.metamask.network, + address: state.metamask.selectedAddress, + } +} + +function mapDispatchToProps (dispatch) { + return { + toWyre: (address) => { + dispatch(actions.buyEth({ service: 'wyre', address, amount: 0 })) + }, + toCoinSwitch: (address) => { + dispatch(actions.buyEth({ service: 'coinswitch', address })) + }, + hideModal: () => { + dispatch(actions.hideModal()) + }, + hideWarning: () => { + dispatch(actions.hideWarning()) + }, + showAccountDetailModal: () => { + dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' })) + }, + toFaucet: network => dispatch(actions.buyEth({ network })), + } +} + +inherits(DepositEtherModal, Component) +function DepositEtherModal (_, context) { + Component.call(this) + + // need to set after i18n locale has loaded + DIRECT_DEPOSIT_ROW_TITLE = context.t('directDepositEther') + DIRECT_DEPOSIT_ROW_TEXT = context.t('directDepositEtherExplainer') + WYRE_ROW_TITLE = context.t('buyWithWyre') + WYRE_ROW_TEXT = context.t('buyWithWyreDescription') + FAUCET_ROW_TITLE = context.t('testFaucet') + COINSWITCH_ROW_TITLE = context.t('buyCoinSwitch') + COINSWITCH_ROW_TEXT = context.t('buyCoinSwitchExplainer') +} + +DepositEtherModal.contextTypes = { + t: PropTypes.func, +} + +module.exports = connect(mapStateToProps, mapDispatchToProps)(DepositEtherModal) + + +DepositEtherModal.prototype.facuetRowText = function (networkName) { + return this.context.t('getEtherFromFaucet', [networkName]) +} + +DepositEtherModal.prototype.renderRow = function ({ + logo, + title, + text, + buttonLabel, + onButtonClick, + hide, + className, + hideButton, + hideTitle, + onBackClick, + showBackButton, +}) { + if (hide) { + return null + } + + return h('div', { + className: className || 'deposit-ether-modal__buy-row', + }, [ + + onBackClick && showBackButton && h('div.deposit-ether-modal__buy-row__back', { + onClick: onBackClick, + }, [ + + h('i.fa.fa-arrow-left.cursor-pointer'), + + ]), + + h('div.deposit-ether-modal__buy-row__logo-container', [logo]), + + h('div.deposit-ether-modal__buy-row__description', [ + + !hideTitle && h('div.deposit-ether-modal__buy-row__description__title', [title]), + + h('div.deposit-ether-modal__buy-row__description__text', [text]), + + ]), + + !hideButton && h('div.deposit-ether-modal__buy-row__button', [ + h(Button, { + type: 'secondary', + className: 'deposit-ether-modal__deposit-button', + large: true, + onClick: onButtonClick, + }, [buttonLabel]), + ]), + + ]) +} + +DepositEtherModal.prototype.render = function () { + const { network, toWyre, toCoinSwitch, address, toFaucet } = this.props + + const isTestNetwork = ['3', '4', '5', '42'].find(n => n === network) + const networkName = getNetworkDisplayName(network) + + return h('div.page-container.page-container--full-width.page-container--full-height', {}, [ + + h('div.page-container__header', [ + + h('div.page-container__title', [this.context.t('depositEther')]), + + h('div.page-container__subtitle', [ + this.context.t('needEtherInWallet'), + ]), + + h('div.page-container__header-close', { + onClick: () => { + this.props.hideWarning() + this.props.hideModal() + }, + }), + + ]), + + h('.page-container__content', {}, [ + + h('div.deposit-ether-modal__buy-rows', [ + + this.renderRow({ + logo: h('img.deposit-ether-modal__logo', { + src: './images/deposit-eth.svg', + style: { + height: '75px', + width: '75px', + }, + }), + title: DIRECT_DEPOSIT_ROW_TITLE, + text: DIRECT_DEPOSIT_ROW_TEXT, + buttonLabel: this.context.t('viewAccount'), + onButtonClick: () => this.goToAccountDetailsModal(), + }), + + this.renderRow({ + logo: h('i.fa.fa-tint.fa-2x'), + title: FAUCET_ROW_TITLE, + text: this.facuetRowText(networkName), + buttonLabel: this.context.t('getEther'), + onButtonClick: () => toFaucet(network), + hide: !isTestNetwork, + }), + + this.renderRow({ + logo: h('div.deposit-ether-modal__logo', { + style: { + backgroundImage: 'url(\'./images/wyre.svg\')', + height: '40px', + }, + }), + title: WYRE_ROW_TITLE, + text: WYRE_ROW_TEXT, + buttonLabel: this.context.t('continueToWyre'), + onButtonClick: () => toWyre(address), + hide: isTestNetwork, + }), + + this.renderRow({ + logo: h('div.deposit-ether-modal__logo', { + style: { + backgroundImage: 'url(\'./images/coinswitch_logo.png\')', + height: '40px', + }, + }), + title: COINSWITCH_ROW_TITLE, + text: COINSWITCH_ROW_TEXT, + buttonLabel: this.context.t('continueToCoinSwitch'), + onButtonClick: () => toCoinSwitch(address), + hide: isTestNetwork, + }), + + ]), + + ]), + ]) +} + +DepositEtherModal.prototype.goToAccountDetailsModal = function () { + this.props.hideWarning() + this.props.hideModal() + this.props.showAccountDetailModal() +} diff --git a/ui/app/components/app/modals/deposit-ether-modal/deposit-ether-modal.component.js b/ui/app/components/app/modals/deposit-ether-modal/deposit-ether-modal.component.js deleted file mode 100644 index d503d950ef..0000000000 --- a/ui/app/components/app/modals/deposit-ether-modal/deposit-ether-modal.component.js +++ /dev/null @@ -1,168 +0,0 @@ -import PropTypes from 'prop-types' -import React, {Component} from 'react' -import {getNetworkDisplayName} from '../../../../../../app/scripts/controllers/network/util' -import Button from '../../../ui/button' - -export default class DepositEtherModal extends Component { - static contextTypes = { - t: PropTypes.func, - } - - static propTypes = { - network: PropTypes.string.isRequired, - toWyre: PropTypes.func.isRequired, - toCoinSwitch: PropTypes.func.isRequired, - address: PropTypes.string.isRequired, - toFaucet: PropTypes.func.isRequired, - hideWarning: PropTypes.func.isRequired, - hideModal: PropTypes.func.isRequired, - showAccountDetailModal: PropTypes.func.isRequired, - } - - faucetRowText = (networkName) => { - return this.context.t('getEtherFromFaucet', [networkName]) - } - - goToAccountDetailsModal = () => { - this.props.hideWarning() - this.props.hideModal() - this.props.showAccountDetailModal() - } - - renderRow ({ - logo, - title, - text, - buttonLabel, - onButtonClick, - hide, - className, - hideButton, - hideTitle, - onBackClick, - showBackButton, - }) { - if (hide) { - return null - } - - return ( -
    - {onBackClick && showBackButton && ( -
    - -
    - )} -
    {logo}
    -
    - {!hideTitle && ( -
    {title}
    - )} -
    {text}
    -
    - {!hideButton && ( -
    - -
    - )} -
    - ) - } - - render () { - const { network, toWyre, toCoinSwitch, address, toFaucet } = this.props - - const isTestNetwork = ['3', '4', '5', '42'].find(n => n === network) - const networkName = getNetworkDisplayName(network) - - return ( -
    -
    -
    - {this.context.t('depositEther')} -
    -
    - {this.context.t('needEtherInWallet')} -
    -
    { - this.props.hideWarning() - this.props.hideModal() - }} - /> -
    -
    -
    - {this.renderRow({ - logo: ( - - ), - title: this.context.t('directDepositEther'), - text: this.context.t('directDepositEtherExplainer'), - buttonLabel: this.context.t('viewAccount'), - onButtonClick: () => this.goToAccountDetailsModal(), - })} - {this.renderRow({ - logo: , - title: this.context.t('testFaucet'), - text: this.faucetRowText(networkName), - buttonLabel: this.context.t('getEther'), - onButtonClick: () => toFaucet(network), - hide: !isTestNetwork, - })} - {this.renderRow({ - logo: ( -
    - ), - title: this.context.t('buyWithWyre'), - text: this.context.t('buyWithWyreDescription'), - buttonLabel: this.context.t('continueToWyre'), - onButtonClick: () => toWyre(address), - hide: isTestNetwork, - })} - {this.renderRow({ - logo: ( -
    - ), - title: this.context.t('buyCoinSwitch'), - text: this.context.t('buyCoinSwitchExplainer'), - buttonLabel: this.context.t('continueToCoinSwitch'), - onButtonClick: () => toCoinSwitch(address), - hide: isTestNetwork, - })} -
    -
    -
    - ) - } -} diff --git a/ui/app/components/app/modals/deposit-ether-modal/deposit-ether-modal.container.js b/ui/app/components/app/modals/deposit-ether-modal/deposit-ether-modal.container.js deleted file mode 100644 index cccbe76671..0000000000 --- a/ui/app/components/app/modals/deposit-ether-modal/deposit-ether-modal.container.js +++ /dev/null @@ -1,34 +0,0 @@ -import { connect } from 'react-redux' -import { buyEth, hideModal, showModal, hideWarning } from '../../../../store/actions' -import DepositEtherModal from './deposit-ether-modal.component' - -function mapStateToProps (state) { - return { - network: state.metamask.network, - address: state.metamask.selectedAddress, - } -} - -function mapDispatchToProps (dispatch) { - return { - toWyre: (address) => { - dispatch(buyEth({ service: 'wyre', address, amount: 0 })) - }, - toCoinSwitch: (address) => { - dispatch(buyEth({ service: 'coinswitch', address })) - }, - hideModal: () => { - dispatch(hideModal()) - }, - hideWarning: () => { - dispatch(hideWarning()) - }, - showAccountDetailModal: () => { - dispatch(showModal({ name: 'ACCOUNT_DETAILS' })) - }, - toFaucet: network => dispatch(buyEth({ network })), - } -} - - -export default connect(mapStateToProps, mapDispatchToProps)(DepositEtherModal) diff --git a/ui/app/components/app/modals/deposit-ether-modal/index.js b/ui/app/components/app/modals/deposit-ether-modal/index.js deleted file mode 100644 index 01a262a73e..0000000000 --- a/ui/app/components/app/modals/deposit-ether-modal/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './deposit-ether-modal.container' diff --git a/ui/app/components/app/modals/disconnect-account/disconnect-account.component.js b/ui/app/components/app/modals/disconnect-account/disconnect-account.component.js deleted file mode 100644 index 4fe5c7227e..0000000000 --- a/ui/app/components/app/modals/disconnect-account/disconnect-account.component.js +++ /dev/null @@ -1,52 +0,0 @@ -import React, { PureComponent } from 'react' -import PropTypes from 'prop-types' -import Modal from '../../modal' -import Button from '../../../ui/button' - - -export default class DisconnectAccount extends PureComponent { - static propTypes = { - hideModal: PropTypes.func.isRequired, - disconnectAccount: PropTypes.func.isRequired, - accountLabel: PropTypes.string.isRequired, - } - - static contextTypes = { - t: PropTypes.func, - } - - render () { - const { t } = this.context - const { hideModal, disconnectAccount, accountLabel } = this.props - - return ( - hideModal()} - hideFooter - > -
    -
    - { t('disconnectAccountModalDescription', [ accountLabel ]) } -
    - - -
    -
    - ) - } -} diff --git a/ui/app/components/app/modals/disconnect-account/disconnect-account.container.js b/ui/app/components/app/modals/disconnect-account/disconnect-account.container.js deleted file mode 100644 index b0511bb472..0000000000 --- a/ui/app/components/app/modals/disconnect-account/disconnect-account.container.js +++ /dev/null @@ -1,44 +0,0 @@ -import { connect } from 'react-redux' -import { compose } from 'recompose' -import withModalProps from '../../../../helpers/higher-order-components/with-modal-props' -import DisconnectAccount from './disconnect-account.component' -import { getCurrentAccountWithSendEtherInfo } from '../../../../selectors/selectors' -import { removePermissionsFor } from '../../../../store/actions' - -const mapStateToProps = state => { - return { - ...state.appState.modal.modalState.props || {}, - accountLabel: getCurrentAccountWithSendEtherInfo(state).name, - } -} - -const mapDispatchToProps = dispatch => { - return { - disconnectAccount: (domainKey, domain) => { - const permissionMethodNames = domain.permissions.map(perm => perm.parentCapability) - dispatch(removePermissionsFor({ [domainKey]: permissionMethodNames })) - }, - } -} - -const mergeProps = (stateProps, dispatchProps, ownProps) => { - const { - domainKey, - domain, - } = stateProps - const { - disconnectAccount: dispatchDisconnectAccount, - } = dispatchProps - - return { - ...ownProps, - ...stateProps, - ...dispatchProps, - disconnectAccount: () => dispatchDisconnectAccount(domainKey, domain), - } -} - -export default compose( - withModalProps, - connect(mapStateToProps, mapDispatchToProps, mergeProps) -)(DisconnectAccount) diff --git a/ui/app/components/app/modals/disconnect-account/index.js b/ui/app/components/app/modals/disconnect-account/index.js deleted file mode 100644 index 43bfac9fdc..0000000000 --- a/ui/app/components/app/modals/disconnect-account/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './disconnect-account.container' diff --git a/ui/app/components/app/modals/disconnect-account/index.scss b/ui/app/components/app/modals/disconnect-account/index.scss deleted file mode 100644 index 861b7cec22..0000000000 --- a/ui/app/components/app/modals/disconnect-account/index.scss +++ /dev/null @@ -1,25 +0,0 @@ -.disconnect-account-modal { - &__description { - color: #24292E; - margin-bottom: 24px; - } - - &__cancel-button { - border: none; - margin-top: 12px; - } -} - -.disconnect-account-modal-container { - .modal-container__header-text { - @extend %header--18; - } - - .modal-container__content { - padding-bottom: 18px; - - @media screen and (max-width: 575px) { - padding-bottom: 18px; - } - } -} \ No newline at end of file diff --git a/ui/app/components/app/modals/disconnect-all/disconnect-all.component.js b/ui/app/components/app/modals/disconnect-all/disconnect-all.component.js deleted file mode 100644 index 2d29fd9ea0..0000000000 --- a/ui/app/components/app/modals/disconnect-all/disconnect-all.component.js +++ /dev/null @@ -1,54 +0,0 @@ -import React, { PureComponent } from 'react' -import PropTypes from 'prop-types' -import Modal from '../../modal' -import Button from '../../../ui/button' -import { DEFAULT_ROUTE } from '../../../../helpers/constants/routes' - -export default class DisconnectAll extends PureComponent { - static propTypes = { - hideModal: PropTypes.func.isRequired, - disconnectAll: PropTypes.func.isRequired, - history: PropTypes.object.isRequired, - } - - static contextTypes = { - t: PropTypes.func, - } - - render () { - const { t } = this.context - const { hideModal, disconnectAll, history } = this.props - - return ( - hideModal()} - hideFooter - > -
    -
    - { t('disconnectAllModalDescription') } -
    - - -
    -
    - ) - } -} diff --git a/ui/app/components/app/modals/disconnect-all/index.js b/ui/app/components/app/modals/disconnect-all/index.js deleted file mode 100644 index 7fdfac530b..0000000000 --- a/ui/app/components/app/modals/disconnect-all/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './disconnect-all.container' diff --git a/ui/app/components/app/modals/disconnect-all/index.scss b/ui/app/components/app/modals/disconnect-all/index.scss deleted file mode 100644 index 8f69baadeb..0000000000 --- a/ui/app/components/app/modals/disconnect-all/index.scss +++ /dev/null @@ -1,38 +0,0 @@ -.disconnect-all-modal { - height: 160px; - display: flex; - flex-direction: column; - justify-content: space-between; - - &__description { - color: #24292E; - margin-bottom: 24px; - } - - &__cancel-button { - border: none; - margin-top: 12px; - } - - .btn-secondary { - border: none; - } -} - -.disconnect-all-modal-container { - .modal-container__header-text { - font-family: Roboto; - font-style: normal; - font-weight: bold; - font-size: 18px; - color: #24292E; - } - - .modal-container__content { - padding-bottom: 18px; - - @media screen and (max-width: 575px) { - padding-bottom: 18px; - } - } -} diff --git a/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js b/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js index 661bbdefdb..53ff473e4b 100644 --- a/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js +++ b/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js @@ -76,22 +76,18 @@ export default class EditApprovalPermission extends PureComponent { className="edit-approval-permission__edit-section__radio-button" onClick={() => this.setState({ selectedOptionIsUnlimited: true })} > -
    +
    { selectedOptionIsUnlimited &&
    }
    -
    +
    { tokenAmount < tokenBalance ? t('proposedApprovalLimit') @@ -111,22 +107,18 @@ export default class EditApprovalPermission extends PureComponent { className="edit-approval-permission__edit-section__radio-button" onClick={() => this.setState({ selectedOptionIsUnlimited: false })} > -
    +
    { !selectedOptionIsUnlimited &&
    }
    -
    +
    { t('customSpendLimit') }
    diff --git a/ui/app/components/app/modals/export-private-key-modal.js b/ui/app/components/app/modals/export-private-key-modal.js new file mode 100644 index 0000000000..1e1aaeb741 --- /dev/null +++ b/ui/app/components/app/modals/export-private-key-modal.js @@ -0,0 +1,178 @@ +const log = require('loglevel') +const Component = require('react').Component +const PropTypes = require('prop-types') +const h = require('react-hyperscript') +const inherits = require('util').inherits +const connect = require('react-redux').connect +const { stripHexPrefix } = require('ethereumjs-util') +const actions = require('../../../store/actions') +const AccountModalContainer = require('./account-modal-container') +const { getSelectedIdentity } = require('../../../selectors/selectors') +const ReadOnlyInput = require('../../ui/readonly-input') +const copyToClipboard = require('copy-to-clipboard') +const { checksumAddress } = require('../../../helpers/utils/util') +import Button from '../../ui/button' + +function mapStateToPropsFactory () { + let selectedIdentity = null + return function mapStateToProps (state) { + // We should **not** change the identity displayed here even if it changes from underneath us. + // If we do, we will be showing the user one private key and a **different** address and name. + // Note that the selected identity **will** change from underneath us when we unlock the keyring + // which is the expected behavior that we are side-stepping. + selectedIdentity = selectedIdentity || getSelectedIdentity(state) + return { + warning: state.appState.warning, + privateKey: state.appState.accountDetail.privateKey, + network: state.metamask.network, + selectedIdentity, + previousModalState: state.appState.modal.previousModalState.name, + } + } +} + +function mapDispatchToProps (dispatch) { + return { + exportAccount: (password, address) => { + return dispatch(actions.exportAccount(password, address)) + .then((res) => { + dispatch(actions.hideWarning()) + return res + }) + }, + showAccountDetailModal: () => dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' })), + hideModal: () => dispatch(actions.hideModal()), + } +} + +inherits(ExportPrivateKeyModal, Component) +function ExportPrivateKeyModal () { + Component.call(this) + + this.state = { + password: '', + privateKey: null, + showWarning: true, + } +} + +ExportPrivateKeyModal.contextTypes = { + t: PropTypes.func, +} + +module.exports = connect(mapStateToPropsFactory, mapDispatchToProps)(ExportPrivateKeyModal) + + +ExportPrivateKeyModal.prototype.exportAccountAndGetPrivateKey = function (password, address) { + const { exportAccount } = this.props + + exportAccount(password, address) + .then(privateKey => this.setState({ + privateKey, + showWarning: false, + })) + .catch((e) => log.error(e)) +} + +ExportPrivateKeyModal.prototype.renderPasswordLabel = function (privateKey) { + return h('span.private-key-password-label', privateKey + ? this.context.t('copyPrivateKey') + : this.context.t('typePassword') + ) +} + +ExportPrivateKeyModal.prototype.renderPasswordInput = function (privateKey) { + const plainKey = privateKey && stripHexPrefix(privateKey) + + return privateKey + ? h(ReadOnlyInput, { + wrapperClass: 'private-key-password-display-wrapper', + inputClass: 'private-key-password-display-textarea', + textarea: true, + value: plainKey, + onClick: () => copyToClipboard(plainKey), + }) + : h('input.private-key-password-input', { + type: 'password', + onChange: event => this.setState({ password: event.target.value }), + }) +} + +ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, address, hideModal) { + return h('div.export-private-key-buttons', {}, [ + !privateKey && h(Button, { + type: 'default', + large: true, + className: 'export-private-key__button export-private-key__button--cancel', + onClick: () => hideModal(), + }, this.context.t('cancel')), + + (privateKey + ? ( + h(Button, { + type: 'secondary', + large: true, + className: 'export-private-key__button', + onClick: () => hideModal(), + }, this.context.t('done')) + ) : ( + h(Button, { + type: 'secondary', + large: true, + className: 'export-private-key__button', + disabled: !this.state.password, + onClick: () => this.exportAccountAndGetPrivateKey(this.state.password, address), + }, this.context.t('confirm')) + ) + ), + + ]) +} + +ExportPrivateKeyModal.prototype.render = function () { + const { + selectedIdentity, + warning, + showAccountDetailModal, + hideModal, + previousModalState, + } = this.props + const { name, address } = selectedIdentity + + const { + privateKey, + showWarning, + } = this.state + + return h(AccountModalContainer, { + selectedIdentity, + showBackButton: previousModalState === 'ACCOUNT_DETAILS', + backButtonAction: () => showAccountDetailModal(), + }, [ + + h('span.account-name', name), + + h(ReadOnlyInput, { + wrapperClass: 'ellip-address-wrapper', + inputClass: 'qr-ellip-address ellip-address', + value: checksumAddress(address), + }), + + h('div.account-modal-divider'), + + h('span.modal-body-title', this.context.t('showPrivateKeys')), + + h('div.private-key-password', {}, [ + this.renderPasswordLabel(privateKey), + + this.renderPasswordInput(privateKey), + + showWarning && warning ? h('span.private-key-password-error', warning) : null, + ]), + + h('div.private-key-password-warning', this.context.t('privateKeyWarning')), + + this.renderButtons(privateKey, address, hideModal), + + ]) +} diff --git a/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.component.js b/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.component.js deleted file mode 100644 index fd70cbc5b2..0000000000 --- a/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.component.js +++ /dev/null @@ -1,168 +0,0 @@ -import log from 'loglevel' -import PropTypes from 'prop-types' -import React, { Component } from 'react' - -const { stripHexPrefix } = require('ethereumjs-util') -const copyToClipboard = require('copy-to-clipboard') -const { checksumAddress } = require('../../../../helpers/utils/util') -const ReadOnlyInput = require('../../../ui/readonly-input') -import Button from '../../../ui/button' -import AccountModalContainer from '../account-modal-container' - -export default class ExportPrivateKeyModal extends Component { - static contextTypes = { - t: PropTypes.func, - } - - static defaultProps = { - warning: null, - previousModalState: null, - } - - static propTypes = { - exportAccount: PropTypes.func.isRequired, - selectedIdentity: PropTypes.object.isRequired, - warning: PropTypes.node, - showAccountDetailModal: PropTypes.func.isRequired, - hideModal: PropTypes.func.isRequired, - previousModalState: PropTypes.string, - } - - state = { - password: '', - privateKey: null, - showWarning: true, - } - - exportAccountAndGetPrivateKey = (password, address) => { - const { exportAccount } = this.props - - exportAccount(password, address) - .then(privateKey => this.setState({ - privateKey, - showWarning: false, - })) - .catch((e) => log.error(e)) - } - - renderPasswordLabel (privateKey) { - return ( - - { - privateKey - ? this.context.t('copyPrivateKey') - : this.context.t('typePassword') - } - - ) - } - - renderPasswordInput (privateKey) { - const plainKey = privateKey && stripHexPrefix(privateKey) - - if (!privateKey) { - return ( - this.setState({ password: event.target.value })} - /> - ) - } - - return ( - copyToClipboard(plainKey)} - /> - ) - } - - renderButtons (privateKey, address, hideModal) { - return ( -
    - {!privateKey && ( - - )} - { - privateKey - ? ( - - ) - : ( - - ) - } -
    - ) - } - - render () { - const { - selectedIdentity, - warning, - showAccountDetailModal, - hideModal, - previousModalState, - } = this.props - const { name, address } = selectedIdentity - - const { - privateKey, - showWarning, - } = this.state - - return ( - showAccountDetailModal()} - > - {name} - -
    - {this.context.t('showPrivateKeys')} -
    - {this.renderPasswordLabel(privateKey)} - {this.renderPasswordInput(privateKey)} - { - (showWarning && warning) - ? {warning} - : null - } -
    -
    {this.context.t('privateKeyWarning')}
    - {this.renderButtons(privateKey, address, hideModal)} - - ) - } -} diff --git a/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.container.js b/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.container.js deleted file mode 100644 index 1b8f63c947..0000000000 --- a/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.container.js +++ /dev/null @@ -1,38 +0,0 @@ -import { connect } from 'react-redux' -import { exportAccount, hideWarning, showModal, hideModal } from '../../../../store/actions' -import { getSelectedIdentity } from '../../../../selectors/selectors' -import ExportPrivateKeyModal from './export-private-key-modal.component' - -function mapStateToPropsFactory () { - let selectedIdentity = null - return function mapStateToProps (state) { - // We should **not** change the identity displayed here even if it changes from underneath us. - // If we do, we will be showing the user one private key and a **different** address and name. - // Note that the selected identity **will** change from underneath us when we unlock the keyring - // which is the expected behavior that we are side-stepping. - selectedIdentity = selectedIdentity || getSelectedIdentity(state) - return { - warning: state.appState.warning, - privateKey: state.appState.accountDetail.privateKey, - network: state.metamask.network, - selectedIdentity, - previousModalState: state.appState.modal.previousModalState.name, - } - } -} - -function mapDispatchToProps (dispatch) { - return { - exportAccount: (password, address) => { - return dispatch(exportAccount(password, address)) - .then((res) => { - dispatch(hideWarning()) - return res - }) - }, - showAccountDetailModal: () => dispatch(showModal({ name: 'ACCOUNT_DETAILS' })), - hideModal: () => dispatch(hideModal()), - } -} - -export default connect(mapStateToPropsFactory, mapDispatchToProps)(ExportPrivateKeyModal) diff --git a/ui/app/components/app/modals/export-private-key-modal/index.js b/ui/app/components/app/modals/export-private-key-modal/index.js deleted file mode 100644 index 996c995ca7..0000000000 --- a/ui/app/components/app/modals/export-private-key-modal/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './export-private-key-modal.container' diff --git a/ui/app/components/app/modals/hide-token-confirmation-modal.js b/ui/app/components/app/modals/hide-token-confirmation-modal.js index c3709e9239..e2b098923c 100644 --- a/ui/app/components/app/modals/hide-token-confirmation-modal.js +++ b/ui/app/components/app/modals/hide-token-confirmation-modal.js @@ -1,5 +1,6 @@ -import PropTypes from 'prop-types' -import React, { Component } from 'react' +const Component = require('react').Component +const PropTypes = require('prop-types') +const h = require('react-hyperscript') const inherits = require('util').inherits const connect = require('react-redux').connect const actions = require('../../../store/actions') @@ -39,43 +40,44 @@ HideTokenConfirmationModal.contextTypes = { module.exports = connect(mapStateToProps, mapDispatchToProps)(HideTokenConfirmationModal) -HideTokenConfirmationModal.prototype.render = function HideTokenConfirmationModal () { +HideTokenConfirmationModal.prototype.render = function () { const { token, network, hideToken, hideModal, assetImages } = this.props const { symbol, address } = token const image = assetImages[address] - return ( -
    -
    -
    - {this.context.t('hideTokenPrompt')} -
    - -
    {symbol}
    -
    - {this.context.t('readdToken')} -
    -
    - - -
    -
    -
    - ) + return h('div.hide-token-confirmation', {}, [ + h('div.hide-token-confirmation__container', { + }, [ + h('div.hide-token-confirmation__title', {}, [ + this.context.t('hideTokenPrompt'), + ]), + + h(Identicon, { + className: 'hide-token-confirmation__identicon', + diameter: 45, + address, + network, + image, + }), + + h('div.hide-token-confirmation__symbol', {}, symbol), + + h('div.hide-token-confirmation__copy', {}, [ + this.context.t('readdToken'), + ]), + + h('div.hide-token-confirmation__buttons', {}, [ + h('button.btn-default.hide-token-confirmation__button.btn--large', { + onClick: () => hideModal(), + }, [ + this.context.t('cancel'), + ]), + h('button.btn-secondary.hide-token-confirmation__button.btn--large', { + onClick: () => hideToken(address), + }, [ + this.context.t('hide'), + ]), + ]), + ]), + ]) } diff --git a/ui/app/components/app/modals/index.scss b/ui/app/components/app/modals/index.scss index dbf47265f3..da7a27b840 100644 --- a/ui/app/components/app/modals/index.scss +++ b/ui/app/components/app/modals/index.scss @@ -11,9 +11,3 @@ @import './add-to-addressbook-modal/index'; @import './edit-approval-permission/index'; - -@import './disconnect-account/index'; - -@import './disconnect-all/index'; - -@import './new-account-modal/index'; diff --git a/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js b/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js index 9dc9533156..6f32253820 100644 --- a/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js +++ b/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js @@ -75,8 +75,7 @@ export default class MetaMetricsOptInModal extends Component {
    - This data is aggregated and is therefore anonymous for the purposes of General Data Protection Regulation (EU) 2016/679. For more information in relation to our privacy practices, please see our  - , + contents: [ + h(DepositEtherModal, {}, []), + ], onHide: (props) => props.hideWarning(), mobileModalStyle: { width: '100%', @@ -115,88 +115,9 @@ const MODALS = { }, ADD_TO_ADDRESSBOOK: { - contents: , - mobileModalStyle: { - width: '95%', - top: '10%', - boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', - transform: 'none', - left: '0', - right: '0', - margin: '0 auto', - borderRadius: '10px', - }, - laptopModalStyle: { - width: '375px', - top: '10%', - boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', - transform: 'none', - left: '0', - right: '0', - margin: '0 auto', - borderRadius: '10px', - }, - contentStyle: { - borderRadius: '10px', - }, - }, - - NEW_ACCOUNT: { - contents: , - mobileModalStyle: { - width: '95%', - top: '10%', - boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', - transform: 'none', - left: '0', - right: '0', - margin: '0 auto', - borderRadius: '10px', - }, - laptopModalStyle: { - width: '375px', - top: '10%', - boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', - transform: 'none', - left: '0', - right: '0', - margin: '0 auto', - borderRadius: '10px', - }, - contentStyle: { - borderRadius: '10px', - }, - }, - - DISCONNECT_ACCOUNT: { - contents: , - mobileModalStyle: { - width: '95%', - top: '10%', - boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', - transform: 'none', - left: '0', - right: '0', - margin: '0 auto', - borderRadius: '10px', - }, - laptopModalStyle: { - width: '375px', - top: '10%', - boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', - transform: 'none', - left: '0', - right: '0', - margin: '0 auto', - borderRadius: '10px', - }, - contentStyle: { - borderRadius: '10px', - }, - }, - - DISCONNECT_ALL: { - contents: , + contents: [ + h(AddToAddressBookModal, {}, []), + ], mobileModalStyle: { width: '95%', top: '10%', @@ -223,17 +144,23 @@ const MODALS = { }, ACCOUNT_DETAILS: { - contents: , + contents: [ + h(AccountDetailsModal, {}, []), + ], ...accountModalStyle, }, EXPORT_PRIVATE_KEY: { - contents: , + contents: [ + h(ExportPrivateKeyModal, {}, []), + ], ...accountModalStyle, }, HIDE_TOKEN_CONFIRMATION: { - contents: , + contents: [ + h(HideTokenConfirmationModal, {}, []), + ], mobileModalStyle: { width: '95%', top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh', @@ -244,8 +171,21 @@ const MODALS = { }, }, + CLEAR_APPROVED_ORIGINS: { + contents: h(ClearApprovedOrigins), + mobileModalStyle: { + ...modalContainerMobileStyle, + }, + laptopModalStyle: { + ...modalContainerLaptopStyle, + }, + contentStyle: { + borderRadius: '8px', + }, + }, + METAMETRICS_OPT_IN_MODAL: { - contents: , + contents: h(MetaMetricsOptInModal), mobileModalStyle: { ...modalContainerMobileStyle, width: '100%', @@ -262,7 +202,12 @@ const MODALS = { }, GAS_PRICE_INFO_MODAL: { - contents: , + contents: [ + h(NotifcationModal, { + header: 'gasPriceNoDenom', + message: 'gasPriceInfoModalContent', + }), + ], mobileModalStyle: { width: '95%', top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh', @@ -274,7 +219,12 @@ const MODALS = { }, GAS_LIMIT_INFO_MODAL: { - contents: , + contents: [ + h(NotifcationModal, { + header: 'gasLimit', + message: 'gasLimitInfoModalContent', + }), + ], mobileModalStyle: { width: '95%', top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh', @@ -286,7 +236,7 @@ const MODALS = { }, CONFIRM_RESET_ACCOUNT: { - contents: , + contents: h(ConfirmResetAccount), mobileModalStyle: { ...modalContainerMobileStyle, }, @@ -299,7 +249,7 @@ const MODALS = { }, CONFIRM_REMOVE_ACCOUNT: { - contents: , + contents: h(ConfirmRemoveAccount), mobileModalStyle: { ...modalContainerMobileStyle, }, @@ -312,7 +262,7 @@ const MODALS = { }, CONFIRM_DELETE_NETWORK: { - contents: , + contents: h(ConfirmDeleteNetwork), mobileModalStyle: { ...modalContainerMobileStyle, }, @@ -325,7 +275,9 @@ const MODALS = { }, CUSTOMIZE_GAS: { - contents: , + contents: [ + h(ConfirmCustomizeGasModal), + ], mobileModalStyle: { width: '100vw', height: '100vh', @@ -354,7 +306,7 @@ const MODALS = { }, EDIT_APPROVAL_PERMISSION: { - contents: , + contents: h(EditApprovalPermission), mobileModalStyle: { width: '95vw', height: '100vh', @@ -380,7 +332,7 @@ const MODALS = { TRANSACTION_CONFIRMED: { disableBackdropClick: true, - contents: , + contents: h(TransactionConfirmed), mobileModalStyle: { ...modalContainerMobileStyle, }, @@ -393,7 +345,7 @@ const MODALS = { }, QR_SCANNER: { - contents: , + contents: h(QRScanner), mobileModalStyle: { ...modalContainerMobileStyle, }, @@ -406,7 +358,7 @@ const MODALS = { }, CANCEL_TRANSACTION: { - contents: , + contents: h(CancelTransaction), mobileModalStyle: { ...modalContainerMobileStyle, }, @@ -419,7 +371,7 @@ const MODALS = { }, REJECT_TRANSACTIONS: { - contents: , + contents: h(RejectTransactions), mobileModalStyle: { ...modalContainerMobileStyle, }, @@ -478,26 +430,25 @@ Modal.prototype.render = function () { const modalStyle = modal[isMobileView() ? 'mobileModalStyle' : 'laptopModalStyle'] const contentStyle = modal.contentStyle || {} - return ( - { + return h(FadeModal, + { + className: 'modal', + keyboard: false, + onHide: () => { if (modal.onHide) { modal.onHide(this.props) } this.onHide(modal.customOnHideOpts) - }} - ref={(ref) => { + }, + ref: (ref) => { this.modalRef = ref - }} - modalStyle={modalStyle} - contentStyle={contentStyle} - backdropStyle={BACKDROPSTYLE} - closeOnClick={!disableBackdropClick} - > - {children} - + }, + modalStyle, + contentStyle, + backdropStyle: BACKDROPSTYLE, + closeOnClick: !disableBackdropClick, + }, + children, ) } diff --git a/ui/app/components/app/modals/new-account-modal/index.js b/ui/app/components/app/modals/new-account-modal/index.js deleted file mode 100644 index 2c8b788904..0000000000 --- a/ui/app/components/app/modals/new-account-modal/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './new-account-modal.container' diff --git a/ui/app/components/app/modals/new-account-modal/index.scss b/ui/app/components/app/modals/new-account-modal/index.scss deleted file mode 100644 index d6c2d0ac1c..0000000000 --- a/ui/app/components/app/modals/new-account-modal/index.scss +++ /dev/null @@ -1,37 +0,0 @@ -.new-account-modal { - @extend %col-nowrap; - @extend %modal; - - &__content { - @extend %col-nowrap; - padding: 1.5rem; - border-bottom: 1px solid $Grey-100; - - &__header { - @extend %h3; - } - } - - &__input-label { - color: $Grey-600; - margin-top: 1.25rem; - } - - &__input { - @extend %input; - margin-top: 0.75rem; - - &::placeholder { - color: $Grey-300; - } - } - - &__footer { - @extend %row-nowrap; - padding: 1rem; - - button + button { - margin-left: 1rem; - } - } -} diff --git a/ui/app/components/app/modals/new-account-modal/new-account-modal.component.js b/ui/app/components/app/modals/new-account-modal/new-account-modal.component.js deleted file mode 100644 index 26224ae63b..0000000000 --- a/ui/app/components/app/modals/new-account-modal/new-account-modal.component.js +++ /dev/null @@ -1,78 +0,0 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' -import Button from '../../../ui/button/button.component' - -export default class NewAccountModal extends Component { - - static contextTypes = { - t: PropTypes.func, - } - - static propTypes = { - hideModal: PropTypes.func.isRequired, - newAccountNumber: PropTypes.number.isRequired, - onSave: PropTypes.func.isRequired, - } - - state = { - alias: '', - } - - onChange = e => { - this.setState({ - alias: e.target.value, - }) - } - - onSubmit = () => { - this.props.onSave(this.state.alias) - .then(this.props.hideModal) - } - - onKeyPress = e => { - if (e.key === 'Enter' && this.state.alias) { - this.onSubmit() - } - } - - render () { - const { t } = this.context - - return ( -
    -
    -
    - {t('newAccount')} -
    -
    - {t('accountName')} -
    - -
    -
    - - -
    -
    - ) - } -} diff --git a/ui/app/components/app/modals/new-account-modal/new-account-modal.container.js b/ui/app/components/app/modals/new-account-modal/new-account-modal.container.js deleted file mode 100644 index 812e98dbd1..0000000000 --- a/ui/app/components/app/modals/new-account-modal/new-account-modal.container.js +++ /dev/null @@ -1,44 +0,0 @@ -import { connect } from 'react-redux' -import NewAccountModal from './new-account-modal.component' -import actions from '../../../../store/actions' - -function mapStateToProps (state) { - return { - ...state.appState.modal.modalState.props || {}, - } -} - -function mapDispatchToProps (dispatch) { - return { - hideModal: () => dispatch(actions.hideModal()), - createAccount: newAccountName => { - return dispatch(actions.addNewAccount()) - .then(newAccountAddress => { - if (newAccountName) { - dispatch(actions.setAccountLabel(newAccountAddress, newAccountName)) - } - return newAccountAddress - }) - }, - } -} - -function mergeProps (stateProps, dispatchProps) { - const { - onCreateNewAccount, - } = stateProps - const { - createAccount, - } = dispatchProps - - return { - ...stateProps, - ...dispatchProps, - onSave: (newAccountName) => { - return createAccount(newAccountName) - .then(newAccountAddress => onCreateNewAccount(newAccountAddress)) - }, - } -} - -export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(NewAccountModal) diff --git a/ui/app/components/app/modals/notification-modal.js b/ui/app/components/app/modals/notification-modal.js index 800cd4f2f8..84d9004b7b 100644 --- a/ui/app/components/app/modals/notification-modal.js +++ b/ui/app/components/app/modals/notification-modal.js @@ -1,13 +1,10 @@ -import PropTypes from 'prop-types' -import React, {Component} from 'react' -import {connect} from 'react-redux' -import { hideModal } from '../../../store/actions' +const { Component } = require('react') +const PropTypes = require('prop-types') +const h = require('react-hyperscript') +const connect = require('react-redux').connect +const actions = require('../../../store/actions') class NotificationModal extends Component { - static contextProps = { - t: PropTypes.func.isRequired, - } - render () { const { header, @@ -18,48 +15,43 @@ class NotificationModal extends Component { onConfirm, } = this.props - const { t } = this.context - const showButtons = showCancelButton || showConfirmButton - return ( -
    -
    -
    - {this.context.t(header)} -
    -
    -
    - {this.context.t(message)} -
    -
    -
    - {showButtons && ( -
    - {showCancelButton && ( -
    - {t('cancel')} -
    - )} - {showConfirmButton && ( -
    { - onConfirm() - hideModal() - }} - > - {t('confirm')} -
    - )} -
    - )} -
    -
    - ) + return h('div', [ + h('div.notification-modal__wrapper', { + }, [ + + h('div.notification-modal__header', {}, [ + this.context.t(header), + ]), + + h('div.notification-modal__message-wrapper', {}, [ + h('div.notification-modal__message', {}, [ + this.context.t(message), + ]), + ]), + + h('div.modal-close-x', { + onClick: hideModal, + }), + + showButtons && h('div.notification-modal__buttons', [ + + showCancelButton && h('div.btn-default.notification-modal__buttons__btn', { + onClick: hideModal, + }, 'Cancel'), + + showConfirmButton && h('div.button.btn-secondary.notification-modal__buttons__btn', { + onClick: () => { + onConfirm() + hideModal() + }, + }, 'Confirm'), + + ]), + + ]), + ]) } } @@ -76,7 +68,7 @@ NotificationModal.propTypes = { const mapDispatchToProps = dispatch => { return { hideModal: () => { - dispatch(hideModal()) + dispatch(actions.hideModal()) }, } } diff --git a/ui/app/components/app/multiple-notifications/multiple-notifications.component.js b/ui/app/components/app/multiple-notifications/multiple-notifications.component.js index 59f5400450..f9f6fe8877 100644 --- a/ui/app/components/app/multiple-notifications/multiple-notifications.component.js +++ b/ui/app/components/app/multiple-notifications/multiple-notifications.component.js @@ -38,13 +38,9 @@ export default class MultipleNotifications extends PureComponent { className="home-notification-wrapper__i-container" onClick={() => this.setState({ showAll: !showAll })} > - {childrenToRender.length > 1 ? ( - - ) : null} + {childrenToRender.length > 1 ? : null}
    ) diff --git a/ui/app/components/app/network-display/network-display.component.js b/ui/app/components/app/network-display/network-display.component.js index 1ecec3179d..bcac637bd4 100644 --- a/ui/app/components/app/network-display/network-display.component.js +++ b/ui/app/components/app/network-display/network-display.component.js @@ -38,15 +38,13 @@ export default class NetworkDisplay extends Component { return networkClass ?
    - : ( -
    - ) + :
    } render () { @@ -63,15 +61,13 @@ export default class NetworkDisplay extends Component { { networkClass ?
    - : ( -
    - ) + :
    }
    { type === 'rpc' && nickname ? nickname : this.context.t(type) } diff --git a/ui/app/components/app/network.js b/ui/app/components/app/network.js index 88204aef7c..d46906a669 100644 --- a/ui/app/components/app/network.js +++ b/ui/app/components/app/network.js @@ -1,6 +1,7 @@ -import PropTypes from 'prop-types' -import React, {Component} from 'react' - +const Component = require('react').Component +const PropTypes = require('prop-types') +const h = require('react-hyperscript') +const connect = require('react-redux').connect const classnames = require('classnames') const inherits = require('util').inherits const NetworkDropdownIcon = require('./dropdowns/components/network-dropdown-icon') @@ -9,7 +10,8 @@ Network.contextTypes = { t: PropTypes.func, } -module.exports = Network +module.exports = connect()(Network) + inherits(Network, Component) @@ -17,14 +19,15 @@ function Network () { Component.call(this) } -Network.prototype.render = function Network () { +Network.prototype.render = function () { + const props = this.props const context = this.context - const networkNumber = this.props.network + const networkNumber = props.network let providerName, providerNick, providerUrl try { - providerName = this.props.provider.type - providerNick = this.props.provider.nickname || '' - providerUrl = this.props.provider.rpcTarget + providerName = props.provider.type + providerNick = props.provider.nickname || '' + providerUrl = props.provider.rpcTarget } catch (e) { providerName = null } @@ -53,124 +56,96 @@ Network.prototype.render = function Network () { } return ( -
    { + }), + title: hoverText, + onClick: (event) => { if (!this.props.disabled) { this.props.onClick(event) } - }} - > - {(function () { + }, + }, [ + (function () { switch (iconName) { case 'ethereum-network': - return ( -
    - -
    - {context.t('mainnet')} -
    -
    -
    - ) + return h('.network-indicator', [ + h(NetworkDropdownIcon, { + backgroundColor: '#038789', // $blue-lagoon + nonSelectBackgroundColor: '#15afb2', + loading: networkNumber === 'loading', + }), + h('.network-name', context.t('mainnet')), + h('.network-indicator__down-arrow'), + ]) case 'ropsten-test-network': - return ( -
    - -
    - {context.t('ropsten')} -
    -
    -
    - ) + return h('.network-indicator', [ + h(NetworkDropdownIcon, { + backgroundColor: '#e91550', // $crimson + nonSelectBackgroundColor: '#ec2c50', + loading: networkNumber === 'loading', + }), + h('.network-name', context.t('ropsten')), + h('.network-indicator__down-arrow'), + ]) case 'kovan-test-network': - return ( -
    - -
    - {context.t('kovan')} -
    -
    -
    - ) + return h('.network-indicator', [ + h(NetworkDropdownIcon, { + backgroundColor: '#690496', // $purple + nonSelectBackgroundColor: '#b039f3', + loading: networkNumber === 'loading', + }), + h('.network-name', context.t('kovan')), + h('.network-indicator__down-arrow'), + ]) case 'rinkeby-test-network': - return ( -
    - -
    - {context.t('rinkeby')} -
    -
    -
    - ) + return h('.network-indicator', [ + h(NetworkDropdownIcon, { + backgroundColor: '#ebb33f', // $tulip-tree + nonSelectBackgroundColor: '#ecb23e', + loading: networkNumber === 'loading', + }), + h('.network-name', context.t('rinkeby')), + h('.network-indicator__down-arrow'), + ]) case 'goerli-test-network': - return ( -
    - -
    {context.t('goerli')}
    -
    -
    - ) + return h('.network-indicator', [ + h(NetworkDropdownIcon, { + backgroundColor: '#3099f2', // $dodger-blue + nonSelectBackgroundColor: '#ecb23e', + loading: networkNumber === 'loading', + }), + h('.network-name', context.t('goerli')), + h('.network-indicator__down-arrow'), + ]) default: - return ( -
    - {networkNumber === 'loading' - ? ( - this.props.onClick(event)} - > - - - ) - : ( - - )} -
    - { - providerName === 'localhost' - ? context.t('localhost') - : providerNick || context.t('privateNetwork') - } -
    -
    -
    - ) + return h('.network-indicator', [ + networkNumber === 'loading' + ? h('span.pointer.network-loading-spinner', { + onClick: (event) => this.props.onClick(event), + }, [ + h('img', { + title: context.t('attemptingConnect'), + src: 'images/loading.svg', + }), + ]) + : h('i.fa.fa-question-circle.fa-lg', { + style: { + color: 'rgb(125, 128, 130)', + }, + }), + + h('.network-name', providerName === 'localhost' ? context.t('localhost') : providerNick || context.t('privateNetwork')), + h('.network-indicator__down-arrow'), + ]) } - })()} -
    + })(), + ]) ) } diff --git a/ui/app/components/app/permission-page-container/index.js b/ui/app/components/app/permission-page-container/index.js deleted file mode 100644 index ea3b8daaa2..0000000000 --- a/ui/app/components/app/permission-page-container/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export {default} from './permission-page-container.container' -export {default as PermissionPageContainerContent} from './permission-page-container-content' -export {default as PermissionPageContainerHeader} from './permission-page-container-header' diff --git a/ui/app/components/app/permission-page-container/index.scss b/ui/app/components/app/permission-page-container/index.scss deleted file mode 100644 index 7979867fa7..0000000000 --- a/ui/app/components/app/permission-page-container/index.scss +++ /dev/null @@ -1,281 +0,0 @@ -.permission-approval-container { - display: flex; - border: none; - box-shadow: none; - margin-top: 45px; - width: 466px; - min-height: 468px; - - &__header { - display: flex; - flex-direction: column; - align-items: flex-end; - border-bottom: 1px solid $geyser; - padding: 9px; - } - - &__title { - @extend %header--18; - line-height: 25px; - text-align: center; - position: fixed; - left: 0; - width: 100%; - } - - &__content { - display: flex; - overflow-y: auto; - flex: 1; - flex-direction: column; - color: #7C808E; - - &--redirect { - margin-top: 60px; - } - - h1, h2 { - color: #4A4A4A; - display: flex; - justify-content: center; - text-align: center; - } - - h2 { - font-size: 16px; - line-height: 18px; - padding: 20px; - } - - h1 { - font-size: 22px; - line-height: 26px; - padding: 20px; - } - - p { - padding: 0 40px; - text-align: center; - font-size: 12px; - line-height: 18px; - } - - a, a:hover { - color: $dodger-blue; - } - - section { - h1 { - padding: 30px 0px 0px 0px; - } - - h2 { - padding: 0px 0px 20px 0px; - } - } - - &__requested { - text-align: left; - } - - &__revoke-note { - margin-top: 24px; - } - - &__checkbox { - margin-right: 10px; - } - - &__permission { - margin-top: 18px; - - i { - color: #6A737D; - } - label { - margin-left: 6px; - color: #24292E; - } - } - - .permission-approval-visual { - display: flex; - flex-direction: row; - justify-content: space-evenly; - position: relative; - margin: 0 32px; - margin-top: 40px; - - section { - display: flex; - flex-direction: column; - align-items: center; - flex: 1; - } - - h1 { - font-size: 14px; - line-height: 18px; - padding: 8px 0 0; - } - - h2 { - font-size: 12px; - line-height: 17px; - color: #6A737D; - padding: 0; - } - - &__check { - width: 40px; - height: 40px; - background: white url("/images/permissions-check.svg") no-repeat; - margin-top: 24px; - z-index: 1; - } - - &__reject { - background: white; - z-index: 1; - display: flex; - justify-content: center; - align-items: center; - - i { - color: #D73A49; - transform: scale(3); - } - } - - &__broken-line { - z-index: 0; - position: absolute; - top: 43px; - } - - &__identicon, .icon-with-fallback__identicon { - width: 32px; - height: 32px; - z-index: 1; - - &--default { - background-color: #777A87; - color: white; - width: 64px; - height: 64px; - border-radius: 32px; - display: flex; - align-items: center; - justify-content: center; - font-weight: bold; - z-index: 1; - } - } - - &__identicon-container, .icon-with-fallback__identicon-container { - padding: 1rem; - flex: 1; - position: relative; - width: 100%; - display: flex; - justify-content: center; - align-items: center; - } - - &__identicon-border, .icon-with-fallback__identicon-border { - height: 64px; - width: 64px; - border-radius: 50%; - border: 1px solid white; - position: absolute; - background: #FFFFFF; - box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.25); - } - - &:before { - border-top: 2px dashed #CDD1E4; - content: ""; - margin: 0 auto; - position: absolute; - top: 32px; - left: 0; - bottom: 0; - right: 0; - width: 65%; - z-index: -1; - } - - &__account-info { - display: flex; - flex-direction: column; - align-items: center; - - &__label { - @extend %content-text; - line-height: 20px; - color: #000000; - } - - &__address { - @extend %font; - font-size: 12px; - line-height: 17px; - color: #6A737D; - } - } - } - - .secure-badge { - display: flex; - justify-content: center; - padding: 25px; - } - } - - &__permissions-header { - @extend %content-text; - line-height: 20px; - color: #6A737D; - - &--redirect { - text-align: center; - } - } - - &__permissions-container { - display: flex; - flex-direction: column; - margin-top: 33px; - } - - .page-container__footer { - border-top: none; - align-items: center; - - header { - width: 300px; - } - } - - &__permissions-header-redirect { - text-align: center; - } - - @media screen and (max-width: 575px) { - width: 100%; - margin-top: 25px; - padding: 10px; - - &__title { - position: initial; - } - - &__content-approval-visual { - margin-top: 16px; - } - - .page-container__footer header { - padding: 0; - } - } -} diff --git a/ui/app/components/app/permission-page-container/permission-page-container-content/index.js b/ui/app/components/app/permission-page-container/permission-page-container-content/index.js deleted file mode 100644 index 899d168f93..0000000000 --- a/ui/app/components/app/permission-page-container/permission-page-container-content/index.js +++ /dev/null @@ -1 +0,0 @@ -export {default} from './permission-page-container-content.component' 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 deleted file mode 100644 index d6a62dbbfa..0000000000 --- a/ui/app/components/app/permission-page-container/permission-page-container-content/permission-page-container-content.component.js +++ /dev/null @@ -1,163 +0,0 @@ -import PropTypes from 'prop-types' -import React, { PureComponent } from 'react' -import Identicon from '../../../ui/identicon' -import IconWithFallBack from '../../../ui/icon-with-fallback' -import classnames from 'classnames' - -export default class PermissionPageContainerContent extends PureComponent { - - static propTypes = { - requestMetadata: PropTypes.object.isRequired, - domainMetadata: PropTypes.object.isRequired, - selectedPermissions: PropTypes.object.isRequired, - permissionsDescriptions: PropTypes.object.isRequired, - onPermissionToggle: PropTypes.func.isRequired, - selectedAccount: PropTypes.object, - redirect: PropTypes.bool, - permissionRejected: PropTypes.bool, - } - - static defaultProps = { - redirect: null, - permissionRejected: null, - selectedAccount: {}, - } - - static contextTypes = { - t: PropTypes.func, - } - - renderAccountInfo = (account) => { - return ( -
    -
    - { account.label } -
    -
    - { account.truncatedAddress } -
    -
    - ) - } - - renderPermissionApprovalVisual = () => { - const { - requestMetadata, domainMetadata, selectedAccount, redirect, permissionRejected, - } = this.props - - return ( -
    -
    - - { redirect ? null :

    {domainMetadata.name}

    } - { redirect ? null :

    {requestMetadata.origin}

    } -
    - { permissionRejected - ? - : - } - -
    -
    -
    - -
    - { redirect ? null : this.renderAccountInfo(selectedAccount) } -
    -
    - ) - } - - renderRequestedPermissions () { - const { - selectedPermissions, permissionsDescriptions, onPermissionToggle, - } = this.props - const { t } = this.context - - const items = Object.keys(selectedPermissions).map((methodName) => { - - // the request will almost certainly be reject by rpc-cap if this happens - if (!permissionsDescriptions[methodName]) { - console.warn(`Unknown permission requested: ${methodName}`) - } - const description = permissionsDescriptions[methodName] || methodName - // don't allow deselecting eth_accounts - const isDisabled = methodName === 'eth_accounts' - - return ( -
    { - if (!isDisabled) { - onPermissionToggle(methodName) - } - }} - > - { selectedPermissions[methodName] - ? - : - } - -
    - ) - }) - - return ( -
    - {items} -
    { t('revokeInPermissions') }
    -
    - ) - } - - render () { - const { domainMetadata, redirect, permissionRejected } = this.props - const { t } = this.context - - let titleArgs - if (redirect && permissionRejected) { - titleArgs = [ 'cancelledConnectionWithMetaMask' ] - } else if (redirect) { - titleArgs = [ 'connectingWithMetaMask' ] - } else if (domainMetadata.extensionId) { - titleArgs = [ 'externalExtension', [domainMetadata.extensionId] ] - } else { - titleArgs = [ 'likeToConnect', [domainMetadata.name] ] - } - - return ( -
    -
    - { t(...titleArgs) } -
    - {this.renderPermissionApprovalVisual()} - { !redirect - ? ( -
    -
    - { domainMetadata.extensionId - ? t('thisWillAllowExternalExtension', [domainMetadata.extensionId]) - : t('thisWillAllow', [domainMetadata.name]) - } -
    - { this.renderRequestedPermissions() } -
    - ) - : ( -
    - { t('redirectingBackToDapp') } -
    - ) - } -
    - ) - } -} diff --git a/ui/app/components/app/permission-page-container/permission-page-container-header/index.js b/ui/app/components/app/permission-page-container/permission-page-container-header/index.js deleted file mode 100644 index 45ef9036b1..0000000000 --- a/ui/app/components/app/permission-page-container/permission-page-container-header/index.js +++ /dev/null @@ -1 +0,0 @@ -export {default} from './permission-page-container-header.component' diff --git a/ui/app/components/app/permission-page-container/permission-page-container.component.js b/ui/app/components/app/permission-page-container/permission-page-container.component.js deleted file mode 100644 index b7cbcd6ba6..0000000000 --- a/ui/app/components/app/permission-page-container/permission-page-container.component.js +++ /dev/null @@ -1,151 +0,0 @@ -import PropTypes from 'prop-types' -import React, { Component } from 'react' -import deepEqual from 'fast-deep-equal' -import { PermissionPageContainerContent } from '.' -import { PageContainerFooter } from '../../ui/page-container' - -export default class PermissionPageContainer extends Component { - - static propTypes = { - approvePermissionsRequest: PropTypes.func.isRequired, - rejectPermissionsRequest: PropTypes.func.isRequired, - selectedIdentity: PropTypes.object, - permissionsDescriptions: PropTypes.object.isRequired, - request: PropTypes.object, - redirect: PropTypes.bool, - permissionRejected: PropTypes.bool, - requestMetadata: PropTypes.object, - targetDomainMetadata: PropTypes.object.isRequired, - }; - - static defaultProps = { - redirect: null, - permissionRejected: null, - request: {}, - requestMetadata: {}, - selectedIdentity: {}, - }; - - static contextTypes = { - t: PropTypes.func, - metricsEvent: PropTypes.func, - }; - - state = { - selectedPermissions: this.getRequestedMethodState( - this.getRequestedMethodNames(this.props) - ), - } - - componentDidUpdate () { - const newMethodNames = this.getRequestedMethodNames(this.props) - - if (!deepEqual(Object.keys(this.state.selectedPermissions), newMethodNames)) { - // this should be a new request, so just overwrite - this.setState({ - selectedPermissions: this.getRequestedMethodState(newMethodNames), - }) - } - } - - getRequestedMethodState (methodNames) { - return methodNames.reduce( - (acc, methodName) => { - acc[methodName] = true - return acc - }, - {} - ) - } - - getRequestedMethodNames (props) { - return Object.keys(props.request.permissions || {}) - } - - onPermissionToggle = methodName => { - this.setState({ - selectedPermissions: { - ...this.state.selectedPermissions, - [methodName]: !this.state.selectedPermissions[methodName], - }, - }) - } - - componentDidMount () { - this.context.metricsEvent({ - eventOpts: { - category: 'Auth', - action: 'Connect', - name: 'Tab Opened', - }, - }) - } - - onCancel = () => { - const { request, rejectPermissionsRequest } = this.props - rejectPermissionsRequest(request.metadata.id) - } - - onSubmit = () => { - const { - request: _request, approvePermissionsRequest, rejectPermissionsRequest, selectedIdentity, - } = this.props - - const request = { - ..._request, - permissions: { ..._request.permissions }, - } - - Object.keys(this.state.selectedPermissions).forEach(key => { - if (!this.state.selectedPermissions[key]) { - delete request.permissions[key] - } - }) - - if (Object.keys(request.permissions).length > 0) { - approvePermissionsRequest(request, [selectedIdentity.address]) - } else { - rejectPermissionsRequest(request.metadata.id) - } - } - - render () { - const { - requestMetadata, - targetDomainMetadata, - permissionsDescriptions, - selectedIdentity, - redirect, - permissionRejected, - } = this.props - - return ( -
    - - { !redirect - ? ( - this.onCancel()} - cancelText={this.context.t('cancel')} - onSubmit={() => this.onSubmit()} - submitText={this.context.t('submit')} - submitButtonType="confirm" - buttonSizeLarge={false} - /> - ) - : null - } -
    - ) - } -} diff --git a/ui/app/components/app/permission-page-container/permission-page-container.container.js b/ui/app/components/app/permission-page-container/permission-page-container.container.js deleted file mode 100644 index f83393c70e..0000000000 --- a/ui/app/components/app/permission-page-container/permission-page-container.container.js +++ /dev/null @@ -1,28 +0,0 @@ -import { connect } from 'react-redux' -import { compose } from 'recompose' -import { withRouter } from 'react-router-dom' -import PermissionPageContainer from './permission-page-container.component' -import { - getPermissionsDescriptions, - getDomainMetadata, -} from '../../../selectors/selectors' - -const mapStateToProps = (state, ownProps) => { - const { request, cachedOrigin } = ownProps - const { metadata: requestMetadata = {} } = request || {} - - const domainMetadata = getDomainMetadata(state) - const origin = requestMetadata.origin || cachedOrigin - const targetDomainMetadata = (domainMetadata[origin] || { name: origin, icon: null }) - - return { - permissionsDescriptions: getPermissionsDescriptions(state), - requestMetadata, - targetDomainMetadata, - } -} - -export default compose( - withRouter, - connect(mapStateToProps) -)(PermissionPageContainer) diff --git a/ui/app/components/app/provider-page-container/index.js b/ui/app/components/app/provider-page-container/index.js new file mode 100644 index 0000000000..927c35940c --- /dev/null +++ b/ui/app/components/app/provider-page-container/index.js @@ -0,0 +1,3 @@ +export {default} from './provider-page-container.component' +export {default as ProviderPageContainerContent} from './provider-page-container-content' +export {default as ProviderPageContainerHeader} from './provider-page-container-header' diff --git a/ui/app/components/app/provider-page-container/index.scss b/ui/app/components/app/provider-page-container/index.scss new file mode 100644 index 0000000000..8d35ac1790 --- /dev/null +++ b/ui/app/components/app/provider-page-container/index.scss @@ -0,0 +1,121 @@ +.provider-approval-container { + display: flex; + + &__header { + display: flex; + flex-direction: column; + align-items: flex-end; + border-bottom: 1px solid $geyser; + padding: 9px; + } + + &__content { + display: flex; + overflow-y: auto; + flex: 1; + flex-direction: column; + justify-content: space-between; + color: #7C808E; + + h1, h2 { + color: #4A4A4A; + display: flex; + justify-content: center; + text-align: center; + } + + h2 { + font-size: 16px; + line-height: 18px; + padding: 20px; + } + + h1 { + font-size: 22px; + line-height: 26px; + padding: 20px; + } + + p { + padding: 0 40px; + text-align: center; + font-size: 12px; + line-height: 18px; + } + + a, a:hover { + color: $dodger-blue; + } + + .provider-approval-visual { + display: flex; + flex-direction: row; + justify-content: space-evenly; + position: relative; + margin: 0 32px; + + section { + display: flex; + flex-direction: column; + align-items: center; + flex: 1; + } + + h1 { + font-size: 14px; + line-height: 18px; + padding: 8px 0 0; + } + + h2 { + font-size: 10px; + line-height: 14px; + padding: 0; + color: #A2A4AC; + } + + &__check { + width: 40px; + height: 40px; + background: white url("/images/provider-approval-check.svg") no-repeat; + margin-top: 14px; + } + + &__identicon { + width: 64px; + height: 64px; + + &--default { + background-color: #777A87; + color: white; + width: 64px; + height: 64px; + border-radius: 32px; + display: flex; + align-items: center; + justify-content: center; + font-weight: bold; + } + } + + &:before { + border-top: 2px dashed #CDD1E4; + content: ""; + margin: 0 auto; + position: absolute; + top: 32px; + left: 0; + bottom: 0; + right: 0; + width: 65%; + z-index: -1; + } + } + + .secure-badge { + display: flex; + justify-content: center; + padding: 25px; + } + } +} diff --git a/ui/app/components/app/provider-page-container/provider-page-container-content/index.js b/ui/app/components/app/provider-page-container/provider-page-container-content/index.js new file mode 100644 index 0000000000..73e491adc8 --- /dev/null +++ b/ui/app/components/app/provider-page-container/provider-page-container-content/index.js @@ -0,0 +1 @@ +export {default} from './provider-page-container-content.container' diff --git a/ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.component.js b/ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.component.js new file mode 100644 index 0000000000..4062b130f6 --- /dev/null +++ b/ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.component.js @@ -0,0 +1,87 @@ +import PropTypes from 'prop-types' +import React, {PureComponent} from 'react' +import Identicon from '../../../ui/identicon' + +export default class ProviderPageContainerContent extends PureComponent { + static propTypes = { + origin: PropTypes.string.isRequired, + selectedIdentity: PropTypes.object.isRequired, + siteImage: PropTypes.string, + siteTitle: PropTypes.string, + hostname: PropTypes.string, + extensionId: PropTypes.string, + } + + static contextTypes = { + t: PropTypes.func, + }; + + renderConnectVisual = (title, identifier) => { + const { selectedIdentity, siteImage } = this.props + + return ( +
    +
    + {siteImage ? ( + + ) : ( + + {title.charAt(0).toUpperCase()} + + )} +

    {title}

    +

    {identifier}

    +
    + +
    + +

    {selectedIdentity.name}

    +
    +
    + ) + } + + render () { + const { siteTitle, hostname, extensionId } = this.props + const { t } = this.context + + const title = extensionId ? + 'External Extension' : + siteTitle || hostname + + const identifier = extensionId ? + `Extension ID: '${extensionId}'` : + hostname + + return ( +
    +
    +

    {t('connectRequest')}

    + {this.renderConnectVisual(title, identifier)} +

    {t('providerRequest', [title])}

    +

    + {t('providerRequestInfo')} +
    + + {t('learnMore')}. + +

    +
    +
    + +
    +
    + ) + } +} diff --git a/ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.container.js b/ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.container.js new file mode 100644 index 0000000000..4dbdddd161 --- /dev/null +++ b/ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.container.js @@ -0,0 +1,11 @@ +import { connect } from 'react-redux' +import ProviderPageContainerContent from './provider-page-container-content.component' +import { getSelectedIdentity } from '../../../../selectors/selectors' + +const mapStateToProps = (state) => { + return { + selectedIdentity: getSelectedIdentity(state), + } +} + +export default connect(mapStateToProps)(ProviderPageContainerContent) diff --git a/ui/app/components/app/provider-page-container/provider-page-container-header/index.js b/ui/app/components/app/provider-page-container/provider-page-container-header/index.js new file mode 100644 index 0000000000..430627d3a3 --- /dev/null +++ b/ui/app/components/app/provider-page-container/provider-page-container-header/index.js @@ -0,0 +1 @@ +export {default} from './provider-page-container-header.component' diff --git a/ui/app/components/app/permission-page-container/permission-page-container-header/permission-page-container-header.component.js b/ui/app/components/app/provider-page-container/provider-page-container-header/provider-page-container-header.component.js similarity index 58% rename from ui/app/components/app/permission-page-container/permission-page-container-header/permission-page-container-header.component.js rename to ui/app/components/app/provider-page-container/provider-page-container-header/provider-page-container-header.component.js index 8ba3444ba2..41bf6c3dd2 100644 --- a/ui/app/components/app/permission-page-container/permission-page-container-header/permission-page-container-header.component.js +++ b/ui/app/components/app/provider-page-container/provider-page-container-header/provider-page-container-header.component.js @@ -1,10 +1,10 @@ import React, {PureComponent} from 'react' import NetworkDisplay from '../../network-display' -export default class PermissionPageContainerHeader extends PureComponent { +export default class ProviderPageContainerHeader extends PureComponent { render () { return ( -
    +
    ) diff --git a/ui/app/components/app/provider-page-container/provider-page-container.component.js b/ui/app/components/app/provider-page-container/provider-page-container.component.js new file mode 100644 index 0000000000..7d152e4cbb --- /dev/null +++ b/ui/app/components/app/provider-page-container/provider-page-container.component.js @@ -0,0 +1,107 @@ +import PropTypes from 'prop-types' +import React, {PureComponent} from 'react' +import { ProviderPageContainerContent, ProviderPageContainerHeader } from '.' +import { PageContainerFooter } from '../../ui/page-container' +import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../../app/scripts/lib/enums' +import { getEnvironmentType } from '../../../../../app/scripts/lib/util' + +export default class ProviderPageContainer extends PureComponent { + static propTypes = { + approveProviderRequestByOrigin: PropTypes.func.isRequired, + rejectProviderRequestByOrigin: PropTypes.func.isRequired, + origin: PropTypes.string.isRequired, + siteImage: PropTypes.string, + siteTitle: PropTypes.string, + hostname: PropTypes.string, + extensionId: PropTypes.string, + }; + + static contextTypes = { + t: PropTypes.func, + metricsEvent: PropTypes.func, + }; + + componentDidMount () { + if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) { + window.addEventListener('beforeunload', this._beforeUnload) + } + this.context.metricsEvent({ + eventOpts: { + category: 'Auth', + action: 'Connect', + name: 'Popup Opened', + }, + }) + } + + _beforeUnload = () => { + const { origin, rejectProviderRequestByOrigin } = this.props + this.context.metricsEvent({ + eventOpts: { + category: 'Auth', + action: 'Connect', + name: 'Cancel Connect Request Via Notification Close', + }, + }) + this._removeBeforeUnload() + rejectProviderRequestByOrigin(origin) + } + + _removeBeforeUnload () { + window.removeEventListener('beforeunload', this._beforeUnload) + } + + componentWillUnmount () { + this._removeBeforeUnload() + } + + onCancel = () => { + const { origin, rejectProviderRequestByOrigin } = this.props + this.context.metricsEvent({ + eventOpts: { + category: 'Auth', + action: 'Connect', + name: 'Canceled', + }, + }) + this._removeBeforeUnload() + rejectProviderRequestByOrigin(origin) + } + + onSubmit = () => { + const { approveProviderRequestByOrigin, origin } = this.props + this.context.metricsEvent({ + eventOpts: { + category: 'Auth', + action: 'Connect', + name: 'Confirmed', + }, + }) + this._removeBeforeUnload() + approveProviderRequestByOrigin(origin) + } + + render () { + const {origin, siteImage, siteTitle, hostname, extensionId} = this.props + + return ( +
    + + + this.onCancel()} + cancelText={this.context.t('cancel')} + onSubmit={() => this.onSubmit()} + submitText={this.context.t('connect')} + submitButtonType="confirm" + /> +
    + ) + } +} diff --git a/ui/app/components/app/selected-account/tests/selected-account-component.test.js b/ui/app/components/app/selected-account/tests/selected-account-component.test.js index 42e5c8f1b6..78a94d1c89 100644 --- a/ui/app/components/app/selected-account/tests/selected-account-component.test.js +++ b/ui/app/components/app/selected-account/tests/selected-account-component.test.js @@ -5,12 +5,10 @@ import SelectedAccount from '../selected-account.component' describe('SelectedAccount Component', () => { it('should render checksummed address', () => { - const wrapper = render(( - - ), { context: { t: () => {}}}) + const wrapper = render(, { context: { t: () => {}}}) // Checksummed version of address is displayed assert.equal(wrapper.find('.selected-account__address').text(), '0x1B82...5C9D') assert.equal(wrapper.find('.selected-account__name').text(), 'testName') diff --git a/ui/app/components/app/shift-list-item.js b/ui/app/components/app/shift-list-item.js new file mode 100644 index 0000000000..f5fa00047b --- /dev/null +++ b/ui/app/components/app/shift-list-item.js @@ -0,0 +1,204 @@ +const inherits = require('util').inherits +const Component = require('react').Component +const PropTypes = require('prop-types') +const h = require('react-hyperscript') +const connect = require('react-redux').connect +const explorerLink = require('etherscan-link').createExplorerLink +const actions = require('../../store/actions') +const { formatDate, addressSummary } = require('../../helpers/utils/util') + +const CopyButton = require('../ui/copyButton') +const EthBalance = require('../ui/eth-balance') +const Tooltip = require('../ui/tooltip') + + +ShiftListItem.contextTypes = { + t: PropTypes.func, +} + +module.exports = connect(mapStateToProps)(ShiftListItem) + + +function mapStateToProps (state) { + return { + selectedAddress: state.metamask.selectedAddress, + conversionRate: state.metamask.conversionRate, + currentCurrency: state.metamask.currentCurrency, + } +} + +inherits(ShiftListItem, Component) + +function ShiftListItem () { + Component.call(this) +} + +ShiftListItem.prototype.render = function () { + return h('div.transaction-list-item.tx-list-clickable', { + style: { + paddingTop: '20px', + paddingBottom: '20px', + justifyContent: 'space-around', + alignItems: 'center', + flexDirection: 'row', + }, + }, [ + h('div', { + style: { + width: '0px', + position: 'relative', + bottom: '19px', + }, + }, [ + h('img', { + src: 'https://shapeshift.io/logo.png', + style: { + height: '35px', + width: '132px', + position: 'absolute', + clip: 'rect(0px,30px,34px,0px)', + }, + }), + ]), + + this.renderInfo(), + this.renderUtilComponents(), + ]) +} + +ShiftListItem.prototype.renderUtilComponents = function () { + var props = this.props + const { conversionRate, currentCurrency } = props + + switch (props.response.status) { + case 'no_deposits': + return h('.flex-row', [ + h(CopyButton, { + value: this.props.depositAddress, + }), + h(Tooltip, { + title: this.context.t('qrCode'), + }, [ + h('i.fa.fa-qrcode.pointer.pop-hover', { + onClick: () => props.dispatch(actions.reshowQrCode(props.depositAddress, props.depositType)), + style: { + margin: '5px', + marginLeft: '23px', + marginRight: '12px', + fontSize: '20px', + color: '#F7861C', + }, + }), + ]), + ]) + case 'received': + return h('.flex-row') + + case 'complete': + return h('.flex-row', [ + h(CopyButton, { + value: this.props.response.transaction, + }), + h(EthBalance, { + value: `${props.response.outgoingCoin}`, + conversionRate, + currentCurrency, + width: '55px', + shorten: true, + needsParse: false, + incoming: true, + style: { + fontSize: '15px', + color: '#01888C', + }, + }), + ]) + + case 'failed': + return '' + default: + return '' + } +} + +ShiftListItem.prototype.renderInfo = function () { + var props = this.props + switch (props.response.status) { + case 'no_deposits': + return h('.flex-column', { + style: { + overflow: 'hidden', + }, + }, [ + h('div', { + style: { + fontSize: 'x-small', + color: '#ABA9AA', + width: '100%', + }, + }, this.context.t('toETHviaShapeShift', [props.depositType])), + h('div', this.context.t('noDeposits')), + h('div', { + style: { + fontSize: 'x-small', + color: '#ABA9AA', + width: '100%', + }, + }, formatDate(props.time)), + ]) + case 'received': + return h('.flex-column', { + style: { + width: '200px', + overflow: 'hidden', + }, + }, [ + h('div', { + style: { + fontSize: 'x-small', + color: '#ABA9AA', + width: '100%', + }, + }, this.context.t('toETHviaShapeShift', [props.depositType])), + h('div', this.context.t('conversionProgress')), + h('div', { + style: { + fontSize: 'x-small', + color: '#ABA9AA', + width: '100%', + }, + }, formatDate(props.time)), + ]) + case 'complete': + var url = explorerLink(props.response.transaction, parseInt('1')) + + return h('.flex-column.pointer', { + style: { + width: '200px', + overflow: 'hidden', + }, + onClick: () => global.platform.openWindow({ url }), + }, [ + h('div', { + style: { + fontSize: 'x-small', + color: '#ABA9AA', + width: '100%', + }, + }, this.context.t('fromShapeShift')), + h('div', formatDate(props.time)), + h('div', { + style: { + fontSize: 'x-small', + color: '#ABA9AA', + width: '100%', + }, + }, addressSummary(props.response.transaction)), + ]) + + case 'failed': + return h('span.error', '(' + this.context.t('failed') + ')') + default: + return '' + } +} diff --git a/ui/app/components/app/shift-list-item/index.js b/ui/app/components/app/shift-list-item/index.js deleted file mode 100644 index fd6796983d..0000000000 --- a/ui/app/components/app/shift-list-item/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './shift-list-item.container' diff --git a/ui/app/components/app/shift-list-item/shift-list-item.component.js b/ui/app/components/app/shift-list-item/shift-list-item.component.js deleted file mode 100644 index 5e8797d27b..0000000000 --- a/ui/app/components/app/shift-list-item/shift-list-item.component.js +++ /dev/null @@ -1,240 +0,0 @@ -import PropTypes from 'prop-types' -import React, { Component } from 'react' -const explorerLink = require('etherscan-link').createExplorerLink -const actions = require('../../../store/actions') -const { formatDate, addressSummary } = require('../../../helpers/utils/util') - -const CopyButton = require('../../ui/copyButton') -const EthBalance = require('../../ui/eth-balance').default -const Tooltip = require('../../ui/tooltip') - -export default class ShiftListItem extends Component { - static contextTypes = { - t: PropTypes.func, - } - - static defaultProps = { - conversionRate: undefined, - currentCurrency: undefined, - } - - static propTypes = { - depositType: PropTypes.string.isRequired, - dispatch: PropTypes.func.isRequired, - depositAddress: PropTypes.string.isRequired, - conversionRate: PropTypes.any, - currentCurrency: PropTypes.any, - time: PropTypes.string.isRequired, - response: PropTypes.shape({ - outgoingCoin: PropTypes.number.isRequired, - status: PropTypes.string.isRequired, - transaction: PropTypes.string.isRequired, - }), - } - - renderUtilComponents () { - const { conversionRate, currentCurrency } = this.props - - switch (this.props.response.status) { - case 'no_deposits': - return ( -
    - - - { - this.props.dispatch(actions.reshowQrCode(this.props.depositAddress, this.props.depositType)) - }} - style={{ - margin: '5px', - marginLeft: '23px', - marginRight: '12px', - fontSize: '20px', - color: '#F7861C', - }} - /> - -
    - ) - case 'received': - return
    - - case 'complete': - return ( -
    - - -
    - ) - - case 'failed': - return '' - - default: - return '' - } - } - - renderInfo () { - switch (this.props.response.status) { - case 'no_deposits': - return ( -
    -
    - {this.context.t('toETHviaShapeShift', [this.props.depositType])} -
    -
    - {this.context.t('noDeposits')} -
    -
    - {formatDate(this.props.time)} -
    -
    - ) - - case 'received': - return ( -
    -
    - {this.context.t('toETHviaShapeShift', [this.props.depositType])} -
    -
    - {this.context.t('conversionProgress')} -
    -
    - {formatDate(this.props.time)} -
    -
    - ) - - case 'complete': - const url = explorerLink(this.props.response.transaction, parseInt('1')) - return ( -
    global.platform.openWindow({ url })} - > -
    - {this.context.t('fromShapeShift')} -
    -
    - {formatDate(this.props.time)} -
    -
    - {addressSummary(this.props.response.transaction)} -
    -
    - ) - - case 'failed': - return ( - - {`(${this.context.t('failed')})`} - - ) - - default: - return '' - } - } - - render () { - return ( -
    -
    - -
    - {this.renderInfo()} - {this.renderUtilComponents()} -
    - ) - } -} diff --git a/ui/app/components/app/shift-list-item/shift-list-item.container.js b/ui/app/components/app/shift-list-item/shift-list-item.container.js deleted file mode 100644 index 1d7645dfdf..0000000000 --- a/ui/app/components/app/shift-list-item/shift-list-item.container.js +++ /dev/null @@ -1,12 +0,0 @@ -import {connect} from 'react-redux' -import ShiftListItem from './shift-list-item.component' - -function mapStateToProps (state) { - return { - selectedAddress: state.metamask.selectedAddress, - conversionRate: state.metamask.conversionRate, - currentCurrency: state.metamask.currentCurrency, - } -} - -export default connect(mapStateToProps)(ShiftListItem) diff --git a/ui/app/components/app/sidebars/sidebar.component.js b/ui/app/components/app/sidebars/sidebar.component.js index 6437675c94..484b87e4b7 100644 --- a/ui/app/components/app/sidebars/sidebar.component.js +++ b/ui/app/components/app/sidebars/sidebar.component.js @@ -20,15 +20,13 @@ export default class Sidebar extends Component { renderOverlay () { const { onOverlayClose } = this.props - return ( -
    { - onOverlayClose && onOverlayClose() - this.props.hideSidebar() - }} - /> - ) + return
    { + onOverlayClose && onOverlayClose() + this.props.hideSidebar() + } + } /> } renderSidebarContent () { diff --git a/ui/app/components/app/sidebars/tests/sidebars-component.test.js b/ui/app/components/app/sidebars/tests/sidebars-component.test.js index 3416ad8f67..5f6657dde1 100644 --- a/ui/app/components/app/sidebars/tests/sidebars-component.test.js +++ b/ui/app/components/app/sidebars/tests/sidebars-component.test.js @@ -16,14 +16,12 @@ describe('Sidebar Component', function () { let wrapper beforeEach(() => { - wrapper = shallow(( - - )) + wrapper = shallow() }) afterEach(() => { diff --git a/ui/app/components/app/signature-request-original.js b/ui/app/components/app/signature-request-original.js new file mode 100644 index 0000000000..e23d724cb7 --- /dev/null +++ b/ui/app/components/app/signature-request-original.js @@ -0,0 +1,357 @@ +const Component = require('react').Component +const PropTypes = require('prop-types') +const h = require('react-hyperscript') +const inherits = require('util').inherits +import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../app/scripts/lib/enums' +import { getEnvironmentType } from '../../../../app/scripts/lib/util' +import Identicon from '../ui/identicon' +const connect = require('react-redux').connect +const ethUtil = require('ethereumjs-util') +const classnames = require('classnames') +const { compose } = require('recompose') +const { withRouter } = require('react-router-dom') +const { ObjectInspector } = require('react-inspector') + +import AccountListItem from '../../pages/send/account-list-item/account-list-item.component' + +const actions = require('../../store/actions') +const { conversionUtil } = require('../../helpers/utils/conversion-util') + +const { + getSelectedAccount, + getCurrentAccountWithSendEtherInfo, + getSelectedAddress, + conversionRateSelector, +} = require('../../selectors/selectors.js') + +import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm-transaction.duck' +import Button from '../ui/button' + +const { DEFAULT_ROUTE } = require('../../helpers/constants/routes') + +function mapStateToProps (state) { + return { + balance: getSelectedAccount(state).balance, + selectedAccount: getCurrentAccountWithSendEtherInfo(state), + selectedAddress: getSelectedAddress(state), + requester: null, + requesterAddress: null, + conversionRate: conversionRateSelector(state), + } +} + +function mapDispatchToProps (dispatch) { + return { + goHome: () => dispatch(actions.goHome()), + clearConfirmTransaction: () => dispatch(clearConfirmTransaction()), + } +} + +function mergeProps (stateProps, dispatchProps, ownProps) { + const { + signPersonalMessage, + signTypedMessage, + cancelPersonalMessage, + cancelTypedMessage, + signMessage, + cancelMessage, + txData, + } = ownProps + + const { type } = txData + + let cancel + let sign + if (type === 'personal_sign') { + cancel = cancelPersonalMessage + sign = signPersonalMessage + } else if (type === 'eth_signTypedData') { + cancel = cancelTypedMessage + sign = signTypedMessage + } else if (type === 'eth_sign') { + cancel = cancelMessage + sign = signMessage + } + + return { + ...ownProps, + ...stateProps, + ...dispatchProps, + txData, + cancel, + sign, + } +} + +SignatureRequest.contextTypes = { + t: PropTypes.func, + metricsEvent: PropTypes.func, +} + +module.exports = compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps, mergeProps) +)(SignatureRequest) + + +inherits(SignatureRequest, Component) +function SignatureRequest (props) { + Component.call(this) + + this.state = { + selectedAccount: props.selectedAccount, + } + this._beforeUnload = this._beforeUnload.bind(this) +} + +SignatureRequest.prototype._beforeUnload = function (event) { + const { clearConfirmTransaction, cancel } = this.props + const { metricsEvent } = this.context + metricsEvent({ + eventOpts: { + category: 'Transactions', + action: 'Sign Request', + name: 'Cancel Sig Request Via Notification Close', + }, + }) + clearConfirmTransaction() + cancel(event) +} + +SignatureRequest.prototype._removeBeforeUnload = function () { + if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) { + window.removeEventListener('beforeunload', this._beforeUnload) + } +} + +SignatureRequest.prototype.componentDidMount = function () { + if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) { + window.addEventListener('beforeunload', this._beforeUnload) + } +} + +SignatureRequest.prototype.componentWillUnmount = function () { + this._removeBeforeUnload() +} + +SignatureRequest.prototype.renderHeader = function () { + return h('div.request-signature__header', [ + + h('div.request-signature__header-background'), + + h('div.request-signature__header__text', this.context.t('sigRequest')), + + h('div.request-signature__header__tip-container', [ + h('div.request-signature__header__tip'), + ]), + + ]) +} + +SignatureRequest.prototype.renderAccount = function () { + const { selectedAccount } = this.state + + return h('div.request-signature__account', [ + + h('div.request-signature__account-text', [this.context.t('account') + ':']), + + h('div.request-signature__account-item', [ + h(AccountListItem, { + account: selectedAccount, + displayBalance: false, + }), + ]), + ]) +} + +SignatureRequest.prototype.renderBalance = function () { + const { balance, conversionRate } = this.props + + const balanceInEther = conversionUtil(balance, { + fromNumericBase: 'hex', + toNumericBase: 'dec', + fromDenomination: 'WEI', + numberOfDecimals: 6, + conversionRate, + }) + + return h('div.request-signature__balance', [ + + h('div.request-signature__balance-text', `${this.context.t('balance')}:`), + + h('div.request-signature__balance-value', `${balanceInEther} ETH`), + + ]) +} + +SignatureRequest.prototype.renderAccountInfo = function () { + return h('div.request-signature__account-info', [ + + this.renderAccount(), + + this.renderRequestIcon(), + + this.renderBalance(), + + ]) +} + +SignatureRequest.prototype.renderRequestIcon = function () { + const { requesterAddress } = this.props + + return h('div.request-signature__request-icon', [ + h(Identicon, { + diameter: 40, + address: requesterAddress, + }), + ]) +} + +SignatureRequest.prototype.renderRequestInfo = function () { + return h('div.request-signature__request-info', [ + + h('div.request-signature__headline', [ + this.context.t('yourSigRequested'), + ]), + + ]) +} + +SignatureRequest.prototype.msgHexToText = function (hex) { + try { + const stripped = ethUtil.stripHexPrefix(hex) + const buff = Buffer.from(stripped, 'hex') + return buff.length === 32 ? hex : buff.toString('utf8') + } catch (e) { + return hex + } +} + +// eslint-disable-next-line react/display-name +SignatureRequest.prototype.renderTypedData = function (data) { + const { domain, message } = JSON.parse(data) + return [ + h('div.request-signature__typed-container', [ + domain ? h('div', [ + h('h1', 'Domain'), + h(ObjectInspector, { data: domain, expandLevel: 1, name: 'domain' }), + ]) : '', + message ? h('div', [ + h('h1', 'Message'), + h(ObjectInspector, { data: message, expandLevel: 1, name: 'message' }), + ]) : '', + ]), + ] +} + +SignatureRequest.prototype.renderBody = function () { + let rows + let notice = this.context.t('youSign') + ':' + + const { txData } = this.props + const { type, msgParams: { data } } = txData + + if (type === 'personal_sign') { + rows = [{ name: this.context.t('message'), value: this.msgHexToText(data) }] + } else if (type === 'eth_signTypedData') { + rows = data + } else if (type === 'eth_sign') { + rows = [{ name: this.context.t('message'), value: data }] + notice = [this.context.t('signNotice'), + h('span.request-signature__help-link', { + onClick: () => { + global.platform.openWindow({ + url: 'https://metamask.zendesk.com/hc/en-us/articles/360015488751', + }) + }, + }, this.context.t('learnMore'))] + } + + return h('div.request-signature__body', {}, [ + + this.renderAccountInfo(), + + this.renderRequestInfo(), + + h('div.request-signature__notice', { + className: classnames({ + 'request-signature__notice': type === 'personal_sign' || type === 'eth_signTypedData', + 'request-signature__warning': type === 'eth_sign', + }), + }, [notice]), + + h('div.request-signature__rows', + rows.map(({ name, value }, index) => { + if (typeof value === 'boolean') { + value = value.toString() + } + return h('div.request-signature__row', { key: `request-signature-row-${index}` }, [ + h('div.request-signature__row-title', [`${name}:`]), + h('div.request-signature__row-value', value), + ]) + }) + ), + ]) +} + +SignatureRequest.prototype.renderFooter = function () { + const { cancel, sign } = this.props + + return h('div.request-signature__footer', [ + h(Button, { + type: 'default', + large: true, + className: 'request-signature__footer__cancel-button', + onClick: event => { + this._removeBeforeUnload() + cancel(event).then(() => { + this.context.metricsEvent({ + eventOpts: { + category: 'Transactions', + action: 'Sign Request', + name: 'Cancel', + }, + }) + this.props.clearConfirmTransaction() + this.props.history.push(DEFAULT_ROUTE) + }) + }, + }, this.context.t('cancel')), + h(Button, { + type: 'secondary', + large: true, + className: 'request-signature__footer__sign-button', + onClick: event => { + this._removeBeforeUnload() + sign(event).then(() => { + this.context.metricsEvent({ + eventOpts: { + category: 'Transactions', + action: 'Sign Request', + name: 'Confirm', + }, + }) + this.props.clearConfirmTransaction() + this.props.history.push(DEFAULT_ROUTE) + }) + }, + }, this.context.t('sign')), + ]) +} + +SignatureRequest.prototype.render = function () { + return ( + + h('div.request-signature__container', [ + + this.renderHeader(), + + this.renderBody(), + + this.renderFooter(), + + ]) + + ) + +} diff --git a/ui/app/components/app/signature-request-original/index.js b/ui/app/components/app/signature-request-original/index.js deleted file mode 100644 index 00a9067857..0000000000 --- a/ui/app/components/app/signature-request-original/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './signature-request-original.container' diff --git a/ui/app/components/app/signature-request-original/signature-request-original.component.js b/ui/app/components/app/signature-request-original/signature-request-original.component.js deleted file mode 100644 index 9a92ba5fbe..0000000000 --- a/ui/app/components/app/signature-request-original/signature-request-original.component.js +++ /dev/null @@ -1,324 +0,0 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' -import ethUtil from 'ethereumjs-util' -import classnames from 'classnames' -import { ObjectInspector } from 'react-inspector' - -import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../../app/scripts/lib/enums' -import { getEnvironmentType } from '../../../../../app/scripts/lib/util' -import Identicon from '../../ui/identicon' -import AccountListItem from '../../../pages/send/account-list-item/account-list-item.component' -import { conversionUtil } from '../../../helpers/utils/conversion-util' -import Button from '../../ui/button' -import { DEFAULT_ROUTE } from '../../../helpers/constants/routes' - -export default class SignatureRequestOriginal extends Component { - static contextTypes = { - t: PropTypes.func.isRequired, - metricsEvent: PropTypes.func.isRequired, - } - - static propTypes = { - balance: PropTypes.string, - cancel: PropTypes.func.isRequired, - clearConfirmTransaction: PropTypes.func.isRequired, - conversionRate: PropTypes.number, - history: PropTypes.object.isRequired, - requesterAddress: PropTypes.string, - selectedAccount: PropTypes.string, - sign: PropTypes.func.isRequired, - txData: PropTypes.object.isRequired, - } - - state = { - selectedAccount: this.props.selectedAccount, - } - - componentDidMount = () => { - if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) { - window.addEventListener('beforeunload', this._beforeUnload) - } - } - - componentWillUnmount = () => { - this._removeBeforeUnload() - } - - _beforeUnload = (event) => { - const { clearConfirmTransaction, cancel } = this.props - const { metricsEvent } = this.context - metricsEvent({ - eventOpts: { - category: 'Transactions', - action: 'Sign Request', - name: 'Cancel Sig Request Via Notification Close', - }, - }) - clearConfirmTransaction() - cancel(event) - } - - _removeBeforeUnload = () => { - if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) { - window.removeEventListener('beforeunload', this._beforeUnload) - } - } - - renderHeader = () => { - return ( -
    -
    - -
    - { this.context.t('sigRequest') } -
    - -
    -
    -
    -
    - ) - } - - renderAccount = () => { - const { selectedAccount } = this.state - - return ( -
    -
    - { `${this.context.t('account')}:` } -
    - -
    - -
    -
    - ) - } - - renderBalance = () => { - const { balance, conversionRate } = this.props - - const balanceInEther = conversionUtil(balance, { - fromNumericBase: 'hex', - toNumericBase: 'dec', - fromDenomination: 'WEI', - numberOfDecimals: 6, - conversionRate, - }) - - return ( -
    -
    - { `${this.context.t('balance')}:` } -
    -
    - { `${balanceInEther} ETH` } -
    -
    - ) - } - - renderRequestIcon = () => { - const { requesterAddress } = this.props - - return ( -
    - -
    - ) - } - - renderAccountInfo = () => { - return ( -
    - { this.renderAccount() } - { this.renderRequestIcon() } - { this.renderBalance() } -
    - ) - } - - renderRequestInfo = () => { - return ( -
    -
    - { this.context.t('yourSigRequested') } -
    -
    - ) - } - - msgHexToText = (hex) => { - try { - const stripped = ethUtil.stripHexPrefix(hex) - const buff = Buffer.from(stripped, 'hex') - return buff.length === 32 ? hex : buff.toString('utf8') - } catch (e) { - return hex - } - } - - renderTypedData = (data) => { - const { domain, message } = JSON.parse(data) - return ( -
    - { - domain - ? ( -
    -

    - Domain -

    - -
    - ) - : '' - } - { - message - ? ( -
    -

    - Message -

    - -
    - ) - : '' - } -
    - ) - } - - renderBody = () => { - let rows - let notice = `${this.context.t('youSign')}:` - - const { txData } = this.props - const { type, msgParams: { data } } = txData - - if (type === 'personal_sign') { - rows = [{ name: this.context.t('message'), value: this.msgHexToText(data) }] - } else if (type === 'eth_signTypedData') { - rows = data - } else if (type === 'eth_sign') { - rows = [{ name: this.context.t('message'), value: data }] - notice = this.context.t('signNotice') - } - - return ( -
    - { this.renderAccountInfo() } - { this.renderRequestInfo() } -
    - { notice } - { - type === 'eth_sign' - ? ( - { - global.platform.openWindow({ - url: 'https://metamask.zendesk.com/hc/en-us/articles/360015488751', - }) - }} - > - { this.context.t('learnMore') } - - ) - : null - } -
    -
    - { - rows.map(({ name, value }, index) => { - if (typeof value === 'boolean') { - value = value.toString() - } - return ( -
    -
    - { `${name}:` } -
    -
    - { value } -
    -
    - ) - }) - } -
    -
    - ) - } - - renderFooter = () => { - const { cancel, sign } = this.props - - return ( -
    - , - -
    - ) - } - - render = () => { - return ( -
    - { this.renderHeader() } - { this.renderBody() } - { this.renderFooter() } -
    - ) - } -} diff --git a/ui/app/components/app/signature-request-original/signature-request-original.container.js b/ui/app/components/app/signature-request-original/signature-request-original.container.js deleted file mode 100644 index be891b4dbe..0000000000 --- a/ui/app/components/app/signature-request-original/signature-request-original.container.js +++ /dev/null @@ -1,72 +0,0 @@ -import { connect } from 'react-redux' -import { compose } from 'recompose' -import { withRouter } from 'react-router-dom' - -import actions from '../../../store/actions' -import { - getSelectedAccount, - getCurrentAccountWithSendEtherInfo, - getSelectedAddress, - conversionRateSelector, -} from '../../../selectors/selectors.js' -import { clearConfirmTransaction } from '../../../ducks/confirm-transaction/confirm-transaction.duck' -import SignatureRequestOriginal from './signature-request-original.component' - -function mapStateToProps (state) { - return { - balance: getSelectedAccount(state).balance, - selectedAccount: getCurrentAccountWithSendEtherInfo(state), - selectedAddress: getSelectedAddress(state), - requester: null, - requesterAddress: null, - conversionRate: conversionRateSelector(state), - } -} - -function mapDispatchToProps (dispatch) { - return { - goHome: () => dispatch(actions.goHome()), - clearConfirmTransaction: () => dispatch(clearConfirmTransaction()), - } -} - -function mergeProps (stateProps, dispatchProps, ownProps) { - const { - signPersonalMessage, - signTypedMessage, - cancelPersonalMessage, - cancelTypedMessage, - signMessage, - cancelMessage, - txData, - } = ownProps - - const { type } = txData - - let cancel - let sign - if (type === 'personal_sign') { - cancel = cancelPersonalMessage - sign = signPersonalMessage - } else if (type === 'eth_signTypedData') { - cancel = cancelTypedMessage - sign = signTypedMessage - } else if (type === 'eth_sign') { - cancel = cancelMessage - sign = signMessage - } - - return { - ...ownProps, - ...stateProps, - ...dispatchProps, - txData, - cancel, - sign, - } -} - -export default compose( - withRouter, - connect(mapStateToProps, mapDispatchToProps, mergeProps) -)(SignatureRequestOriginal) diff --git a/ui/app/components/app/signature-request/signature-request-header/signature-request-header.component.js b/ui/app/components/app/signature-request/signature-request-header/signature-request-header.component.js index 661a933a08..3ac0c9afb6 100644 --- a/ui/app/components/app/signature-request/signature-request-header/signature-request-header.component.js +++ b/ui/app/components/app/signature-request/signature-request-header/signature-request-header.component.js @@ -14,12 +14,10 @@ export default class SignatureRequestHeader extends PureComponent { return (
    - {selectedAccount && ( - - )} + {selectedAccount && } {name}
    diff --git a/ui/app/components/app/signature-request/tests/signature-request.test.js b/ui/app/components/app/signature-request/tests/signature-request.test.js index 6ff07effc3..68b114dd8a 100644 --- a/ui/app/components/app/signature-request/tests/signature-request.test.js +++ b/ui/app/components/app/signature-request/tests/signature-request.test.js @@ -8,16 +8,11 @@ describe('Signature Request Component', function () { let wrapper beforeEach(() => { - wrapper = shallow(( - - )) + wrapper = shallow() }) describe('render', () => { diff --git a/ui/app/components/app/token-cell.js b/ui/app/components/app/token-cell.js new file mode 100644 index 0000000000..495b9502be --- /dev/null +++ b/ui/app/components/app/token-cell.js @@ -0,0 +1,177 @@ +const Component = require('react').Component +const PropTypes = require('prop-types') +const h = require('react-hyperscript') +const inherits = require('util').inherits +const connect = require('react-redux').connect +import Identicon from '../ui/identicon' +const prefixForNetwork = require('../../../lib/etherscan-prefix-for-network') +const selectors = require('../../selectors/selectors') +const actions = require('../../store/actions') +const { conversionUtil, multiplyCurrencies } = require('../../helpers/utils/conversion-util') + +const TokenMenuDropdown = require('./dropdowns/token-menu-dropdown.js') + +function mapStateToProps (state) { + return { + network: state.metamask.network, + currentCurrency: state.metamask.currentCurrency, + selectedTokenAddress: state.metamask.selectedTokenAddress, + userAddress: selectors.getSelectedAddress(state), + contractExchangeRates: state.metamask.contractExchangeRates, + conversionRate: state.metamask.conversionRate, + sidebarOpen: state.appState.sidebar.isOpen, + } +} + +function mapDispatchToProps (dispatch) { + return { + setSelectedToken: address => dispatch(actions.setSelectedToken(address)), + hideSidebar: () => dispatch(actions.hideSidebar()), + } +} + +module.exports = connect(mapStateToProps, mapDispatchToProps)(TokenCell) + +inherits(TokenCell, Component) +function TokenCell () { + Component.call(this) + + this.state = { + tokenMenuOpen: false, + } +} + +TokenCell.contextTypes = { + metricsEvent: PropTypes.func, +} + +TokenCell.prototype.render = function () { + const { tokenMenuOpen } = this.state + const props = this.props + const { + address, + symbol, + string, + network, + setSelectedToken, + selectedTokenAddress, + contractExchangeRates, + conversionRate, + hideSidebar, + sidebarOpen, + currentCurrency, + // userAddress, + image, + } = props + let currentTokenToFiatRate + let currentTokenInFiat + let formattedFiat = '' + + if (contractExchangeRates[address]) { + currentTokenToFiatRate = multiplyCurrencies( + contractExchangeRates[address], + conversionRate + ) + currentTokenInFiat = conversionUtil(string, { + fromNumericBase: 'dec', + fromCurrency: symbol, + toCurrency: currentCurrency.toUpperCase(), + numberOfDecimals: 2, + conversionRate: currentTokenToFiatRate, + }) + formattedFiat = currentTokenInFiat.toString() === '0' + ? '' + : `${currentTokenInFiat} ${currentCurrency.toUpperCase()}` + } + + const showFiat = Boolean(currentTokenInFiat) && currentCurrency.toUpperCase() !== symbol + + return ( + h('div.token-list-item', { + className: `token-list-item ${selectedTokenAddress === address ? 'token-list-item--active' : ''}`, + // style: { cursor: network === '1' ? 'pointer' : 'default' }, + // onClick: this.view.bind(this, address, userAddress, network), + onClick: () => { + setSelectedToken(address) + this.context.metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Token Menu', + name: 'Clicked Token', + }, + }) + selectedTokenAddress !== address && sidebarOpen && hideSidebar() + }, + }, [ + + h(Identicon, { + className: 'token-list-item__identicon', + diameter: 50, + address, + network, + image, + }), + + h('div.token-list-item__balance-ellipsis', null, [ + h('div.token-list-item__balance-wrapper', null, [ + h('div.token-list-item__token-balance', `${string || 0}`), + h('div.token-list-item__token-symbol', symbol), + showFiat && h('div.token-list-item__fiat-amount', { + style: {}, + }, formattedFiat), + ]), + + h('i.fa.fa-ellipsis-h.fa-lg.token-list-item__ellipsis.cursor-pointer', { + onClick: (e) => { + e.stopPropagation() + this.setState({ tokenMenuOpen: true }) + }, + }), + + ]), + + + tokenMenuOpen && h(TokenMenuDropdown, { + onClose: () => this.setState({ tokenMenuOpen: false }), + token: { symbol, address }, + }), + + /* + h('button', { + onClick: this.send.bind(this, address), + }, 'SEND'), + */ + + ]) + ) +} + +TokenCell.prototype.send = function (address, event) { + event.preventDefault() + event.stopPropagation() + const url = tokenFactoryFor(address) + if (url) { + navigateTo(url) + } +} + +TokenCell.prototype.view = function (address, userAddress, network) { + const url = etherscanLinkFor(address, userAddress, network) + if (url) { + navigateTo(url) + } +} + +function navigateTo (url) { + global.platform.openWindow({ url }) +} + +function etherscanLinkFor (tokenAddress, address, network) { + const prefix = prefixForNetwork(network) + return `https://${prefix}etherscan.io/token/${tokenAddress}?a=${address}` +} + +function tokenFactoryFor (tokenAddress) { + return `https://tokenfactory.surge.sh/#/token/${tokenAddress}` +} + diff --git a/ui/app/components/app/token-cell/index.js b/ui/app/components/app/token-cell/index.js deleted file mode 100644 index f0f3bcb4aa..0000000000 --- a/ui/app/components/app/token-cell/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './token-cell.container' diff --git a/ui/app/components/app/token-cell/token-cell.component.js b/ui/app/components/app/token-cell/token-cell.component.js deleted file mode 100644 index dc2bbb6d3a..0000000000 --- a/ui/app/components/app/token-cell/token-cell.component.js +++ /dev/null @@ -1,141 +0,0 @@ -import classnames from 'classnames' -import PropTypes from 'prop-types' -import React, { Component } from 'react' -import Identicon from '../../ui/identicon' -const prefixForNetwork = require('../../../../lib/etherscan-prefix-for-network') -const { conversionUtil, multiplyCurrencies } = require('../../../helpers/utils/conversion-util') - -const TokenMenuDropdown = require('../dropdowns/token-menu-dropdown.js') - -export default class TokenCell extends Component { - static contextTypes = { - metricsEvent: PropTypes.func, - } - - state = { - tokenMenuOpen: false, - } - - send (address, event) { - event.preventDefault() - event.stopPropagation() - const url = tokenFactoryFor(address) - if (url) { - navigateTo(url) - } - } - - view (address, userAddress, network) { - const url = etherscanLinkFor(address, userAddress, network) - if (url) { - navigateTo(url) - } - } - - render () { - const { tokenMenuOpen } = this.state - const props = this.props - const { - address, - symbol, - string, - network, - setSelectedToken, - selectedTokenAddress, - contractExchangeRates, - conversionRate, - hideSidebar, - sidebarOpen, - currentCurrency, - // userAddress, - image, - } = props - let currentTokenToFiatRate - let currentTokenInFiat - let formattedFiat = '' - - if (contractExchangeRates[address]) { - currentTokenToFiatRate = multiplyCurrencies( - contractExchangeRates[address], - conversionRate - ) - currentTokenInFiat = conversionUtil(string, { - fromNumericBase: 'dec', - fromCurrency: symbol, - toCurrency: currentCurrency.toUpperCase(), - numberOfDecimals: 2, - conversionRate: currentTokenToFiatRate, - }) - formattedFiat = currentTokenInFiat.toString() === '0' - ? '' - : `${currentTokenInFiat} ${currentCurrency.toUpperCase()}` - } - - const showFiat = Boolean(currentTokenInFiat) && currentCurrency.toUpperCase() !== symbol - - return ( -
    { - setSelectedToken(address) - this.context.metricsEvent({ - eventOpts: { - category: 'Navigation', - action: 'Token Menu', - name: 'Clicked Token', - }, - }) - selectedTokenAddress !== address && sidebarOpen && hideSidebar() - }} - > - -
    -
    -
    {string || 0}
    -
    {symbol}
    - {showFiat && ( -
    - {formattedFiat} -
    - )} -
    - { - e.stopPropagation() - this.setState({ tokenMenuOpen: true }) - }} - /> -
    - {tokenMenuOpen && ( - this.setState({ tokenMenuOpen: false })} - token={{ symbol, address }} - /> - )} -
    - ) - } -} - -function navigateTo (url) { - global.platform.openWindow({ url }) -} - -function etherscanLinkFor (tokenAddress, address, network) { - const prefix = prefixForNetwork(network) - return `https://${prefix}etherscan.io/token/${tokenAddress}?a=${address}` -} - -function tokenFactoryFor (tokenAddress) { - return `https://tokenfactory.surge.sh/#/token/${tokenAddress}` -} - diff --git a/ui/app/components/app/token-cell/token-cell.container.js b/ui/app/components/app/token-cell/token-cell.container.js deleted file mode 100644 index 176e93008b..0000000000 --- a/ui/app/components/app/token-cell/token-cell.container.js +++ /dev/null @@ -1,25 +0,0 @@ -import { connect } from 'react-redux' -import { setSelectedToken, hideSidebar } from '../../../store/actions' -import { getSelectedAddress } from '../../../selectors/selectors' -import TokenCell from './token-cell.component' - -function mapStateToProps (state) { - return { - network: state.metamask.network, - currentCurrency: state.metamask.currentCurrency, - selectedTokenAddress: state.metamask.selectedTokenAddress, - userAddress: getSelectedAddress(state), - contractExchangeRates: state.metamask.contractExchangeRates, - conversionRate: state.metamask.conversionRate, - sidebarOpen: state.appState.sidebar.isOpen, - } -} - -function mapDispatchToProps (dispatch) { - return { - setSelectedToken: address => dispatch(setSelectedToken(address)), - hideSidebar: () => dispatch(hideSidebar()), - } -} - -export default connect(mapStateToProps, mapDispatchToProps)(TokenCell) diff --git a/ui/app/components/app/token-list.js b/ui/app/components/app/token-list.js index ee2f85cacb..000ca6b3f9 100644 --- a/ui/app/components/app/token-list.js +++ b/ui/app/components/app/token-list.js @@ -1,8 +1,9 @@ -import PropTypes from 'prop-types' -import React, { Component } from 'react' -import TokenCell from './token-cell' +const Component = require('react').Component +const PropTypes = require('prop-types') +const h = require('react-hyperscript') const inherits = require('util').inherits const TokenTracker = require('eth-token-tracker') +const TokenCell = require('./token-cell.js') const connect = require('react-redux').connect const selectors = require('../../selectors/selectors') const log = require('loglevel') @@ -43,64 +44,53 @@ function TokenList () { Component.call(this) } -TokenList.prototype.render = function TokenList () { +TokenList.prototype.render = function () { const { userAddress, assetImages } = this.props const state = this.state const { tokens, isLoading, error } = state if (isLoading) { - return ( -
    - {this.context.t('loadingTokens')} -
    - ) + return this.message(this.context.t('loadingTokens')) } if (error) { log.error(error) - return ( -
    - {this.context.t('troubleTokenBalances')} - { - global.platform.openWindow({ - url: `https://ethplorer.io/address/${userAddress}`, - }) - }} - > - {this.context.t('here')} - -
    - ) + return h('.hotFix', { + style: { + padding: '80px', + }, + }, [ + this.context.t('troubleTokenBalances'), + h('span.hotFix', { + style: { + color: 'rgba(247, 134, 28, 1)', + cursor: 'pointer', + }, + onClick: () => { + global.platform.openWindow({ + url: `https://ethplorer.io/address/${userAddress}`, + }) + }, + }, this.context.t('here')), + ]) } - return ( -
    - {tokens.map((tokenData, index) => { - tokenData.image = assetImages[tokenData.address] - return ( - - ) - })} -
    - ) + return h('div', tokens.map((tokenData) => { + tokenData.image = assetImages[tokenData.address] + return h(TokenCell, tokenData) + })) + +} + +TokenList.prototype.message = function (body) { + return h('div', { + style: { + display: 'flex', + height: '250px', + alignItems: 'center', + justifyContent: 'center', + padding: '30px', + }, + }, body) } TokenList.prototype.componentDidMount = function () { @@ -115,9 +105,7 @@ TokenList.prototype.createFreshTokenTracker = function () { this.tracker.removeListener('error', this.showError) } - if (!global.ethereumProvider) { - return - } + if (!global.ethereumProvider) return const { userAddress } = this.props this.tracker = new TokenTracker({ @@ -166,9 +154,7 @@ TokenList.prototype.componentDidUpdate = function (prevProps) { const oldTokensLength = tokens ? tokens.length : 0 const tokensLengthUnchanged = oldTokensLength === newTokens.length - if (tokensLengthUnchanged && shouldUpdateTokens) { - return - } + if (tokensLengthUnchanged && shouldUpdateTokens) return this.setState({ isLoading: true }) this.createFreshTokenTracker() @@ -182,10 +168,21 @@ TokenList.prototype.updateBalances = function (tokens) { } TokenList.prototype.componentWillUnmount = function () { - if (!this.tracker) { - return - } + if (!this.tracker) return this.tracker.stop() this.tracker.removeListener('update', this.balanceUpdater) this.tracker.removeListener('error', this.showError) } + +// function uniqueMergeTokens (tokensA, tokensB = []) { +// const uniqueAddresses = [] +// const result = [] +// tokensA.concat(tokensB).forEach((token) => { +// const normal = normalizeAddress(token.address) +// if (!uniqueAddresses.includes(normal)) { +// uniqueAddresses.push(normal) +// result.push(token) +// } +// }) +// return result +// } diff --git a/ui/app/components/app/transaction-action/tests/transaction-action.component.test.js b/ui/app/components/app/transaction-action/tests/transaction-action.component.test.js index f97e81d155..16f2e256f8 100644 --- a/ui/app/components/app/transaction-action/tests/transaction-action.component.test.js +++ b/ui/app/components/app/transaction-action/tests/transaction-action.component.test.js @@ -35,13 +35,11 @@ describe('TransactionAction Component', () => { }, } - const wrapper = shallow(( - - ), { context: { t }}) + const wrapper = shallow(, { context: { t }}) assert.equal(wrapper.find('.transaction-action').length, 1) wrapper.setState({ transactionAction: 'sentEther' }) diff --git a/ui/app/components/app/transaction-breakdown/transaction-breakdown.component.js b/ui/app/components/app/transaction-breakdown/transaction-breakdown.component.js index 0e10733375..5642e0fa52 100644 --- a/ui/app/components/app/transaction-breakdown/transaction-breakdown.component.js +++ b/ui/app/components/app/transaction-breakdown/transaction-breakdown.component.js @@ -50,12 +50,10 @@ export default class TransactionBreakdown extends PureComponent { className="transaction-breakdown__row-title" > {typeof gas !== 'undefined' - ? ( - - ) + ? : '?' } @@ -74,15 +72,13 @@ export default class TransactionBreakdown extends PureComponent { } {typeof gasPrice !== 'undefined' - ? ( - - ) + ? : '?' } diff --git a/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js b/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js index f3bd51b939..f27c74970d 100644 --- a/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js +++ b/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js @@ -39,6 +39,7 @@ export default class TransactionListItemDetails extends PureComponent { state = { justCopied: false, + cancelDisabled: false, } handleEtherscanClick = () => { @@ -191,17 +192,15 @@ export default class TransactionListItemDetails extends PureComponent { { - showRetry && ( - - - - ) + showRetry && + + }
    diff --git a/ui/app/components/app/transaction-list-item/index.scss b/ui/app/components/app/transaction-list-item/index.scss index 9804ecd976..e0c62199e3 100644 --- a/ui/app/components/app/transaction-list-item/index.scss +++ b/ui/app/components/app/transaction-list-item/index.scss @@ -139,7 +139,6 @@ &__expander { max-height: 0px; width: 100%; - overflow: hidden; &--show { max-height: 1000px; 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 3106f7fc36..9ab0105f99 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 @@ -236,22 +236,18 @@ export default class TransactionListItem extends PureComponent { )} /> { showEstimatedTime - ? ( - - ) + ? : null } { this.renderPrimaryCurrency() } { this.renderSecondaryCurrency() }
    -
    +
    { showTransactionDetails && (
    diff --git a/ui/app/components/app/transaction-view-balance/tests/token-view-balance.component.test.js b/ui/app/components/app/transaction-view-balance/tests/token-view-balance.component.test.js index d948355fc3..0e2882e9c0 100644 --- a/ui/app/components/app/transaction-view-balance/tests/token-view-balance.component.test.js +++ b/ui/app/components/app/transaction-view-balance/tests/token-view-balance.component.test.js @@ -25,16 +25,14 @@ describe('TransactionViewBalance Component', () => { }) it('should render ETH balance properly', () => { - const wrapper = shallow(( - - ), { context: { t, metricsEvent } }) + const wrapper = shallow(, { context: { t, metricsEvent } }) assert.equal(wrapper.find('.transaction-view-balance').length, 1) assert.equal(wrapper.find('.transaction-view-balance__button').length, 2) @@ -57,17 +55,15 @@ describe('TransactionViewBalance Component', () => { symbol: 'ABC', } - const wrapper = shallow(( - - ), { context: { t } }) + const wrapper = shallow(, { context: { t } }) assert.equal(wrapper.find('.transaction-view-balance').length, 1) assert.equal(wrapper.find('.transaction-view-balance__button').length, 1) diff --git a/ui/app/components/app/wallet-view.js b/ui/app/components/app/wallet-view.js new file mode 100644 index 0000000000..55aeec3330 --- /dev/null +++ b/ui/app/components/app/wallet-view.js @@ -0,0 +1,178 @@ +const Component = require('react').Component +const PropTypes = require('prop-types') +const connect = require('react-redux').connect +const h = require('react-hyperscript') +const { withRouter } = require('react-router-dom') +const { compose } = require('recompose') +const inherits = require('util').inherits +const { checksumAddress } = require('../../helpers/utils/util') +// const AccountDropdowns = require('./dropdowns/index.js').AccountDropdowns +const actions = require('../../store/actions') +import BalanceComponent from '../ui/balance' +const TokenList = require('./token-list') +const selectors = require('../../selectors/selectors') +const { ADD_TOKEN_ROUTE } = require('../../helpers/constants/routes') + +import AddTokenButton from './add-token-button' +import AccountDetails from './account-details' + +module.exports = compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(WalletView) + +WalletView.contextTypes = { + t: PropTypes.func, + metricsEvent: PropTypes.func, +} + +WalletView.defaultProps = { + responsiveDisplayClassname: '', +} + +function mapStateToProps (state) { + + return { + network: state.metamask.network, + sidebarOpen: state.appState.sidebar.isOpen, + identities: state.metamask.identities, + accounts: selectors.getMetaMaskAccounts(state), + keyrings: state.metamask.keyrings, + selectedAddress: selectors.getSelectedAddress(state), + selectedAccount: selectors.getSelectedAccount(state), + selectedTokenAddress: state.metamask.selectedTokenAddress, + } +} + +function mapDispatchToProps (dispatch) { + return { + showSendPage: () => dispatch(actions.showSendPage()), + hideSidebar: () => dispatch(actions.hideSidebar()), + unsetSelectedToken: () => dispatch(actions.setSelectedToken()), + showAddTokenPage: () => dispatch(actions.showAddTokenPage()), + } +} + +inherits(WalletView, Component) +function WalletView () { + Component.call(this) +} + +WalletView.prototype.renderWalletBalance = function () { + const { + selectedTokenAddress, + selectedAccount, + unsetSelectedToken, + hideSidebar, + sidebarOpen, + } = this.props + + const selectedClass = selectedTokenAddress + ? '' + : 'wallet-balance-wrapper--active' + const className = `flex-column wallet-balance-wrapper ${selectedClass}` + + return h('div', { className }, [ + h('div.wallet-balance', + { + onClick: () => { + unsetSelectedToken() + selectedTokenAddress && sidebarOpen && hideSidebar() + }, + }, + [ + h(BalanceComponent, { + balanceValue: selectedAccount ? selectedAccount.balance : '', + style: {}, + }), + ] + ), + ]) +} + +WalletView.prototype.renderAddToken = function () { + const { + sidebarOpen, + hideSidebar, + history, + } = this.props + const { metricsEvent } = this.context + + return h(AddTokenButton, { + onClick () { + history.push(ADD_TOKEN_ROUTE) + metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Token Menu', + name: 'Clicked "Add Token"', + }, + }) + if (sidebarOpen) { + hideSidebar() + } + }, + }) +} + +WalletView.prototype.render = function () { + const { + responsiveDisplayClassname, + selectedAddress, + keyrings, + identities, + network, + } = this.props + // temporary logs + fake extra wallets + + const checksummedAddress = checksumAddress(selectedAddress, network) + + if (!selectedAddress) { + throw new Error('selectedAddress should not be ' + String(selectedAddress)) + } + + const keyring = keyrings.find((kr) => { + return kr.accounts.includes(selectedAddress) + }) + + let label = '' + let type + if (keyring) { + type = keyring.type + if (type !== 'HD Key Tree') { + if (type.toLowerCase().search('hardware') !== -1) { + label = this.context.t('hardware') + } else { + label = this.context.t('imported') + } + } + } + + return h('div.wallet-view.flex-column', { + style: {}, + className: responsiveDisplayClassname, + }, [ + + h(AccountDetails, { + label, + checksummedAddress, + name: identities[selectedAddress].name, + }), + + this.renderWalletBalance(), + + h(TokenList), + + this.renderAddToken(), + ]) +} + +// TODO: Extra wallets, for dev testing. Remove when PRing to master. +// const extraWallet = h('div.flex-column.wallet-balance-wrapper', {}, [ +// h('div.wallet-balance', {}, [ +// h(BalanceComponent, { +// balanceValue: selectedAccount.balance, +// style: {}, +// }), +// ]), +// ]) diff --git a/ui/app/components/app/wallet-view/index.js b/ui/app/components/app/wallet-view/index.js deleted file mode 100644 index bc8fd26aba..0000000000 --- a/ui/app/components/app/wallet-view/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './wallet-view.container' diff --git a/ui/app/components/app/wallet-view/wallet-view.component.js b/ui/app/components/app/wallet-view/wallet-view.component.js deleted file mode 100644 index dc3abf2593..0000000000 --- a/ui/app/components/app/wallet-view/wallet-view.component.js +++ /dev/null @@ -1,147 +0,0 @@ -import classnames from 'classnames' -import PropTypes from 'prop-types' -import React, { Component } from 'react' -import BalanceComponent from '../../ui/balance' -import AddTokenButton from '../add-token-button' -import AccountDetails from '../account-details' - -const { checksumAddress } = require('../../../helpers/utils/util') -const TokenList = require('../token-list') -const { ADD_TOKEN_ROUTE, CONNECTED_ROUTE } = require('../../../helpers/constants/routes') - -export default class WalletView extends Component { - static contextTypes = { - t: PropTypes.func, - metricsEvent: PropTypes.func, - } - - static defaultProps = { - responsiveDisplayClassname: '', - selectedAccount: null, - selectedTokenAddress: null, - } - - static propTypes = { - selectedTokenAddress: PropTypes.string, - selectedAccount: PropTypes.object, - selectedAddress: PropTypes.string.isRequired, - keyrings: PropTypes.array.isRequired, - responsiveDisplayClassname: PropTypes.string, - identities: PropTypes.object.isRequired, - history: PropTypes.object.isRequired, - unsetSelectedToken: PropTypes.func.isRequired, - sidebarOpen: PropTypes.bool.isRequired, - hideSidebar: PropTypes.func.isRequired, - } - - renderWalletBalance () { - const { - selectedTokenAddress, - selectedAccount, - unsetSelectedToken, - hideSidebar, - sidebarOpen, - } = this.props - - return ( -
    -
    { - unsetSelectedToken() - selectedTokenAddress && sidebarOpen && hideSidebar() - }} - > - -
    -
    - ) - } - - renderAddToken () { - const { - sidebarOpen, - hideSidebar, - history, - } = this.props - const { metricsEvent } = this.context - - return ( - { - history.push(ADD_TOKEN_ROUTE) - metricsEvent({ - eventOpts: { - category: 'Navigation', - action: 'Token Menu', - name: 'Clicked "Add Token"', - }, - }) - if (sidebarOpen) { - hideSidebar() - } - }} - /> - ) - } - - showConnectedSites = () => { - const { - sidebarOpen, - hideSidebar, - history, - } = this.props - history.push(CONNECTED_ROUTE) - if (sidebarOpen) { - hideSidebar() - } - } - - render () { - const { - responsiveDisplayClassname, - selectedAddress, - keyrings, - identities, - } = this.props - - const checksummedAddress = checksumAddress(selectedAddress) - - const keyring = keyrings.find((kr) => { - return kr.accounts.includes(selectedAddress) - }) - - let label = '' - let type - if (keyring) { - type = keyring.type - if (type !== 'HD Key Tree') { - if (type.toLowerCase().search('hardware') !== -1) { - label = this.context.t('hardware') - } else { - label = this.context.t('imported') - } - } - } - - return ( -
    - - {this.renderWalletBalance()} - - {this.renderAddToken()} -
    - ) - } -} diff --git a/ui/app/components/app/wallet-view/wallet-view.container.js b/ui/app/components/app/wallet-view/wallet-view.container.js deleted file mode 100644 index f0329e3c36..0000000000 --- a/ui/app/components/app/wallet-view/wallet-view.container.js +++ /dev/null @@ -1,33 +0,0 @@ -import { connect } from 'react-redux' -import { withRouter } from 'react-router-dom' -import { compose } from 'recompose' -import WalletView from './wallet-view.component' -import {showSendPage, hideSidebar, setSelectedToken, showAddTokenPage} from '../../../store/actions' -import * as selectors from '../../../selectors/selectors' - -function mapStateToProps (state) { - return { - network: state.metamask.network, - sidebarOpen: state.appState.sidebar.isOpen, - identities: state.metamask.identities, - accounts: selectors.getMetaMaskAccounts(state), - keyrings: state.metamask.keyrings, - selectedAddress: selectors.getSelectedAddress(state), - selectedAccount: selectors.getSelectedAccount(state), - selectedTokenAddress: state.metamask.selectedTokenAddress, - } -} - -function mapDispatchToProps (dispatch) { - return { - showSendPage: () => dispatch(showSendPage()), - hideSidebar: () => dispatch(hideSidebar()), - unsetSelectedToken: () => dispatch(setSelectedToken()), - showAddTokenPage: () => dispatch(showAddTokenPage()), - } -} - -export default compose( - withRouter, - connect(mapStateToProps, mapDispatchToProps) -)(WalletView) diff --git a/ui/app/components/ui/alert/index.js b/ui/app/components/ui/alert/index.js index 7d09de3cba..da2ca4b661 100644 --- a/ui/app/components/ui/alert/index.js +++ b/ui/app/components/ui/alert/index.js @@ -1,12 +1,17 @@ -import classnames from 'classnames' -import PropTypes from 'prop-types' -import React, { Component } from 'react' +const { Component } = require('react') +const PropTypes = require('prop-types') +const h = require('react-hyperscript') class Alert extends Component { - state = { - visible: false, - msg: false, - className: '', + + constructor (props) { + super(props) + + this.state = { + visble: false, + msg: false, + className: '', + } } componentWillReceiveProps (nextProps) { @@ -21,14 +26,14 @@ class Alert extends Component { this.setState({ msg: props.msg, visible: true, - className: 'visible', + className: '.visible', }) } animateOut () { this.setState({ msg: null, - className: 'hidden', + className: '.hidden', }) setTimeout(_ => { @@ -40,9 +45,9 @@ class Alert extends Component { render () { 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/ui/button-group/button-group.stories.js b/ui/app/components/ui/button-group/button-group.stories.js index 1596680c1a..c58c628b33 100644 --- a/ui/app/components/ui/button-group/button-group.stories.js +++ b/ui/app/components/ui/button-group/button-group.stories.js @@ -6,7 +6,7 @@ import Button from '../button' import { text, boolean } from '@storybook/addon-knobs/react' storiesOf('ButtonGroup', module) - .add('with Buttons', () => ( + .add('with Buttons', () => - )) - .add('with a disabled Button', () => ( + ) + .add('with a disabled Button', () => - )) + ) diff --git a/ui/app/components/ui/button-group/tests/button-group-component.test.js b/ui/app/components/ui/button-group/tests/button-group-component.test.js index b07b16cc92..663d86c74c 100644 --- a/ui/app/components/ui/button-group/tests/button-group-component.test.js +++ b/ui/app/components/ui/button-group/tests/button-group-component.test.js @@ -21,16 +21,12 @@ describe('ButtonGroup Component', function () { let wrapper beforeEach(() => { - wrapper = shallow(( - - {mockButtons} - - )) + wrapper = shallow({mockButtons}) }) afterEach(() => { diff --git a/ui/app/components/ui/button/button.stories.js b/ui/app/components/ui/button/button.stories.js index 6540d5aa1f..9df53439dd 100644 --- a/ui/app/components/ui/button/button.stories.js +++ b/ui/app/components/ui/button/button.stories.js @@ -6,7 +6,7 @@ import { text, boolean } from '@storybook/addon-knobs/react' // ', 'secondary', 'default', 'warning', 'danger', 'danger-primary', 'link'], 'primary')} storiesOf('Button', module) - .add('Button - Primary', () => ( + .add('Button - Primary', () => - )) - .add('Button - Secondary', () => ( + ) + .add('Button - Secondary', () => - )) - .add('Button - Default', () => ( + ) + .add('Button - Default', () => - )) - .add('Button - Warning', () => ( + ) + .add('Button - Warning', () => - )) - .add('Button - Danger', () => ( + ) + .add('Button - Danger', () => - )) - .add('Button - Danger Primary', () => ( + ) + .add('Button - Danger Primary', () => - )) - .add('Button - Link', () => ( + ) + .add('Button - Link', () => - )) + ) diff --git a/ui/app/components/ui/copyButton.js b/ui/app/components/ui/copyButton.js index d235fea04b..a60d335233 100644 --- a/ui/app/components/ui/copyButton.js +++ b/ui/app/components/ui/copyButton.js @@ -1,66 +1,66 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' -import { connect } from 'react-redux' - +const Component = require('react').Component +const PropTypes = require('prop-types') +const h = require('react-hyperscript') +const inherits = require('util').inherits const copyToClipboard = require('copy-to-clipboard') +const connect = require('react-redux').connect + const Tooltip = require('./tooltip') -class CopyButton extends Component { - static contextTypes = { - t: PropTypes.func, - } +CopyButton.contextTypes = { + t: PropTypes.func, +} + +module.exports = connect()(CopyButton) + + +inherits(CopyButton, Component) +function CopyButton () { + Component.call(this) +} - static defaultProps = { - title: null, - } +// As parameters, accepts: +// "value", which is the value to copy (mandatory) +// "title", which is the text to show on hover (optional, defaults to 'Copy') +CopyButton.prototype.render = function () { + const props = this.props + const state = this.state || {} - static propTypes = { - value: PropTypes.string.isRequired, - title: PropTypes.string, - } + const value = props.value + const copied = state.copied - state = {} + const message = copied ? this.context.t('copiedButton') : props.title || this.context.t('copyButton') - debounceRestore = () => { - this.setState({ copied: true }) - clearTimeout(this.timeout) - this.timeout = setTimeout(() => { - this.setState({ copied: false }) - }, 850) - } + return h('.copy-button', { + style: { + display: 'flex', + alignItems: 'center', + }, + }, [ - render () { - const state = this.state - const props = this.props - const value = props.value - const copied = state.copied - const message = copied ? this.context.t('copiedButton') : props.title || this.context.t('copyButton') + h(Tooltip, { + title: message, + }, [ + h('i.fa.fa-clipboard.cursor-pointer.color-orange', { + style: { + margin: '5px', + }, + onClick: (event) => { + event.preventDefault() + event.stopPropagation() + copyToClipboard(value) + this.debounceRestore() + }, + }), + ]), - return ( -
    - - { - event.preventDefault() - event.stopPropagation() - copyToClipboard(value) - this.debounceRestore() - }} - /> - -
    - ) - } + ]) } -module.exports = connect()(CopyButton) +CopyButton.prototype.debounceRestore = function () { + this.setState({ copied: true }) + clearTimeout(this.timeout) + this.timeout = setTimeout(() => { + this.setState({ copied: false }) + }, 850) +} diff --git a/ui/app/components/ui/currency-display/tests/currency-display.component.test.js b/ui/app/components/ui/currency-display/tests/currency-display.component.test.js index 387493da64..d9ef052f1b 100644 --- a/ui/app/components/ui/currency-display/tests/currency-display.component.test.js +++ b/ui/app/components/ui/currency-display/tests/currency-display.component.test.js @@ -5,25 +5,21 @@ import CurrencyDisplay from '../currency-display.component' describe('CurrencyDisplay Component', () => { it('should render text with a className', () => { - const wrapper = shallow(( - - )) + const wrapper = shallow() assert.ok(wrapper.hasClass('currency-display')) assert.equal(wrapper.text(), '$123.45') }) it('should render text with a prefix', () => { - const wrapper = shallow(( - - )) + const wrapper = shallow() assert.ok(wrapper.hasClass('currency-display')) assert.equal(wrapper.text(), '-$123.45') diff --git a/ui/app/components/ui/editable-label.js b/ui/app/components/ui/editable-label.js index 43b2888193..8eb10e1742 100644 --- a/ui/app/components/ui/editable-label.js +++ b/ui/app/components/ui/editable-label.js @@ -1,17 +1,16 @@ -import classnames from 'classnames' -import PropTypes from 'prop-types' -import React, { Component } from 'react' +const { Component } = require('react') +const PropTypes = require('prop-types') +const h = require('react-hyperscript') +const classnames = require('classnames') class EditableLabel extends Component { - static propTypes = { - onSubmit: PropTypes.func.isRequired, - defaultValue: PropTypes.string, - className: PropTypes.string, - } + constructor (props) { + super(props) - state = { - isEditing: false, - value: this.props.defaultValue || '', + this.state = { + isEditing: false, + value: props.defaultValue || '', + } } handleSubmit () { @@ -25,41 +24,46 @@ class EditableLabel extends Component { .then(() => this.setState({ isEditing: false })) } + saveIfEnter (event) { + if (event.key === 'Enter') { + this.handleSubmit() + } + } + renderEditing () { const { value } = this.state - return [( - { + return ([ + h('input.large-input.editable-label__input', { + type: 'text', + required: true, + dir: 'auto', + value: this.state.value, + onKeyPress: (event) => { if (event.key === 'Enter') { this.handleSubmit() } - }} - onChange={event => this.setState({ value: event.target.value })} - className={classnames('large-input', 'editable-label__input', { - 'editable-label__input--error': value === '', - })} - /> - ), ( -
    - this.handleSubmit()} /> -
    - )] + }, + onChange: event => this.setState({ value: event.target.value }), + className: classnames({ 'editable-label__input--error': value === '' }), + }), + h('div.editable-label__icon-wrapper', [ + h('i.fa.fa-check.editable-label__icon', { + onClick: () => this.handleSubmit(), + }), + ]), + ]) } renderReadonly () { - return [( -
    {this.state.value}
    - ), ( -
    - this.setState({ isEditing: true })} /> -
    - )] + return ([ + h('div.editable-label__value', this.state.value), + h('div.editable-label__icon-wrapper', [ + h('i.fa.fa-pencil.editable-label__icon', { + onClick: () => this.setState({ isEditing: true }), + }), + ]), + ]) } render () { @@ -67,15 +71,19 @@ class EditableLabel extends Component { const { className } = this.props return ( -
    - { - isEditing - ? this.renderEditing() - : this.renderReadonly() - } -
    + h('div.editable-label', { className: classnames(className) }, + isEditing + ? this.renderEditing() + : this.renderReadonly() + ) ) } } +EditableLabel.propTypes = { + onSubmit: PropTypes.func.isRequired, + defaultValue: PropTypes.string, + className: PropTypes.string, +} + module.exports = EditableLabel diff --git a/ui/app/components/ui/eth-balance.js b/ui/app/components/ui/eth-balance.js new file mode 100644 index 0000000000..7d577b7162 --- /dev/null +++ b/ui/app/components/ui/eth-balance.js @@ -0,0 +1,102 @@ +const { Component } = require('react') +const h = require('react-hyperscript') +const connect = require('react-redux').connect +const { inherits } = require('util') +const { + formatBalance, + generateBalanceObject, +} = require('../../helpers/utils/util') +const Tooltip = require('./tooltip.js') +const FiatValue = require('./fiat-value.js') + +module.exports = connect(mapStateToProps)(EthBalanceComponent) +function mapStateToProps (state) { + return { + ticker: state.metamask.ticker, + } +} + +inherits(EthBalanceComponent, Component) +function EthBalanceComponent () { + Component.call(this) +} + +EthBalanceComponent.prototype.render = function () { + const props = this.props + const { ticker, value, style, width, needsParse = true } = props + + const formattedValue = value ? formatBalance(value, 6, needsParse, ticker) : '...' + + return ( + + h('.ether-balance.ether-balance-amount', { + style, + }, [ + h('div', { + style: { + display: 'inline', + width, + }, + }, this.renderBalance(formattedValue)), + ]) + + ) +} +EthBalanceComponent.prototype.renderBalance = function (value) { + if (value === 'None') return value + if (value === '...') return value + + const { + conversionRate, + shorten, + incoming, + currentCurrency, + hideTooltip, + styleOveride = {}, + showFiat = true, + } = this.props + const { fontSize, color, fontFamily, lineHeight } = styleOveride + + const { shortBalance, balance, label } = generateBalanceObject(value, shorten ? 1 : 3) + const balanceToRender = shorten ? shortBalance : balance + + const [ethNumber, ethSuffix] = value.split(' ') + const containerProps = hideTooltip ? {} : { + position: 'bottom', + title: `${ethNumber} ${ethSuffix}`, + } + + return ( + h(hideTooltip ? 'div' : Tooltip, + containerProps, + h('div.flex-column', [ + h('.flex-row', { + style: { + alignItems: 'flex-end', + lineHeight: lineHeight || '13px', + fontFamily: fontFamily || 'Montserrat Light', + textRendering: 'geometricPrecision', + }, + }, [ + h('div', { + style: { + width: '100%', + textAlign: 'right', + fontSize: fontSize || 'inherit', + color: color || 'inherit', + }, + }, incoming ? `+${balanceToRender}` : balanceToRender), + h('div', { + style: { + color: color || '#AEAEAE', + fontSize: fontSize || '12px', + marginLeft: '5px', + }, + }, label), + ]), + + showFiat ? h(FiatValue, { value: this.props.value, conversionRate, currentCurrency }) : null, + ]) + ) + ) +} diff --git a/ui/app/components/ui/eth-balance/eth-balance.component.js b/ui/app/components/ui/eth-balance/eth-balance.component.js deleted file mode 100644 index 925395602d..0000000000 --- a/ui/app/components/ui/eth-balance/eth-balance.component.js +++ /dev/null @@ -1,137 +0,0 @@ -import PropTypes from 'prop-types' -import React, {Component} from 'react' - -const { - formatBalance, - generateBalanceObject, -} = require('../../../helpers/utils/util') -const Tooltip = require('../tooltip.js') -const FiatValue = require('../fiat-value.js') - -export default class EthBalance extends Component { - static defaultProps = { - style: null, - styleOverride: {}, - showFiat: true, - needsParse: true, - width: undefined, - shorten: false, - incoming: false, - } - - static propTypes = { - conversionRate: PropTypes.any.isRequired, - shorten: PropTypes.bool, - incoming: PropTypes.bool, - currentCurrency: PropTypes.string.isRequired, - hideTooltip: PropTypes.bool, - styleOverride: PropTypes.object, - showFiat: PropTypes.bool, - ticker: PropTypes.string.isRequired, - value: PropTypes.string.isRequired, - style: PropTypes.object, - width: PropTypes.string, - needsParse: PropTypes.bool, - } - - renderBalance (value) { - if (value === 'None') { - return value - } - if (value === '...') { - return value - } - - const { - conversionRate, - shorten, - incoming, - currentCurrency, - hideTooltip, - styleOverride = {}, - showFiat = true, - } = this.props - const { fontSize, color, fontFamily, lineHeight } = styleOverride - - const { shortBalance, balance, label } = generateBalanceObject(value, shorten ? 1 : 3) - const balanceToRender = shorten ? shortBalance : balance - - const [ethNumber, ethSuffix] = value.split(' ') - const containerProps = hideTooltip ? {} : { - position: 'bottom', - title: `${ethNumber} ${ethSuffix}`, - } - - const TooltipComponent = hideTooltip ? 'div' : Tooltip - - return ( - -
    -
    -
    - { - incoming - ? `+${balanceToRender}` - : balanceToRender - } -
    -
    - {label} -
    -
    -
    - { - showFiat - ? ( - - ) - : null - } -
    - ) - } - - render () { - const { ticker, value, style, width, needsParse } = this.props - const formattedValue = value - ? formatBalance(value, 6, needsParse, ticker) - : '...' - - return ( -
    -
    - {this.renderBalance(formattedValue)} -
    -
    - ) - } -} diff --git a/ui/app/components/ui/eth-balance/eth-balance.container.js b/ui/app/components/ui/eth-balance/eth-balance.container.js deleted file mode 100644 index 8e36b6f78e..0000000000 --- a/ui/app/components/ui/eth-balance/eth-balance.container.js +++ /dev/null @@ -1,10 +0,0 @@ -import { connect } from 'react-redux' -import EthBalance from './eth-balance.component' - -function mapStateToProps (state) { - return { - ticker: state.metamask.ticker, - } -} - -export default connect(mapStateToProps)(EthBalance) diff --git a/ui/app/components/ui/eth-balance/index.js b/ui/app/components/ui/eth-balance/index.js deleted file mode 100644 index f409917843..0000000000 --- a/ui/app/components/ui/eth-balance/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './eth-balance.container' diff --git a/ui/app/components/ui/export-text-container/export-text-container.component.js b/ui/app/components/ui/export-text-container/export-text-container.component.js index 23ae0b0472..21fd5ecec2 100644 --- a/ui/app/components/ui/export-text-container/export-text-container.component.js +++ b/ui/app/components/ui/export-text-container/export-text-container.component.js @@ -1,5 +1,6 @@ -import React, { Component } from 'react' +const { Component } = require('react') const PropTypes = require('prop-types') +const h = require('react-hyperscript') const copyToClipboard = require('copy-to-clipboard') const { exportAsFile } = require('../../../helpers/utils/util') @@ -9,33 +10,25 @@ class ExportTextContainer extends Component { const { t } = this.context return ( -
    -
    -
    - {text} -
    -
    -
    -
    copyToClipboard(text)} - > - -
    - {t('copyToClipboard')} -
    -
    -
    exportAsFile(filename, text)} - > - -
    - {t('saveAsCsvFile')} -
    -
    -
    -
    + h('.export-text-container', [ + h('.export-text-container__text-container', [ + h('.export-text-container__text.notranslate', text), + ]), + h('.export-text-container__buttons-container', [ + h('.export-text-container__button.export-text-container__button--copy', { + onClick: () => copyToClipboard(text), + }, [ + h('img', { src: 'images/copy-to-clipboard.svg' }), + h('.export-text-container__button-text', t('copyToClipboard')), + ]), + h('.export-text-container__button', { + onClick: () => exportAsFile(filename, text), + }, [ + h('img', { src: 'images/download.svg' }), + h('.export-text-container__button-text', t('saveAsCsvFile')), + ]), + ]), + ]) ) } } diff --git a/ui/app/components/ui/fiat-value.js b/ui/app/components/ui/fiat-value.js index a02264b01c..02111ba49d 100644 --- a/ui/app/components/ui/fiat-value.js +++ b/ui/app/components/ui/fiat-value.js @@ -1,4 +1,5 @@ -import React, { Component } from 'react' +const Component = require('react').Component +const h = require('react-hyperscript') const inherits = require('util').inherits const formatBalance = require('../../helpers/utils/util').formatBalance @@ -16,11 +17,9 @@ FiatValue.prototype.render = function () { const value = formatBalance(props.value, 6) - if (value === 'None') { - return value - } - let fiatDisplayNumber, fiatTooltipNumber - const splitBalance = value.split(' ') + if (value === 'None') return value + var fiatDisplayNumber, fiatTooltipNumber + var splitBalance = value.split(' ') if (conversionRate !== 0) { fiatTooltipNumber = Number(splitBalance[0]) * conversionRate @@ -37,38 +36,31 @@ function fiatDisplay (fiatDisplayNumber, fiatSuffix, styleOveride = {}) { const { fontSize, color, fontFamily, lineHeight } = styleOveride if (fiatDisplayNumber !== 'N/A') { - return ( -
    -
    - {fiatDisplayNumber} -
    -
    - {fiatSuffix} -
    -
    - ) + return h('.flex-row', { + style: { + alignItems: 'flex-end', + lineHeight: lineHeight || '13px', + fontFamily: fontFamily || 'Montserrat Light', + textRendering: 'geometricPrecision', + }, + }, [ + h('div', { + style: { + width: '100%', + textAlign: 'right', + fontSize: fontSize || '12px', + color: color || '#333333', + }, + }, fiatDisplayNumber), + h('div', { + style: { + color: color || '#AEAEAE', + marginLeft: '5px', + fontSize: fontSize || '12px', + }, + }, fiatSuffix), + ]) } else { - return
    + return h('div') } } diff --git a/ui/app/components/ui/hex-to-decimal/tests/hex-to-decimal.component.test.js b/ui/app/components/ui/hex-to-decimal/tests/hex-to-decimal.component.test.js index 848ef2c4f0..c98da9ad40 100644 --- a/ui/app/components/ui/hex-to-decimal/tests/hex-to-decimal.component.test.js +++ b/ui/app/components/ui/hex-to-decimal/tests/hex-to-decimal.component.test.js @@ -5,24 +5,20 @@ import HexToDecimal from '../hex-to-decimal.component' describe('HexToDecimal Component', () => { it('should render a prefixed hex as a decimal with a className', () => { - const wrapper = shallow(( - - )) + const wrapper = shallow() assert.ok(wrapper.hasClass('hex-to-decimal')) assert.equal(wrapper.text(), '12345') }) it('should render an unprefixed hex as a decimal with a className', () => { - const wrapper = shallow(( - - )) + const wrapper = shallow() assert.ok(wrapper.hasClass('hex-to-decimal')) assert.equal(wrapper.text(), '6789') diff --git a/ui/app/components/ui/icon-with-fallback/icon-with-fallback.component.js b/ui/app/components/ui/icon-with-fallback/icon-with-fallback.component.js deleted file mode 100644 index 13b3e93d78..0000000000 --- a/ui/app/components/ui/icon-with-fallback/icon-with-fallback.component.js +++ /dev/null @@ -1,42 +0,0 @@ -import React, { PureComponent } from 'react' -import PropTypes from 'prop-types' - -export default class IconWithFallback extends PureComponent { - static propTypes = { - icon: PropTypes.string, - name: PropTypes.string, - } - - static defaultProps = { - name: '', - icon: null, - } - - state = { - iconError: false, - } - - render () { - const { icon, name } = this.props - - return ( -
    -
    - { !this.state.iconError && icon - ? ( - this.setState({ iconError: true })} - /> - ) - : ( - - { name.length ? name.charAt(0).toUpperCase() : '' } - - ) - } -
    - ) - } -} diff --git a/ui/app/components/ui/icon-with-fallback/index.js b/ui/app/components/ui/icon-with-fallback/index.js deleted file mode 100644 index 8c1f9a154b..0000000000 --- a/ui/app/components/ui/icon-with-fallback/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './icon-with-fallback.component' diff --git a/ui/app/components/ui/icon-with-fallback/index.scss b/ui/app/components/ui/icon-with-fallback/index.scss deleted file mode 100644 index 02ffe371df..0000000000 --- a/ui/app/components/ui/icon-with-fallback/index.scss +++ /dev/null @@ -1,30 +0,0 @@ -.icon-with-fallback { - &__identicon-container { - position: relative; - display: flex; - justify-content: center; - align-items: center; - height: 32px; - width: 32px; - } - - &__identicon-border { - height: 32px; - width: 32px; - border-radius: 50%; - border: 1px solid #F2F3F4; - position: absolute; - background: #FFFFFF; - } - - &__identicon { - width: 24px; - height: 24px; - z-index: 1; - - &--default { - z-index: 1; - color: black; - } - } -} diff --git a/ui/app/components/ui/identicon/tests/identicon.component.test.js b/ui/app/components/ui/identicon/tests/identicon.component.test.js index b1efb158cb..2944818f5f 100644 --- a/ui/app/components/ui/identicon/tests/identicon.component.test.js +++ b/ui/app/components/ui/identicon/tests/identicon.component.test.js @@ -18,7 +18,7 @@ describe('Identicon', () => { it('renders default eth_logo identicon with no props', () => { const wrapper = mount( - + ) assert.equal(wrapper.find('img.balance-icon').prop('src'), './images/eth_logo.svg') diff --git a/ui/app/components/ui/loading-screen/loading-screen.component.js b/ui/app/components/ui/loading-screen/loading-screen.component.js index b3fb3308d5..6b843cfee0 100644 --- a/ui/app/components/ui/loading-screen/loading-screen.component.js +++ b/ui/app/components/ui/loading-screen/loading-screen.component.js @@ -1,33 +1,31 @@ -import React, {Component} from 'react' +const { Component } = require('react') +const h = require('react-hyperscript') const PropTypes = require('prop-types') const Spinner = require('../spinner') class LoadingScreen extends Component { - static defaultProps = { - loadingMessage: null, - } - - static propTypes = { - loadingMessage: PropTypes.string, - } - renderMessage () { const { loadingMessage } = this.props - return loadingMessage - ? {loadingMessage} - : null + return loadingMessage && h('span', loadingMessage) } render () { return ( -
    -
    - - {this.renderMessage()} -
    -
    + h('.loading-overlay', [ + h('.loading-overlay__container', [ + h(Spinner, { + color: '#F7C06C', + }), + + this.renderMessage(), + ]), + ]) ) } } +LoadingScreen.propTypes = { + loadingMessage: PropTypes.string, +} + module.exports = LoadingScreen diff --git a/ui/app/components/ui/mascot.js b/ui/app/components/ui/mascot.js index f849617450..3b0d3e31b4 100644 --- a/ui/app/components/ui/mascot.js +++ b/ui/app/components/ui/mascot.js @@ -1,5 +1,6 @@ -import React, { Component } from 'react' const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') const metamaskLogo = require('metamask-logo') const debounce = require('debounce') @@ -19,22 +20,20 @@ function Mascot ({width = '200', height = '200'}) { this.unfollowMouse = this.logo.setFollowMouse.bind(this.logo, false) } -Mascot.prototype.render = function Mascot () { +Mascot.prototype.render = function () { // this is a bit hacky // the event emitter is on `this.props` // and we dont get that until render this.handleAnimationEvents() - return ( -
    - ) + + return h('#metamask-mascot-container', { + style: { zIndex: 0 }, + }) } Mascot.prototype.componentDidMount = function () { - const targetDivId = 'metamask-mascot-container' - const container = document.getElementById(targetDivId) + var targetDivId = 'metamask-mascot-container' + var container = document.getElementById(targetDivId) container.appendChild(this.logo.container) } @@ -47,9 +46,7 @@ Mascot.prototype.componentWillUnmount = function () { Mascot.prototype.handleAnimationEvents = function () { // only setup listeners once - if (this.animations) { - return - } + if (this.animations) return this.animations = this.props.animationEventEmitter this.animations.on('point', this.lookAt.bind(this)) this.animations.on('setFollowMouse', this.logo.setFollowMouse.bind(this.logo)) diff --git a/ui/app/components/ui/page-container/page-container-footer/page-container-footer.component.js b/ui/app/components/ui/page-container/page-container-footer/page-container-footer.component.js index 338df83d89..a2cf0100bf 100644 --- a/ui/app/components/ui/page-container/page-container-footer/page-container-footer.component.js +++ b/ui/app/components/ui/page-container/page-container-footer/page-container-footer.component.js @@ -14,7 +14,6 @@ export default class PageContainerFooter extends Component { disabled: PropTypes.bool, submitButtonType: PropTypes.string, hideCancel: PropTypes.bool, - buttonSizeLarge: PropTypes.bool, } static contextTypes = { @@ -32,27 +31,24 @@ export default class PageContainerFooter extends Component { submitButtonType, hideCancel, cancelButtonType, - buttonSizeLarge = false, } = this.props return (
    - {!hideCancel && ( - - )} + {!hideCancel && } - -
    - ) + return h('div.hw-list-pagination', [ + h( + 'button.hw-list-pagination__button', + { + onClick: this.goToPreviousPage, + }, + `< ${this.context.t('prev')}` + ), + + h( + 'button.hw-list-pagination__button', + { + onClick: this.goToNextPage, + }, + `${this.context.t('next')} >` + ), + ]) } renderButtons () { @@ -133,49 +139,40 @@ class AccountList extends Component { buttonProps.disabled = true } - return ( -
    - - -
    - ) + return h('div.new-account-connect-form__buttons', {}, [ + h(Button, { + type: 'default', + large: true, + className: 'new-account-connect-form__button', + onClick: this.props.onCancel.bind(this), + }, [this.context.t('cancel')]), + + h(Button, { + type: 'primary', + large: true, + className: 'new-account-connect-form__button unlock', + disabled, + onClick: this.props.onUnlockAccount.bind(this, this.props.device), + }, [this.context.t('unlock')]), + ]) } renderForgetDevice () { - return ( -
    - ) + return h('div.hw-forget-device-container', {}, [ + h('a', { + onClick: this.props.onForgetDevice.bind(this, this.props.device), + }, this.context.t('forgetDevice')), + ]) } render () { - return ( -
    - {this.renderHeader()} - {this.renderAccounts()} - {this.renderPagination()} - {this.renderButtons()} - {this.renderForgetDevice()} -
    - ) + return h('div.new-account-connect-form.account-list', {}, [ + this.renderHeader(), + this.renderAccounts(), + this.renderPagination(), + this.renderButtons(), + this.renderForgetDevice(), + ]) } } diff --git a/ui/app/pages/create-account/connect-hardware/connect-screen.js b/ui/app/pages/create-account/connect-hardware/connect-screen.js index 6be85f5b7b..3b45e72936 100644 --- a/ui/app/pages/create-account/connect-hardware/connect-screen.js +++ b/ui/app/pages/create-account/connect-hardware/connect-screen.js @@ -1,228 +1,202 @@ -import classnames from 'classnames' -import PropTypes from 'prop-types' -import React, { Component } from 'react' +const { Component } = require('react') +const PropTypes = require('prop-types') +const h = require('react-hyperscript') import Button from '../../../components/ui/button' class ConnectScreen extends Component { - static contextTypes = { - t: PropTypes.func, + constructor (props) { + super(props) + this.state = { + selectedDevice: null, + } } - static propTypes = { - connectToHardwareWallet: PropTypes.func.isRequired, - browserSupported: PropTypes.bool.isRequired, - } + connect = () => { + if (this.state.selectedDevice) { + this.props.connectToHardwareWallet(this.state.selectedDevice) + } + return null + } - state = { - selectedDevice: null, - } + renderConnectToTrezorButton () { + return h( + `button.hw-connect__btn${this.state.selectedDevice === 'trezor' ? '.selected' : ''}`, + { onClick: _ => this.setState({selectedDevice: 'trezor'}) }, + h('img.hw-connect__btn__img', { + src: 'images/trezor-logo.svg', + }) + ) + } - connect = () => { - if (this.state.selectedDevice) { - this.props.connectToHardwareWallet(this.state.selectedDevice) + renderConnectToLedgerButton () { + return h( + `button.hw-connect__btn${this.state.selectedDevice === 'ledger' ? '.selected' : ''}`, + { onClick: _ => this.setState({selectedDevice: 'ledger'}) }, + h('img.hw-connect__btn__img', { + src: 'images/ledger-logo.svg', + }) + ) } - return null - } - renderConnectToTrezorButton () { - return ( - - ) - } + renderButtons () { + return ( + h('div', {}, [ + h('div.hw-connect__btn-wrapper', {}, [ + this.renderConnectToLedgerButton(), + this.renderConnectToTrezorButton(), + ]), + h(Button, { + type: 'primary', + large: true, + className: 'hw-connect__connect-btn', + onClick: this.connect, + disabled: !this.state.selectedDevice, + }, this.context.t('connect')), + ]) + ) + } - renderConnectToLedgerButton () { - return ( - - ) - } + renderUnsupportedBrowser () { + return ( + h('div.new-account-connect-form.unsupported-browser', {}, [ + h('div.hw-connect', [ + h('h3.hw-connect__title', {}, this.context.t('browserNotSupported')), + h('p.hw-connect__msg', {}, this.context.t('chromeRequiredForHardwareWallets')), + ]), + h(Button, { + type: 'primary', + large: true, + onClick: () => global.platform.openWindow({ + url: 'https://google.com/chrome', + }), + }, this.context.t('downloadGoogleChrome')), + ]) + ) + } - renderButtons () { - return ( -
    -
    - {this.renderConnectToLedgerButton()} - {this.renderConnectToTrezorButton()} -
    - -
    - ) - } + renderHeader () { + return ( + h('div.hw-connect__header', {}, [ + h('h3.hw-connect__header__title', {}, this.context.t('hardwareWallets')), + h('p.hw-connect__header__msg', {}, this.context.t('hardwareWalletsMsg')), + ]) + ) + } - renderUnsupportedBrowser () { - return ( -
    -
    -

    {this.context.t('browserNotSupported')}

    -

    {this.context.t('chromeRequiredForHardwareWallets')}

    -
    - -
    - ) - } + getAffiliateLinks () { + const links = { + trezor: `Trezor`, + ledger: `Ledger`, + } - renderHeader () { - return ( -
    -

    {this.context.t('hardwareWallets')}

    -

    {this.context.t('hardwareWalletsMsg')}

    -
    - ) - } + const text = this.context.t('orderOneHere') + const response = text.replace('Trezor', links.trezor).replace('Ledger', links.ledger) - getAffiliateLinks () { - const links = { - trezor: `Trezor`, - ledger: `Ledger`, + return h('div.hw-connect__get-hw__msg', { dangerouslySetInnerHTML: {__html: response }}) } - const text = this.context.t('orderOneHere') - const response = text.replace('Trezor', links.trezor).replace('Ledger', links.ledger) - - return ( -
    - ) - } - - renderTrezorAffiliateLink () { - return ( -
    -

    {this.context.t('dontHaveAHardwareWallet')}

    - {this.getAffiliateLinks()} -
    - ) - } + renderTrezorAffiliateLink () { + return h('div.hw-connect__get-hw', {}, [ + h('p.hw-connect__get-hw__msg', {}, this.context.t('dontHaveAHardwareWallet')), + this.getAffiliateLinks(), + ]) + } - scrollToTutorial = () => { - if (this.referenceNode) { - this.referenceNode.scrollIntoView({behavior: 'smooth'}) + scrollToTutorial = () => { + if (this.referenceNode) this.referenceNode.scrollIntoView({behavior: 'smooth'}) } - } - renderLearnMore () { - return ( -

    - {this.context.t('learnMore')} - -

    - ) - } + renderLearnMore () { + return ( + h('p.hw-connect__learn-more', { + onClick: this.scrollToTutorial, + }, [ + this.context.t('learnMore'), + h('img.hw-connect__learn-more__arrow', { src: 'images/caret-right.svg'}), + ]) + ) + } - renderTutorialSteps () { - const steps = [ - { - asset: 'hardware-wallet-step-1', - dimensions: {width: '225px', height: '75px'}, - title: this.context.t('step1HardwareWallet'), - message: this.context.t('step1HardwareWalletMsg'), - }, - { - asset: 'hardware-wallet-step-2', - dimensions: {width: '300px', height: '100px'}, - title: this.context.t('step2HardwareWallet'), - message: this.context.t('step2HardwareWalletMsg'), + renderTutorialSteps () { + const steps = [ + { + asset: 'hardware-wallet-step-1', + dimensions: {width: '225px', height: '75px'}, + title: this.context.t('step1HardwareWallet'), + message: this.context.t('step1HardwareWalletMsg'), + }, + { + asset: 'hardware-wallet-step-2', + dimensions: {width: '300px', height: '100px'}, + title: this.context.t('step2HardwareWallet'), + message: this.context.t('step2HardwareWalletMsg'), + }, + { + asset: 'hardware-wallet-step-3', + dimensions: {width: '120px', height: '90px'}, + title: this.context.t('step3HardwareWallet'), + message: this.context.t('step3HardwareWalletMsg'), + }, + ] + + return h('.hw-tutorial', { + ref: node => { this.referenceNode = node }, }, - { - asset: 'hardware-wallet-step-3', - dimensions: {width: '120px', height: '90px'}, - title: this.context.t('step3HardwareWallet'), - message: this.context.t('step3HardwareWalletMsg'), - }, - ] - - return ( -
    { - this.referenceNode = node - }} - > - {steps.map((step, index) => ( -
    -

    {step.title}

    -

    {step.message}

    - -
    - ))} -
    - ) - } + steps.map((step) => ( + h('div.hw-connect', {}, [ + h('h3.hw-connect__title', {}, step.title), + h('p.hw-connect__msg', {}, step.message), + h('img.hw-connect__step-asset', { src: `images/${step.asset}.svg`, ...step.dimensions }), + ]) + )) + ) + } - renderFooter () { - return ( -
    -

    {this.context.t('readyToConnect')}

    - {this.renderButtons()} -

    - {this.context.t('havingTroubleConnecting')} - - {this.context.t('getHelp')} - -

    -
    - ) - } + renderFooter () { + return ( + h('div.hw-connect__footer', {}, [ + h('h3.hw-connect__footer__title', {}, this.context.t('readyToConnect')), + this.renderButtons(), + h('p.hw-connect__footer__msg', {}, [ + this.context.t('havingTroubleConnecting'), + h('a.hw-connect__footer__link', { + href: 'https://support.metamask.io/', + target: '_blank', + }, this.context.t('getHelp')), + ]), + ]) + ) + } - renderConnectScreen () { - return ( -
    - {this.renderHeader()} - {this.renderButtons()} - {this.renderTrezorAffiliateLink()} - {this.renderLearnMore()} - {this.renderTutorialSteps()} - {this.renderFooter()} -
    - ) - } + renderConnectScreen () { + return ( + h('div.new-account-connect-form', {}, [ + this.renderHeader(), + this.renderButtons(), + this.renderTrezorAffiliateLink(), + this.renderLearnMore(), + this.renderTutorialSteps(), + this.renderFooter(), + ]) + ) + } - render () { - if (this.props.browserSupported) { - return this.renderConnectScreen() + render () { + if (this.props.browserSupported) { + return this.renderConnectScreen() + } + return this.renderUnsupportedBrowser() } - return this.renderUnsupportedBrowser() - } +} + +ConnectScreen.propTypes = { + connectToHardwareWallet: PropTypes.func.isRequired, + browserSupported: PropTypes.bool.isRequired, +} + +ConnectScreen.contextTypes = { + t: PropTypes.func, } module.exports = ConnectScreen diff --git a/ui/app/pages/create-account/connect-hardware/index.js b/ui/app/pages/create-account/connect-hardware/index.js index 351699e8a1..66851c7806 100644 --- a/ui/app/pages/create-account/connect-hardware/index.js +++ b/ui/app/pages/create-account/connect-hardware/index.js @@ -1,5 +1,6 @@ -import React, { Component } from 'react' +const { Component } = require('react') const PropTypes = require('prop-types') +const h = require('react-hyperscript') const connect = require('react-redux').connect const actions = require('../../../store/actions') const { getMetaMaskAccounts } = require('../../../selectors/selectors') @@ -115,11 +116,10 @@ class ConnectHardwareForm extends Component { } }) .catch(e => { - const errorMessage = e.message - if (errorMessage === 'Window blocked') { + if (e === 'Window blocked') { this.setState({ browserSupported: false, error: null}) - } else if (errorMessage !== 'Window closed' && errorMessage !== 'Popup closed') { - this.setState({ error: errorMessage }) + } else if (e !== 'Window closed' && e !== 'Popup closed') { + this.setState({ error: e.toString() }) } }) } @@ -134,7 +134,7 @@ class ConnectHardwareForm extends Component { unlocked: false, }) }).catch(e => { - this.setState({ error: e.message }) + this.setState({ error: e.toString() }) }) } @@ -162,10 +162,10 @@ class ConnectHardwareForm extends Component { name: 'Error connecting hardware wallet', }, customVariables: { - error: e.message, + error: e.toString(), }, }) - this.setState({ error: e.message }) + this.setState({ error: e.toString() }) }) } @@ -175,53 +175,40 @@ class ConnectHardwareForm extends Component { renderError () { return this.state.error - ? ( - - {this.state.error} - - ) + ? h('span.error', { style: { margin: '20px 20px 10px', display: 'block', textAlign: 'center' } }, this.state.error) : null } renderContent () { if (!this.state.accounts.length) { - return ( - - ) + return h(ConnectScreen, { + connectToHardwareWallet: this.connectToHardwareWallet, + browserSupported: this.state.browserSupported, + }) } - return ( - - ) + return h(AccountList, { + onPathChange: this.onPathChange, + selectedPath: this.props.defaultHdPaths[this.state.device], + device: this.state.device, + accounts: this.state.accounts, + selectedAccount: this.state.selectedAccount, + onAccountChange: this.onAccountChange, + network: this.props.network, + getPage: this.getPage, + history: this.props.history, + onUnlockAccount: this.onUnlockAccount, + onForgetDevice: this.onForgetDevice, + onCancel: this.onCancel, + onAccountRestriction: this.onAccountRestriction, + }) } render () { - return ( -
    - {this.renderError()} - {this.renderContent()} -
    - ) + return h('div', [ + this.renderError(), + this.renderContent(), + ]) } } diff --git a/ui/app/pages/create-account/create-account.component.js b/ui/app/pages/create-account/create-account.component.js index 1f8bc0cc56..aa05af9759 100644 --- a/ui/app/pages/create-account/create-account.component.js +++ b/ui/app/pages/create-account/create-account.component.js @@ -23,15 +23,15 @@ export default class CreateAccountPage extends Component { return (
    -
    history.push(NEW_ACCOUNT_ROUTE)}> - {this.context.t('create')} -
    -
    history.push(IMPORT_ACCOUNT_ROUTE)}> - {this.context.t('import')} -
    -
    history.push(CONNECT_HARDWARE_ROUTE)}> - {this.context.t('connect')} -
    +
    history.push(NEW_ACCOUNT_ROUTE)}>{ + this.context.t('create') + }
    +
    history.push(IMPORT_ACCOUNT_ROUTE)}>{ + this.context.t('import') + }
    +
    history.push(CONNECT_HARDWARE_ROUTE)}>{ + this.context.t('connect') + }
    ) } diff --git a/ui/app/pages/create-account/import-account/index.js b/ui/app/pages/create-account/import-account/index.js index 5ef837715d..48d8f8838b 100644 --- a/ui/app/pages/create-account/import-account/index.js +++ b/ui/app/pages/create-account/import-account/index.js @@ -1,5 +1,6 @@ -import React, { Component } from 'react' const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') const PropTypes = require('prop-types') const connect = require('react-redux').connect import Select from 'react-select' @@ -34,45 +35,47 @@ AccountImportSubview.prototype.render = function () { const { type } = state return ( -
    -
    - {this.context.t('importAccountMsg')} - { + }, + onClick: () => { global.platform.openWindow({ url: 'https://metamask.zendesk.com/hc/en-us/articles/360015289932', }) - }} - > - {this.context.t('here')} - -
    -
    -
    - {this.context.t('selectType')} -
    - -
    - - -
    - { - error - ? {error} - : null - } -
    + }, + }), + + h('input.new-account-import-form__input-password', { + type: 'password', + placeholder: this.context.t('enterPassword'), + id: 'json-password-box', + onKeyPress: this.createKeyringOnEnter.bind(this), + }), + + h('div.new-account-create-form__buttons', {}, [ + + h(Button, { + type: 'default', + large: true, + className: 'new-account-create-form__button', + onClick: () => this.props.history.push(DEFAULT_ROUTE), + }, [this.context.t('cancel')]), + + h(Button, { + type: 'secondary', + large: true, + className: 'new-account-create-form__button', + onClick: () => this.createNewKeychain(), + }, [this.context.t('import')]), + + ]), + + error ? h('span.error', error) : null, + ]) ) } - onLoad (event) { - this.setState({ - fileContents: event.target.result, - }) + onLoad (event, file) { + this.setState({file: file, fileContents: event.target.result}) } createKeyringOnEnter (event) { @@ -83,7 +89,14 @@ class JsonImportSubview extends Component { createNewKeychain () { const { firstAddress, displayWarning, importNewJsonAccount, setSelectedAddress, history } = this.props - const { fileContents } = this.state + const state = this.state + + if (!state) { + const message = this.context.t('validFileImport') + return displayWarning(message) + } + + const { fileContents } = state if (!fileContents) { const message = this.context.t('needImportFile') diff --git a/ui/app/pages/create-account/import-account/private-key.js b/ui/app/pages/create-account/import-account/private-key.js index bbb2b70bdf..0cdf25ce9b 100644 --- a/ui/app/pages/create-account/import-account/private-key.js +++ b/ui/app/pages/create-account/import-account/private-key.js @@ -1,5 +1,6 @@ -import React, { Component } from 'react' const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') const { withRouter } = require('react-router-dom') const { compose } = require('recompose') const PropTypes = require('prop-types') @@ -43,49 +44,47 @@ function PrivateKeyImportView () { Component.call(this) } -PrivateKeyImportView.prototype.render = function PrivateKeyImportView () { +PrivateKeyImportView.prototype.render = function () { const { error, displayWarning } = this.props return ( -
    - - {this.context.t('pastePrivateKey')} - -
    - this.createKeyringOnEnter(e)} - /> -
    -
    - - -
    - { - error - ? {error} - : null - } -
    + }, + }, [this.context.t('cancel')]), + + h(Button, { + type: 'secondary', + large: true, + className: 'new-account-create-form__button', + onClick: () => this.createNewKeychain(), + }, [this.context.t('import')]), + + ]), + + error ? h('span.error', error) : null, + ]) ) } diff --git a/ui/app/pages/create-account/import-account/seed.js b/ui/app/pages/create-account/import-account/seed.js new file mode 100644 index 0000000000..73332f9261 --- /dev/null +++ b/ui/app/pages/create-account/import-account/seed.js @@ -0,0 +1,35 @@ +const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') +const PropTypes = require('prop-types') +const connect = require('react-redux').connect + +SeedImportSubview.contextTypes = { + t: PropTypes.func, +} + +module.exports = connect(mapStateToProps)(SeedImportSubview) + + +function mapStateToProps () { + return {} +} + +inherits(SeedImportSubview, Component) +function SeedImportSubview () { + Component.call(this) +} + +SeedImportSubview.prototype.render = function () { + return ( + h('div', { + style: { + }, + }, [ + this.context.t('pasteSeed'), + h('textarea'), + h('br'), + h('button', this.context.t('submit')), + ]) + ) +} diff --git a/ui/app/pages/create-account/new-account.component.js b/ui/app/pages/create-account/new-account.component.js index fbb5e128f0..6dc6419b5e 100644 --- a/ui/app/pages/create-account/new-account.component.js +++ b/ui/app/pages/create-account/new-account.component.js @@ -49,8 +49,7 @@ export default class NewAccountCreateForm extends Component { {this.context.t('accountName')}
    - this.setState({ newAccountName: event.target.value })} @@ -62,17 +61,13 @@ export default class NewAccountCreateForm extends Component { large className="new-account-create-form__button" onClick={() => history.push(DEFAULT_ROUTE)} - > - {this.context.t('cancel')} - + >{this.context.t('cancel')} + >{this.context.t('create')}
    ) 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 53d4614ade..e1c0b21ed6 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,12 +175,6 @@ export default class ImportWithSeedPhrase extends PureComponent { return !passwordError && !confirmPasswordError && !seedPhraseError } - onTermsKeyPress = ({key}) => { - if (key === ' ' || key === 'Enter') { - this.toggleTermsCheck() - } - } - toggleTermsCheck = () => { this.context.metricsEvent({ eventOpts: { @@ -189,6 +183,7 @@ export default class ImportWithSeedPhrase extends PureComponent { name: 'Check ToS', }, }) + this.setState((prevState) => ({ termsChecked: !prevState.termsChecked, })) @@ -272,19 +267,11 @@ export default class ImportWithSeedPhrase extends PureComponent { largeLabel />
    -
    +
    {termsChecked ? : null}
    - - I have read and agree to the  - + 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: { @@ -128,12 +136,6 @@ 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 @@ -193,19 +195,11 @@ export default class NewAccount extends PureComponent { largeLabel />
    -
    +
    {termsChecked ? : null}
    - - I have read and agree to the  -
    + I have read and agree to the { - const { history, completeOnboarding, completionMetaMetricsName, onboardingInitiator } = this.props - - await completeOnboarding() - this.context.metricsEvent({ - eventOpts: { - category: 'Onboarding', - action: 'Onboarding Complete', - name: completionMetaMetricsName, - }, - }) - - if (onboardingInitiator) { - await returnToOnboardingInitiator(onboardingInitiator) - } - history.push(DEFAULT_ROUTE) } render () { const { t } = this.context - const { onboardingInitiator } = this.props + const { history, completeOnboarding, completionMetaMetricsName } = this.props return (
    @@ -86,17 +62,20 @@ export default class EndOfFlowScreen extends PureComponent { - { - onboardingInitiator ? ( - - ) : null - }
    ) } diff --git a/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.container.js b/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.container.js index 2bb9ef15ab..38313806c4 100644 --- a/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.container.js +++ b/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.container.js @@ -1,22 +1,21 @@ import { connect } from 'react-redux' import EndOfFlow from './end-of-flow.component' import { setCompletedOnboarding } from '../../../store/actions' -import { getOnboardingInitiator } from '../first-time-flow.selectors' const firstTimeFlowTypeNameMap = { create: 'New Wallet Created', 'import': 'New Wallet Imported', } -const mapStateToProps = (state) => { - const { metamask: { firstTimeFlowType } } = state +const mapStateToProps = ({ metamask }) => { + const { firstTimeFlowType } = metamask return { completionMetaMetricsName: firstTimeFlowTypeNameMap[firstTimeFlowType], - onboardingInitiator: getOnboardingInitiator(state), } } + const mapDispatchToProps = dispatch => { return { completeOnboarding: () => dispatch(setCompletedOnboarding()), diff --git a/ui/app/pages/first-time-flow/end-of-flow/index.scss b/ui/app/pages/first-time-flow/end-of-flow/index.scss index de603fce4d..d7eb4513b1 100644 --- a/ui/app/pages/first-time-flow/end-of-flow/index.scss +++ b/ui/app/pages/first-time-flow/end-of-flow/index.scss @@ -50,4 +50,4 @@ font-size: 80px; margin-top: 70px; } -} +} \ No newline at end of file diff --git a/ui/app/pages/first-time-flow/first-time-flow.selectors.js b/ui/app/pages/first-time-flow/first-time-flow.selectors.js index 74cad5e12b..e6cd5a84ac 100644 --- a/ui/app/pages/first-time-flow/first-time-flow.selectors.js +++ b/ui/app/pages/first-time-flow/first-time-flow.selectors.js @@ -4,6 +4,12 @@ import { DEFAULT_ROUTE, } from '../../helpers/constants/routes' +const selectors = { + getFirstTimeFlowTypeRoute, +} + +module.exports = selectors + function getFirstTimeFlowTypeRoute (state) { const { firstTimeFlowType } = state.metamask @@ -18,25 +24,3 @@ function getFirstTimeFlowTypeRoute (state) { return nextRoute } - -const getOnboardingInitiator = (state) => { - const { onboardingTabs } = state.metamask - - if (!onboardingTabs || Object.keys(onboardingTabs).length !== 1) { - return null - } - - const location = Object.keys(onboardingTabs)[0] - const tabId = onboardingTabs[location] - return { - location, - tabId, - } -} - -const selectors = { - getFirstTimeFlowTypeRoute, - getOnboardingInitiator, -} - -module.exports = selectors diff --git a/ui/app/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js b/ui/app/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js index 14c85b9238..ad1ffbf428 100644 --- a/ui/app/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js +++ b/ui/app/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js @@ -142,8 +142,7 @@ export default class MetaMetricsOptIn extends Component { disabled={false} />
    - This data is aggregated and is therefore anonymous for the purposes of General Data Protection Regulation (EU) 2016/679. For more information in relation to our privacy practices, please see our  - { - const tab = await (new Promise((resolve) => { - extension.tabs.update(onboardingInitiator.tabId, { active: true }, (tab) => { - if (tab) { - resolve(tab) - } else { - // silence console message about unchecked error - if (extension.runtime.lastError) { - log.debug(extension.runtime.lastError) - } - resolve() - } - }) - })) - - if (!tab) { - // this case can happen if the tab was closed since being checked with `extension.tabs.get` - log.warn(`Setting current tab to onboarding initator has failed; falling back to redirect`) - window.location.assign(onboardingInitiator.location) - } else { - window.close() - } -} - -export const returnToOnboardingInitiator = async (onboardingInitiator) => { - const tab = await (new Promise((resolve) => { - extension.tabs.get(onboardingInitiator.tabId, (tab) => { - if (tab) { - resolve(tab) - } else { - // silence console message about unchecked error - if (extension.runtime.lastError) { - log.debug(extension.runtime.lastError) - } - resolve() - } - }) - })) - - if (tab) { - await returnToOnboardingInitiatorTab(onboardingInitiator) - } else { - window.location.assign(onboardingInitiator.location) - } -} diff --git a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/draggable-seed.component.js b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/draggable-seed.component.js index e9bef631e7..7e0652f8f7 100644 --- a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/draggable-seed.component.js +++ b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/draggable-seed.component.js @@ -13,7 +13,7 @@ class DraggableSeed extends Component { isOver: PropTypes.bool, canDrop: PropTypes.bool, // Own Props - onClick: PropTypes.func, + onClick: PropTypes.func.isRequired, setHoveringIndex: PropTypes.func.isRequired, index: PropTypes.number, draggingSeedIndex: PropTypes.number, @@ -25,7 +25,6 @@ class DraggableSeed extends Component { static defaultProps = { className: '', - onClick: undefined, } componentWillReceiveProps (nextProps) { diff --git a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.scss b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.scss index 583d92a0bb..dfe9868cfa 100644 --- a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.scss +++ b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.scss @@ -60,7 +60,7 @@ } button { - margin-top: 0px; + margin-top: 0xp; } &__buttons { diff --git a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js index 922685059e..4144fb8789 100644 --- a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js +++ b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js @@ -3,10 +3,8 @@ import PropTypes from 'prop-types' import classnames from 'classnames' import LockIcon from '../../../../components/ui/lock-icon' import Button from '../../../../components/ui/button' -import Snackbar from '../../../../components/ui/snackbar' import { INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE, DEFAULT_ROUTE } from '../../../../helpers/constants/routes' import { exportAsFile } from '../../../../helpers/utils/util' -import { returnToOnboardingInitiator } from '../../onboarding-initiator-util' export default class RevealSeedPhrase extends PureComponent { static contextTypes = { @@ -19,10 +17,6 @@ export default class RevealSeedPhrase extends PureComponent { seedPhrase: PropTypes.string, setSeedPhraseBackedUp: PropTypes.func, setCompletedOnboarding: PropTypes.func, - onboardingInitiator: PropTypes.exact({ - location: PropTypes.string, - tabId: PropTypes.number, - }), } state = { @@ -33,7 +27,8 @@ export default class RevealSeedPhrase extends PureComponent { exportAsFile('MetaMask Secret Backup Phrase', this.props.seedPhrase, 'text/plain') } - handleNext = () => { + handleNext = event => { + event.preventDefault() const { isShowingSeedPhrase } = this.state const { history } = this.props @@ -52,8 +47,9 @@ export default class RevealSeedPhrase extends PureComponent { history.push(INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE) } - handleSkip = async () => { - const { history, setSeedPhraseBackedUp, setCompletedOnboarding, onboardingInitiator } = this.props + handleSkip = event => { + event.preventDefault() + const { history, setSeedPhraseBackedUp, setCompletedOnboarding } = this.props this.context.metricsEvent({ eventOpts: { @@ -63,12 +59,10 @@ export default class RevealSeedPhrase extends PureComponent { }, }) - await Promise.all([setCompletedOnboarding(), setSeedPhraseBackedUp(false)]) - - if (onboardingInitiator) { - await returnToOnboardingInitiator(onboardingInitiator) - } - history.push(DEFAULT_ROUTE) + Promise.all([setCompletedOnboarding(), setSeedPhraseBackedUp(false)]) + .then(() => { + history.push(DEFAULT_ROUTE) + }) } renderSecretWordsContainer () { @@ -78,12 +72,10 @@ export default class RevealSeedPhrase extends PureComponent { return (
    - ) } diff --git a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.container.js b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.container.js index 11a26fb6d2..7ada36afc9 100644 --- a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.container.js +++ b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.container.js @@ -4,13 +4,6 @@ import { setCompletedOnboarding, setSeedPhraseBackedUp, } from '../../../../store/actions' -import { getOnboardingInitiator } from '../../first-time-flow.selectors' - -const mapStateToProps = (state) => { - return { - onboardingInitiator: getOnboardingInitiator(state), - } -} const mapDispatchToProps = dispatch => { return { @@ -19,4 +12,4 @@ const mapDispatchToProps = dispatch => { } } -export default connect(mapStateToProps, mapDispatchToProps)(RevealSeedPhrase) +export default connect(null, mapDispatchToProps)(RevealSeedPhrase) diff --git a/ui/app/pages/home/home.component.js b/ui/app/pages/home/home.component.js index 84179c5686..e51c821779 100644 --- a/ui/app/pages/home/home.component.js +++ b/ui/app/pages/home/home.component.js @@ -8,13 +8,13 @@ import DaiMigrationNotification from '../../components/app/dai-migration-compone import MultipleNotifications from '../../components/app/multiple-notifications' import WalletView from '../../components/app/wallet-view' import TransactionView from '../../components/app/transaction-view' +import ProviderApproval from '../provider-approval' import { RESTORE_VAULT_ROUTE, CONFIRM_TRANSACTION_ROUTE, CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE, INITIALIZE_BACKUP_SEED_PHRASE_ROUTE, - CONNECT_ROUTE, } from '../../helpers/constants/routes' export default class Home extends PureComponent { @@ -23,6 +23,7 @@ export default class Home extends PureComponent { } static defaultProps = { + unsetMigratedPrivacyMode: null, hasDaiV1Token: false, } @@ -31,6 +32,9 @@ export default class Home extends PureComponent { forgottenPassword: PropTypes.bool, suggestedTokens: PropTypes.object, unconfirmedTransactionsCount: PropTypes.number, + providerRequests: PropTypes.array, + showPrivacyModeNotification: PropTypes.bool.isRequired, + unsetMigratedPrivacyMode: PropTypes.func, shouldShowSeedPhraseReminder: PropTypes.bool, isPopup: PropTypes.bool, threeBoxSynced: PropTypes.bool, @@ -42,20 +46,14 @@ export default class Home extends PureComponent { setShowRestorePromptToFalse: PropTypes.func, threeBoxLastUpdated: PropTypes.number, hasDaiV1Token: PropTypes.bool, - hasPermissionRequests: PropTypes.bool, } componentWillMount () { const { history, unconfirmedTransactionsCount = 0, - hasPermissionRequests, } = this.props - if (hasPermissionRequests) { - history.push(CONNECT_ROUTE) - } - if (unconfirmedTransactionsCount > 0) { history.push(CONFIRM_TRANSACTION_ROUTE) } @@ -89,8 +87,11 @@ export default class Home extends PureComponent { const { t } = this.context const { forgottenPassword, + providerRequests, history, hasDaiV1Token, + showPrivacyModeNotification, + unsetMigratedPrivacyMode, shouldShowSeedPhraseReminder, isPopup, selectedAddress, @@ -105,6 +106,11 @@ export default class Home extends PureComponent { return } + if (providerRequests && providerRequests.length > 0) { + return ( + + ) + } return (
    @@ -116,45 +122,58 @@ export default class Home extends PureComponent { ? ( + { + showPrivacyModeNotification + ? { + unsetMigratedPrivacyMode() + window.open('https://medium.com/metamask/42549d4870fa', '_blank', 'noopener') + }} + ignoreText={t('dismiss')} + onIgnore={() => { + unsetMigratedPrivacyMode() + }} + key="home-privacyModeDefault" + /> + : null + } { shouldShowSeedPhraseReminder - ? ( - { - if (isPopup) { - global.platform.openExtensionInBrowser(INITIALIZE_BACKUP_SEED_PHRASE_ROUTE) - } else { - history.push(INITIALIZE_BACKUP_SEED_PHRASE_ROUTE) - } - }} - infoText={t('backupApprovalInfo')} - key="home-backupApprovalNotice" - /> - ) + ? { + if (isPopup) { + global.platform.openExtensionInBrowser(INITIALIZE_BACKUP_SEED_PHRASE_ROUTE) + } else { + history.push(INITIALIZE_BACKUP_SEED_PHRASE_ROUTE) + } + }} + infoText={t('backupApprovalInfo')} + key="home-backupApprovalNotice" + /> : null } { threeBoxLastUpdated && showRestorePrompt - ? ( - { - restoreFromThreeBox(selectedAddress) - .then(() => { - turnThreeBoxSyncingOn() - }) - }} - onIgnore={() => { - setShowRestorePromptToFalse() - }} - key="home-privacyModeDefault" - /> - ) + ? { + restoreFromThreeBox(selectedAddress) + .then(() => { + turnThreeBoxSyncingOn() + }) + }} + onIgnore={() => { + setShowRestorePromptToFalse() + }} + key="home-privacyModeDefault" + /> : null } { diff --git a/ui/app/pages/home/home.container.js b/ui/app/pages/home/home.container.js index f14bb7996a..4a2106a55b 100644 --- a/ui/app/pages/home/home.container.js +++ b/ui/app/pages/home/home.container.js @@ -3,8 +3,9 @@ import { compose } from 'recompose' import { connect } from 'react-redux' import { withRouter } from 'react-router-dom' import { unconfirmedTransactionsCountSelector } from '../../selectors/confirm-transaction' -import { getCurrentEthBalance, getDaiV1Token, hasPermissionRequests } from '../../selectors/selectors' +import { getCurrentEthBalance, getDaiV1Token } from '../../selectors/selectors' import { + unsetMigratedPrivacyMode, restoreFromThreeBox, turnThreeBoxSyncingOn, getThreeBoxLastUpdated, @@ -15,9 +16,11 @@ import { getEnvironmentType } from '../../../../app/scripts/lib/util' import { ENVIRONMENT_TYPE_POPUP } from '../../../../app/scripts/lib/enums' const mapStateToProps = state => { - const { activeTab, metamask, appState } = state + const { metamask, appState } = state const { suggestedTokens, + providerRequests, + migratedPrivacyMode, seedPhraseBackedUp, tokens, threeBoxSynced, @@ -33,7 +36,8 @@ const mapStateToProps = state => { forgottenPassword, suggestedTokens, unconfirmedTransactionsCount: unconfirmedTransactionsCountSelector(state), - activeTab, + providerRequests, + showPrivacyModeNotification: migratedPrivacyMode, shouldShowSeedPhraseReminder: !seedPhraseBackedUp && (parseInt(accountBalance, 16) > 0 || tokens.length > 0), isPopup, threeBoxSynced, @@ -41,11 +45,11 @@ const mapStateToProps = state => { selectedAddress, threeBoxLastUpdated, hasDaiV1Token: Boolean(getDaiV1Token(state)), - hasPermissionRequests: hasPermissionRequests(state), } } const mapDispatchToProps = (dispatch) => ({ + unsetMigratedPrivacyMode: () => dispatch(unsetMigratedPrivacyMode()), turnThreeBoxSyncingOn: () => dispatch(turnThreeBoxSyncingOn()), setupThreeBox: () => { dispatch(getThreeBoxLastUpdated()) diff --git a/ui/app/pages/index.scss b/ui/app/pages/index.scss index deb6bce459..d79b7c28dd 100644 --- a/ui/app/pages/index.scss +++ b/ui/app/pages/index.scss @@ -13,7 +13,3 @@ @import 'keychains/index'; @import 'confirm-approve/index'; - -@import 'permissions-connect/index'; - -@import 'connected-sites/index'; diff --git a/ui/app/pages/keychains/reveal-seed.js b/ui/app/pages/keychains/reveal-seed.js index 0e842594f4..e83e3fd98d 100644 --- a/ui/app/pages/keychains/reveal-seed.js +++ b/ui/app/pages/keychains/reveal-seed.js @@ -1,6 +1,7 @@ -import React, { Component } from 'react' +const { Component } = require('react') const { connect } = require('react-redux') const PropTypes = require('prop-types') +const h = require('react-hyperscript') const classnames = require('classnames') const { requestRevealSeedWords } = require('../../store/actions') @@ -13,11 +14,15 @@ const PASSWORD_PROMPT_SCREEN = 'PASSWORD_PROMPT_SCREEN' const REVEAL_SEED_SCREEN = 'REVEAL_SEED_SCREEN' class RevealSeedPage extends Component { - state = { - screen: PASSWORD_PROMPT_SCREEN, - password: '', - seedWords: null, - error: null, + constructor (props) { + super(props) + + this.state = { + screen: PASSWORD_PROMPT_SCREEN, + password: '', + seedWords: null, + error: null, + } } componentDidMount () { @@ -37,17 +42,15 @@ class RevealSeedPage extends Component { renderWarning () { return ( -
    - -
    -
    - {this.context.t('revealSeedWordsWarningTitle')} -
    -
    - {this.context.t('revealSeedWordsWarning')} -
    -
    -
    + h('.page-container__warning-container', [ + h('img.page-container__warning-icon', { + src: 'images/warning.svg', + }), + h('.page-container__warning-message', [ + h('.page-container__warning-title', [this.context.t('revealSeedWordsWarningTitle')]), + h('div', [this.context.t('revealSeedWordsWarning')]), + ]), + ]) ) } @@ -61,25 +64,24 @@ class RevealSeedPage extends Component { const { t } = this.context return ( -
    this.handleSubmit(event)}> - -
    - this.setState({ password: event.target.value })} - className={classnames('form-control', { 'form-control--error': this.state.error })} - /> -
    - { - this.state.error && ( -
    - {this.state.error} -
    - )} -
    + h('form', { + onSubmit: event => this.handleSubmit(event), + }, [ + h('label.input-label', { + htmlFor: 'password-box', + }, t('enterPasswordContinue')), + h('.input-group', [ + h('input.form-control', { + type: 'password', + placeholder: t('password'), + id: 'password-box', + value: this.state.password, + onChange: event => this.setState({ password: event.target.value }), + className: classnames({ 'form-control--error': this.state.error }), + }), + ]), + this.state.error && h('.reveal-seed__error', this.state.error), + ]) ) } @@ -87,10 +89,13 @@ class RevealSeedPage extends Component { const { t } = this.context return ( -
    - - -
    + h('div', [ + h('label.reveal-seed__label', t('yourPrivateSeedPhrase')), + h(ExportTextContainer, { + text: this.state.seedWords, + filename: t('metamaskSeedWords'), + }), + ]) ) } @@ -102,64 +107,54 @@ class RevealSeedPage extends Component { renderPasswordPromptFooter () { return ( -
    -
    - - -
    -
    + h('.page-container__footer', [ + h('header', [ + h(Button, { + type: 'default', + large: true, + className: 'page-container__footer-button', + onClick: () => this.props.history.push(DEFAULT_ROUTE), + }, this.context.t('cancel')), + h(Button, { + type: 'secondary', + large: true, + className: 'page-container__footer-button', + onClick: event => this.handleSubmit(event), + disabled: this.state.password === '', + }, this.context.t('next')), + ]), + ]) ) } renderRevealSeedFooter () { return ( -
    - -
    + h('.page-container__footer', [ + h(Button, { + type: 'default', + large: true, + className: 'page-container__footer-button', + onClick: () => this.props.history.push(DEFAULT_ROUTE), + }, this.context.t('close')), + ]) ) } render () { return ( -
    -
    -
    - {this.context.t('revealSeedWordsTitle')} -
    -
    - {this.context.t('revealSeedWordsDescription')} -
    -
    -
    - {this.renderWarning()} -
    - {this.renderContent()} -
    -
    - {this.renderFooter()} -
    + h('.page-container', [ + h('.page-container__header', [ + h('.page-container__title', this.context.t('revealSeedWordsTitle')), + h('.page-container__subtitle', this.context.t('revealSeedWordsDescription')), + ]), + h('.page-container__content', [ + this.renderWarning(), + h('.reveal-seed__content', [ + this.renderContent(), + ]), + ]), + this.renderFooter(), + ]) ) } } diff --git a/ui/app/pages/mobile-sync/index.js b/ui/app/pages/mobile-sync/index.js index c7df5ede55..bd23858088 100644 --- a/ui/app/pages/mobile-sync/index.js +++ b/ui/app/pages/mobile-sync/index.js @@ -1 +1,415 @@ -export { default } from './mobile-sync.container' +const { Component } = require('react') +const { connect } = require('react-redux') +const PropTypes = require('prop-types') +const h = require('react-hyperscript') +const classnames = require('classnames') +const PubNub = require('pubnub') + +const { requestRevealSeedWords, fetchInfoToSync } = require('../../store/actions') +const { DEFAULT_ROUTE } = require('../../helpers/constants/routes') +const actions = require('../../store/actions') + +const qrCode = require('qrcode-generator') + +import Button from '../../components/ui/button' +import LoadingScreen from '../../components/ui/loading-screen' + +const PASSWORD_PROMPT_SCREEN = 'PASSWORD_PROMPT_SCREEN' +const REVEAL_SEED_SCREEN = 'REVEAL_SEED_SCREEN' +const KEYS_GENERATION_TIME = 30000 + +class MobileSyncPage extends Component { + static propTypes = { + history: PropTypes.object, + selectedAddress: PropTypes.string, + displayWarning: PropTypes.func, + fetchInfoToSync: PropTypes.func, + requestRevealSeedWords: PropTypes.func, + } + + constructor (props) { + super(props) + + this.state = { + screen: PASSWORD_PROMPT_SCREEN, + password: '', + seedWords: null, + error: null, + syncing: false, + completed: false, + channelName: undefined, + cipherKey: undefined, + } + + this.syncing = false + } + + componentDidMount () { + const passwordBox = document.getElementById('password-box') + if (passwordBox) { + passwordBox.focus() + } + } + + handleSubmit (event) { + event.preventDefault() + this.setState({ seedWords: null, error: null }) + this.props.requestRevealSeedWords(this.state.password) + .then(seedWords => { + this.startKeysGeneration() + this.setState({ seedWords, screen: REVEAL_SEED_SCREEN }) + }) + .catch(error => this.setState({ error: error.message })) + } + + startKeysGeneration () { + this.handle && clearTimeout(this.handle) + this.disconnectWebsockets() + this.generateCipherKeyAndChannelName() + this.initWebsockets() + this.handle = setTimeout(() => { + this.startKeysGeneration() + }, KEYS_GENERATION_TIME) + } + + generateCipherKeyAndChannelName () { + this.cipherKey = `${this.props.selectedAddress.substr(-4)}-${PubNub.generateUUID()}` + this.channelName = `mm-${PubNub.generateUUID()}` + this.setState({cipherKey: this.cipherKey, channelName: this.channelName}) + } + + initWithCipherKeyAndChannelName (cipherKey, channelName) { + this.cipherKey = cipherKey + this.channelName = channelName + } + + initWebsockets () { + // Make sure there are no existing listeners + this.disconnectWebsockets() + + this.pubnub = new PubNub({ + subscribeKey: process.env.PUBNUB_SUB_KEY, + publishKey: process.env.PUBNUB_PUB_KEY, + cipherKey: this.cipherKey, + ssl: true, + }) + + this.pubnubListener = { + message: (data) => { + const {channel, message} = data + // handle message + if (channel !== this.channelName || !message) { + return false + } + + if (message.event === 'start-sync') { + this.startSyncing() + } else if (message.event === 'connection-info') { + this.handle && clearTimeout(this.handle) + this.disconnectWebsockets() + this.initWithCipherKeyAndChannelName(message.cipher, message.channel) + this.initWebsockets() + } else if (message.event === 'end-sync') { + this.disconnectWebsockets() + this.setState({syncing: false, completed: true}) + } + }, + } + + this.pubnub.addListener(this.pubnubListener) + + this.pubnub.subscribe({ + channels: [this.channelName], + withPresence: false, + }) + + } + + disconnectWebsockets () { + if (this.pubnub && this.pubnubListener) { + this.pubnub.removeListener(this.pubnubListener) + } + } + + // Calculating a PubNub Message Payload Size. + calculatePayloadSize (channel, message) { + return encodeURIComponent( + channel + JSON.stringify(message) + ).length + 100 + } + + chunkString (str, size) { + const numChunks = Math.ceil(str.length / size) + const chunks = new Array(numChunks) + for (let i = 0, o = 0; i < numChunks; ++i, o += size) { + chunks[i] = str.substr(o, size) + } + return chunks + } + + notifyError (errorMsg) { + return new Promise((resolve, reject) => { + this.pubnub.publish( + { + message: { + event: 'error-sync', + data: errorMsg, + }, + channel: this.channelName, + sendByPost: false, // true to send via post + storeInHistory: false, + }, + (status, response) => { + if (!status.error) { + resolve() + } else { + reject(response) + } + }) + }) + } + + async startSyncing () { + if (this.syncing) return false + this.syncing = true + this.setState({syncing: true}) + + const { accounts, network, preferences, transactions } = await this.props.fetchInfoToSync() + + const allDataStr = JSON.stringify({ + accounts, + network, + preferences, + transactions, + udata: { + pwd: this.state.password, + seed: this.state.seedWords, + }, + }) + + const chunks = this.chunkString(allDataStr, 17000) + const totalChunks = chunks.length + try { + for (let i = 0; i < totalChunks; i++) { + await this.sendMessage(chunks[i], i + 1, totalChunks) + } + } catch (e) { + this.props.displayWarning('Sync failed :(') + this.setState({syncing: false}) + this.syncing = false + this.notifyError(e.toString()) + } + } + + sendMessage (data, pkg, count) { + return new Promise((resolve, reject) => { + this.pubnub.publish( + { + message: { + event: 'syncing-data', + data, + totalPkg: count, + currentPkg: pkg, + }, + channel: this.channelName, + sendByPost: false, // true to send via post + storeInHistory: false, + }, + (status, response) => { + if (!status.error) { + resolve() + } else { + reject(response) + } + } + ) + }) + } + + + componentWillUnmount () { + this.disconnectWebsockets() + } + + renderWarning (text) { + return ( + h('.page-container__warning-container', [ + h('.page-container__warning-message', [ + h('div', [text]), + ]), + ]) + ) + } + + renderContent () { + const { t } = this.context + + if (this.state.syncing) { + return h(LoadingScreen, {loadingMessage: 'Sync in progress'}) + } + + if (this.state.completed) { + return h('div.reveal-seed__content', {}, + h('label.reveal-seed__label', { + style: { + width: '100%', + textAlign: 'center', + }, + }, t('syncWithMobileComplete')), + ) + } + + return this.state.screen === PASSWORD_PROMPT_SCREEN + ? h('div', {}, [ + this.renderWarning(this.context.t('mobileSyncText')), + h('.reveal-seed__content', [ + this.renderPasswordPromptContent(), + ]), + ]) + : h('div', {}, [ + this.renderWarning(this.context.t('syncWithMobileBeCareful')), + h('.reveal-seed__content', [ this.renderRevealSeedContent() ]), + ]) + } + + renderPasswordPromptContent () { + const { t } = this.context + + return ( + h('form', { + onSubmit: event => this.handleSubmit(event), + }, [ + h('label.input-label', { + htmlFor: 'password-box', + }, t('enterPasswordContinue')), + h('.input-group', [ + h('input.form-control', { + type: 'password', + placeholder: t('password'), + id: 'password-box', + value: this.state.password, + onChange: event => this.setState({ password: event.target.value }), + className: classnames({ 'form-control--error': this.state.error }), + }), + ]), + this.state.error && h('.reveal-seed__error', this.state.error), + ]) + ) + } + + renderRevealSeedContent () { + + const qrImage = qrCode(0, 'M') + qrImage.addData(`metamask-sync:${this.state.channelName}|@|${this.state.cipherKey}`) + qrImage.make() + + const { t } = this.context + return ( + h('div', [ + h('label.reveal-seed__label', { + style: { + width: '100%', + textAlign: 'center', + }, + }, t('syncWithMobileScanThisCode')), + h('.div.qr-wrapper', { + style: { + display: 'flex', + justifyContent: 'center', + }, + dangerouslySetInnerHTML: { + __html: qrImage.createTableTag(4), + }, + }), + ]) + ) + } + + renderFooter () { + return this.state.screen === PASSWORD_PROMPT_SCREEN + ? this.renderPasswordPromptFooter() + : this.renderRevealSeedFooter() + } + + renderPasswordPromptFooter () { + return ( + h('div.new-account-import-form__buttons', {style: {padding: 30}}, [ + + h(Button, { + type: 'default', + large: true, + className: 'new-account-create-form__button', + onClick: () => this.props.history.push(DEFAULT_ROUTE), + }, this.context.t('cancel')), + + h(Button, { + type: 'secondary', + large: true, + className: 'new-account-create-form__button', + onClick: event => this.handleSubmit(event), + disabled: this.state.password === '', + }, this.context.t('next')), + ]) + ) + } + + renderRevealSeedFooter () { + return ( + h('.page-container__footer', {style: {padding: 30}}, [ + h(Button, { + type: 'default', + large: true, + className: 'page-container__footer-button', + onClick: () => this.props.history.push(DEFAULT_ROUTE), + }, this.context.t('close')), + ]) + ) + } + + render () { + return ( + h('.page-container', [ + h('.page-container__header', [ + h('.page-container__title', this.context.t('syncWithMobileTitle')), + this.state.screen === PASSWORD_PROMPT_SCREEN ? h('.page-container__subtitle', this.context.t('syncWithMobileDesc')) : null, + this.state.screen === PASSWORD_PROMPT_SCREEN ? h('.page-container__subtitle', this.context.t('syncWithMobileDescNewUsers')) : null, + ]), + h('.page-container__content', [ + this.renderContent(), + ]), + this.renderFooter(), + ]) + ) + } +} + +MobileSyncPage.propTypes = { + requestRevealSeedWords: PropTypes.func, + fetchInfoToSync: PropTypes.func, + history: PropTypes.object, +} + +MobileSyncPage.contextTypes = { + t: PropTypes.func, +} + +const mapDispatchToProps = dispatch => { + return { + requestRevealSeedWords: password => dispatch(requestRevealSeedWords(password)), + fetchInfoToSync: () => dispatch(fetchInfoToSync()), + displayWarning: (message) => dispatch(actions.displayWarning(message || null)), + } + +} + +const mapStateToProps = state => { + const { + metamask: { selectedAddress }, + } = state + + return { + selectedAddress, + } +} + +module.exports = connect(mapStateToProps, mapDispatchToProps)(MobileSyncPage) diff --git a/ui/app/pages/mobile-sync/mobile-sync.component.js b/ui/app/pages/mobile-sync/mobile-sync.component.js deleted file mode 100644 index 9510afa807..0000000000 --- a/ui/app/pages/mobile-sync/mobile-sync.component.js +++ /dev/null @@ -1,436 +0,0 @@ -import React, { Component } from 'react' - -const PropTypes = require('prop-types') -const classnames = require('classnames') -const PubNub = require('pubnub') -const qrCode = require('qrcode-generator') - -const { DEFAULT_ROUTE } = require('../../helpers/constants/routes') - -import Button from '../../components/ui/button' -import LoadingScreen from '../../components/ui/loading-screen' - -const PASSWORD_PROMPT_SCREEN = 'PASSWORD_PROMPT_SCREEN' -const REVEAL_SEED_SCREEN = 'REVEAL_SEED_SCREEN' -const KEYS_GENERATION_TIME = 30000 - -export default class MobileSyncPage extends Component { - static contextTypes = { - t: PropTypes.func, - } - - static propTypes = { - history: PropTypes.object.isRequired, - selectedAddress: PropTypes.string.isRequired, - displayWarning: PropTypes.func.isRequired, - fetchInfoToSync: PropTypes.func.isRequired, - requestRevealSeedWords: PropTypes.func.isRequired, - } - - - state = { - screen: PASSWORD_PROMPT_SCREEN, - password: '', - seedWords: null, - error: null, - syncing: false, - completed: false, - channelName: undefined, - cipherKey: undefined, - } - - syncing = false - - componentDidMount () { - const passwordBox = document.getElementById('password-box') - if (passwordBox) { - passwordBox.focus() - } - } - - handleSubmit (event) { - event.preventDefault() - this.setState({ seedWords: null, error: null }) - this.props.requestRevealSeedWords(this.state.password) - .then(seedWords => { - this.startKeysGeneration() - this.setState({ seedWords, screen: REVEAL_SEED_SCREEN }) - }) - .catch(error => this.setState({ error: error.message })) - } - - startKeysGeneration () { - this.handle && clearTimeout(this.handle) - this.disconnectWebsockets() - this.generateCipherKeyAndChannelName() - this.initWebsockets() - this.handle = setTimeout(() => { - this.startKeysGeneration() - }, KEYS_GENERATION_TIME) - } - - generateCipherKeyAndChannelName () { - this.cipherKey = `${this.props.selectedAddress.substr(-4)}-${PubNub.generateUUID()}` - this.channelName = `mm-${PubNub.generateUUID()}` - this.setState({cipherKey: this.cipherKey, channelName: this.channelName}) - } - - initWithCipherKeyAndChannelName (cipherKey, channelName) { - this.cipherKey = cipherKey - this.channelName = channelName - } - - initWebsockets () { - // Make sure there are no existing listeners - this.disconnectWebsockets() - - this.pubnub = new PubNub({ - subscribeKey: process.env.PUBNUB_SUB_KEY, - publishKey: process.env.PUBNUB_PUB_KEY, - cipherKey: this.cipherKey, - ssl: true, - }) - - this.pubnubListener = { - message: (data) => { - const {channel, message} = data - // handle message - if (channel !== this.channelName || !message) { - return false - } - - if (message.event === 'start-sync') { - this.startSyncing() - } else if (message.event === 'connection-info') { - this.handle && clearTimeout(this.handle) - this.disconnectWebsockets() - this.initWithCipherKeyAndChannelName(message.cipher, message.channel) - this.initWebsockets() - } else if (message.event === 'end-sync') { - this.disconnectWebsockets() - this.setState({syncing: false, completed: true}) - } - }, - } - - this.pubnub.addListener(this.pubnubListener) - - this.pubnub.subscribe({ - channels: [this.channelName], - withPresence: false, - }) - - } - - disconnectWebsockets () { - if (this.pubnub && this.pubnubListener) { - this.pubnub.removeListener(this.pubnubListener) - } - } - - // Calculating a PubNub Message Payload Size. - calculatePayloadSize (channel, message) { - return encodeURIComponent( - channel + JSON.stringify(message) - ).length + 100 - } - - chunkString (str, size) { - const numChunks = Math.ceil(str.length / size) - const chunks = new Array(numChunks) - for (let i = 0, o = 0; i < numChunks; ++i, o += size) { - chunks[i] = str.substr(o, size) - } - return chunks - } - - notifyError (errorMsg) { - return new Promise((resolve, reject) => { - this.pubnub.publish( - { - message: { - event: 'error-sync', - data: errorMsg, - }, - channel: this.channelName, - sendByPost: false, // true to send via post - storeInHistory: false, - }, - (status, response) => { - if (!status.error) { - resolve() - } else { - reject(response) - } - }) - }) - } - - async startSyncing () { - if (this.syncing) { - return false - } - this.syncing = true - this.setState({syncing: true}) - - const { accounts, network, preferences, transactions } = await this.props.fetchInfoToSync() - - const allDataStr = JSON.stringify({ - accounts, - network, - preferences, - transactions, - udata: { - pwd: this.state.password, - seed: this.state.seedWords, - }, - }) - - const chunks = this.chunkString(allDataStr, 17000) - const totalChunks = chunks.length - try { - for (let i = 0; i < totalChunks; i++) { - await this.sendMessage(chunks[i], i + 1, totalChunks) - } - } catch (e) { - this.props.displayWarning('Sync failed :(') - this.setState({syncing: false}) - this.syncing = false - this.notifyError(e.toString()) - } - } - - sendMessage (data, pkg, count) { - return new Promise((resolve, reject) => { - this.pubnub.publish( - { - message: { - event: 'syncing-data', - data, - totalPkg: count, - currentPkg: pkg, - }, - channel: this.channelName, - sendByPost: false, // true to send via post - storeInHistory: false, - }, - (status, response) => { - if (!status.error) { - resolve() - } else { - reject(response) - } - } - ) - }) - } - - - componentWillUnmount () { - this.disconnectWebsockets() - } - - renderWarning (text) { - return ( -
    -
    -
    {text}
    -
    -
    - ) - } - - renderContent () { - const { syncing, completed, screen } = this.state - const { t } = this.context - - if (syncing) { - return ( - - ) - } - - if (completed) { - return ( -
    - -
    - ) - } - - return screen === PASSWORD_PROMPT_SCREEN - ? ( -
    - {this.renderWarning(this.context.t('mobileSyncText'))} -
    - {this.renderPasswordPromptContent()} -
    -
    - ) - : ( -
    - {this.renderWarning(this.context.t('syncWithMobileBeCareful'))} -
    - {this.renderRevealSeedContent()} -
    -
    - ) - } - - renderPasswordPromptContent () { - const { t } = this.context - - return ( -
    this.handleSubmit(event)}> - -
    - this.setState({ password: event.target.value })} - className={classnames('form-control', { - 'form-control--error': this.state.error, - })} - /> -
    - {this.state.error && ( -
    - {this.state.error} -
    - )} -
    - ) - } - - renderRevealSeedContent () { - const qrImage = qrCode(0, 'M') - qrImage.addData(`metamask-sync:${this.state.channelName}|@|${this.state.cipherKey}`) - qrImage.make() - - const { t } = this.context - return ( -
    - -
    -
    - ) - } - - renderFooter () { - return this.state.screen === PASSWORD_PROMPT_SCREEN - ? this.renderPasswordPromptFooter() - : this.renderRevealSeedFooter() - } - - renderPasswordPromptFooter () { - const { t } = this.context - const { history } = this.props - const { password } = this.state - - return ( -
    - - -
    - ) - } - - renderRevealSeedFooter () { - const { t } = this.context - const { history } = this.props - - return ( -
    - -
    - ) - } - - render () { - const { t } = this.context - const { screen } = this.state - - return ( -
    -
    -
    - {t('syncWithMobileTitle')} -
    - { - screen === PASSWORD_PROMPT_SCREEN - ? ( -
    - {t('syncWithMobileDesc')} -
    - ) - : null - } - { - screen === PASSWORD_PROMPT_SCREEN - ? ( -
    - {t('syncWithMobileDescNewUsers')} -
    - ) - : null - } -
    -
    - {this.renderContent()} -
    - {this.renderFooter()} -
    - ) - } -} diff --git a/ui/app/pages/mobile-sync/mobile-sync.container.js b/ui/app/pages/mobile-sync/mobile-sync.container.js deleted file mode 100644 index 9155638418..0000000000 --- a/ui/app/pages/mobile-sync/mobile-sync.container.js +++ /dev/null @@ -1,25 +0,0 @@ -import { connect } from 'react-redux' -import { displayWarning, requestRevealSeedWords, fetchInfoToSync } from '../../store/actions' -import MobileSyncPage from './mobile-sync.component' - -const mapDispatchToProps = (dispatch) => { - return { - requestRevealSeedWords: password => dispatch(requestRevealSeedWords(password)), - fetchInfoToSync: () => dispatch(fetchInfoToSync()), - displayWarning: (message) => dispatch(displayWarning(message || null)), - } -} - -const mapStateToProps = state => { - const { - metamask: { - selectedAddress, - }, - } = state - - return { - selectedAddress, - } -} - -module.exports = connect(mapStateToProps, mapDispatchToProps)(MobileSyncPage) diff --git a/ui/app/pages/permissions-connect/choose-account/choose-account.component.js b/ui/app/pages/permissions-connect/choose-account/choose-account.component.js deleted file mode 100644 index 1a3a8fc498..0000000000 --- a/ui/app/pages/permissions-connect/choose-account/choose-account.component.js +++ /dev/null @@ -1,108 +0,0 @@ -import PropTypes from 'prop-types' -import React, { Component } from 'react' -import Identicon from '../../../components/ui/identicon' -import { PRIMARY } from '../../../helpers/constants/common' -import UserPreferencedCurrencyDisplay from '../../../components/app/user-preferenced-currency-display' - -export default class ChooseAccount extends Component { - static propTypes = { - accounts: PropTypes.arrayOf(PropTypes.shape({ - address: PropTypes.string, - addressLabel: PropTypes.string, - lastConnectedDate: PropTypes.string, - balance: PropTypes.string, - })).isRequired, - originName: PropTypes.string.isRequired, - selectAccount: PropTypes.func.isRequired, - selectNewAccountViaModal: PropTypes.func.isRequired, - nativeCurrency: PropTypes.string.isRequired, - addressLastConnectedMap: PropTypes.object, - cancelPermissionsRequest: PropTypes.func.isRequired, - permissionsRequestId: PropTypes.string.isRequired, - } - - static defaultProps = { - addressLastConnectedMap: {}, - } - - static contextTypes = { - t: PropTypes.func, - }; - - renderAccountsList = () => { - const { accounts, selectAccount, nativeCurrency, addressLastConnectedMap } = this.props - return ( -
    - { - accounts.map((account, index) => { - const { address, addressLabel, balance } = account - return ( -
    selectAccount(address) } - className="permissions-connect-choose-account__account" - > -
    - -
    -
    { addressLabel }
    - -
    -
    - { addressLastConnectedMap[address] - ? ( -
    - { this.context.t('lastConnected') } - { addressLastConnectedMap[address] } -
    - ) - : null - } -
    - ) - }) - } -
    - ) - } - - render () { - const { originName, selectNewAccountViaModal, permissionsRequestId, cancelPermissionsRequest } = this.props - const { t } = this.context - return ( -
    -
    - { t('chooseAnAcount') } -
    -
    - { t('toConnectWith', [originName]) } -
    - { this.renderAccountsList() } -
    -
    cancelPermissionsRequest(permissionsRequestId) } - className="permissions-connect-choose-account__cancel" - > - { t('cancel') } -
    -
    selectNewAccountViaModal() } - className="permissions-connect-choose-account__new-account" - > - { t('newAccount') } -
    -
    -
    - ) - } -} diff --git a/ui/app/pages/permissions-connect/choose-account/index.js b/ui/app/pages/permissions-connect/choose-account/index.js deleted file mode 100644 index 10b4cd2249..0000000000 --- a/ui/app/pages/permissions-connect/choose-account/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './choose-account.component' diff --git a/ui/app/pages/permissions-connect/choose-account/index.scss b/ui/app/pages/permissions-connect/choose-account/index.scss deleted file mode 100644 index 12c124b181..0000000000 --- a/ui/app/pages/permissions-connect/choose-account/index.scss +++ /dev/null @@ -1,97 +0,0 @@ -.permissions-connect-choose-account { - display: flex; - flex-direction: column; - margin-top: 40px; - width: 100%; - align-items: center; - - &__title { - @extend %header--18; - } - - &__text { - @extend %content-text; - line-height: 25px; - } - - &__accounts-list { - width: 393px; - border: 1px solid #D0D5DA; - box-sizing: border-box; - border-radius: 8px; - margin-top: 36px; - - @media screen and (max-width: 575px) { - width: 100%; - } - } - - &__account-info-wrapper { - display: flex; - justify-content: flex-start; - } - - &__account { - display: flex; - align-items: center; - padding: 16px; - border-bottom: 1px solid #D2D8DD; - justify-content: space-between; - - &:last-of-type { - border-bottom: none; - } - - &:hover { - background: aliceblue; - cursor: pointer; - } - - &__info { - display: flex; - flex-direction: column; - margin-left: 16px; - } - - &__label { - @extend %header--18; - line-height: 25px; - color: #000000; - } - - &__balance { - @extend %content-text; - line-height: 17px; - color: #6A737D; - } - - &__last-connected { - @extend %content-text; - font-size: 10px; - line-height: 140.62%; - display: flex; - flex-direction: column; - align-items: flex-end; - color: #037DD6; - } - } - - &__new-account, &__cancel { - @extend %content-text; - line-height: 20px; - color: #037DD6; - margin-top: 24px; - cursor: pointer; - } - - &__cancel { - color: $Red-400; - } - - &__bottom-buttons { - display: flex; - justify-content: space-around; - width: 393px; - } - -} \ No newline at end of file diff --git a/ui/app/pages/permissions-connect/index.js b/ui/app/pages/permissions-connect/index.js deleted file mode 100644 index 06798a2f61..0000000000 --- a/ui/app/pages/permissions-connect/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './permissions-connect.container' diff --git a/ui/app/pages/permissions-connect/index.scss b/ui/app/pages/permissions-connect/index.scss deleted file mode 100644 index fe2f5182a6..0000000000 --- a/ui/app/pages/permissions-connect/index.scss +++ /dev/null @@ -1,11 +0,0 @@ -@import 'permissions-connect-header/index'; - -@import 'permissions-connect-footer/index'; - -@import 'choose-account/index'; - -.permissions-connect { - width: 100%; - position: relative; - background: white; -} diff --git a/ui/app/pages/permissions-connect/permissions-connect-footer/index.js b/ui/app/pages/permissions-connect/permissions-connect-footer/index.js deleted file mode 100644 index 8096e1e3d6..0000000000 --- a/ui/app/pages/permissions-connect/permissions-connect-footer/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './permissions-connect-footer.component' diff --git a/ui/app/pages/permissions-connect/permissions-connect-footer/index.scss b/ui/app/pages/permissions-connect/permissions-connect-footer/index.scss deleted file mode 100644 index ff805e541d..0000000000 --- a/ui/app/pages/permissions-connect/permissions-connect-footer/index.scss +++ /dev/null @@ -1,27 +0,0 @@ -.permissions-connect-footer { - display: flex; - flex-direction: column; - width: 100%; - align-items: center; - position: absolute; - bottom: 50px; - - @media screen and (max-width: 575px) { - bottom: 5px; - } - - &__text { - @extend %content-text; - font-size: 12px; - line-height: 17px; - color: #6A737D; - display: flex; - margin-top: 10px; - - &--link { - color: #037DD6; - margin-left: 4px; - cursor: pointer; - } - } -} \ No newline at end of file diff --git a/ui/app/pages/permissions-connect/permissions-connect-footer/permissions-connect-footer.component.js b/ui/app/pages/permissions-connect/permissions-connect-footer/permissions-connect-footer.component.js deleted file mode 100644 index b8fad97a31..0000000000 --- a/ui/app/pages/permissions-connect/permissions-connect-footer/permissions-connect-footer.component.js +++ /dev/null @@ -1,27 +0,0 @@ -import PropTypes from 'prop-types' -import React, { Component } from 'react' - -export default class PermissionsConnectFooter extends Component { - static contextTypes = { - t: PropTypes.func, - } - - render () { - const { t } = this.context - return ( -
    - -
    -
    { t('onlyConnectTrust') }
    -
    { - global.platform.openWindow({ url: 'https://medium.com/metamask/privacy-mode-is-now-enabled-by-default-1c1c957f4d57' }) - }} - >{ t('learnAboutRisks') } -
    -
    -
    - ) - } -} diff --git a/ui/app/pages/permissions-connect/permissions-connect-header/index.js b/ui/app/pages/permissions-connect/permissions-connect-header/index.js deleted file mode 100644 index e9de1a06a9..0000000000 --- a/ui/app/pages/permissions-connect/permissions-connect-header/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './permissions-connect-header.component' diff --git a/ui/app/pages/permissions-connect/permissions-connect-header/index.scss b/ui/app/pages/permissions-connect/permissions-connect-header/index.scss deleted file mode 100644 index ca239bf17e..0000000000 --- a/ui/app/pages/permissions-connect/permissions-connect-header/index.scss +++ /dev/null @@ -1,15 +0,0 @@ -.permissions-connect-header { - margin-top: 26px; - margin-left: 20px; - display: flex; - justify-content: space-between; - align-items: flex-end; - - &__page-count { - @extend %content-text; - margin-right: 30px; - font-size: 12px; - line-height: 17px; - color: #6A737D; - } -} \ No newline at end of file diff --git a/ui/app/pages/permissions-connect/permissions-connect-header/permissions-connect-header.component.js b/ui/app/pages/permissions-connect/permissions-connect-header/permissions-connect-header.component.js deleted file mode 100644 index 512866c383..0000000000 --- a/ui/app/pages/permissions-connect/permissions-connect-header/permissions-connect-header.component.js +++ /dev/null @@ -1,25 +0,0 @@ -import PropTypes from 'prop-types' -import React, { Component } from 'react' -import MetaFoxLogo from '../../../components/ui/metafox-logo' -import { DEFAULT_ROUTE } from '../../../helpers/constants/routes' - -export default class PermissionsConnectHeader extends Component { - static propTypes = { - page: PropTypes.number.isRequired, - } - - render () { - const { page } = this.props - return ( -
    - history.push(DEFAULT_ROUTE)} - /> -
    - { `${page}/2` } -
    -
    - ) - } -} diff --git a/ui/app/pages/permissions-connect/permissions-connect.component.js b/ui/app/pages/permissions-connect/permissions-connect.component.js deleted file mode 100644 index 69dcbd390c..0000000000 --- a/ui/app/pages/permissions-connect/permissions-connect.component.js +++ /dev/null @@ -1,208 +0,0 @@ -import PropTypes from 'prop-types' -import React, { Component } from 'react' -import PermissionsConnectHeader from './permissions-connect-header' -import PermissionsConnectFooter from './permissions-connect-footer' -import ChooseAccount from './choose-account' -import { getEnvironmentType } from '../../../../app/scripts/lib/util' -import { - ENVIRONMENT_TYPE_FULLSCREEN, - ENVIRONMENT_TYPE_NOTIFICATION, - ENVIRONMENT_TYPE_POPUP, -} from '../../../../app/scripts/lib/enums' -import { DEFAULT_ROUTE, CONNECTED_ROUTE } from '../../helpers/constants/routes' -import PermissionPageContainer from '../../components/app/permission-page-container' - -export default class PermissionConnect extends Component { - static propTypes = { - approvePermissionsRequest: PropTypes.func.isRequired, - rejectPermissionsRequest: PropTypes.func.isRequired, - getRequestAccountTabIds: PropTypes.func.isRequired, - getCurrentWindowTab: PropTypes.func.isRequired, - accounts: PropTypes.array.isRequired, - originName: PropTypes.string, - showNewAccountModal: PropTypes.func.isRequired, - newAccountNumber: PropTypes.number.isRequired, - nativeCurrency: PropTypes.string, - permissionsRequest: PropTypes.object, - addressLastConnectedMap: PropTypes.object, - requestAccountTabs: PropTypes.object, - permissionsRequestId: PropTypes.string, - domains: PropTypes.object, - history: PropTypes.object.isRequired, - } - - static defaultProps = { - originName: '', - nativeCurrency: '', - permissionsRequest: undefined, - addressLastConnectedMap: {}, - requestAccountTabs: {}, - permissionsRequestId: '', - domains: {}, - } - - static contextTypes = { - t: PropTypes.func, - } - - state = { - page: 1, - selectedAccountAddress: '', - permissionAccepted: null, - originName: this.props.originName, - } - - beforeUnload = () => { - const { permissionsRequestId, rejectPermissionsRequest } = this.props - const { permissionAccepted } = this.state - - if (permissionAccepted === null && permissionsRequestId) { - rejectPermissionsRequest(permissionsRequestId) - } - } - - removeBeforeUnload = () => { - if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_FULLSCREEN) { - window.removeEventListener('beforeunload', this.beforeUnload) - } - } - - componentDidUpdate (prevProps) { - const { domains, permissionsRequestId } = this.props - const { originName, page } = this.state - - if (!permissionsRequestId && prevProps.permissionsRequestId && page !== null) { - const permissionDataForDomain = domains && domains[originName] || {} - const permissionsForDomain = permissionDataForDomain.permissions || [] - const prevPermissionDataForDomain = prevProps.domains && prevProps.domains[originName] || {} - const prevPermissionsForDomain = prevPermissionDataForDomain.permissions || [] - const addedAPermission = permissionsForDomain.length > prevPermissionsForDomain.length - if (addedAPermission) { - this.redirectFlow(true) - } else { - this.redirectFlow(false) - } - } else if (permissionsRequestId && prevProps.permissionsRequestId && - permissionsRequestId !== prevProps.permissionsRequestId && page !== null) { - this.setState({ - originName: this.props.originName, - page: 1, - }) - } - } - - selectAccount = (address) => { - this.setState({ - page: 2, - selectedAccountAddress: address, - }) - } - - redirectFlow (accepted) { - const { requestAccountTabs, history } = this.props - const { originName } = this.state - - this.setState({ - page: null, - permissionAccepted: accepted, - }) - this.removeBeforeUnload() - - if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_FULLSCREEN) { - setTimeout(async () => { - const currentTab = await global.platform.currentTab() - try { - if (currentTab.active) { - await global.platform.switchToTab(requestAccountTabs[originName]) - } - } finally { - global.platform.closeTab(currentTab.id) - } - }, 2000) - } else if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) { - history.push(DEFAULT_ROUTE) - } else if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) { - history.push(CONNECTED_ROUTE) - } - } - - componentDidMount () { - const { - getCurrentWindowTab, - getRequestAccountTabIds, - } = this.props - getCurrentWindowTab() - getRequestAccountTabIds() - - if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_FULLSCREEN) { - window.addEventListener('beforeunload', this.beforeUnload) - } - } - - render () { - const { - approvePermissionsRequest, - rejectPermissionsRequest, - accounts, - showNewAccountModal, - newAccountNumber, - nativeCurrency, - permissionsRequest, - addressLastConnectedMap, - permissionsRequestId, - } = this.props - const { page, selectedAccountAddress, permissionAccepted, originName } = this.state - - return ( -
    - { page !== null - ? - : null - } - { page === 1 - ? ( - this.selectAccount(address)} - selectNewAccountViaModal={() => { - showNewAccountModal({ - onCreateNewAccount: this.selectAccount, - newAccountNumber, - }) - }} - addressLastConnectedMap={addressLastConnectedMap} - cancelPermissionsRequest={requestId => { - if (requestId) { - rejectPermissionsRequest(requestId) - this.redirectFlow(false) - } - }} - permissionsRequestId={permissionsRequestId} - /> - ) - : ( -
    - { - approvePermissionsRequest(requestId, accounts) - this.redirectFlow(true) - }} - rejectPermissionsRequest={requestId => { - rejectPermissionsRequest(requestId) - this.redirectFlow(false) - }} - selectedIdentity={accounts.find(account => account.address === selectedAccountAddress)} - redirect={page === null} - permissionRejected={ permissionAccepted === false } - /> - -
    - ) - } -
    - ) - } -} diff --git a/ui/app/pages/permissions-connect/permissions-connect.container.js b/ui/app/pages/permissions-connect/permissions-connect.container.js deleted file mode 100644 index 1209bd9cca..0000000000 --- a/ui/app/pages/permissions-connect/permissions-connect.container.js +++ /dev/null @@ -1,66 +0,0 @@ -import { connect } from 'react-redux' -import { compose } from 'recompose' -import { withRouter } from 'react-router-dom' -import PermissionApproval from './permissions-connect.component' -import { - getFirstPermissionRequest, - getNativeCurrency, - getAccountsWithLabels, - getLastConnectedInfo, - getPermissionsDomains, -} from '../../selectors/selectors' -import { formatDate } from '../../helpers/utils/util' -import { approvePermissionsRequest, rejectPermissionsRequest, showModal, getCurrentWindowTab, getRequestAccountTabIds } from '../../store/actions' - -const mapStateToProps = state => { - const permissionsRequest = getFirstPermissionRequest(state) - const { metadata = {} } = permissionsRequest || {} - const { origin } = metadata - const nativeCurrency = getNativeCurrency(state) - - const accountsWithLabels = getAccountsWithLabels(state) - - const { requestAccountTabs = {} } = state.appState - - const lastConnectedInfo = getLastConnectedInfo(state) || {} - const addressLastConnectedMap = lastConnectedInfo[origin] || {} - - Object.keys(addressLastConnectedMap).forEach(key => { - addressLastConnectedMap[key] = formatDate(addressLastConnectedMap[key], 'yyyy-M-d') - }) - - const permissionsRequestId = (permissionsRequest && permissionsRequest.metadata) ? permissionsRequest.metadata.id : null - - return { - permissionsRequest, - permissionsRequestId, - accounts: accountsWithLabels, - originName: origin, - newAccountNumber: accountsWithLabels.length + 1, - nativeCurrency, - requestAccountTabs, - addressLastConnectedMap, - domains: getPermissionsDomains(state), - } -} - -const mapDispatchToProps = dispatch => { - return { - approvePermissionsRequest: (requestId, accounts) => dispatch(approvePermissionsRequest(requestId, accounts)), - rejectPermissionsRequest: requestId => dispatch(rejectPermissionsRequest(requestId)), - showNewAccountModal: ({ onCreateNewAccount, newAccountNumber }) => { - return dispatch(showModal({ - name: 'NEW_ACCOUNT', - onCreateNewAccount, - newAccountNumber, - })) - }, - getRequestAccountTabIds: () => dispatch(getRequestAccountTabIds()), - getCurrentWindowTab: () => dispatch(getCurrentWindowTab()), - } -} - -export default compose( - withRouter, - connect(mapStateToProps, mapDispatchToProps) -)(PermissionApproval) diff --git a/ui/app/pages/provider-approval/index.js b/ui/app/pages/provider-approval/index.js new file mode 100644 index 0000000000..4162f31557 --- /dev/null +++ b/ui/app/pages/provider-approval/index.js @@ -0,0 +1 @@ +export { default } from './provider-approval.container' diff --git a/ui/app/pages/provider-approval/provider-approval.component.js b/ui/app/pages/provider-approval/provider-approval.component.js new file mode 100644 index 0000000000..8532fe60df --- /dev/null +++ b/ui/app/pages/provider-approval/provider-approval.component.js @@ -0,0 +1,36 @@ +import PropTypes from 'prop-types' +import React, { Component } from 'react' +import ProviderPageContainer from '../../components/app/provider-page-container' + +export default class ProviderApproval extends Component { + static propTypes = { + approveProviderRequestByOrigin: PropTypes.func.isRequired, + rejectProviderRequestByOrigin: PropTypes.func.isRequired, + providerRequest: PropTypes.exact({ + hostname: PropTypes.string.isRequired, + siteImage: PropTypes.string, + siteTitle: PropTypes.string, + origin: PropTypes.string.isRequired, + extensionId: PropTypes.string, + }).isRequired, + }; + + static contextTypes = { + t: PropTypes.func, + }; + + render () { + const { approveProviderRequestByOrigin, providerRequest, rejectProviderRequestByOrigin } = this.props + return ( + + ) + } +} diff --git a/ui/app/pages/provider-approval/provider-approval.container.js b/ui/app/pages/provider-approval/provider-approval.container.js new file mode 100644 index 0000000000..1e167ddb75 --- /dev/null +++ b/ui/app/pages/provider-approval/provider-approval.container.js @@ -0,0 +1,12 @@ +import { connect } from 'react-redux' +import ProviderApproval from './provider-approval.component' +import { approveProviderRequestByOrigin, rejectProviderRequestByOrigin } from '../../store/actions' + +function mapDispatchToProps (dispatch) { + return { + approveProviderRequestByOrigin: origin => dispatch(approveProviderRequestByOrigin(origin)), + rejectProviderRequestByOrigin: origin => dispatch(rejectProviderRequestByOrigin(origin)), + } +} + +export default connect(null, mapDispatchToProps)(ProviderApproval) diff --git a/ui/app/pages/routes/index.js b/ui/app/pages/routes/index.js index b2a2ced689..01e61b1b4b 100644 --- a/ui/app/pages/routes/index.js +++ b/ui/app/pages/routes/index.js @@ -6,12 +6,7 @@ import { compose } from 'recompose' import actions from '../../store/actions' import log from 'loglevel' import IdleTimer from 'react-idle-timer' -import { - getNetworkIdentifier, - preferencesSelector, - hasPermissionRequests, - getAddressConnectedToCurrentTab, -} from '../../selectors/selectors' +import {getNetworkIdentifier, preferencesSelector} from '../../selectors/selectors' import classnames from 'classnames' // init @@ -30,11 +25,9 @@ import Settings from '../settings' import Authenticated from '../../helpers/higher-order-components/authenticated' import Initialized from '../../helpers/higher-order-components/initialized' import Lock from '../lock' -import PermissionsConnect from '../permissions-connect' -import ConnectedSites from '../connected-sites' const RestoreVaultPage = require('../keychains/restore-vault').default const RevealSeedConfirmation = require('../keychains/reveal-seed') -const MobileSyncPage = require('../mobile-sync').default +const MobileSyncPage = require('../mobile-sync') const AddTokenPage = require('../add-token') const ConfirmAddTokenPage = require('../confirm-add-token') const ConfirmAddSuggestedTokenPage = require('../confirm-add-suggested-token') @@ -74,8 +67,6 @@ import { CONFIRM_TRANSACTION_ROUTE, INITIALIZE_ROUTE, INITIALIZE_UNLOCK_ROUTE, - CONNECT_ROUTE, - CONNECTED_ROUTE, } from '../../helpers/constants/routes' // enums @@ -107,20 +98,6 @@ class Routes extends Component { }) } - componentDidMount () { - const { addressConnectedToCurrentTab, showAccountDetail, selectedAddress } = this.props - if (addressConnectedToCurrentTab && addressConnectedToCurrentTab !== selectedAddress) { - showAccountDetail(addressConnectedToCurrentTab) - } - } - - componentDidUpdate (prevProps) { - const { addressConnectedToCurrentTab, showAccountDetail } = this.props - if (addressConnectedToCurrentTab && addressConnectedToCurrentTab !== prevProps.addressConnectedToCurrentTab) { - showAccountDetail(addressConnectedToCurrentTab) - } - } - renderRoutes () { const { autoLogoutTimeLimit, setLastActiveTime } = this.props @@ -139,8 +116,6 @@ class Routes extends Component { - - ) @@ -166,8 +141,13 @@ class Routes extends Component { return Boolean(matchPath(location.pathname, { path: CONFIRM_TRANSACTION_ROUTE, exact: false })) } + hasProviderRequests () { + const { providerRequests } = this.props + return Array.isArray(providerRequests) && providerRequests.length > 0 + } + hideAppHeader () { - const { location, hasPermissionsRequests } = this.props + const { location } = this.props const isInitializing = Boolean(matchPath(location.pathname, { path: INITIALIZE_ROUTE, exact: false, @@ -182,15 +162,7 @@ class Routes extends Component { } if (window.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_POPUP) { - return this.onConfirmPage() || hasPermissionsRequests - } - - const isHandlingPermissionsRequest = Boolean(matchPath(location.pathname, { - path: CONNECT_ROUTE, exact: false, - })) - - if (hasPermissionsRequests || isHandlingPermissionsRequest) { - return true + return this.onConfirmPage() || this.hasProviderRequests() } } @@ -288,10 +260,8 @@ class Routes extends Component { toggleMetamaskActive () { if (!this.props.isUnlocked) { // currently inactive: redirect to password box - const passwordBox = document.querySelector('input[type=password]') - if (!passwordBox) { - return - } + var passwordBox = document.querySelector('input[type=password]') + if (!passwordBox) return passwordBox.focus() } else { // currently active: deactivate @@ -362,7 +332,6 @@ Routes.propTypes = { textDirection: PropTypes.string, network: PropTypes.string, provider: PropTypes.object, - selectedAddress: PropTypes.string, frequentRpcListDetail: PropTypes.array, currentView: PropTypes.object, sidebar: PropTypes.object, @@ -377,18 +346,12 @@ Routes.propTypes = { isMouseUser: PropTypes.bool, setMouseUserState: PropTypes.func, providerId: PropTypes.string, - hasPermissionsRequests: PropTypes.bool, + providerRequests: PropTypes.array, autoLogoutTimeLimit: PropTypes.number, - addressConnectedToCurrentTab: PropTypes.string, - showAccountDetail: PropTypes.func, -} - -Routes.defaultProps = { - selectedAddress: undefined, } function mapStateToProps (state) { - const { appState } = state + const { appState, metamask } = state const { sidebar, alertOpen, @@ -412,14 +375,12 @@ function mapStateToProps (state) { submittedPendingTransactions: submittedPendingTransactionsSelector(state), network: state.metamask.network, provider: state.metamask.provider, - selectedAddress: state.metamask.selectedAddress, frequentRpcListDetail: state.metamask.frequentRpcListDetail || [], currentCurrency: state.metamask.currentCurrency, isMouseUser: state.appState.isMouseUser, providerId: getNetworkIdentifier(state), autoLogoutTimeLimit, - hasPermissionsRequests: hasPermissionRequests(state), - addressConnectedToCurrentTab: getAddressConnectedToCurrentTab(state), + providerRequests: metamask.providerRequests, } } @@ -430,7 +391,6 @@ function mapDispatchToProps (dispatch) { setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')), setMouseUserState: (isMouseUser) => dispatch(actions.setMouseUserState(isMouseUser)), setLastActiveTime: () => dispatch(actions.setLastActiveTime()), - showAccountDetail: address => dispatch(actions.showAccountDetail(address)), } } diff --git a/ui/app/pages/send/account-list-item/account-list-item.component.js b/ui/app/pages/send/account-list-item/account-list-item.component.js index 8c676f4197..20ced2f099 100644 --- a/ui/app/pages/send/account-list-item/account-list-item.component.js +++ b/ui/app/pages/send/account-list-item/account-list-item.component.js @@ -44,32 +44,30 @@ export default class AccountListItem extends Component { const { name, address, balance } = account || {} - return ( -
    handleClick && handleClick({ name, address, balance })} - > + return (
    handleClick && handleClick({ name, address, balance })} + > -
    - +
    + -
    { name || address }
    +
    { name || address }
    - {icon &&
    { icon }
    } + {icon &&
    { icon }
    } -
    +
    - {displayAddress && name && ( -
    - { checksumAddress(address) } -
    - )} + {displayAddress && name &&
    + { checksumAddress(address) } +
    } - {displayBalance && ( + { + displayBalance && ( -
    +
    { - balanceIsCached - ? * - : null + balanceIsCached ? * : null }
    - {showFiat && ( - - )} + { + showFiat && ( + + ) + }
    - )} + ) + } -
    - ) +
    ) } } diff --git a/ui/app/pages/send/account-list-item/tests/account-list-item-component.test.js b/ui/app/pages/send/account-list-item/tests/account-list-item-component.test.js index e54a8c493e..5a2bd56380 100644 --- a/ui/app/pages/send/account-list-item/tests/account-list-item-component.test.js +++ b/ui/app/pages/send/account-list-item/tests/account-list-item-component.test.js @@ -23,19 +23,17 @@ describe('AccountListItem Component', function () { let wrapper beforeEach(() => { - wrapper = shallow(( - } - /> - ), { context: { t: str => str + '_t' } }) + wrapper = shallow(} + />, { context: { t: str => str + '_t' } }) }) afterEach(() => { diff --git a/ui/app/pages/send/send-content/add-recipient/add-recipient.component.js b/ui/app/pages/send/send-content/add-recipient/add-recipient.component.js index a2e6b49db3..e5edbc08da 100644 --- a/ui/app/pages/send/send-content/add-recipient/add-recipient.component.js +++ b/ui/app/pages/send/send-content/add-recipient/add-recipient.component.js @@ -64,6 +64,7 @@ export default class AddRecipient extends Component { state = { isShowingTransfer: false, + isShowingAllRecent: false, } selectRecipient = (to, nickname = '') => { @@ -155,7 +156,7 @@ export default class AddRecipient extends Component { className="send__select-recipient-wrapper__list__link" onClick={() => this.setState({ isShowingTransfer: false })} > -
    +
    { t('backToAll') }
    { - if (address === ZERO_ADDRESS) { - throw new Error(this.context.t('noAddressForName')) - } - if (address === ZERO_X_ERROR_ADDRESS) { - throw new Error(this.context.t('ensRegistrationError')) - } + if (address === ZERO_ADDRESS) throw new Error(this.context.t('noAddressForName')) + if (address === ZERO_X_ERROR_ADDRESS) throw new Error(this.context.t('ensRegistrationError')) this.props.updateEnsResolution(address) }) .catch((reason) => { @@ -233,11 +230,9 @@ export default class EnsInput extends Component { } ensIconContents () { - const { loadingEns, ensFailure, ensResolution, toError } = this.state + const { loadingEns, ensFailure, ensResolution, toError } = this.state || { ensResolution: ZERO_ADDRESS } - if (toError) { - return - } + if (toError) return if (loadingEns) { return ( diff --git a/ui/app/pages/send/send-content/add-recipient/tests/add-recipient-component.test.js b/ui/app/pages/send/send-content/add-recipient/tests/add-recipient-component.test.js index 115ad5b625..1271c98104 100644 --- a/ui/app/pages/send/send-content/add-recipient/tests/add-recipient-component.test.js +++ b/ui/app/pages/send/send-content/add-recipient/tests/add-recipient-component.test.js @@ -19,25 +19,23 @@ describe('AddRecipient Component', function () { let instance beforeEach(() => { - wrapper = shallow(( - - ), { context: { t: str => str + '_t' } }) + wrapper = shallow(, { context: { t: str => str + '_t' } }) instance = wrapper.instance() }) diff --git a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js index 48bc32d3ac..30ef4c6789 100644 --- a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js +++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js @@ -18,17 +18,15 @@ describe('AmountMaxButton Component', function () { let instance beforeEach(() => { - wrapper = shallow(( - - ), { + wrapper = shallow(, { context: { t: str => str + '_t', metricsEvent: () => {}, diff --git a/ui/app/pages/send/send-content/send-amount-row/tests/send-amount-row-component.test.js b/ui/app/pages/send/send-content/send-amount-row/tests/send-amount-row-component.test.js index fe648d4d80..bfc0f7e64b 100644 --- a/ui/app/pages/send/send-content/send-amount-row/tests/send-amount-row-component.test.js +++ b/ui/app/pages/send/send-content/send-amount-row/tests/send-amount-row-component.test.js @@ -25,25 +25,23 @@ describe('SendAmountRow Component', function () { let instance beforeEach(() => { - wrapper = shallow(( - - ), { context: { t: str => str + '_t' } }) + wrapper = shallow(, { context: { t: str => str + '_t' } }) instance = wrapper.instance() }) diff --git a/ui/app/pages/send/send-content/send-asset-row/send-asset-row.component.js b/ui/app/pages/send/send-content/send-asset-row/send-asset-row.component.js index c0b6045053..1dcd0bd2c2 100644 --- a/ui/app/pages/send/send-content/send-asset-row/send-asset-row.component.js +++ b/ui/app/pages/send/send-content/send-asset-row/send-asset-row.component.js @@ -128,8 +128,7 @@ export default class SendAssetRow extends Component { return (
    this.selectToken(address)} >
    diff --git a/ui/app/pages/send/send-content/send-dropdown-list/send-dropdown-list.component.js b/ui/app/pages/send/send-content/send-dropdown-list/send-dropdown-list.component.js index 42b19397b1..5f5d262311 100644 --- a/ui/app/pages/send/send-content/send-dropdown-list/send-dropdown-list.component.js +++ b/ui/app/pages/send/send-content/send-dropdown-list/send-dropdown-list.component.js @@ -17,7 +17,7 @@ export default class SendDropdownList extends Component { getListItemIcon (accountAddress, activeAddress) { return accountAddress === activeAddress - ? + ? : null } @@ -29,28 +29,24 @@ export default class SendDropdownList extends Component { activeAddress, } = this.props - return ( -
    -
    closeDropdown()} - /> -
    - {accounts.map((account, index) => ( - { - onSelect(account) - closeDropdown() - }} - icon={this.getListItemIcon(account.address, activeAddress)} - key={`send-dropdown-account-#${index}`} - /> - ))} -
    + return (
    +
    closeDropdown()} + /> +
    + {accounts.map((account, index) => { + onSelect(account) + closeDropdown() + }} + icon={this.getListItemIcon(account.address, activeAddress)} + key={`send-dropdown-account-#${index}`} + />)}
    - ) +
    ) } } diff --git a/ui/app/pages/send/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js b/ui/app/pages/send/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js index 77efddfd43..bc6c975863 100644 --- a/ui/app/pages/send/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js +++ b/ui/app/pages/send/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js @@ -17,18 +17,16 @@ describe('SendDropdownList Component', function () { let wrapper beforeEach(() => { - wrapper = shallow(( - - ), { context: { t: str => str + '_t' } }) + wrapper = shallow(, { context: { t: str => str + '_t' } }) }) afterEach(() => { @@ -41,7 +39,7 @@ describe('SendDropdownList Component', function () { it('should return check icon if the passed addresses are the same', () => { assert.deepEqual( wrapper.instance().getListItemIcon('mockAccount0', 'mockAccount0'), - + ) }) diff --git a/ui/app/pages/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js b/ui/app/pages/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js index 473dbb0b14..37af59e291 100644 --- a/ui/app/pages/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js +++ b/ui/app/pages/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js @@ -38,16 +38,12 @@ export default class GasFeeDisplay extends Component {
    ) : gasLoadingError - ? ( -
    - {this.context.t('setGasPrice')} -
    - ) - : ( -
    - {this.context.t('loading')} -
    - ) + ?
    + {this.context.t('setGasPrice')} +
    + :
    + {this.context.t('loading')} +
    } +
    +
    +
    + ) + } + + renderApprovedOriginsList () { + const { t } = this.context + const { approvedOrigins, rejectProviderRequestByOrigin, showClearApprovalModal } = this.props + const approvedEntries = Object.entries(approvedOrigins) + const approvalListEmpty = approvedEntries.length === 0 + + return ( +
    +
    + { t('connected') } + + { t('connectedDescription') } + +
    +
    + { + approvalListEmpty + ?
    + : null + } + { + approvedEntries.map(([origin, { siteTitle, siteImage }]) => ( + { + rejectProviderRequestByOrigin(origin) + }} + /> + )) + } +
    +
    + +
    +
    + ) + } + + render () { + return ( +
    + { this.renderNewOriginInput() } + { this.renderApprovedOriginsList() } +
    + ) + } +} diff --git a/ui/app/pages/settings/connections-tab/connections-tab.container.js b/ui/app/pages/settings/connections-tab/connections-tab.container.js new file mode 100644 index 0000000000..cf3efc2b46 --- /dev/null +++ b/ui/app/pages/settings/connections-tab/connections-tab.container.js @@ -0,0 +1,39 @@ +import ConnectionsTab from './connections-tab.component' +import { compose } from 'recompose' +import { connect } from 'react-redux' +import { withRouter } from 'react-router-dom' +import { + approveProviderRequestByOrigin, + rejectProviderRequestByOrigin, + showModal, +} from '../../../store/actions' + +export const mapStateToProps = state => { + const { + activeTab, + metamask, + } = state + const { + approvedOrigins, + } = metamask + + return { + activeTab, + approvedOrigins, + } +} + +export const mapDispatchToProps = dispatch => { + return { + approveProviderRequestByOrigin: (origin) => dispatch(approveProviderRequestByOrigin(origin)), + rejectProviderRequestByOrigin: (origin) => dispatch(rejectProviderRequestByOrigin(origin)), + showClearApprovalModal: () => dispatch(showModal({ + name: 'CLEAR_APPROVED_ORIGINS', + })), + } +} + +export default compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(ConnectionsTab) diff --git a/ui/app/pages/settings/connections-tab/index.js b/ui/app/pages/settings/connections-tab/index.js new file mode 100644 index 0000000000..b04f4e33a6 --- /dev/null +++ b/ui/app/pages/settings/connections-tab/index.js @@ -0,0 +1 @@ +export { default } from './connections-tab.container' diff --git a/ui/app/pages/settings/connections-tab/index.scss b/ui/app/pages/settings/connections-tab/index.scss new file mode 100644 index 0000000000..249a7193f7 --- /dev/null +++ b/ui/app/pages/settings/connections-tab/index.scss @@ -0,0 +1 @@ +@import './connected-site-row/index'; diff --git a/ui/app/pages/settings/contact-list-tab/add-contact/add-contact.component.js b/ui/app/pages/settings/contact-list-tab/add-contact/add-contact.component.js index 60f097d795..56d4415cf5 100644 --- a/ui/app/pages/settings/contact-list-tab/add-contact/add-contact.component.js +++ b/ui/app/pages/settings/contact-list-tab/add-contact/add-contact.component.js @@ -23,7 +23,7 @@ export default class AddContact extends PureComponent { } state = { - newName: '', + nickname: '', ethAddress: '', ensAddress: '', error: '', @@ -63,9 +63,7 @@ export default class AddContact extends PureComponent { return ( { - this.props.scanQrCode() - }} + scanQrCode={_ => { this.props.scanQrCode() }} onChange={this.dValidate} onPaste={text => this.setState({ ethAddress: text })} onReset={() => this.setState({ ethAddress: '', ensAddress: '' })} @@ -85,14 +83,12 @@ export default class AddContact extends PureComponent { return (
    - {this.state.ensAddress && ( -
    - -
    - { this.state.ensAddress } -
    + {this.state.ensAddress &&
    + +
    + { this.state.ensAddress }
    - )} +
    }
    diff --git a/ui/app/pages/settings/contact-list-tab/contact-list-tab.component.js b/ui/app/pages/settings/contact-list-tab/contact-list-tab.component.js index 835963a83f..f7a01d6721 100644 --- a/ui/app/pages/settings/contact-list-tab/contact-list-tab.component.js +++ b/ui/app/pages/settings/contact-list-tab/contact-list-tab.component.js @@ -49,19 +49,16 @@ export default class ContactListTab extends Component { renderAddButton () { const { history } = this.props - return ( -
    { - history.push(CONTACT_ADD_ROUTE) - }} - > - -
    - ) + return
    { + history.push(CONTACT_ADD_ROUTE) + }}> + +
    } renderMyAccountsButton () { @@ -101,23 +98,19 @@ export default class ContactListTab extends Component { ContactContentComponent = AddContact } - return (ContactContentComponent && ( -
    - -
    - )) + return (ContactContentComponent &&
    + +
    ) } renderAddressBookContent () { const { hideAddressBook, showingMyAccounts } = this.props if (!hideAddressBook && !showingMyAccounts) { - return ( -
    - { this.renderMyAccountsButton() } - { this.renderAddresses() } -
    - ) + return (
    + { this.renderMyAccountsButton() } + { this.renderAddresses() } +
    ) } else if (!hideAddressBook && showingMyAccounts) { return () } @@ -130,11 +123,9 @@ export default class ContactListTab extends Component {
    { this.renderAddressBookContent() } { this.renderContactContent() } - {!addingContact && ( -
    - { this.renderAddButton() } -
    - )} + {!addingContact &&
    + { this.renderAddButton() } +
    }
    ) } diff --git a/ui/app/pages/settings/contact-list-tab/edit-contact/edit-contact.component.js b/ui/app/pages/settings/contact-list-tab/edit-contact/edit-contact.component.js index 88b14f5e3a..9373cbe6ba 100644 --- a/ui/app/pages/settings/contact-list-tab/edit-contact/edit-contact.component.js +++ b/ui/app/pages/settings/contact-list-tab/edit-contact/edit-contact.component.js @@ -45,7 +45,7 @@ export default class EditContact extends PureComponent { return (
    - + -
    - ) + ?
    + +
    : null }
    diff --git a/ui/app/pages/settings/settings.component.js b/ui/app/pages/settings/settings.component.js index 12d395f315..975ab4e35c 100644 --- a/ui/app/pages/settings/settings.component.js +++ b/ui/app/pages/settings/settings.component.js @@ -4,6 +4,7 @@ import { Switch, Route, matchPath, withRouter } from 'react-router-dom' import TabBar from '../../components/app/tab-bar' import c from 'classnames' import SettingsTab from './settings-tab' +import ConnectionsTab from './connections-tab' import NetworksTab from './networks-tab' import AdvancedTab from './advanced-tab' import InfoTab from './info-tab' @@ -14,6 +15,7 @@ import { ADVANCED_ROUTE, SECURITY_ROUTE, GENERAL_ROUTE, + CONNECTIONS_ROUTE, ABOUT_US_ROUTE, SETTINGS_ROUTE, NETWORKS_ROUTE, @@ -133,19 +135,13 @@ class SettingsPage extends PureComponent {
    initialBreadCrumbRoute && history.push(initialBreadCrumbRoute)} - > - {subheaderText} -
    - {breadCrumbTextKey && ( -
    - {' > '}{t(breadCrumbTextKey)} -
    - )} - {isAddressEntryPage && ( -
    - {' > '}{addressName} -
    - )} + >{subheaderText}
    + {breadCrumbTextKey &&
    {' > '}{t(breadCrumbTextKey)}
    } + {isAddressEntryPage &&
    {' > '}{addressName}
    }
    ) } @@ -158,6 +154,7 @@ class SettingsPage extends PureComponent { + { - const { address, name, balance } = account - return { - address, - truncatedAddress: `${address.slice(0, 6)}...${address.slice(-4)}`, - addressLabel: `${name} (...${address.slice(address.length - 4)})`, - label: name, - balance, - } - }) - return accountsWithLabels -} - function getCurrentAccountWithSendEtherInfo (state) { const currentAddress = getSelectedAddress(state) const accounts = accountsWithSendEtherInfoSelector(state) @@ -385,22 +353,6 @@ function getCustomNonceValue (state) { return String(state.metamask.customNonceValue) } -function getPermissionsDescriptions (state) { - return state.metamask.permissionsDescriptions -} - -function getPermissionsRequests (state) { - return state.metamask.permissionsRequests -} - -function getDomainMetadata (state) { - return state.metamask.domainMetadata -} - -function getActiveTab (state) { - return state.activeTab -} - function getMetaMetricState (state) { return { network: getCurrentNetworkId(state), @@ -434,144 +386,3 @@ function getKnownMethodData (state, data) { function getFeatureFlags (state) { return state.metamask.featureFlags } - -function getFirstPermissionRequest (state) { - const requests = getPermissionsRequests(state) - return requests && requests[0] ? requests[0] : null -} - -function hasPermissionRequests (state) { - return Boolean(getFirstPermissionRequest(state)) -} - -function getPermissionsDomains (state) { - return state.metamask.domains -} - -function getAddressConnectedDomainMap (state) { - const { - domains, - domainMetadata, - } = state.metamask - - const addressConnectedIconMap = {} - - if (domains) { - Object.keys(domains).forEach(domainKey => { - const { permissions } = domains[domainKey] - const { icon, name } = domainMetadata[domainKey] || {} - permissions.forEach(perm => { - const caveats = perm.caveats || [] - const exposedAccountCaveat = caveats.find(caveat => caveat.name === 'exposedAccounts') - if (exposedAccountCaveat && exposedAccountCaveat.value && exposedAccountCaveat.value.length) { - exposedAccountCaveat.value.forEach(address => { - const nameToRender = name || domainKey - addressConnectedIconMap[address] = addressConnectedIconMap[address] - ? { ...addressConnectedIconMap[address], [domainKey]: { icon, name: nameToRender } } - : { [domainKey]: { icon, name: nameToRender } } - }) - } - }) - }) - } - - return addressConnectedIconMap -} - -function getDomainToConnectedAddressMap (state) { - const { domains = {} } = state.metamask - - const domainToConnectedAddressMap = mapObjectValues(domains, (_, { permissions }) => { - const ethAccountsPermissions = permissions.filter(permission => permission.parentCapability === 'eth_accounts') - const ethAccountsPermissionsExposedAccountAddresses = ethAccountsPermissions.map(permission => { - const caveats = permission.caveats - const exposedAccountsCaveats = caveats.filter(caveat => caveat.name === 'exposedAccounts') - const exposedAccountsAddresses = exposedAccountsCaveats.map(caveat => caveat.value[0]) - return exposedAccountsAddresses - }) - const allAddressesConnectedToDomain = ethAccountsPermissionsExposedAccountAddresses.reduce((acc, arrayOfAddresses) => { - return [ ...acc, ...arrayOfAddresses ] - }, []) - return allAddressesConnectedToDomain - }) - - return domainToConnectedAddressMap -} - -function getAddressConnectedToCurrentTab (state) { - const domainToConnectedAddressMap = getDomainToConnectedAddressMap(state) - const originOfCurrentTab = getOriginOfCurrentTab(state) - const addressesConnectedToCurrentTab = domainToConnectedAddressMap[originOfCurrentTab] - const addressConnectedToCurrentTab = addressesConnectedToCurrentTab && addressesConnectedToCurrentTab[0] - return addressConnectedToCurrentTab -} - -function getRenderablePermissionsDomains (state) { - const { - domains = {}, - domainMetadata, - permissionsHistory, - permissionsDescriptions, - selectedAddress, - } = state.metamask - - const renderableDomains = Object.keys(domains).reduce((acc, domainKey) => { - const { permissions } = domains[domainKey] - const permissionsWithCaveatsForSelectedAddress = permissions.filter(perm => { - const caveats = perm.caveats || [] - const exposedAccountCaveat = caveats.find(caveat => caveat.name === 'exposedAccounts') - const exposedAccountCaveatValue = exposedAccountCaveat && exposedAccountCaveat.value && exposedAccountCaveat.value.length - ? exposedAccountCaveat.value[0] - : {} - return exposedAccountCaveatValue === selectedAddress - }) - - if (permissionsWithCaveatsForSelectedAddress.length) { - const permissionKeys = permissions.map(permission => permission.parentCapability) - const { - name, - icon, - extensionId, - } = domainMetadata[domainKey] || {} - const permissionsHistoryForDomain = permissionsHistory[domainKey] || {} - const ethAccountsPermissionsForDomain = permissionsHistoryForDomain['eth_accounts'] || {} - const accountsLastConnectedTime = ethAccountsPermissionsForDomain.accounts || {} - const selectedAddressLastConnectedTime = accountsLastConnectedTime[selectedAddress] - - const lastConnectedTime = selectedAddressLastConnectedTime - ? formatDate(selectedAddressLastConnectedTime, 'yyyy-M-d') - : '' - - return [ ...acc, { - name: name || domainKey, - secondaryName: name ? domainKey : '', - icon, - key: domainKey, - lastConnectedTime, - permissionDescriptions: permissionKeys.map(permissionKey => permissionsDescriptions[permissionKey]), - extensionId, - }] - } else { - return acc - } - }, []) - - return renderableDomains -} - -function getOriginOfCurrentTab (state) { - const { activeTab } = state - return activeTab && activeTab.url && getOriginFromUrl(activeTab.url) -} - -function getLastConnectedInfo (state) { - const { permissionsHistory = {} } = state.metamask - const lastConnectedInfoData = Object.keys(permissionsHistory).reduce((acc, origin) => { - const ethAccountsHistory = JSON.parse(JSON.stringify(permissionsHistory[origin]['eth_accounts'])) - return { - ...acc, - [origin]: ethAccountsHistory.accounts, - } - }, {}) - return lastConnectedInfoData -} diff --git a/ui/app/store/actions.js b/ui/app/store/actions.js index a09548f0f1..2642e89e9c 100644 --- a/ui/app/store/actions.js +++ b/ui/app/store/actions.js @@ -17,7 +17,7 @@ const { hasUnconfirmedTransactions } = require('../helpers/utils/confirm-tx.util const gasDuck = require('../ducks/gas/gas.duck') const WebcamUtils = require('../../lib/webcam-utils') -const actions = { +var actions = { _setBackgroundConnection: _setBackgroundConnection, GO_HOME: 'GO_HOME', @@ -328,6 +328,7 @@ const actions = { setUseNativeCurrencyAsPrimaryCurrencyPreference, setShowFiatConversionOnTestnetsPreference, setAutoLogoutTimeLimit, + unsetMigratedPrivacyMode, // Onboarding setCompletedOnboarding, @@ -351,12 +352,9 @@ const actions = { createSpeedUpTransaction, createRetryTransaction, - // Permissions - approvePermissionsRequest, - clearPermissions, - rejectPermissionsRequest, - removePermissionsFor, - legacyExposeAccounts, + approveProviderRequestByOrigin, + rejectProviderRequestByOrigin, + clearApprovedOrigins, setFirstTimeFlowType, SET_FIRST_TIME_FLOW_TYPE: 'SET_FIRST_TIME_FLOW_TYPE', @@ -397,18 +395,11 @@ const actions = { turnThreeBoxSyncingOnAndInitialize, tryReverseResolveAddress, - - getRequestAccountTabIds, - getCurrentWindowTab, - SET_REQUEST_ACCOUNT_TABS: 'SET_REQUEST_ACCOUNT_TABS', - SET_CURRENT_WINDOW_TAB: 'SET_CURRENT_WINDOW_TAB', - getOpenMetamaskTabsIds, - SET_OPEN_METAMASK_TAB_IDS: 'SET_OPEN_METAMASK_TAB_IDS', } module.exports = actions -let background = null +var background = null function _setBackgroundConnection (backgroundConnection) { background = backgroundConnection } @@ -685,9 +676,7 @@ function addNewKeyring (type, opts) { log.debug(`background.addNewKeyring`) background.addNewKeyring(type, opts, (err) => { dispatch(actions.hideLoadingIndication()) - if (err) { - return dispatch(actions.displayWarning(err.message)) - } + if (err) return dispatch(actions.displayWarning(err.message)) dispatch(actions.showAccountsPage()) }) } @@ -2180,7 +2169,7 @@ function requestExportAccount () { } function exportAccount (password, address) { - const self = this + var self = this return function (dispatch) { dispatch(self.showLoadingIndication()) @@ -2300,9 +2289,7 @@ function pairUpdate (coin) { dispatch(actions.hideWarning()) shapeShiftRequest('marketinfo', {pair: `${coin.toLowerCase()}_eth`}, (mktResponse) => { dispatch(actions.hideSubLoadingIndication()) - if (mktResponse.error) { - return dispatch(actions.displayWarning(mktResponse.error)) - } + if (mktResponse.error) return dispatch(actions.displayWarning(mktResponse.error)) dispatch({ type: actions.PAIR_UPDATE, value: { @@ -2314,15 +2301,13 @@ function pairUpdate (coin) { } function shapeShiftSubview () { - const pair = 'btc_eth' + var pair = 'btc_eth' return (dispatch) => { dispatch(actions.showSubLoadingIndication()) shapeShiftRequest('marketinfo', {pair}, (mktResponse) => { shapeShiftRequest('getcoins', {}, (response) => { dispatch(actions.hideSubLoadingIndication()) - if (mktResponse.error) { - return dispatch(actions.displayWarning(mktResponse.error)) - } + if (mktResponse.error) return dispatch(actions.displayWarning(mktResponse.error)) dispatch({ type: actions.SHAPESHIFT_SUBVIEW, value: { @@ -2340,10 +2325,8 @@ function coinShiftRquest (data, marketData) { dispatch(actions.showLoadingIndication()) shapeShiftRequest('shift', { method: 'POST', data}, (response) => { dispatch(actions.hideLoadingIndication()) - if (response.error) { - return dispatch(actions.displayWarning(response.error)) - } - const message = ` + if (response.error) return dispatch(actions.displayWarning(response.error)) + var message = ` Deposit your ${response.depositType} to the address below:` log.debug(`background.createShapeShiftTx`) background.createShapeShiftTx(response.deposit, response.depositType) @@ -2377,11 +2360,9 @@ function reshowQrCode (data, coin) { return (dispatch) => { dispatch(actions.showLoadingIndication()) shapeShiftRequest('marketinfo', {pair: `${coin.toLowerCase()}_eth`}, (mktResponse) => { - if (mktResponse.error) { - return dispatch(actions.displayWarning(mktResponse.error)) - } + if (mktResponse.error) return dispatch(actions.displayWarning(mktResponse.error)) - const message = [ + var message = [ `Deposit your ${coin} to the address below:`, `Deposit Limit: ${mktResponse.limit}`, `Deposit Minimum:${mktResponse.minimum}`, @@ -2394,10 +2375,10 @@ function reshowQrCode (data, coin) { } function shapeShiftRequest (query, options = {}, cb) { - let queryResponse, method + var queryResponse, method options.method ? method = options.method : method = 'GET' - const requestListner = function () { + var requestListner = function () { try { queryResponse = JSON.parse(this.responseText) if (cb) { @@ -2412,12 +2393,12 @@ function shapeShiftRequest (query, options = {}, cb) { } } - const shapShiftReq = new XMLHttpRequest() + var shapShiftReq = new XMLHttpRequest() shapShiftReq.addEventListener('load', requestListner) shapShiftReq.open(method, `https://shapeshift.io/${query}/${options.pair ? options.pair : ''}`, true) if (options.method === 'POST') { - const jsonObj = JSON.stringify(options.data) + var jsonObj = JSON.stringify(options.data) shapShiftReq.setRequestHeader('Content-Type', 'application/json') return shapShiftReq.send(jsonObj) } else { @@ -2718,59 +2699,24 @@ function setPendingTokens (pendingTokens) { } } -// Permissions - -/** - * Approves the permission requests with the given IDs. - * @param {string} requestId - The id of the permissions request. - * @param {string[]} accounts - The accounts to expose, if any. - */ -function approvePermissionsRequest (requestId, accounts) { +function approveProviderRequestByOrigin (origin) { return () => { - background.approvePermissionsRequest(requestId, accounts) + background.approveProviderRequestByOrigin(origin) } } -/** - * Rejects the permission requests with the given IDs. - * @param {Array} requestId - */ -function rejectPermissionsRequest (requestId) { +function rejectProviderRequestByOrigin (origin) { return () => { - background.rejectPermissionsRequest(requestId) + background.rejectProviderRequestByOrigin(origin) } } -/** - * Exposes the given account(s) to the given origin. - * Call ONLY as a result of direct user action. - */ -function legacyExposeAccounts (origin, accounts) { - return () => { - return background.legacyExposeAccounts(origin, accounts) - } -} - -/** - * Clears the given permissions for the given origin. - */ -function removePermissionsFor (domains) { +function clearApprovedOrigins () { return () => { - background.removePermissionsFor(domains) + background.clearApprovedOrigins() } } -/** - * Clears all permissions for all domains. - */ -function clearPermissions () { - return () => { - background.clearPermissions() - } -} - -// //// - function setFirstTimeFlowType (type) { return (dispatch) => { log.debug(`background.setFirstTimeFlowType`) @@ -2891,6 +2837,12 @@ function getTokenParams (tokenAddress) { } } +function unsetMigratedPrivacyMode () { + return () => { + background.unsetMigratedPrivacyMode() + } +} + function setSeedPhraseBackedUp (seedPhraseBackupState) { return (dispatch) => { log.debug(`background.setSeedPhraseBackedUp`) @@ -3028,46 +2980,3 @@ function getNextNonce () { }) } } - -function setRequestAccountTabIds (requestAccountTabIds) { - return { - type: actions.SET_REQUEST_ACCOUNT_TABS, - value: requestAccountTabIds, - } -} - -function getRequestAccountTabIds () { - return async (dispatch) => { - const requestAccountTabIds = await pify(background.getRequestAccountTabIds).call(background) - dispatch(setRequestAccountTabIds(requestAccountTabIds)) - } -} - -function setOpenMetamaskTabsIDs (openMetaMaskTabIDs) { - return { - type: actions.SET_OPEN_METAMASK_TAB_IDS, - value: openMetaMaskTabIDs, - } -} - -function getOpenMetamaskTabsIds () { - return async (dispatch) => { - const openMetaMaskTabIDs = await pify(background.getOpenMetamaskTabsIds).call(background) - dispatch(setOpenMetamaskTabsIDs(openMetaMaskTabIDs)) - } -} - -function setCurrentWindowTab (currentWindowTab) { - return { - type: actions.SET_CURRENT_WINDOW_TAB, - value: currentWindowTab, - } -} - - -function getCurrentWindowTab () { - return async (dispatch) => { - const currentWindowTab = await global.platform.currentTab() - dispatch(setCurrentWindowTab(currentWindowTab)) - } -} diff --git a/ui/index.js b/ui/index.js index 6f7a439639..058c09be0b 100644 --- a/ui/index.js +++ b/ui/index.js @@ -1,5 +1,5 @@ -import React from 'react' const render = require('react-dom').render +const h = require('react-hyperscript') const Root = require('./app/pages') const actions = require('./app/store/actions') const configureStore = require('./app/store/store') @@ -13,13 +13,11 @@ module.exports = launchMetamaskUi log.setLevel(global.METAMASK_DEBUG ? 'debug' : 'warn') function launchMetamaskUi (opts, cb) { - const {backgroundConnection} = opts + var {backgroundConnection} = opts actions._setBackgroundConnection(backgroundConnection) // check if we are unlocked first backgroundConnection.getState(function (err, metamaskState) { - if (err) { - return cb(err) - } + if (err) return cb(err) startApp(metamaskState, backgroundConnection, opts) .then((store) => { cb(null, store) @@ -29,9 +27,7 @@ function launchMetamaskUi (opts, cb) { async function startApp (metamaskState, backgroundConnection, opts) { // parse opts - if (!metamaskState.featureFlags) { - metamaskState.featureFlags = {} - } + if (!metamaskState.featureFlags) metamaskState.featureFlags = {} const currentLocaleMessages = metamaskState.currentLocale ? await fetchLocale(metamaskState.currentLocale) @@ -96,11 +92,11 @@ async function startApp (metamaskState, backgroundConnection, opts) { // start app render( - , - opts.container, - ) + h(Root, { + // inject initial state + store: store, + } + ), opts.container) return store } diff --git a/ui/lib/icon-factory.js b/ui/lib/icon-factory.js index cbaa496cd4..2ea943297d 100644 --- a/ui/lib/icon-factory.js +++ b/ui/lib/icon-factory.js @@ -1,4 +1,4 @@ -let iconFactory +var iconFactory const isValidAddress = require('ethereumjs-util').isValidAddress const { checksumAddress } = require('../app/helpers/utils/util') const contractMap = require('eth-contract-metadata') @@ -26,18 +26,18 @@ IconFactory.prototype.iconForAddress = function (address, diameter) { // returns svg dom element IconFactory.prototype.generateIdenticonSvg = function (address, diameter) { - const cacheId = `${address}:${diameter}` + var cacheId = `${address}:${diameter}` // check cache, lazily generate and populate cache - const identicon = this.cache[cacheId] || (this.cache[cacheId] = this.generateNewIdenticon(address, diameter)) + var identicon = this.cache[cacheId] || (this.cache[cacheId] = this.generateNewIdenticon(address, diameter)) // create a clean copy so you can modify it - const cleanCopy = identicon.cloneNode(true) + var cleanCopy = identicon.cloneNode(true) return cleanCopy } // creates a new identicon IconFactory.prototype.generateNewIdenticon = function (address, diameter) { - const numericRepresentation = jsNumberForAddress(address) - const identicon = this.jazzicon(diameter, numericRepresentation) + var numericRepresentation = jsNumberForAddress(address) + var identicon = this.jazzicon(diameter, numericRepresentation) return identicon } @@ -58,8 +58,8 @@ function imageElFor (address) { } function jsNumberForAddress (address) { - const addr = address.slice(2, 10) - const seed = parseInt(addr, 16) + var addr = address.slice(2, 10) + var seed = parseInt(addr, 16) return seed } diff --git a/ui/lib/persistent-form.js b/ui/lib/persistent-form.js index 112e4415b9..d4dc20b030 100644 --- a/ui/lib/persistent-form.js +++ b/ui/lib/persistent-form.js @@ -15,7 +15,7 @@ PersistentForm.prototype.componentDidMount = function () { const fields = document.querySelectorAll('[data-persistent-formid]') const store = this.getPersistentStore() - for (let i = 0; i < fields.length; i++) { + for (var i = 0; i < fields.length; i++) { const field = fields[i] const key = field.getAttribute('data-persistent-formid') const cached = store[key] @@ -52,7 +52,7 @@ PersistentForm.prototype.persistentFieldDidUpdate = function (event) { PersistentForm.prototype.componentWillUnmount = function () { const fields = document.querySelectorAll('[data-persistent-formid]') - for (let i = 0; i < fields.length; i++) { + for (var i = 0; i < fields.length; i++) { const field = fields[i] field.removeEventListener(eventName, this.persistentFieldDidUpdate.bind(this)) } diff --git a/yarn.lock b/yarn.lock index 6fd4075431..e3eb4951e1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12218,7 +12218,7 @@ fuse.js@^3.4.4: resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-3.4.5.tgz#8954fb43f9729bd5dbcb8c08f251db552595a7a6" integrity sha512-s9PGTaQIkT69HaeoTVjwGsLfb8V8ScJLx5XGFcKHg0MqLUH/UZ4EKOtqtXX9k7AFqCGxD1aJmYb8Q5VYDibVRQ== -gaba@^1.6.0, gaba@^1.9.0: +gaba@^1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/gaba/-/gaba-1.9.0.tgz#ccd9f99c56687b5acd39f9e3ceb435b2a59b6aa1" integrity sha512-HoVreAdZssL0jNHuzZ7WP+YKZ0riu44jVDWxhQ9hsgPuzxbVEsz9fO/HDxqAdNZS1Cswayq6+ciZ3HSCFWMKbQ== @@ -14300,11 +14300,6 @@ interpret@^1.2.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== -intersect-objects@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/intersect-objects/-/intersect-objects-1.0.0.tgz#b7630d28994b89b0f04d44728106136549ce816e" - integrity sha512-MS1xypHKJhWopnJgn4IbitVvt2vFy2KjINQJAPhAtDejZ+ZbMDfyPc6JsS/mWFRt9Eoku4A4usE4f2loEOoeKQ== - into-stream@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-3.1.0.tgz#96fb0a936c12babd6ff1752a17d05616abd094c6" From 542ed0c4357d05323986e5d83af73685f82d4a53 Mon Sep 17 00:00:00 2001 From: ryanml Date: Mon, 6 Jan 2020 11:11:37 -0700 Subject: [PATCH 2/2] Home and Route component updates --- brave/ui/app/pages/home/home.container.js | 9 +++++++-- brave/ui/app/pages/routes/index.js | 4 ---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/brave/ui/app/pages/home/home.container.js b/brave/ui/app/pages/home/home.container.js index 2a412372e9..435fc7e7d1 100644 --- a/brave/ui/app/pages/home/home.container.js +++ b/brave/ui/app/pages/home/home.container.js @@ -3,7 +3,7 @@ import { compose } from 'recompose' import { connect } from 'react-redux' import { withRouter } from 'react-router-dom' import { unconfirmedTransactionsCountSelector } from '../../../../../ui/app/selectors/confirm-transaction' -import { getCurrentEthBalance, getDaiV1Token, hasPermissionRequests } from '../../../../../ui/app/selectors/selectors' +import { getCurrentEthBalance, getDaiV1Token } from '../../../../../ui/app/selectors/selectors' import { addTokens, setHardwareConnect, @@ -11,6 +11,7 @@ import { turnThreeBoxSyncingOn, getThreeBoxLastUpdated, setShowRestorePromptToFalse, + unsetMigratedPrivacyMode, } from '../../store/actions' import batToken from '../../store/bat-token' import { setThreeBoxLastUpdated } from '../../../../../ui/app/ducks/app/app' @@ -28,6 +29,8 @@ const mapStateToProps = state => { selectedAddress, batTokenAdded, hardwareConnect, + providerRequests, + migratedPrivacyMode, } = metamask const accountBalance = getCurrentEthBalance(state) const { forgottenPassword, threeBoxLastUpdated } = appState @@ -46,13 +49,15 @@ const mapStateToProps = state => { selectedAddress, threeBoxLastUpdated, hasDaiV1Token: Boolean(getDaiV1Token(state)), - hasPermissionRequests: hasPermissionRequests(state), batTokenAdded, hardwareConnect, + providerRequests, + showPrivacyModeNotification: migratedPrivacyMode, } } const mapDispatchToProps = (dispatch) => ({ + unsetMigratedPrivacyMode: () => dispatch(unsetMigratedPrivacyMode()), turnThreeBoxSyncingOn: () => dispatch(turnThreeBoxSyncingOn()), setupThreeBox: () => { dispatch(getThreeBoxLastUpdated()) diff --git a/brave/ui/app/pages/routes/index.js b/brave/ui/app/pages/routes/index.js index 9df7e3f088..bcbd549d36 100644 --- a/brave/ui/app/pages/routes/index.js +++ b/brave/ui/app/pages/routes/index.js @@ -9,8 +9,6 @@ import { withRouter } from 'react-router-dom' import { getNetworkIdentifier, preferencesSelector, - hasPermissionRequests, - getAddressConnectedToCurrentTab, } from '../../../../../ui/app/selectors/selectors' import BraveHeader from '../../components/app/header' import { @@ -50,8 +48,6 @@ function mapStateToProps (state) { isMouseUser: state.appState.isMouseUser, providerId: getNetworkIdentifier(state), autoLogoutTimeLimit, - hasPermissionsRequests: hasPermissionRequests(state), - addressConnectedToCurrentTab: getAddressConnectedToCurrentTab(state), batTokenAdded: state.metamask.batTokenAdded, rewardsDisclosureAccepted: state.metamask.rewardsDisclosureAccepted, hardwareConnect: state.metamask.hardwareConnect,