1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Merge pull request #9160 from MetaMask/Version-v8.0.7

Version v8.0.7 RC
This commit is contained in:
Mark Stacey 2020-08-10 15:07:25 -03:00 committed by GitHub
commit 1a5218ce8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
100 changed files with 1064 additions and 1512 deletions

View File

@ -38,6 +38,9 @@ workflows:
- test-unit-global: - test-unit-global:
requires: requires:
- prep-deps - prep-deps
- validate-source-maps:
requires:
- prep-build
- test-mozilla-lint: - test-mozilla-lint:
requires: requires:
- prep-deps - prep-deps
@ -49,6 +52,7 @@ workflows:
- test-lint-lockfile - test-lint-lockfile
- test-unit - test-unit
- test-unit-global - test-unit-global
- validate-source-maps
- test-mozilla-lint - test-mozilla-lint
- test-e2e-chrome - test-e2e-chrome
- test-e2e-firefox - test-e2e-firefox
@ -358,6 +362,18 @@ jobs:
- run: - run:
name: test:unit:global name: test:unit:global
command: yarn test:unit:global command: yarn test:unit:global
validate-source-maps:
docker:
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Validate source maps
command: yarn validate-source-maps
test-mozilla-lint: test-mozilla-lint:
docker: docker:
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88 - image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88

View File

@ -44,7 +44,6 @@ install_github_cli
printf '%s\n' "Creating a Pull Request to sync 'master' with 'develop'" printf '%s\n' "Creating a Pull Request to sync 'master' with 'develop'"
if ! hub pull-request \ if ! hub pull-request \
--reviewer '@MetaMask/extension-release-team' \
--message "Master => develop" --message 'Merge latest release back into develop' \ --message "Master => develop" --message 'Merge latest release back into develop' \
--base "$CIRCLE_PROJECT_USERNAME:$base_branch" \ --base "$CIRCLE_PROJECT_USERNAME:$base_branch" \
--head "$CIRCLE_PROJECT_USERNAME:$CIRCLE_BRANCH"; --head "$CIRCLE_PROJECT_USERNAME:$CIRCLE_BRANCH";

View File

@ -45,7 +45,6 @@ install_github_cli
printf '%s\n' "Creating a Pull Request for $version on GitHub" printf '%s\n' "Creating a Pull Request for $version on GitHub"
if ! hub pull-request \ if ! hub pull-request \
--reviewer '@MetaMask/extension-release-team' \
--message "${CIRCLE_BRANCH/-/ } RC" --message ':package: :rocket:' \ --message "${CIRCLE_BRANCH/-/ } RC" --message ':package: :rocket:' \
--base "$CIRCLE_PROJECT_USERNAME:$base_branch" \ --base "$CIRCLE_PROJECT_USERNAME:$base_branch" \
--head "$CIRCLE_PROJECT_USERNAME:$CIRCLE_BRANCH"; --head "$CIRCLE_PROJECT_USERNAME:$CIRCLE_BRANCH";

1
.gitignore vendored
View File

@ -34,6 +34,7 @@ builds.zip
test-artifacts test-artifacts
test-builds test-builds
build-artifacts
#ignore css output and sourcemaps #ignore css output and sourcemaps
ui/app/css/output/ ui/app/css/output/

View File

@ -2,6 +2,18 @@
## Current Develop Branch ## Current Develop Branch
## 8.0.7 Fri Aug 07 2020
- [#9065](https://github.com/MetaMask/metamask-extension/pull/9065): Change title of "Reveal Seed Words" page to "Reveal Seed Phrase"
- [#8974](https://github.com/MetaMask/metamask-extension/pull/8974): Add tooltip to copy button for contacts and seed phrase
- [#9063](https://github.com/MetaMask/metamask-extension/pull/9063): Fix broken UI upon failed password validation
- [#9075](https://github.com/MetaMask/metamask-extension/pull/9075): Fix shifted popup notification when browser is in fullscreen on macOS
- [#9085](https://github.com/MetaMask/metamask-extension/pull/9085): Support longer text in network dropdown
- [#8873](https://github.com/MetaMask/metamask-extension/pull/8873): Fix onboarding bug where user can be asked to verify seed phrase twice
- [#9104](https://github.com/MetaMask/metamask-extension/pull/9104): Replace "Email us" button with "Contact us" button
- [#9137](https://github.com/MetaMask/metamask-extension/pull/9137): Fix bug where `accountsChanged` events stop after a dapp connection is closed.
- [#9152](https://github.com/MetaMask/metamask-extension/pull/9152): Fix network name alignment
- [#9144](https://github.com/MetaMask/metamask-extension/pull/9144): Add web3 usage metrics and prepare for web3 removal
## 8.0.6 Wed Jul 22 2020 ## 8.0.6 Wed Jul 22 2020
- [#9030](https://github.com/MetaMask/metamask-extension/pull/9030): Hide "delete" button when editing contact of wallet account - [#9030](https://github.com/MetaMask/metamask-extension/pull/9030): Hide "delete" button when editing contact of wallet account
- [#9031](https://github.com/MetaMask/metamask-extension/pull/9031): Fix crash upon removing contact - [#9031](https://github.com/MetaMask/metamask-extension/pull/9031): Fix crash upon removing contact

View File

@ -359,9 +359,6 @@
"editContact": { "editContact": {
"message": "ዕውቂያን አርትዕ" "message": "ዕውቂያን አርትዕ"
}, },
"emailUs": {
"message": "ኢሜይል ያድርጉልን!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "ፈተናውን አልፈዋል - የዘር ሐረግዎን ደህንነቱ በተጠበቀ መንገድ ያስቀምጡ፣ የእርስዎ ሃላፊነት ነው! " "message": "ፈተናውን አልፈዋል - የዘር ሐረግዎን ደህንነቱ በተጠበቀ መንገድ ያስቀምጡ፣ የእርስዎ ሃላፊነት ነው! "
}, },

View File

@ -359,9 +359,6 @@
"editContact": { "editContact": {
"message": "تعديل جهة الاتصال" "message": "تعديل جهة الاتصال"
}, },
"emailUs": {
"message": "راسلنا عبر البريد الإلكتروني!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "لقد نجحت في الاختبار - احتفظ بعبارة الأمان الخاصة بك في مكان آمن، إنها مسؤوليتك!" "message": "لقد نجحت في الاختبار - احتفظ بعبارة الأمان الخاصة بك في مكان آمن، إنها مسؤوليتك!"
}, },

View File

@ -359,9 +359,6 @@
"editContact": { "editContact": {
"message": "Редактиране на контакт" "message": "Редактиране на контакт"
}, },
"emailUs": {
"message": "Изпратете ни имейл!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "Преминахте теста - пазете основната си фраза на сигурно място, това е Ваша отговорност!" "message": "Преминахте теста - пазете основната си фраза на сигурно място, това е Ваша отговорност!"
}, },

View File

@ -359,9 +359,6 @@
"editContact": { "editContact": {
"message": "পরিচিতি সম্পাদনা করুন" "message": "পরিচিতি সম্পাদনা করুন"
}, },
"emailUs": {
"message": "আমাদের ইমেল করুন!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "আপনি টেস্টটি পাস করেছেন - আপনার সীডফ্রেজ নিরাপদে রাখুন, এটি আপনার দায়িত্ব!" "message": "আপনি টেস্টটি পাস করেছেন - আপনার সীডফ্রেজ নিরাপদে রাখুন, এটি আপনার দায়িত্ব!"
}, },

View File

@ -356,9 +356,6 @@
"editContact": { "editContact": {
"message": "Editar Contacte" "message": "Editar Contacte"
}, },
"emailUs": {
"message": "Envia'ns un correu!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "Has passat el test - mantingues la teva seedphrase segura, és la teva responsabilitat!" "message": "Has passat el test - mantingues la teva seedphrase segura, és la teva responsabilitat!"
}, },

View File

@ -148,9 +148,6 @@
"edit": { "edit": {
"message": "Upravit" "message": "Upravit"
}, },
"emailUs": {
"message": "Napište nám e-mail!"
},
"enterPassword": { "enterPassword": {
"message": "Zadejte heslo" "message": "Zadejte heslo"
}, },

View File

@ -359,9 +359,6 @@
"editContact": { "editContact": {
"message": "Redigér Kontakt" "message": "Redigér Kontakt"
}, },
"emailUs": {
"message": "Send os en email!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "Du bestod testen - pas godt på din seed-sætning, det er dit ansvar!" "message": "Du bestod testen - pas godt på din seed-sætning, det er dit ansvar!"
}, },

View File

@ -344,9 +344,6 @@
"editContact": { "editContact": {
"message": "Kontakt bearbeiten" "message": "Kontakt bearbeiten"
}, },
"emailUs": {
"message": "Schreib uns eine Mail!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "Sie haben den Test bestanden - bewahren Sie Ihre mnemonische Phrase sicher auf, Sie sind dafür verantwortlich! " "message": "Sie haben den Test bestanden - bewahren Sie Ihre mnemonische Phrase sicher auf, Sie sind dafür verantwortlich! "
}, },

View File

@ -356,9 +356,6 @@
"editContact": { "editContact": {
"message": "Επεξεργασία Επαφής" "message": "Επεξεργασία Επαφής"
}, },
"emailUs": {
"message": "Στείλτε μας email!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "Περάσατε τη δοκιμή - κρατήστε τη φράση φύτρου σας ασφαλή, είναι δική σας ευθύνη!" "message": "Περάσατε τη δοκιμή - κρατήστε τη φράση φύτρου σας ασφαλή, είναι δική σας ευθύνη!"
}, },

View File

@ -409,6 +409,9 @@
"contactsSettingsDescription": { "contactsSettingsDescription": {
"message": "Add, edit, remove, and manage your contacts" "message": "Add, edit, remove, and manage your contacts"
}, },
"contactUs": {
"message": "Contact us!"
},
"continueToWyre": { "continueToWyre": {
"message": "Continue to Wyre" "message": "Continue to Wyre"
}, },
@ -535,9 +538,6 @@
"editPermission": { "editPermission": {
"message": "Edit Permission" "message": "Edit Permission"
}, },
"emailUs": {
"message": "Email us!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "You passed the test - keep your seedphrase safe, it's your responsibility!" "message": "You passed the test - keep your seedphrase safe, it's your responsibility!"
}, },
@ -1196,7 +1196,7 @@
"message": "Restore" "message": "Restore"
}, },
"revealSeedWords": { "revealSeedWords": {
"message": "Reveal Seed Words" "message": "Reveal Seed Phrase"
}, },
"revealSeedWordsTitle": { "revealSeedWordsTitle": {
"message": "Seed Phrase" "message": "Seed Phrase"

View File

@ -313,9 +313,6 @@
"editContact": { "editContact": {
"message": "Editar Contacto" "message": "Editar Contacto"
}, },
"emailUs": {
"message": "¡Envíanos un correo!"
},
"endOfFlowMessage8": { "endOfFlowMessage8": {
"message": "MetaMask no puede recuperar tu seedphrase. Saber más." "message": "MetaMask no puede recuperar tu seedphrase. Saber más."
}, },

View File

@ -356,9 +356,6 @@
"editContact": { "editContact": {
"message": "Editar contacto" "message": "Editar contacto"
}, },
"emailUs": {
"message": "¡Envíanos un correo electrónico!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "Pasaste la prueba. Es responsabilidad tuya mantener la frase de inicialización segura." "message": "Pasaste la prueba. Es responsabilidad tuya mantener la frase de inicialización segura."
}, },

View File

@ -359,9 +359,6 @@
"editContact": { "editContact": {
"message": "Muuda kontakti" "message": "Muuda kontakti"
}, },
"emailUs": {
"message": "Saatke meile e-kiri!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "Läbisite kontrolli hoidke seemnefraasi turvaliselt, see on teie vastutusel!" "message": "Läbisite kontrolli hoidke seemnefraasi turvaliselt, see on teie vastutusel!"
}, },

View File

@ -359,9 +359,6 @@
"editContact": { "editContact": {
"message": "ویرایش تماس" "message": "ویرایش تماس"
}, },
"emailUs": {
"message": "به ما ایمیل کنید!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "شما در امتحان موفق شدید - عبارت بازیابی را مصؤن نگهدارید، این مسؤلیت شماست!" "message": "شما در امتحان موفق شدید - عبارت بازیابی را مصؤن نگهدارید، این مسؤلیت شماست!"
}, },

View File

@ -356,9 +356,6 @@
"editContact": { "editContact": {
"message": "Muokkaa yhteystietoa" "message": "Muokkaa yhteystietoa"
}, },
"emailUs": {
"message": "Lähetä meille sähköpostia!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "Läpäisit kokeen pidä salaustekstisi turvassa. Se on sinun vastuullasi!" "message": "Läpäisit kokeen pidä salaustekstisi turvassa. Se on sinun vastuullasi!"
}, },

View File

@ -332,9 +332,6 @@
"editContact": { "editContact": {
"message": "I-edit ang Contact" "message": "I-edit ang Contact"
}, },
"emailUs": {
"message": "Mag-email sa amin!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "Pumasa ka sa test - panatilihing ligtas ang iyong seedphrase, responsibilidad mo ito!" "message": "Pumasa ka sa test - panatilihing ligtas ang iyong seedphrase, responsibilidad mo ito!"
}, },

View File

@ -347,9 +347,6 @@
"editContact": { "editContact": {
"message": "Modifier le contact" "message": "Modifier le contact"
}, },
"emailUs": {
"message": "Envoyez-nous un email !"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "Vous avez réussi l'essai : gardez votre phrase de départ en sécurité, c'est de votre responsabilité !" "message": "Vous avez réussi l'essai : gardez votre phrase de départ en sécurité, c'est de votre responsabilité !"
}, },

View File

@ -359,9 +359,6 @@
"editContact": { "editContact": {
"message": "ערוך איש קשר" "message": "ערוך איש קשר"
}, },
"emailUs": {
"message": "שלח/י לנו מייל!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "עברת את הבחינה - יש לשמור את ה-seedphrase שלך במקום בטוח, זה באחריותך!" "message": "עברת את הבחינה - יש לשמור את ה-seedphrase שלך במקום בטוח, זה באחריותך!"
}, },

View File

@ -359,9 +359,6 @@
"editContact": { "editContact": {
"message": "संपर्क संपादित करें" "message": "संपर्क संपादित करें"
}, },
"emailUs": {
"message": "हमें ईमेल करें!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "आपने परीक्षा उत्तीर्ण कर ली - अपने बीज वाक्यांश को सुरक्षित रखें, यह आपकी जिम्मेदारी है!" "message": "आपने परीक्षा उत्तीर्ण कर ली - अपने बीज वाक्यांश को सुरक्षित रखें, यह आपकी जिम्मेदारी है!"
}, },

View File

@ -124,9 +124,6 @@
"edit": { "edit": {
"message": "संपादित करें" "message": "संपादित करें"
}, },
"emailUs": {
"message": "हमें ईमेल करें!"
},
"enterPassword": { "enterPassword": {
"message": "पासवर्ड दर्ज करें" "message": "पासवर्ड दर्ज करें"
}, },

View File

@ -359,9 +359,6 @@
"editContact": { "editContact": {
"message": "Uredi kontakt" "message": "Uredi kontakt"
}, },
"emailUs": {
"message": "Pošaljite nam elektroničku poruku!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "Prošli ste test čuvajte svoju početnu rečenicu jer ste vi odgovorni za nju!" "message": "Prošli ste test čuvajte svoju početnu rečenicu jer ste vi odgovorni za nju!"
}, },

View File

@ -205,9 +205,6 @@
"edit": { "edit": {
"message": "Korije" "message": "Korije"
}, },
"emailUs": {
"message": "Imèl nou!"
},
"enterPassword": { "enterPassword": {
"message": "Mete modpas" "message": "Mete modpas"
}, },

View File

@ -359,9 +359,6 @@
"editContact": { "editContact": {
"message": "Kapcsolatok szerkesztése" "message": "Kapcsolatok szerkesztése"
}, },
"emailUs": {
"message": "Írjon nekünk!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "Átment a teszten - tartsa biztonságban a seed mondatot, ez az ön felelőssége!" "message": "Átment a teszten - tartsa biztonságban a seed mondatot, ez az ön felelőssége!"
}, },

View File

@ -350,9 +350,6 @@
"editContact": { "editContact": {
"message": "Sunting Kontak" "message": "Sunting Kontak"
}, },
"emailUs": {
"message": "Kirimkan kami email!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "Anda lulus tes - jagalah agar frasa benih Anda aman, itu tanggung jawab Anda!" "message": "Anda lulus tes - jagalah agar frasa benih Anda aman, itu tanggung jawab Anda!"
}, },

View File

@ -531,9 +531,6 @@
"editPermission": { "editPermission": {
"message": "Modifica Permessi" "message": "Modifica Permessi"
}, },
"emailUs": {
"message": "Mandaci una mail!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "Hai passato il test - tieni la tua frase seed al sicuro, è tua responsabilità!" "message": "Hai passato il test - tieni la tua frase seed al sicuro, è tua responsabilità!"
}, },

View File

@ -359,9 +359,6 @@
"editContact": { "editContact": {
"message": "ಸಂಪರ್ಕವನ್ನು ಸಂಪಾದಿಸಿ" "message": "ಸಂಪರ್ಕವನ್ನು ಸಂಪಾದಿಸಿ"
}, },
"emailUs": {
"message": "ನಮಗೆ ಇಮೇಲ್ ಮಾಡಿ!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "ನೀವು ಪರೀಕ್ಷೆಯನ್ನು ಪಾಸ್ ಮಾಡಿರುವಿರಿ - ನಿಮ್ಮ ಸೀಡ್‌ಫ್ರೇಸ್ ಸುರಕ್ಷಿತವಾಗಿರಿಸಿ, ಅದು ನಿಮ್ಮ ಜವಾಬ್ದಾರಿಯಾಗಿದೆ!" "message": "ನೀವು ಪರೀಕ್ಷೆಯನ್ನು ಪಾಸ್ ಮಾಡಿರುವಿರಿ - ನಿಮ್ಮ ಸೀಡ್‌ಫ್ರೇಸ್ ಸುರಕ್ಷಿತವಾಗಿರಿಸಿ, ಅದು ನಿಮ್ಮ ಜವಾಬ್ದಾರಿಯಾಗಿದೆ!"
}, },

View File

@ -356,9 +356,6 @@
"editContact": { "editContact": {
"message": "연락처 수정" "message": "연락처 수정"
}, },
"emailUs": {
"message": "저자에게 메일 보내기!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "인증을 통과했습니다 - 시드 구문을 안전하게 보관하세요. 그것은 당신의 의무입니다." "message": "인증을 통과했습니다 - 시드 구문을 안전하게 보관하세요. 그것은 당신의 의무입니다."
}, },

View File

@ -359,9 +359,6 @@
"editContact": { "editContact": {
"message": "Taisyti kontaktą" "message": "Taisyti kontaktą"
}, },
"emailUs": {
"message": "Rašykite mums el. paštu!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "Jūs perėjote testą laikykite saugiai savo atkūrimo frazę, tai jūsų atsakomybė!" "message": "Jūs perėjote testą laikykite saugiai savo atkūrimo frazę, tai jūsų atsakomybė!"
}, },

View File

@ -359,9 +359,6 @@
"editContact": { "editContact": {
"message": "Rediģēt līgumu" "message": "Rediģēt līgumu"
}, },
"emailUs": {
"message": "Raksiet mums e-pastu!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "Jūs izgājāt pārbaudi — glabājiet atkopšanas frāzi drošībā, tā ir jūsu atbildība!" "message": "Jūs izgājāt pārbaudi — glabājiet atkopšanas frāzi drošībā, tā ir jūsu atbildība!"
}, },

View File

@ -347,9 +347,6 @@
"editContact": { "editContact": {
"message": "Edit Kenalan" "message": "Edit Kenalan"
}, },
"emailUs": {
"message": "Hantarkan e-mel kepada kami!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "Anda telah lulus ujian - simpan frasa benih anda di tempat yang selamat, itu tanggungjawab anda!" "message": "Anda telah lulus ujian - simpan frasa benih anda di tempat yang selamat, itu tanggungjawab anda!"
}, },

View File

@ -118,9 +118,6 @@
"edit": { "edit": {
"message": "Bewerk" "message": "Bewerk"
}, },
"emailUs": {
"message": "Email ons!"
},
"enterPassword": { "enterPassword": {
"message": "Voer wachtwoord in" "message": "Voer wachtwoord in"
}, },

View File

@ -356,9 +356,6 @@
"editContact": { "editContact": {
"message": "Rediger kontakt" "message": "Rediger kontakt"
}, },
"emailUs": {
"message": "Send oss en e-post!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "Du besto prøven - oppbevar den mnemoniske gjenopprettingsfrasen din på et sikkert sted, det er ditt ansvar! " "message": "Du besto prøven - oppbevar den mnemoniske gjenopprettingsfrasen din på et sikkert sted, det er ditt ansvar! "
}, },

View File

@ -356,9 +356,6 @@
"editContact": { "editContact": {
"message": "Edytuj kontakt" "message": "Edytuj kontakt"
}, },
"emailUs": {
"message": "Napisz do nas!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "Zaliczyłeś test zabezpiecz swoją frazę seed, to Twoja odpowiedzialność!" "message": "Zaliczyłeś test zabezpiecz swoją frazę seed, to Twoja odpowiedzialność!"
}, },

View File

@ -124,9 +124,6 @@
"edit": { "edit": {
"message": "Editar" "message": "Editar"
}, },
"emailUs": {
"message": "Fale connosco!"
},
"enterPassword": { "enterPassword": {
"message": "Introduza palavra-passe" "message": "Introduza palavra-passe"
}, },

View File

@ -353,9 +353,6 @@
"editContact": { "editContact": {
"message": "Editar Contato" "message": "Editar Contato"
}, },
"emailUs": {
"message": "Escreva para nós!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "Você passou no teste — mantenha sua frase semente segura, a responsabilidade é sua!" "message": "Você passou no teste — mantenha sua frase semente segura, a responsabilidade é sua!"
}, },

View File

@ -359,9 +359,6 @@
"editContact": { "editContact": {
"message": "Editați contact" "message": "Editați contact"
}, },
"emailUs": {
"message": "Trimiteți-ne un email!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "Ați trecut testul păstrați expresia sursă în siguranță, este răspunderea dvs.!" "message": "Ați trecut testul păstrați expresia sursă în siguranță, este răspunderea dvs.!"
}, },

View File

@ -154,9 +154,6 @@
"edit": { "edit": {
"message": "Редактировать" "message": "Редактировать"
}, },
"emailUs": {
"message": "Свяжитесь с нами по электронной почте!"
},
"enterPassword": { "enterPassword": {
"message": "Введите пароль" "message": "Введите пароль"
}, },

View File

@ -350,9 +350,6 @@
"editContact": { "editContact": {
"message": "Upraviť kontakt" "message": "Upraviť kontakt"
}, },
"emailUs": {
"message": "Napište nám e-mail!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "Úspešne ste prešli testom uchovávajte svoju seed frázu v bezpečí. Je to vaša zodpovednosť!" "message": "Úspešne ste prešli testom uchovávajte svoju seed frázu v bezpečí. Je to vaša zodpovednosť!"
}, },

View File

@ -359,9 +359,6 @@
"editContact": { "editContact": {
"message": "Uredi stik" "message": "Uredi stik"
}, },
"emailUs": {
"message": "Pišite nam!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "Opravili ste test - vaše geslo seed phrase je vaša odgovornost - skrbno pazite nanj!" "message": "Opravili ste test - vaše geslo seed phrase je vaša odgovornost - skrbno pazite nanj!"
}, },

View File

@ -356,9 +356,6 @@
"editContact": { "editContact": {
"message": "Izmeni kontakt" "message": "Izmeni kontakt"
}, },
"emailUs": {
"message": "Pošaljite nam e-poštu!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "Prošli ste test - čuvajte svoju frazu početnih vrednosti, to je vaša odgovornost!" "message": "Prošli ste test - čuvajte svoju frazu početnih vrednosti, to je vaša odgovornost!"
}, },

View File

@ -353,9 +353,6 @@
"editContact": { "editContact": {
"message": "Redigera kontakt" "message": "Redigera kontakt"
}, },
"emailUs": {
"message": "Mejla oss!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "Du klarade testet. Håll din seed phrase hemlig, den är på ditt ansvar!" "message": "Du klarade testet. Håll din seed phrase hemlig, den är på ditt ansvar!"
}, },

View File

@ -353,9 +353,6 @@
"editContact": { "editContact": {
"message": "Hariri Mawasiliano" "message": "Hariri Mawasiliano"
}, },
"emailUs": {
"message": "Tutumie barua pepe!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "Umefaulu jaribio - weka kirai chako cha kuanzia mahali salama, ni wajibu wako!" "message": "Umefaulu jaribio - weka kirai chako cha kuanzia mahali salama, ni wajibu wako!"
}, },

View File

@ -139,9 +139,6 @@
"edit": { "edit": {
"message": "திருத்து" "message": "திருத்து"
}, },
"emailUs": {
"message": "எங்களுக்கு மின்னஞ்சல்!"
},
"enterPassword": { "enterPassword": {
"message": "கடவுச்சொல்லை உள்ளிடவும்" "message": "கடவுச்சொல்லை உள்ளிடவும்"
}, },

View File

@ -178,9 +178,6 @@
"editContact": { "editContact": {
"message": "แก้ไขผู้ติดต่อ" "message": "แก้ไขผู้ติดต่อ"
}, },
"emailUs": {
"message": "อีเมลหาเรา!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "คุณผ่านการทดสอบแล้ว กรุณารับผิดชอบในการรักษา Seed Phrase ให้ปลอดภัย" "message": "คุณผ่านการทดสอบแล้ว กรุณารับผิดชอบในการรักษา Seed Phrase ให้ปลอดภัย"
}, },

View File

@ -151,9 +151,6 @@
"edit": { "edit": {
"message": "Düzenle" "message": "Düzenle"
}, },
"emailUs": {
"message": "Bize e-posta atın!"
},
"enterPassword": { "enterPassword": {
"message": "Parolanızı girin" "message": "Parolanızı girin"
}, },

View File

@ -359,9 +359,6 @@
"editContact": { "editContact": {
"message": "Редагувати контракт" "message": "Редагувати контракт"
}, },
"emailUs": {
"message": "Напишіть нам!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "Ви пройшли тест, зберігайте вашу початкову фразу в безпеці - це ваша відповідальність!" "message": "Ви пройшли тест, зберігайте вашу початкову фразу в безпеці - це ваша відповідальність!"
}, },

View File

@ -359,9 +359,6 @@
"editContact": { "editContact": {
"message": "编辑联系人" "message": "编辑联系人"
}, },
"emailUs": {
"message": "联系我们"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "您已通过测试 - 请妥善保管你的种子密语。这是您的责任!" "message": "您已通过测试 - 请妥善保管你的种子密语。这是您的责任!"
}, },

View File

@ -356,9 +356,6 @@
"editContact": { "editContact": {
"message": "編輯聯絡資訊" "message": "編輯聯絡資訊"
}, },
"emailUs": {
"message": "寄 Email 給我們!"
},
"endOfFlowMessage1": { "endOfFlowMessage1": {
"message": "你通過測試了—安全存放助記詞,這是你的責任!" "message": "你通過測試了—安全存放助記詞,這是你的責任!"
}, },

View File

@ -1,7 +1,7 @@
{ {
"name": "__MSG_appName__", "name": "__MSG_appName__",
"short_name": "__MSG_appName__", "short_name": "__MSG_appName__",
"version": "8.0.6", "version": "8.0.7",
"manifest_version": 2, "manifest_version": 2,
"author": "https://metamask.io", "author": "https://metamask.io",
"description": "__MSG_appDescription__", "description": "__MSG_appDescription__",

View File

@ -7,7 +7,7 @@ import { CapabilitiesController as RpcCap } from 'rpc-cap'
import { ethErrors } from 'eth-json-rpc-errors' import { ethErrors } from 'eth-json-rpc-errors'
import { cloneDeep } from 'lodash' import { cloneDeep } from 'lodash'
import createMethodMiddleware from './methodMiddleware' import createPermissionsMethodMiddleware from './permissionsMethodMiddleware'
import PermissionsLogController from './permissionsLog' import PermissionsLogController from './permissionsLog'
// Methods that do not require any permissions to use: // Methods that do not require any permissions to use:
@ -90,7 +90,7 @@ export class PermissionsController {
engine.push(this.permissionsLog.createMiddleware()) engine.push(this.permissionsLog.createMiddleware())
engine.push(createMethodMiddleware({ engine.push(createPermissionsMethodMiddleware({
addDomainMetadata: this.addDomainMetadata.bind(this), addDomainMetadata: this.addDomainMetadata.bind(this),
getAccounts: this.getAccounts.bind(this, origin), getAccounts: this.getAccounts.bind(this, origin),
getUnlockPromise: () => this._getUnlockPromise(true), getUnlockPromise: () => this._getUnlockPromise(true),

View File

@ -4,7 +4,7 @@ import { ethErrors } from 'eth-json-rpc-errors'
/** /**
* Create middleware for handling certain methods and preprocessing permissions requests. * Create middleware for handling certain methods and preprocessing permissions requests.
*/ */
export default function createMethodMiddleware ({ export default function createPermissionsMethodMiddleware ({
addDomainMetadata, addDomainMetadata,
getAccounts, getAccounts,
getUnlockPromise, getUnlockPromise,

View File

@ -1,34 +1,30 @@
export default function getRestrictedMethods ({ getIdentities, getKeyringAccounts }) { export default function getRestrictedMethods ({ getIdentities, getKeyringAccounts }) {
return { return {
'eth_accounts': { 'eth_accounts': {
method: (_, res, __, end) => { method: async (_, res, __, end) => {
getKeyringAccounts() try {
.then((accounts) => { const accounts = await getKeyringAccounts()
const identities = getIdentities() const identities = getIdentities()
res.result = accounts res.result = accounts.sort((firstAddress, secondAddress) => {
.sort((firstAddress, secondAddress) => { if (!identities[firstAddress]) {
if (!identities[firstAddress]) { throw new Error(`Missing identity for address ${firstAddress}`)
throw new Error(`Missing identity for address ${firstAddress}`) } else if (!identities[secondAddress]) {
} else if (!identities[secondAddress]) { throw new Error(`Missing identity for address ${secondAddress}`)
throw new Error(`Missing identity for address ${secondAddress}`) } else if (identities[firstAddress].lastSelected === identities[secondAddress].lastSelected) {
} else if (identities[firstAddress].lastSelected === identities[secondAddress].lastSelected) { return 0
return 0 } else if (identities[firstAddress].lastSelected === undefined) {
} else if (identities[firstAddress].lastSelected === undefined) { return 1
return 1 } else if (identities[secondAddress].lastSelected === undefined) {
} else if (identities[secondAddress].lastSelected === undefined) { return -1
return -1 }
}
return identities[secondAddress].lastSelected - identities[firstAddress].lastSelected return identities[secondAddress].lastSelected - identities[firstAddress].lastSelected
})
end()
}) })
.catch( end()
(err) => { } catch (err) {
res.error = err res.error = err
end(err) end(err)
}, }
)
}, },
}, },
} }

View File

@ -434,7 +434,7 @@ export default class TransactionController extends EventEmitter {
// Since this transaction is async, // Since this transaction is async,
// we need to keep track of what is currently being signed, // we need to keep track of what is currently being signed,
// So that we do not increment nonce + resubmit something // So that we do not increment nonce + resubmit something
// that is already being incrmented & signed. // that is already being incremented & signed.
if (this.inProcessOfSigning.has(txId)) { if (this.inProcessOfSigning.has(txId)) {
return return
} }

View File

@ -1,5 +1,3 @@
/*global Web3*/
// need to make sure we aren't affected by overlapping namespaces // need to make sure we aren't affected by overlapping namespaces
// and that we dont affect the app with our namespace // and that we dont affect the app with our namespace
// mostly a fix for web3's BigNumber if AMD's "define" is defined... // mostly a fix for web3's BigNumber if AMD's "define" is defined...
@ -37,9 +35,7 @@ import LocalMessageDuplexStream from 'post-message-stream'
import { initProvider } from '@metamask/inpage-provider' import { initProvider } from '@metamask/inpage-provider'
// TODO:deprecate:2020 // TODO:deprecate:2020
import 'web3/dist/web3.min.js' import setupWeb3 from './lib/setupWeb3.js'
import setupDappAutoReload from './lib/auto-reload.js'
restoreContextAfterImports() restoreContextAfterImports()
@ -59,11 +55,9 @@ initProvider({
connectionStream: metamaskStream, connectionStream: metamaskStream,
}) })
//
// TODO:deprecate:2020 // TODO:deprecate:2020
// // Setup web3
// setup web3
if (typeof window.web3 !== 'undefined') { if (typeof window.web3 !== 'undefined') {
throw new Error(`MetaMask detected another web3. throw new Error(`MetaMask detected another web3.
@ -73,18 +67,5 @@ if (typeof window.web3 !== 'undefined') {
and try again.`) and try again.`)
} }
const web3 = new Web3(window.ethereum) // proxy web3, assign to window, and set up site auto reload
web3.setProvider = function () { setupWeb3(log)
log.debug('MetaMask - overrode web3.setProvider')
}
log.debug('MetaMask - injected web3')
Object.defineProperty(window.ethereum, '_web3Ref', {
enumerable: false,
writable: true,
configurable: true,
value: web3.eth,
})
// setup dapp auto reload AND proxy web3
setupDappAutoReload(web3, window.ethereum._publicConfigStore)

View File

@ -1,20 +1,16 @@
import { getBackgroundMetaMetricState } from '../../../ui/app/selectors' import { getBackgroundMetaMetricState } from '../../../ui/app/selectors'
import { sendMetaMetricsEvent } from '../../../ui/app/helpers/utils/metametrics.util' import { sendMetaMetricsEvent } from '../../../ui/app/helpers/utils/metametrics.util'
const inDevelopment = process.env.NODE_ENV === 'development' export default function backgroundMetaMetricsEvent (metaMaskState, eventData) {
const METAMETRICS_TRACKING_URL = inDevelopment eventData.eventOpts['category'] = 'Background'
? 'http://www.metamask.io/metametrics'
: 'http://www.metamask.io/metametrics-prod'
export default function backEndMetaMetricsEvent (metaMaskState, eventData) {
const stateEventData = getBackgroundMetaMetricState({ metamask: metaMaskState }) const stateEventData = getBackgroundMetaMetricState({ metamask: metaMaskState })
if (stateEventData.participateInMetaMetrics) { if (stateEventData.participateInMetaMetrics) {
sendMetaMetricsEvent({ sendMetaMetricsEvent({
...stateEventData, ...stateEventData,
...eventData, ...eventData,
url: METAMETRICS_TRACKING_URL + '/backend', currentPath: '/background',
}) })
} }
} }

View File

@ -0,0 +1,32 @@
/**
* Returns a middleware that implements the following RPC methods:
* - metamask_logInjectedWeb3Usage
*
* @param {Object} opts - The middleware options
* @param {string} opts.origin - The origin for the middleware stack
* @param {Function} opts.sendMetrics - A function for sending a metrics event
* @returns {(req: any, res: any, next: Function, end: Function) => void}
*/
export default function createMethodMiddleware ({ origin, sendMetrics }) {
return function methodMiddleware (req, res, next, end) {
switch (req.method) {
case 'metamask_logInjectedWeb3Usage':
const { action, name } = req.params[0]
sendMetrics({
action,
name,
customVariables: { origin },
})
res.result = true
break
default:
return next()
}
return end()
}
}

View File

@ -56,7 +56,7 @@ export default class NotificationManager {
}) })
// Firefox currently ignores left/top for create, but it works for update // Firefox currently ignores left/top for create, but it works for update
if (popupWindow.left !== left) { if (popupWindow.left !== left && popupWindow.state !== 'fullscreen') {
await this.platform.updateWindowPosition(popupWindow.id, left, top) await this.platform.updateWindowPosition(popupWindow.id, left, top)
} }
this._popupId = popupWindow.id this._popupId = popupWindow.id

View File

@ -16,41 +16,33 @@ const seedPhraseVerifier = {
* @returns {Promise<void>} - Promises undefined * @returns {Promise<void>} - Promises undefined
* *
*/ */
verifyAccounts (createdAccounts, seedWords) { async verifyAccounts (createdAccounts, seedWords) {
if (!createdAccounts || createdAccounts.length < 1) {
throw new Error('No created accounts defined.')
}
return new Promise((resolve, reject) => { const keyringController = new KeyringController({})
const Keyring = keyringController.getKeyringClassForType('HD Key Tree')
const opts = {
mnemonic: seedWords,
numberOfAccounts: createdAccounts.length,
}
if (!createdAccounts || createdAccounts.length < 1) { const keyring = new Keyring(opts)
return reject(new Error('No created accounts defined.')) const restoredAccounts = await keyring.getAccounts()
log.debug('Created accounts: ' + JSON.stringify(createdAccounts))
log.debug('Restored accounts: ' + JSON.stringify(restoredAccounts))
if (restoredAccounts.length !== createdAccounts.length) {
// this should not happen...
throw new Error('Wrong number of accounts')
}
for (let i = 0; i < restoredAccounts.length; i++) {
if (restoredAccounts[i].toLowerCase() !== createdAccounts[i].toLowerCase()) {
throw new Error('Not identical accounts! Original: ' + createdAccounts[i] + ', Restored: ' + restoredAccounts[i])
} }
}
const keyringController = new KeyringController({})
const Keyring = keyringController.getKeyringClassForType('HD Key Tree')
const opts = {
mnemonic: seedWords,
numberOfAccounts: createdAccounts.length,
}
const keyring = new Keyring(opts)
keyring.getAccounts()
.then((restoredAccounts) => {
log.debug('Created accounts: ' + JSON.stringify(createdAccounts))
log.debug('Restored accounts: ' + JSON.stringify(restoredAccounts))
if (restoredAccounts.length !== createdAccounts.length) {
// this should not happen...
return reject(new Error('Wrong number of accounts'))
}
for (let i = 0; i < restoredAccounts.length; i++) {
if (restoredAccounts[i].toLowerCase() !== createdAccounts[i].toLowerCase()) {
return reject(new Error('Not identical accounts! Original: ' + createdAccounts[i] + ', Restored: ' + restoredAccounts[i]))
}
}
return resolve()
})
})
}, },
} }

View File

@ -5,7 +5,6 @@ import extractEthjsErrorMessage from './extractEthjsErrorMessage'
const METAMASK_DEBUG = process.env.METAMASK_DEBUG const METAMASK_DEBUG = process.env.METAMASK_DEBUG
const METAMASK_ENVIRONMENT = process.env.METAMASK_ENVIRONMENT const METAMASK_ENVIRONMENT = process.env.METAMASK_ENVIRONMENT
const SENTRY_DSN_PROD = 'https://3567c198f8a8412082d32655da2961d0@sentry.io/273505'
const SENTRY_DSN_DEV = 'https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496' const SENTRY_DSN_DEV = 'https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496'
// This describes the subset of Redux state attached to errors sent to Sentry // This describes the subset of Redux state attached to errors sent to Sentry
@ -72,12 +71,17 @@ export const SENTRY_STATE = {
export default function setupSentry ({ release, getState }) { export default function setupSentry ({ release, getState }) {
let sentryTarget let sentryTarget
if (METAMASK_DEBUG || process.env.IN_TEST) { if (METAMASK_DEBUG) {
return
} else if (METAMASK_ENVIRONMENT === 'production') {
if (!process.env.SENTRY_DSN) {
throw new Error(`Missing SENTRY_DSN environment variable in production environment`)
}
console.log(`Setting up Sentry Remote Error Reporting for '${METAMASK_ENVIRONMENT}': SENTRY_DSN`)
sentryTarget = process.env.SENTRY_DSN
} else {
console.log(`Setting up Sentry Remote Error Reporting for '${METAMASK_ENVIRONMENT}': SENTRY_DSN_DEV`) console.log(`Setting up Sentry Remote Error Reporting for '${METAMASK_ENVIRONMENT}': SENTRY_DSN_DEV`)
sentryTarget = SENTRY_DSN_DEV sentryTarget = SENTRY_DSN_DEV
} else {
console.log(`Setting up Sentry Remote Error Reporting for '${METAMASK_ENVIRONMENT}': SENTRY_DSN_PROD`)
sentryTarget = SENTRY_DSN_PROD
} }
Sentry.init({ Sentry.init({

View File

@ -1,26 +1,67 @@
/*global Web3*/
// TODO:deprecate:2020 // TODO:deprecate:2020
// Delete this file
export default function setupDappAutoReload (web3, observable) { import 'web3/dist/web3.min.js'
const shouldLogUsage = !([
'docs.metamask.io',
'metamask.github.io',
'metamask.io',
].includes(window.location.hostname))
export default function setupWeb3 (log) {
// export web3 as a global, checking for usage // export web3 as a global, checking for usage
let reloadInProgress = false let reloadInProgress = false
let lastTimeUsed let lastTimeUsed
let lastSeenNetwork let lastSeenNetwork
let hasBeenWarned = false let hasBeenWarned = false
const web3 = new Web3(window.ethereum)
web3.setProvider = function () {
log.debug('MetaMask - overrode web3.setProvider')
}
log.debug('MetaMask - injected web3')
Object.defineProperty(window.ethereum, '_web3Ref', {
enumerable: false,
writable: true,
configurable: true,
value: web3.eth,
})
const web3Proxy = new Proxy(web3, { const web3Proxy = new Proxy(web3, {
get: (_web3, key) => { get: (_web3, key) => {
// get the time of use // get the time of use
lastTimeUsed = Date.now() lastTimeUsed = Date.now()
// show warning once on web3 access // show warning once on web3 access
if (!hasBeenWarned && key !== 'currentProvider') { if (!hasBeenWarned) {
console.warn(`MetaMask: We will stop injecting web3 in Q4 2020.\nPlease see this article for more information: https://medium.com/metamask/no-longer-injecting-web3-js-4a899ad6e59e`) console.warn(`MetaMask: We will stop injecting web3 in Q4 2020.\nPlease see this article for more information: https://medium.com/metamask/no-longer-injecting-web3-js-4a899ad6e59e`)
hasBeenWarned = true hasBeenWarned = true
} }
if (shouldLogUsage) {
window.ethereum.request({
method: 'metamask_logInjectedWeb3Usage',
params: [{ action: 'window.web3 get', name: key }],
})
}
// return value normally // return value normally
return _web3[key] return _web3[key]
}, },
set: (_web3, key, value) => { set: (_web3, key, value) => {
if (shouldLogUsage) {
window.ethereum.request({
method: 'metamask_logInjectedWeb3Usage',
params: [{ action: 'window.web3 set', name: key }],
})
}
// set value normally // set value normally
_web3[key] = value _web3[key] = value
}, },
@ -33,7 +74,7 @@ export default function setupDappAutoReload (web3, observable) {
value: web3Proxy, value: web3Proxy,
}) })
observable.subscribe(function (state) { window.ethereum._publicConfigStore.subscribe((state) => {
// if the auto refresh on network change is false do not // if the auto refresh on network change is false do not
// do anything // do anything
if (!window.ethereum.autoRefreshOnNetworkChange) { if (!window.ethereum.autoRefreshOnNetworkChange) {

View File

@ -19,6 +19,7 @@ import createEngineStream from 'json-rpc-middleware-stream/engineStream'
import createFilterMiddleware from 'eth-json-rpc-filters' import createFilterMiddleware from 'eth-json-rpc-filters'
import createSubscriptionManager from 'eth-json-rpc-filters/subscriptionManager' import createSubscriptionManager from 'eth-json-rpc-filters/subscriptionManager'
import createLoggerMiddleware from './lib/createLoggerMiddleware' import createLoggerMiddleware from './lib/createLoggerMiddleware'
import createMethodMiddleware from './lib/createMethodMiddleware'
import createOriginMiddleware from './lib/createOriginMiddleware' import createOriginMiddleware from './lib/createOriginMiddleware'
import createTabIdMiddleware from './lib/createTabIdMiddleware' import createTabIdMiddleware from './lib/createTabIdMiddleware'
import createOnboardingMiddleware from './lib/createOnboardingMiddleware' import createOnboardingMiddleware from './lib/createOnboardingMiddleware'
@ -66,7 +67,7 @@ import {
PhishingController, PhishingController,
} from '@metamask/controllers' } from '@metamask/controllers'
import backEndMetaMetricsEvent from './lib/backend-metametrics' import backgroundMetaMetricsEvent from './lib/background-metametrics'
export default class MetamaskController extends EventEmitter { export default class MetamaskController extends EventEmitter {
@ -249,18 +250,11 @@ export default class MetamaskController extends EventEmitter {
this.platform.showTransactionNotification(txMeta) this.platform.showTransactionNotification(txMeta)
const { txReceipt } = txMeta const { txReceipt } = txMeta
const participateInMetaMetrics = this.preferencesController.getParticipateInMetaMetrics() if (txReceipt && txReceipt.status === '0x0') {
if (txReceipt && txReceipt.status === '0x0' && participateInMetaMetrics) { this.sendBackgroundMetaMetrics({
const metamaskState = await this.getState() action: 'Transactions',
backEndMetaMetricsEvent(metamaskState, { name: 'On Chain Failure',
customVariables: { customVariables: { errorMessage: txMeta.simulationFails?.reason },
errorMessage: txMeta.simulationFails?.reason,
},
eventOpts: {
category: 'backend',
action: 'Transactions',
name: 'On Chain Failure',
},
}) })
} }
} }
@ -478,6 +472,7 @@ export default class MetamaskController extends EventEmitter {
// vault management // vault management
submitPassword: nodeify(this.submitPassword, this), submitPassword: nodeify(this.submitPassword, this),
verifyPassword: nodeify(this.verifyPassword, this),
// network management // network management
setProviderType: nodeify(networkController.setProviderType, networkController), setProviderType: nodeify(networkController.setProviderType, networkController),
@ -808,6 +803,15 @@ export default class MetamaskController extends EventEmitter {
return this.keyringController.fullUpdate() return this.keyringController.fullUpdate()
} }
/**
* Submits a user's password to check its validity.
*
* @param {string} password The user's password
*/
async verifyPassword (password) {
await this.keyringController.verifyPassword(password)
}
/** /**
* @type Identity * @type Identity
* @property {string} name - The account nickname. * @property {string} name - The account nickname.
@ -1627,6 +1631,10 @@ export default class MetamaskController extends EventEmitter {
location, location,
registerOnboarding: this.onboardingController.registerOnboarding, registerOnboarding: this.onboardingController.registerOnboarding,
})) }))
engine.push(createMethodMiddleware({
origin,
sendMetrics: this.sendBackgroundMetaMetrics.bind(this),
}))
// filter and subscription polyfills // filter and subscription polyfills
engine.push(filterMiddleware) engine.push(filterMiddleware)
engine.push(subscriptionManager.middleware) engine.push(subscriptionManager.middleware)
@ -1712,7 +1720,7 @@ export default class MetamaskController extends EventEmitter {
delete connections[id] delete connections[id]
if (Object.keys(connections.length === 0)) { if (Object.keys(connections).length === 0) {
delete this.connections[origin] delete this.connections[origin]
} }
} }
@ -1827,6 +1835,22 @@ export default class MetamaskController extends EventEmitter {
return nonceLock.nextNonce return nonceLock.nextNonce
} }
async sendBackgroundMetaMetrics ({ action, name, customVariables } = {}) {
if (!action || !name) {
throw new Error('Must provide action and name.')
}
const metamaskState = await this.getState()
backgroundMetaMetricsEvent(metamaskState, {
customVariables,
eventOpts: {
action,
name,
},
})
}
//============================================================================= //=============================================================================
// CONFIG // CONFIG
//============================================================================= //=============================================================================

View File

@ -206,6 +206,9 @@ function createScriptTasks ({ browserPlatforms, livereload }) {
mangle: { mangle: {
reserved: [ 'MetamaskInpageProvider' ], reserved: [ 'MetamaskInpageProvider' ],
}, },
sourceMap: {
content: true,
},
})) }))
} }
@ -313,33 +316,23 @@ function createScriptTasks ({ browserPlatforms, livereload }) {
bundler = bundler.external(opts.externalDependencies) bundler = bundler.external(opts.externalDependencies)
} }
let environment const environment = getEnvironment({ devMode: opts.devMode, test: opts.testing })
if (opts.devMode) { if (environment === 'production' && !process.env.SENTRY_DSN) {
environment = 'development' throw new Error('Missing SENTRY_DSN environment variable')
} else if (opts.testing) {
environment = 'testing'
} else if (process.env.CIRCLE_BRANCH === 'master') {
environment = 'production'
} else if (/^Version-v(\d+)[.](\d+)[.](\d+)/.test(process.env.CIRCLE_BRANCH)) {
environment = 'release-candidate'
} else if (process.env.CIRCLE_BRANCH === 'develop') {
environment = 'staging'
} else if (process.env.CIRCLE_PULL_REQUEST) {
environment = 'pull-request'
} else {
environment = 'other'
} }
// Inject variables into bundle // Inject variables into bundle
bundler.transform(envify({ bundler.transform(envify({
METAMASK_DEBUG: opts.devMode, METAMASK_DEBUG: opts.devMode,
METAMASK_ENVIRONMENT: environment, METAMASK_ENVIRONMENT: environment,
METAMETRICS_PROJECT_ID: process.env.METAMETRICS_PROJECT_ID,
NODE_ENV: opts.devMode ? 'development' : 'production', NODE_ENV: opts.devMode ? 'development' : 'production',
IN_TEST: opts.testing ? 'true' : false, IN_TEST: opts.testing ? 'true' : false,
PUBNUB_SUB_KEY: process.env.PUBNUB_SUB_KEY || '', PUBNUB_SUB_KEY: process.env.PUBNUB_SUB_KEY || '',
PUBNUB_PUB_KEY: process.env.PUBNUB_PUB_KEY || '', PUBNUB_PUB_KEY: process.env.PUBNUB_PUB_KEY || '',
ETH_GAS_STATION_API_KEY: process.env.ETH_GAS_STATION_API_KEY || '', ETH_GAS_STATION_API_KEY: process.env.ETH_GAS_STATION_API_KEY || '',
CONF: opts.devMode ? conf : ({}), CONF: opts.devMode ? conf : ({}),
SENTRY_DSN: process.env.SENTRY_DSN,
}), { }), {
global: true, global: true,
}) })
@ -363,3 +356,22 @@ function createScriptTasks ({ browserPlatforms, livereload }) {
function beep () { function beep () {
process.stdout.write('\x07') process.stdout.write('\x07')
} }
function getEnvironment ({ devMode, test }) {
// get environment slug
if (devMode) {
return 'development'
} else if (test) {
return 'testing'
} else if (process.env.CIRCLE_BRANCH === 'master') {
return 'production'
} else if (/^Version-v(\d+)[.](\d+)[.](\d+)/.test(process.env.CIRCLE_BRANCH)) {
return 'release-candidate'
} else if (process.env.CIRCLE_BRANCH === 'develop') {
return 'staging'
} else if (process.env.CIRCLE_PULL_REQUEST) {
return 'pull-request'
} else {
return 'other'
}
}

View File

@ -13,13 +13,31 @@ const fsAsync = pify(fs)
// if not working it may error or print minified garbage // if not working it may error or print minified garbage
// //
start().catch(console.error) start().catch((error) => {
console.error(error)
process.exit(1)
})
async function start () { async function start () {
const targetFiles = [`inpage.js`, `contentscript.js`, `ui.js`, `background.js`] const targetFiles = [
`background.js`,
// `bg-libs`, skipped because source maps are invalid due to browserify bug: https://github.com/browserify/browserify/issues/1971
// `contentscript.js`, skipped because the validator is erroneously sampling the inlined `inpage.js` script
`inpage.js`,
'phishing-detect.js',
`ui.js`,
// `ui-libs.js`, skipped because source maps are invalid due to browserify bug: https://github.com/browserify/browserify/issues/1971
]
let valid = true
for (const buildName of targetFiles) { for (const buildName of targetFiles) {
await validateSourcemapForFile({ buildName }) const fileIsValid = await validateSourcemapForFile({ buildName })
valid = valid && fileIsValid
}
if (!valid) {
process.exit(1)
} }
} }
@ -59,6 +77,7 @@ async function validateSourcemapForFile ({ buildName }) {
console.log(` sampling from ${consumer.sources.length} files`) console.log(` sampling from ${consumer.sources.length} files`)
let sampleCount = 0 let sampleCount = 0
let valid = true
const buildLines = rawBuild.split('\n') const buildLines = rawBuild.split('\n')
const targetString = 'new Error' const targetString = 'new Error'
@ -71,6 +90,7 @@ async function validateSourcemapForFile ({ buildName }) {
const result = consumer.originalPositionFor(position) const result = consumer.originalPositionFor(position)
// warn if source content is missing // warn if source content is missing
if (!result.source) { if (!result.source) {
valid = false
console.warn(`!! missing source for position: ${JSON.stringify(position)}`) console.warn(`!! missing source for position: ${JSON.stringify(position)}`)
// const buildLine = buildLines[position.line - 1] // const buildLine = buildLines[position.line - 1]
console.warn(` origin in build:`) console.warn(` origin in build:`)
@ -86,14 +106,27 @@ async function validateSourcemapForFile ({ buildName }) {
const portion = line.slice(result.column) const portion = line.slice(result.column)
const isMaybeValid = portion.includes(targetString) const isMaybeValid = portion.includes(targetString)
if (!isMaybeValid) { if (!isMaybeValid) {
console.error('Sourcemap seems invalid:') valid = false
console.log(`\n========================== ${result.source} ====================================\n`) console.error(`Sourcemap seems invalid:\n${getFencedCode(result.source, line)}`)
console.log(line)
console.log(`\n==============================================================================\n`)
} }
}) })
}) })
console.log(` checked ${sampleCount} samples`) console.log(` checked ${sampleCount} samples`)
return valid
}
const CODE_FENCE_LENGTH = 80
const TITLE_PADDING_LENGTH = 1
function getFencedCode (filename, code) {
const title = `${' '.repeat(TITLE_PADDING_LENGTH)}${filename}${' '.repeat(TITLE_PADDING_LENGTH)}`
const openingFenceLength = Math.max(CODE_FENCE_LENGTH - (filename.length + (TITLE_PADDING_LENGTH * 2)), 0)
const startOpeningFenceLength = Math.floor(openingFenceLength / 2)
const endOpeningFenceLength = Math.ceil(openingFenceLength / 2)
const openingFence = `${'='.repeat(startOpeningFenceLength)}${title}${'='.repeat(endOpeningFenceLength)}`
const closingFence = '='.repeat(CODE_FENCE_LENGTH)
return `${openingFence}\n${code}\n${closingFence}\n`
} }
function indicesOf (substring, string) { function indicesOf (substring, string) {

View File

@ -22,8 +22,6 @@
"test:unit:strict": "mocha --exit --require test/env.js --require test/setup.js --recursive \"test/unit/**/permissions/*.js\"", "test:unit:strict": "mocha --exit --require test/env.js --require test/setup.js --recursive \"test/unit/**/permissions/*.js\"",
"test:unit:path": "mocha --exit --require test/env.js --require test/setup.js --recursive", "test:unit:path": "mocha --exit --require test/env.js --require test/setup.js --recursive",
"test:e2e:chrome": "SELENIUM_BROWSER=chrome test/e2e/run-all.sh", "test:e2e:chrome": "SELENIUM_BROWSER=chrome test/e2e/run-all.sh",
"test:web3:chrome": "SELENIUM_BROWSER=chrome test/e2e/run-web3.sh",
"test:web3:firefox": "SELENIUM_BROWSER=firefox test/e2e/run-web3.sh",
"test:e2e:firefox": "SELENIUM_BROWSER=firefox test/e2e/run-all.sh", "test:e2e:firefox": "SELENIUM_BROWSER=firefox test/e2e/run-all.sh",
"test:coverage": "nyc --silent --check-coverage yarn test:unit:strict && nyc --silent --no-clean yarn test:unit:lax && nyc report --reporter=text --reporter=html", "test:coverage": "nyc --silent --check-coverage yarn test:unit:strict && nyc --silent --no-clean yarn test:unit:lax && nyc report --reporter=text --reporter=html",
"test:coverage:strict": "nyc --check-coverage yarn test:unit:strict", "test:coverage:strict": "nyc --check-coverage yarn test:unit:strict",
@ -37,6 +35,7 @@
"lint:shellcheck": "./development/shellcheck.sh", "lint:shellcheck": "./development/shellcheck.sh",
"lint:styles": "stylelint '*/**/*.scss'", "lint:styles": "stylelint '*/**/*.scss'",
"lint:lockfile": "lockfile-lint --path yarn.lock --allowed-hosts npm yarn github.com codeload.github.com --empty-hostname false --allowed-schemes \"https:\" \"git+https:\"", "lint:lockfile": "lockfile-lint --path yarn.lock --allowed-hosts npm yarn github.com codeload.github.com --empty-hostname false --allowed-schemes \"https:\" \"git+https:\"",
"validate-source-maps": "node ./development/sourcemap-validator.js",
"verify-locales": "node ./development/verify-locale-strings.js", "verify-locales": "node ./development/verify-locale-strings.js",
"verify-locales:fix": "node ./development/verify-locale-strings.js --fix", "verify-locales:fix": "node ./development/verify-locale-strings.js --fix",
"mozilla-lint": "addons-linter dist/firefox", "mozilla-lint": "addons-linter dist/firefox",
@ -52,10 +51,13 @@
"generate:migration": "./development/generate-migration.sh" "generate:migration": "./development/generate-migration.sh"
}, },
"resolutions": { "resolutions": {
"**/configstore/dot-prop": "^5.1.1",
"**/ethers/elliptic": "^6.5.3",
"**/knex/minimist": "^1.2.5", "**/knex/minimist": "^1.2.5",
"**/optimist/minimist": "^1.2.5", "**/optimist/minimist": "^1.2.5",
"**/socketcluster/minimist": "^1.2.5", "**/socketcluster/minimist": "^1.2.5",
"3box/ipfs/ipld-zcash/zcash-bitcore-lib/lodash": "^4.17.19", "3box/ipfs/ipld-zcash/zcash-bitcore-lib/lodash": "^4.17.19",
"3box/ipfs/ipld-zcash/zcash-bitcore-lib/elliptic": "^6.5.3",
"ganache-core/lodash": "^4.17.19" "ganache-core/lodash": "^4.17.19"
}, },
"dependencies": { "dependencies": {
@ -98,7 +100,7 @@
"eth-json-rpc-filters": "^4.1.1", "eth-json-rpc-filters": "^4.1.1",
"eth-json-rpc-infura": "^4.0.2", "eth-json-rpc-infura": "^4.0.2",
"eth-json-rpc-middleware": "^5.0.2", "eth-json-rpc-middleware": "^5.0.2",
"eth-keyring-controller": "^6.0.1", "eth-keyring-controller": "^6.1.0",
"eth-method-registry": "^1.2.0", "eth-method-registry": "^1.2.0",
"eth-phishing-detect": "^1.1.4", "eth-phishing-detect": "^1.1.4",
"eth-query": "^2.1.2", "eth-query": "^2.1.2",
@ -119,12 +121,12 @@
"fuse.js": "^3.2.0", "fuse.js": "^3.2.0",
"human-standard-token-abi": "^2.0.0", "human-standard-token-abi": "^2.0.0",
"jazzicon": "^2.0.0", "jazzicon": "^2.0.0",
"json-rpc-engine": "^5.1.8", "json-rpc-engine": "^5.2.0",
"json-rpc-middleware-stream": "^2.1.1", "json-rpc-middleware-stream": "^2.1.1",
"jsonschema": "^1.2.4", "jsonschema": "^1.2.4",
"lodash": "^4.17.19", "lodash": "^4.17.19",
"loglevel": "^1.4.1", "loglevel": "^1.4.1",
"luxon": "^1.23.0", "luxon": "^1.24.1",
"metamask-logo": "^2.1.4", "metamask-logo": "^2.1.4",
"multihashes": "^0.4.12", "multihashes": "^0.4.12",
"nanoid": "^2.1.6", "nanoid": "^2.1.6",
@ -157,7 +159,7 @@
"redux": "^4.0.5", "redux": "^4.0.5",
"redux-thunk": "^2.3.0", "redux-thunk": "^2.3.0",
"reselect": "^3.0.1", "reselect": "^3.0.1",
"rpc-cap": "^3.0.1", "rpc-cap": "^3.1.0",
"safe-event-emitter": "^1.0.1", "safe-event-emitter": "^1.0.1",
"safe-json-stringify": "^1.2.0", "safe-json-stringify": "^1.2.0",
"single-call-balance-checker-abi": "^1.0.0", "single-call-balance-checker-abi": "^1.0.0",
@ -192,8 +194,8 @@
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"babel-loader": "^8.0.6", "babel-loader": "^8.0.6",
"babelify": "^10.0.0", "babelify": "^10.0.0",
"brfs": "^1.6.1", "brfs": "^2.0.2",
"browserify": "^16.2.3", "browserify": "^16.5.1",
"browserify-derequire": "^1.0.1", "browserify-derequire": "^1.0.1",
"browserify-transform-tools": "^1.7.0", "browserify-transform-tools": "^1.7.0",
"chai": "^4.1.0", "chai": "^4.1.0",
@ -230,13 +232,13 @@
"gulp-imagemin": "^6.1.0", "gulp-imagemin": "^6.1.0",
"gulp-livereload": "4.0.0", "gulp-livereload": "4.0.0",
"gulp-multi-process": "^1.3.1", "gulp-multi-process": "^1.3.1",
"gulp-rename": "^1.4.0", "gulp-rename": "^2.0.0",
"gulp-replace": "^1.0.0", "gulp-replace": "^1.0.0",
"gulp-rtlcss": "^1.4.0", "gulp-rtlcss": "^1.4.0",
"gulp-sass": "^4.0.0", "gulp-sass": "^4.0.0",
"gulp-sourcemaps": "^2.6.0", "gulp-sourcemaps": "^2.6.0",
"gulp-stylelint": "^13.0.0", "gulp-stylelint": "^13.0.0",
"gulp-terser-js": "^5.0.0", "gulp-terser-js": "^5.2.2",
"gulp-watch": "^5.0.1", "gulp-watch": "^5.0.1",
"gulp-zip": "^4.0.0", "gulp-zip": "^4.0.0",
"jsdom": "^11.2.0", "jsdom": "^11.2.0",
@ -251,7 +253,7 @@
"proxyquire": "^2.1.3", "proxyquire": "^2.1.3",
"randomcolor": "^0.5.4", "randomcolor": "^0.5.4",
"rc": "^1.2.8", "rc": "^1.2.8",
"react-devtools": "^4.4.0", "react-devtools": "^4.8.0",
"react-test-renderer": "^16.12.0", "react-test-renderer": "^16.12.0",
"read-installed": "^4.0.3", "read-installed": "^4.0.3",
"redux-mock-store": "^1.5.4", "redux-mock-store": "^1.5.4",
@ -263,10 +265,10 @@
"selenium-webdriver": "^4.0.0-alpha.5", "selenium-webdriver": "^4.0.0-alpha.5",
"serve-handler": "^6.1.2", "serve-handler": "^6.1.2",
"sesify": "^4.2.1", "sesify": "^4.2.1",
"sesify-viz": "^3.0.5", "sesify-viz": "^3.0.10",
"sinon": "^9.0.0", "sinon": "^9.0.0",
"source-map": "^0.7.2", "source-map": "^0.7.2",
"source-map-explorer": "^2.0.1", "source-map-explorer": "^2.4.2",
"string.prototype.matchall": "^4.0.2", "string.prototype.matchall": "^4.0.2",
"style-loader": "^0.21.0", "style-loader": "^0.21.0",
"stylelint": "^13.6.1", "stylelint": "^13.6.1",

View File

@ -5,72 +5,91 @@ set -e
set -u set -u
set -o pipefail set -o pipefail
retry () {
retry=0
limit="${METAMASK_E2E_RETRY_LIMIT:-3}"
while [[ $retry -lt $limit ]]
do
"$@" && break
retry=$(( retry + 1 ))
sleep 1
done
if [[ $retry == "$limit" ]]
then
exit 1
fi
}
export PATH="$PATH:./node_modules/.bin" export PATH="$PATH:./node_modules/.bin"
mocha --no-timeouts test/e2e/tests/*.spec.js for spec in test/e2e/tests/*.spec.js
do
retry mocha --no-timeouts "${spec}"
done
concurrently --kill-others \ retry concurrently --kill-others \
--names 'dapp,e2e' \ --names 'dapp,e2e' \
--prefix '[{time}][{name}]' \ --prefix '[{time}][{name}]' \
--success first \ --success first \
'yarn dapp' \ 'yarn dapp' \
'mocha test/e2e/metamask-ui.spec' 'mocha test/e2e/metamask-ui.spec'
concurrently --kill-others \ retry concurrently --kill-others \
--names 'dapp,e2e' \ --names 'dapp,e2e' \
--prefix '[{time}][{name}]' \ --prefix '[{time}][{name}]' \
--success first \ --success first \
'yarn dapp' \ 'yarn dapp' \
'mocha test/e2e/metamask-responsive-ui.spec' 'mocha test/e2e/metamask-responsive-ui.spec'
concurrently --kill-others \ retry concurrently --kill-others \
--names 'dapp,e2e' \ --names 'dapp,e2e' \
--prefix '[{time}][{name}]' \ --prefix '[{time}][{name}]' \
--success first \ --success first \
'yarn dapp' \ 'yarn dapp' \
'mocha test/e2e/signature-request.spec' 'mocha test/e2e/signature-request.spec'
concurrently --kill-others \ retry concurrently --kill-others \
--names 'e2e' \ --names 'e2e' \
--prefix '[{time}][{name}]' \ --prefix '[{time}][{name}]' \
--success first \ --success first \
'mocha test/e2e/from-import-ui.spec' 'mocha test/e2e/from-import-ui.spec'
concurrently --kill-others \ retry concurrently --kill-others \
--names 'e2e' \ --names 'e2e' \
--prefix '[{time}][{name}]' \ --prefix '[{time}][{name}]' \
--success first \ --success first \
'mocha test/e2e/send-edit.spec' 'mocha test/e2e/send-edit.spec'
concurrently --kill-others \ retry concurrently --kill-others \
--names 'dapp,e2e' \ --names 'dapp,e2e' \
--prefix '[{time}][{name}]' \ --prefix '[{time}][{name}]' \
--success first \ --success first \
'yarn dapp' \ 'yarn dapp' \
'mocha test/e2e/ethereum-on.spec' 'mocha test/e2e/ethereum-on.spec'
concurrently --kill-others \ retry concurrently --kill-others \
--names 'dapp,e2e' \ --names 'dapp,e2e' \
--prefix '[{time}][{name}]' \ --prefix '[{time}][{name}]' \
--success first \ --success first \
'yarn dapp' \ 'yarn dapp' \
'mocha test/e2e/permissions.spec' 'mocha test/e2e/permissions.spec'
concurrently --kill-others \ retry concurrently --kill-others \
--names 'sendwithprivatedapp,e2e' \ --names 'sendwithprivatedapp,e2e' \
--prefix '[{time}][{name}]' \ --prefix '[{time}][{name}]' \
--success first \ --success first \
'yarn sendwithprivatedapp' \ 'yarn sendwithprivatedapp' \
'mocha test/e2e/incremental-security.spec' 'mocha test/e2e/incremental-security.spec'
concurrently --kill-others \ retry concurrently --kill-others \
--names 'dapp,e2e' \ --names 'dapp,e2e' \
--prefix '[{time}][{name}]' \ --prefix '[{time}][{name}]' \
--success first \ --success first \
'yarn dapp' \ 'yarn dapp' \
'mocha test/e2e/address-book.spec' 'mocha test/e2e/address-book.spec'
concurrently --kill-others \ retry concurrently --kill-others \
--names '3box,dapp,e2e' \ --names '3box,dapp,e2e' \
--prefix '[{time}][{name}]' \ --prefix '[{time}][{name}]' \
--success first \ --success first \

View File

@ -1,13 +0,0 @@
#!/usr/bin/env bash
set -e
set -u
set -o pipefail
export PATH="$PATH:./node_modules/.bin"
concurrently --kill-others \
--names 'dapp,e2e' \
--prefix '[{time}][{name}]' \
'node development/static-server.js test/web3 --port 8080' \
'sleep 5 && mocha test/e2e/web3.spec'

View File

@ -1,288 +0,0 @@
const assert = require('assert')
const webdriver = require('selenium-webdriver')
const { By } = webdriver
const {
regularDelayMs,
largeDelayMs,
} = require('./helpers')
const { buildWebDriver } = require('./webdriver')
const enLocaleMessages = require('../../app/_locales/en/messages.json')
describe('Using MetaMask with an existing account', function () {
let driver
const testSeedPhrase = 'forum vessel pink push lonely enact gentle tail admit parrot grunt dress'
const button = async (x) => {
const buttoncheck = x
await buttoncheck.click()
await driver.delay(largeDelayMs)
const [results] = await driver.findElements(By.css('#results'))
const resulttext = await results.getText()
const parsedData = JSON.parse(resulttext)
return (parsedData)
}
this.timeout(0)
this.bail(true)
before(async function () {
const result = await buildWebDriver()
driver = result.driver
})
afterEach(async function () {
if (process.env.SELENIUM_BROWSER === 'chrome') {
const errors = await driver.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 driver.verboseReportOnFailure(this.currentTest.title)
}
})
after(async function () {
await driver.quit()
})
describe('First time flow starting from an existing seed phrase', function () {
it('clicks the continue button on the welcome screen', async function () {
await driver.findElement(By.css('.welcome-page__header'))
await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`))
await driver.delay(largeDelayMs)
})
it('clicks the "Import Wallet" option', async function () {
await driver.clickElement(By.xpath(`//button[contains(text(), 'Import wallet')]`))
await driver.delay(largeDelayMs)
})
it('clicks the "No thanks" option on the metametrics opt-in screen', async function () {
await driver.clickElement(By.css('.btn-default'))
await driver.delay(largeDelayMs)
})
it('imports a seed phrase', async function () {
const [seedTextArea] = await driver.findElements(By.css('input[placeholder="Paste seed phrase from clipboard"]'))
await seedTextArea.sendKeys(testSeedPhrase)
await driver.delay(regularDelayMs)
const [password] = await driver.findElements(By.id('password'))
await password.sendKeys('correct horse battery staple')
const [confirmPassword] = await driver.findElements(By.id('confirm-password'))
confirmPassword.sendKeys('correct horse battery staple')
await driver.clickElement(By.css('.first-time-flow__terms'))
await driver.clickElement(By.xpath(`//button[contains(text(), 'Import')]`))
await driver.delay(regularDelayMs)
})
it('clicks through the success screen', async function () {
await driver.findElement(By.xpath(`//div[contains(text(), 'Congratulations')]`))
await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`))
await driver.delay(regularDelayMs)
})
})
describe('opens dapp', function () {
it('switches to mainnet', async function () {
await driver.clickElement(By.css('.network-name'))
await driver.delay(regularDelayMs)
await driver.clickElement(By.xpath(`//span[contains(text(), 'Main Ethereum Network')]`))
await driver.delay(largeDelayMs * 2)
})
it('connects to dapp', async function () {
await driver.openNewPage('http://127.0.0.1:8080/')
await driver.delay(regularDelayMs)
await driver.clickElement(By.xpath(`//button[contains(text(), 'Connect')]`))
await driver.delay(regularDelayMs)
await driver.waitUntilXWindowHandles(3)
const windowHandles = await driver.getAllWindowHandles()
const extension = windowHandles[0]
const popup = await driver.switchToWindowWithTitle('MetaMask Notification', windowHandles)
const dapp = windowHandles.find((handle) => handle !== extension && handle !== popup)
await driver.delay(regularDelayMs)
await driver.clickElement(By.xpath(`//button[contains(text(), 'Connect')]`))
await driver.switchToWindow(dapp)
await driver.delay(regularDelayMs)
})
})
describe('testing web3 methods', function () {
it('testing hexa methods', async function () {
const List = await driver.findClickableElements(By.className('hexaNumberMethods'))
for (let i = 0; i < List.length; i++) {
try {
const parsedData = await button(List[i])
console.log(parsedData)
const result = parseInt(parsedData.result, 16)
assert.equal((typeof result === 'number'), true)
await driver.delay(regularDelayMs)
} catch (err) {
console.log(err)
assert(false)
}
}
})
it('testing booleanMethods', async function () {
const List = await driver.findClickableElement(By.className('booleanMethods'))
for (let i = 0; i < List.length; i++) {
try {
const parsedData = await button(List[i])
console.log(parsedData)
const result = parsedData.result
assert.equal(result, false)
await driver.delay(regularDelayMs)
} catch (err) {
console.log(err)
assert(false)
}
}
})
it('testing transactionMethods', async function () {
const List = await driver.findClickableElement(By.className('transactionMethods'))
for (let i = 0; i < List.length; i++) {
try {
const parsedData = await button(List[i])
console.log(parsedData.result.blockHash)
const result = []
result.push(parseInt(parsedData.result.blockHash, 16))
result.push(parseInt(parsedData.result.blockNumber, 16))
result.push(parseInt(parsedData.result.gas, 16))
result.push(parseInt(parsedData.result.gasPrice, 16))
result.push(parseInt(parsedData.result.hash, 16))
result.push(parseInt(parsedData.result.input, 16))
result.push(parseInt(parsedData.result.nonce, 16))
result.push(parseInt(parsedData.result.r, 16))
result.push(parseInt(parsedData.result.s, 16))
result.push(parseInt(parsedData.result.v, 16))
result.push(parseInt(parsedData.result.to, 16))
result.push(parseInt(parsedData.result.value, 16))
result.forEach((value) => {
assert.equal((typeof value === 'number'), true)
})
} catch (err) {
console.log(err)
assert(false)
}
}
})
it('testing blockMethods', async function () {
const List = await driver.findClickableElement(By.className('blockMethods'))
for (let i = 0; i < List.length; i++) {
try {
const parsedData = await button(List[i])
console.log(JSON.stringify(parsedData) + i)
console.log(parsedData.result.parentHash)
const result = parseInt(parsedData.result.parentHash, 16)
assert.equal((typeof result === 'number'), true)
await driver.delay(regularDelayMs)
} catch (err) {
console.log(err)
assert(false)
}
}
})
it('testing methods', async function () {
const List = await driver.findClickableElement(By.className('methods'))
let parsedData
let result
for (let i = 0; i < List.length; i++) {
try {
if (i === 2) {
parsedData = await button(List[i])
console.log(parsedData.result.blockHash)
result = parseInt(parsedData.result.blockHash, 16)
assert.equal((typeof result === 'number' || (result === 0)), true)
await driver.delay(regularDelayMs)
} else {
parsedData = await button(List[i])
console.log(parsedData.result)
result = parseInt(parsedData.result, 16)
assert.equal((typeof result === 'number' || (result === 0)), true)
await driver.delay(regularDelayMs)
}
} catch (err) {
console.log(err)
assert(false)
}
}
})
})
})

View File

@ -18,7 +18,6 @@ describe('NetworkController', function () {
.reply(200) .reply(200)
networkController = new NetworkController() networkController = new NetworkController()
networkController.initializeProvider(networkControllerProviderConfig)
}) })
afterEach(function () { afterEach(function () {
@ -34,8 +33,9 @@ describe('NetworkController', function () {
assert.equal(providerProxy.test, true) assert.equal(providerProxy.test, true)
}) })
}) })
describe('#getNetworkState', function () { describe('#getNetworkState', function () {
it('should return loading when new', function () { it('should return "loading" when new', function () {
const networkState = networkController.getNetworkState() const networkState = networkController.getNetworkState()
assert.equal(networkState, 'loading', 'network is loading') assert.equal(networkState, 'loading', 'network is loading')
}) })
@ -51,11 +51,13 @@ describe('NetworkController', function () {
describe('#setProviderType', function () { describe('#setProviderType', function () {
it('should update provider.type', function () { it('should update provider.type', function () {
networkController.initializeProvider(networkControllerProviderConfig)
networkController.setProviderType('mainnet') networkController.setProviderType('mainnet')
const type = networkController.getProviderConfig().type const type = networkController.getProviderConfig().type
assert.equal(type, 'mainnet', 'provider type is updated') assert.equal(type, 'mainnet', 'provider type is updated')
}) })
it('should set the network to loading', function () { it('should set the network to loading', function () {
networkController.initializeProvider(networkControllerProviderConfig)
networkController.setProviderType('mainnet') networkController.setProviderType('mainnet')
const loading = networkController.isNetworkLoading() const loading = networkController.isNetworkLoading()
assert.ok(loading, 'network is loading') assert.ok(loading, 'network is loading')

View File

@ -19,25 +19,44 @@ export function grantPermissions (permController, origin, permissions) {
} }
/** /**
* Sets the underlying rpc-cap requestUserApproval function, and returns * Returns a wrapper for the given permissions controller's requestUserApproval
* a promise that's resolved once it has been set. * function, so we don't have to worry about its internals.
* *
* This function must be called on the given permissions controller every * @param {PermissionsController} permController - The permissions controller.
* time you want such a Promise. As of writing, it's only called once per test. * @return {Function} A convenient wrapper for the requestUserApproval function.
*/
export function getRequestUserApprovalHelper (permController) {
/**
* Returns a request object that can be passed to requestUserApproval.
*
* @param {string} id - The internal permissions request ID (not the RPC request ID).
* @param {string} [origin] - The origin of the request, if necessary.
* @returns {Object} The corresponding request object.
*/
return (id, origin = 'defaultOrigin') => {
return permController.permissions.requestUserApproval({ metadata: { id, origin } })
}
}
/**
* Returns a Promise that resolves once a pending user approval has been set.
* Calls the underlying requestUserApproval function as normal, and restores it
* once the Promise is resolved.
*
* This function must be called on the permissions controller for each request.
* *
* @param {PermissionsController} - A permissions controller. * @param {PermissionsController} - A permissions controller.
* @returns {Promise<void>} A Promise that resolves once a pending approval * @returns {Promise<void>} A Promise that resolves once a pending approval
* has been set. * has been set.
*/ */
export function getUserApprovalPromise (permController) { export function getUserApprovalPromise (permController) {
return new Promise((resolveForCaller) => { const originalFunction = permController.permissions.requestUserApproval
permController.permissions.requestUserApproval = async (req) => { return new Promise((resolveHelperPromise) => {
const { origin, metadata: { id } } = req permController.permissions.requestUserApproval = (req) => {
const userApprovalPromise = originalFunction(req)
return new Promise((resolve, reject) => { permController.permissions.requestUserApproval = originalFunction
permController.pendingApprovals.set(id, { origin, resolve, reject }) resolveHelperPromise()
resolveForCaller() return userApprovalPromise
})
} }
}) })
} }

View File

@ -15,6 +15,7 @@ import {
} from '../../../../../app/scripts/controllers/permissions' } from '../../../../../app/scripts/controllers/permissions'
import { import {
getRequestUserApprovalHelper,
grantPermissions, grantPermissions,
} from './helpers' } from './helpers'
@ -58,12 +59,6 @@ const initPermController = (notifications = initNotifications()) => {
}) })
} }
const getMockRequestUserApprovalFunction = (permController) => (id, origin) => {
return new Promise((resolve, reject) => {
permController.pendingApprovals.set(id, { origin, resolve, reject })
})
}
describe('permissions controller', function () { describe('permissions controller', function () {
describe('getAccounts', function () { describe('getAccounts', function () {
@ -951,13 +946,11 @@ describe('permissions controller', function () {
describe('approvePermissionsRequest', function () { describe('approvePermissionsRequest', function () {
let permController, mockRequestUserApproval let permController, requestUserApproval
beforeEach(function () { beforeEach(function () {
permController = initPermController() permController = initPermController()
mockRequestUserApproval = getMockRequestUserApprovalFunction( requestUserApproval = getRequestUserApprovalHelper(permController)
permController,
)
}) })
it('does nothing if called on non-existing request', async function () { it('does nothing if called on non-existing request', async function () {
@ -994,14 +987,14 @@ describe('permissions controller', function () {
PERMS.requests.eth_accounts(), PERMS.requests.eth_accounts(),
) )
const requestRejection = assert.rejects( const rejectionPromise = assert.rejects(
mockRequestUserApproval(REQUEST_IDS.a), requestUserApproval(REQUEST_IDS.a),
ERRORS.validatePermittedAccounts.invalidParam(), ERRORS.validatePermittedAccounts.invalidParam(),
'should reject bad accounts', 'should reject with "null" accounts',
) )
await permController.approvePermissionsRequest(request, null) await permController.approvePermissionsRequest(request, null)
await requestRejection await rejectionPromise
assert.equal( assert.equal(
permController.pendingApprovals.size, 0, permController.pendingApprovals.size, 0,
@ -1014,7 +1007,7 @@ describe('permissions controller', function () {
const request = PERMS.approvedRequest(REQUEST_IDS.a, {}) const request = PERMS.approvedRequest(REQUEST_IDS.a, {})
const requestRejection = assert.rejects( const requestRejection = assert.rejects(
mockRequestUserApproval(REQUEST_IDS.a), requestUserApproval(REQUEST_IDS.a),
ERRORS.approvePermissionsRequest.noPermsRequested(), ERRORS.approvePermissionsRequest.noPermsRequested(),
'should reject if no permissions in request', 'should reject if no permissions in request',
) )
@ -1036,7 +1029,7 @@ describe('permissions controller', function () {
const requestApproval = assert.doesNotReject( const requestApproval = assert.doesNotReject(
async () => { async () => {
perms = await mockRequestUserApproval(REQUEST_IDS.a) perms = await requestUserApproval(REQUEST_IDS.a)
}, },
'should not reject single valid request', 'should not reject single valid request',
) )
@ -1065,14 +1058,14 @@ describe('permissions controller', function () {
const approval1 = assert.doesNotReject( const approval1 = assert.doesNotReject(
async () => { async () => {
perms1 = await mockRequestUserApproval(REQUEST_IDS.a) perms1 = await requestUserApproval(REQUEST_IDS.a, DOMAINS.a.origin)
}, },
'should not reject request', 'should not reject request',
) )
const approval2 = assert.doesNotReject( const approval2 = assert.doesNotReject(
async () => { async () => {
perms2 = await mockRequestUserApproval(REQUEST_IDS.b) perms2 = await requestUserApproval(REQUEST_IDS.b, DOMAINS.b.origin)
}, },
'should not reject request', 'should not reject request',
) )
@ -1105,13 +1098,11 @@ describe('permissions controller', function () {
describe('rejectPermissionsRequest', function () { describe('rejectPermissionsRequest', function () {
let permController, mockRequestUserApproval let permController, requestUserApproval
beforeEach(async function () { beforeEach(async function () {
permController = initPermController() permController = initPermController()
mockRequestUserApproval = getMockRequestUserApprovalFunction( requestUserApproval = getRequestUserApprovalHelper(permController)
permController,
)
}) })
it('does nothing if called on non-existing request', async function () { it('does nothing if called on non-existing request', async function () {
@ -1135,7 +1126,7 @@ describe('permissions controller', function () {
it('rejects single existing request', async function () { it('rejects single existing request', async function () {
const requestRejection = assert.rejects( const requestRejection = assert.rejects(
mockRequestUserApproval(REQUEST_IDS.a), requestUserApproval(REQUEST_IDS.a),
ERRORS.rejectPermissionsRequest.rejection(), ERRORS.rejectPermissionsRequest.rejection(),
'should reject with expected error', 'should reject with expected error',
) )
@ -1152,13 +1143,13 @@ describe('permissions controller', function () {
it('rejects requests regardless of order', async function () { it('rejects requests regardless of order', async function () {
const requestRejection1 = assert.rejects( const requestRejection1 = assert.rejects(
mockRequestUserApproval(REQUEST_IDS.b), requestUserApproval(REQUEST_IDS.b, DOMAINS.b.origin),
ERRORS.rejectPermissionsRequest.rejection(), ERRORS.rejectPermissionsRequest.rejection(),
'should reject with expected error', 'should reject with expected error',
) )
const requestRejection2 = assert.rejects( const requestRejection2 = assert.rejects(
mockRequestUserApproval(REQUEST_IDS.c), requestUserApproval(REQUEST_IDS.c, DOMAINS.c.origin),
ERRORS.rejectPermissionsRequest.rejection(), ERRORS.rejectPermissionsRequest.rejection(),
'should reject with expected error', 'should reject with expected error',
) )

View File

@ -70,11 +70,15 @@ describe('permissions middleware', function () {
) )
const res = {} const res = {}
const userApprovalPromise = getUserApprovalPromise(permController)
const pendingApproval = assert.doesNotReject( const pendingApproval = assert.doesNotReject(
aMiddleware(req, res), aMiddleware(req, res),
'should not reject permissions request', 'should not reject permissions request',
) )
await userApprovalPromise
assert.equal( assert.equal(
permController.pendingApprovals.size, 1, permController.pendingApprovals.size, 1,
'perm controller should have single pending approval', 'perm controller should have single pending approval',
@ -131,11 +135,15 @@ describe('permissions middleware', function () {
// send, approve, and validate first request // send, approve, and validate first request
// note use of ACCOUNTS.a.permitted // note use of ACCOUNTS.a.permitted
let userApprovalPromise = getUserApprovalPromise(permController)
const pendingApproval1 = assert.doesNotReject( const pendingApproval1 = assert.doesNotReject(
aMiddleware(req1, res1), aMiddleware(req1, res1),
'should not reject permissions request', 'should not reject permissions request',
) )
await userApprovalPromise
const id1 = permController.pendingApprovals.keys().next().value const id1 = permController.pendingApprovals.keys().next().value
const approvedReq1 = PERMS.approvedRequest(id1, PERMS.requests.eth_accounts()) const approvedReq1 = PERMS.approvedRequest(id1, PERMS.requests.eth_accounts())
@ -187,11 +195,15 @@ describe('permissions middleware', function () {
// send, approve, and validate second request // send, approve, and validate second request
// note use of ACCOUNTS.b.permitted // note use of ACCOUNTS.b.permitted
userApprovalPromise = getUserApprovalPromise(permController)
const pendingApproval2 = assert.doesNotReject( const pendingApproval2 = assert.doesNotReject(
aMiddleware(req2, res2), aMiddleware(req2, res2),
'should not reject permissions request', 'should not reject permissions request',
) )
await userApprovalPromise
const id2 = permController.pendingApprovals.keys().next().value const id2 = permController.pendingApprovals.keys().next().value
const approvedReq2 = PERMS.approvedRequest(id2, { ...requestedPerms2 }) const approvedReq2 = PERMS.approvedRequest(id2, { ...requestedPerms2 })
@ -251,12 +263,16 @@ describe('permissions middleware', function () {
const expectedError = ERRORS.rejectPermissionsRequest.rejection() const expectedError = ERRORS.rejectPermissionsRequest.rejection()
const userApprovalPromise = getUserApprovalPromise(permController)
const requestRejection = assert.rejects( const requestRejection = assert.rejects(
aMiddleware(req, res), aMiddleware(req, res),
expectedError, expectedError,
'request should be rejected with correct error', 'request should be rejected with correct error',
) )
await userApprovalPromise
assert.equal( assert.equal(
permController.pendingApprovals.size, 1, permController.pendingApprovals.size, 1,
'perm controller should have single pending approval', 'perm controller should have single pending approval',
@ -343,11 +359,15 @@ describe('permissions middleware', function () {
) )
const resA1 = {} const resA1 = {}
let userApprovalPromise = getUserApprovalPromise(permController)
const requestApproval1 = assert.doesNotReject( const requestApproval1 = assert.doesNotReject(
aMiddleware(reqA1, resA1), aMiddleware(reqA1, resA1),
'should not reject permissions request', 'should not reject permissions request',
) )
await userApprovalPromise
// create and start processing first request for second origin // create and start processing first request for second origin
const reqB1 = RPC_REQUESTS.requestPermission( const reqB1 = RPC_REQUESTS.requestPermission(
@ -355,11 +375,15 @@ describe('permissions middleware', function () {
) )
const resB1 = {} const resB1 = {}
userApprovalPromise = getUserApprovalPromise(permController)
const requestApproval2 = assert.doesNotReject( const requestApproval2 = assert.doesNotReject(
bMiddleware(reqB1, resB1), bMiddleware(reqB1, resB1),
'should not reject permissions request', 'should not reject permissions request',
) )
await userApprovalPromise
assert.equal( assert.equal(
permController.pendingApprovals.size, 2, permController.pendingApprovals.size, 2,
'perm controller should have expected number of pending approvals', 'perm controller should have expected number of pending approvals',
@ -373,12 +397,17 @@ describe('permissions middleware', function () {
) )
const resA2 = {} const resA2 = {}
await assert.rejects( userApprovalPromise = getUserApprovalPromise(permController)
const requestApprovalFail = assert.rejects(
aMiddleware(reqA2, resA2), aMiddleware(reqA2, resA2),
expectedError, expectedError,
'request should be rejected with correct error', 'request should be rejected with correct error',
) )
await userApprovalPromise
await requestApprovalFail
assert.ok( assert.ok(
( (
!resA2.result && resA2.error && !resA2.result && resA2.error &&

View File

@ -1,105 +0,0 @@
<html>
<head>
<title>Web3 Test Dapp</title>
</head>
<body>
<div style="display: flex; flex-flow: column;">
<div style="display: flex; font-size: 1.25rem;">hexaNumberMethods</div>
<div style="display: flex;">
<button id="eth_blockNumber" class="hexaNumberMethods">eth_blockNumber</button>
<button id="eth_gasPrice" class="hexaNumberMethods">eth_gasPrice</button>
<button id="eth_newBlockFilter" class="hexaNumberMethods">eth_newBlockFilter</button>
<button id="eth_newPendingTransactionFilter" class="hexaNumberMethods">
eth_newPendingTransactionFilter
</button>
<button id="eth_getUncleCountByBlockHash" class="hexaNumberMethods">
eth_getUncleCountByBlockHash
</button>
<button id="eth_getBlockTransactionCountByHash" class="hexaNumberMethods">
getBlockTransactionCountByHash
</button>
</div>
<div style="display: flex ;">
<button id="eth_getTransactionCount" class="hexaNumberMethods">eth_getTransactionCount</button>
<button id="eth_getBalance" class="hexaNumberMethods">eth_getBalance</button>
<button id="eth_estimateGas" class="hexaNumberMethods">eth_estimateGas</button>
</div>
<div style="display: flex ;">
<button id="eth_getUncleCountByBlockNumber" class="hexaNumberMethods">
eth_getUncleCountByBlockNumber
</button>
<button id='eth_getBlockTransactionCountByNumber' class="hexaNumberMethods">
eth_getBlockTransactionCountByNumber
</button>
<button id="eth_protocolVersion" class="hexaNumberMethods">eth_protocolVersion</button>
<button id="eth_getCode" class="hexaNumberMethods">eth_getCode</button>
</div>
</div>
<div style="display: flex; flex-flow: column;">
<div style="display: flex; font-size: 1.25rem;">booleanMethods</div>
<div style="display: flex ;">
<button id="eth_uninstallFilter" class = 'booleanMethods'>eth_uninstallFilter</button>
<button id="eth_mining" class = 'booleanMethods'>eth_mining</button>
<button id="eth_syncing" class = 'booleanMethods'>eth_syncing</button>
</div>
</div>
<div style="display: flex; flex-flow: column;">
<div style="display: flex; font-size: 1.25rem;" >transactionMethods</div>
<div style="display: flex ;">
<button id="eth_getTransactionByHash" class='transactionMethods'>eth_getTransactionByHash</button>
<button id="eth_getTransactionByBlockHashAndIndex" class = 'transactionMethods'>
eth_getTransactionByBlockHashAndIndex
</button>
<button id="eth_getTransactionByBlockNumberAndIndex" class="transactionMethods">
eth_getTransactionByBlockNumberAndIndex
</button>
</div>
</div>
<div style="display: flex; flex-flow: column;">
<div style="display: flex; font-size: 1.25rem;">blockMethods</div>
<div style="display: flex ;">
<button id="eth_getUncleByBlockHashAndIndex" class="blockMethods">
eth_getUncleByBlockHashAndIndex
</button>
<button id="eth_getBlockByHash" class="blockMethods">eth_getBlockByHash</button>
</div>
<div style="display: flex ;">
<button id="eth_getBlockByNumber" class="blockMethods">eth_getBlockByNumber</button>
</div>
</div>
<div style="display: flex; flex-flow: column;">
<div style="display: flex; font-size: 1.25rem;">Methods</div>
<div style="display: flex ;">
<button id="eth_call" class = 'methods'>eth_call</button>
<button id="eth_getStorageAt" class="methods">eth_getStorageAt</button>
<button id="eth_getTransactionReceipt" class="methods">
eth_getTransactionReceipt
</button>
</div>
</div>
<div style="display: flex; flex-flow: column;">
<div id='results'></div>
</div>
</div>
<script src="schema.js"></script>
<script src="web3.js"></script>
</body>
</html>

View File

@ -1,209 +0,0 @@
/* eslint no-unused-vars: 0 */
const params = {
// diffrent params used in the methods
param: [],
blockHashParams: '0xb3b20624f8f0f86eb50dd04688409e5cea4bd02d700bf6e79e9384d47d6a5a35',
filterParams: ['0xfe704947a3cd3ca12541458a4321c869'],
transactionHashParams: [
'0xbb3a336e3f823ec18197f1e13ee875700f08f03e2cab75f0d0b118dabb44cba0',
],
blockHashAndIndexParams: [
'0xb3b20624f8f0f86eb50dd04688409e5cea4bd02d700bf6e79e9384d47d6a5a35',
'0x0',
],
uncleByBlockNumberAndIndexParams: ['0x29c', '0x0'],
blockParameterParams: '0x5bad55',
data: '0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675',
addressParams: '0xc94770007dda54cF92009BFF0dE90c06F603a09f',
getStorageAtParams: [
'0x295a70b2de5e3953354a6a8344e616ed314d7251',
'0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9',
'0x65a8db',
],
getCodeParams: ['0x06012c8cf97bead5deae237070f9587f8e7a266d', '0x65a8db'],
estimateTransaction: {
from: '0xb60e8dd61c5d32be8058bb8eb970870f07233155',
to: '0xd46e8dd67c5d32be8058bb8eb970870f07244567',
gas: '0x76c0',
gasPrice: '0x9184e72a000',
value: '0x9184e72a',
data: '0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675',
},
filterGetLogs: [{ 'blockHash': '0x7c5a35e9cb3e8ae0e221ab470abae9d446c3a5626ce6689fc777dcffcab52c70', 'topics': ['0x241ea03ca20251805084d27d4440371c34a0b85ff108f6bb5611248f73818b80'] }],
block: {
__required: [],
number: 'Q',
hash: 'D32',
parentHash: 'D32',
nonce: 'D',
sha3Uncles: 'D',
logsBloom: 'D',
transactionsRoot: 'D',
stateRoot: 'D',
receiptsRoot: 'D',
miner: 'D',
difficulty: 'Q',
totalDifficulty: 'Q',
extraData: 'D',
size: 'Q',
gasLimit: 'Q',
gasUsed: 'Q',
timestamp: 'Q',
transactions: ['DATA|Transaction'],
uncles: ['D'],
},
transaction: {
__required: [],
hash: 'D32',
nonce: 'Q',
blockHash: 'D32',
blockNumber: 'Q',
transactionIndex: 'Q',
from: 'D20',
to: 'D20',
value: 'Q',
gasPrice: 'Q',
gas: 'Q',
input: 'D',
},
receipt: {
__required: [],
transactionHash: 'D32',
transactionIndex: 'Q',
blockHash: 'D32',
blockNumber: 'Q',
cumulativeGasUsed: 'Q',
gasUsed: 'Q',
contractAddress: 'D20',
logs: ['FilterChange'],
},
filterChange: {
__required: [],
removed: 'B',
logIndex: 'Q',
transactionIndex: 'Q',
transactionHash: 'D32',
blockHash: 'D32',
blockNumber: 'Q',
address: 'D20',
data: 'Array|DATA',
topics: ['D'],
},
}
const methods = {
hexaNumberMethods: {
// these are the methods which have output in the form of hexa decimal numbers
eth_blockNumber: ['eth_blockNumber', params.param, 'Q'],
eth_gasPrice: ['eth_gasPrice', params.param, 'Q'],
eth_newBlockFilter: ['eth_newBlockFilter', params.param, 'Q'],
eth_newPendingTransactionFilter: [
'eth_newPendingTransactionFilter',
params.param,
'Q',
],
eth_getUncleCountByBlockHash: [
'eth_getUncleCountByBlockHash',
[params.blockHashParams],
'Q',
1,
],
eth_getBlockTransactionCountByHash: [
'eth_getBlockTransactionCountByHash',
[params.blockHashParams],
'Q',
1,
],
eth_getTransactionCount: [
'eth_getTransactionCount',
[params.addressParams, params.blockParameterParams],
'Q',
1,
2,
],
eth_getBalance: ['eth_getBalance', [params.addressParams, 'latest'], 'Q', 1, 2],
eth_estimateGas: ['eth_estimateGas', [params.estimateTransaction], 'Q', 1],
eth_getUncleCountByBlockNumber: [
'eth_getUncleCountByBlockNumber',
[params.blockParameterParams],
'Q',
1,
],
eth_getBlockTransactionCountByNumber: [
'eth_getBlockTransactionCountByNumber',
['latest'],
'Q',
1,
],
eth_protocolVersion: ['eth_protocolVersion', params.param, 'S'],
eth_getCode: ['eth_getCode', params.getCodeParams, 'D', 1, 2],
},
booleanMethods: {
// these are the methods which have output in the form of boolean
eth_uninstallFilter: ['eth_uninstallFilter', params.filterParams, 'B', 1],
eth_mining: ['eth_mining', params.param, 'B'],
eth_syncing: ['eth_syncing', params.param, 'B|EthSyncing'],
},
transactionMethods: {
// these are the methods which have output in the form of transaction object
eth_getTransactionByHash: [
'eth_getTransactionByHash',
params.transactionHashParams,
params.transaction,
1,
],
eth_getTransactionByBlockHashAndIndex: [
'eth_getTransactionByBlockHashAndIndex',
params.blockHashAndIndexParams,
params.transaction,
2,
],
eth_getTransactionByBlockNumberAndIndex: [
'eth_getTransactionByBlockNumberAndIndex',
[params.blockParameterParams, '0x0'],
params.transaction,
2,
],
},
blockMethods: {
// these are the methods which have output in the form of a block
eth_getUncleByBlockNumberAndIndex: [
'eth_getUncleByBlockNumberAndIndex',
params.uncleByBlockNumberAndIndexParams,
params.block,
2,
],
eth_getBlockByHash: [
'eth_getBlockByHash',
[params.params, false],
params.block,
2,
],
eth_getBlockByNumber: [
'eth_getBlockByNumber',
[params.blockParameterParams, false],
params.block,
2,
],
},
methods: {
// these are the methods which have output in the form of bytes data
eth_call: ['eth_call', [params.estimateTransaction, 'latest'], 'D', 1, 2],
eth_getStorageAt: ['eth_getStorageAt', params.getStorageAtParams, 'D', 2, 2],
eth_getTransactionReceipt: [
'eth_getTransactionReceipt',
params.transactionHashParams,
params.receipt,
1,
],
},
}

View File

@ -1,34 +0,0 @@
/* eslint no-undef: 0 */
const json = methods
web3.currentProvider.enable().then(() => {
Object.keys(json).forEach((methodGroupKey) => {
console.log(methodGroupKey)
const methodGroup = json[methodGroupKey]
console.log(methodGroup)
Object.keys(methodGroup).forEach((methodKey) => {
const methodButton = document.getElementById(methodKey)
methodButton.addEventListener('click', () => {
window.ethereum.sendAsync({
method: methodKey,
params: methodGroup[methodKey][1],
}, (err, result) => {
if (err) {
console.log(err)
console.log(methodKey)
} else {
document.getElementById('results').innerHTML = JSON.stringify(result)
}
})
})
})
})
})

View File

@ -1,52 +1,47 @@
import React, { Component } from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import copyToClipboard from 'copy-to-clipboard'
import { exportAsFile } from '../../../helpers/utils/util' import { exportAsFile } from '../../../helpers/utils/util'
import Copy from '../icon/copy-icon.component' import Copy from '../icon/copy-icon.component'
import { useI18nContext } from '../../../hooks/useI18nContext'
import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard'
class ExportTextContainer extends Component { function ExportTextContainer ({ text = '' }) {
render () { const t = useI18nContext()
const { text = '' } = this.props const [copied, handleCopy] = useCopyToClipboard()
const { t } = this.context
return ( return (
<div className="export-text-container"> <div className="export-text-container">
<div className="export-text-container__text-container"> <div className="export-text-container__text-container">
<div className="export-text-container__text notranslate"> <div className="export-text-container__text notranslate">{text}</div>
{text} </div>
<div className="export-text-container__buttons-container">
<div
className="export-text-container__button export-text-container__button--copy"
onClick={() => {
handleCopy(text)
}}
>
<Copy size={17} color="#3098DC" />
<div className="export-text-container__button-text">
{copied ? t('copiedExclamation') : t('copyToClipboard')}
</div> </div>
</div> </div>
<div className="export-text-container__buttons-container"> <div
<div className="export-text-container__button"
className="export-text-container__button export-text-container__button--copy" onClick={() => exportAsFile('', text)}
onClick={() => copyToClipboard(text)} >
> <img src="images/download.svg" alt="" />
<Copy size={17} color="#3098DC" /> <div className="export-text-container__button-text">
<div className="export-text-container__button-text"> {t('saveAsCsvFile')}
{t('copyToClipboard')}
</div>
</div>
<div
className="export-text-container__button"
onClick={() => exportAsFile('', text)}
>
<img src="images/download.svg" alt="" />
<div className="export-text-container__button-text">
{t('saveAsCsvFile')}
</div>
</div> </div>
</div> </div>
</div> </div>
) </div>
} )
} }
ExportTextContainer.propTypes = { ExportTextContainer.propTypes = {
text: PropTypes.string, text: PropTypes.string,
} }
ExportTextContainer.contextTypes = { export default React.memo(ExportTextContainer)
t: PropTypes.func,
}
export default ExportTextContainer

View File

@ -41,7 +41,7 @@ export function MetaMetricsProvider ({ children }) {
const numberOfAccounts = useSelector(getNumberOfAccounts) const numberOfAccounts = useSelector(getNumberOfAccounts)
const history = useHistory() const history = useHistory()
const [state, setState] = useState(() => ({ const [state, setState] = useState(() => ({
currentPath: window.location.href, currentPath: (new URL(window.location.href)).pathname,
previousPath: '', previousPath: '',
})) }))
@ -49,7 +49,7 @@ export function MetaMetricsProvider ({ children }) {
useEffect(() => { useEffect(() => {
const unlisten = history.listen(() => setState((prevState) => ({ const unlisten = history.listen(() => setState((prevState) => ({
currentPath: window.location.href, currentPath: (new URL(window.location.href)).pathname,
previousPath: prevState.currentPath, previousPath: prevState.currentPath,
}))) })))
// remove this listener if the component is no longer mounted // remove this listener if the component is no longer mounted
@ -59,8 +59,8 @@ export function MetaMetricsProvider ({ children }) {
const metricsEvent = useCallback((config = {}, overrides = {}) => { const metricsEvent = useCallback((config = {}, overrides = {}) => {
const { eventOpts = {} } = config const { eventOpts = {} } = config
const { name = '' } = eventOpts const { name = '' } = eventOpts
const { pathname: overRidePathName = '' } = overrides const { currentPath: overrideCurrentPath = '' } = overrides
const isSendFlow = Boolean(name.match(/^send|^confirm/) || overRidePathName.match(/send|confirm/)) const isSendFlow = Boolean(name.match(/^send|^confirm/) || overrideCurrentPath.match(/send|confirm/))
if (participateInMetaMetrics || config.isOptIn) { if (participateInMetaMetrics || config.isOptIn) {
return sendMetaMetricsEvent({ return sendMetaMetricsEvent({

View File

@ -75,7 +75,6 @@
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
height: 16px;
} }
.dropdown-menu-item .fa.delete { .dropdown-menu-item .fa.delete {
@ -175,7 +174,7 @@
} }
.network-dropdown-content { .network-dropdown-content {
height: 36px; min-height: 36px;
width: 265px; width: 265px;
color: $dusty-gray; color: $dusty-gray;
font-family: Roboto; font-family: Roboto;

View File

@ -6,6 +6,7 @@ import * as Sentry from '@sentry/browser'
const warned = {} const warned = {}
const missingMessageErrors = {} const missingMessageErrors = {}
const missingSubstitutionErrors = {}
/** /**
* Returns a localized message for the given key * Returns a localized message for the given key
@ -55,7 +56,11 @@ export const getMessage = (localeCode, localeMessages, key, substitutions) => {
return part return part
} }
const substituteIndex = Number(subMatch[1]) - 1 const substituteIndex = Number(subMatch[1]) - 1
if (substitutions[substituteIndex] == null) { if (substitutions[substituteIndex] == null && !missingSubstitutionErrors[localeCode]?.[key]) {
if (!missingSubstitutionErrors[localeCode]) {
missingSubstitutionErrors[localeCode] = {}
}
missingSubstitutionErrors[localeCode][key] = true
const error = new Error(`Insufficient number of substitutions for message: '${phrase}'`) const error = new Error(`Insufficient number of substitutions for message: '${phrase}'`)
log.error(error) log.error(error)
Sentry.captureException(error) Sentry.captureException(error)

View File

@ -2,10 +2,15 @@
import ethUtil from 'ethereumjs-util' import ethUtil from 'ethereumjs-util'
const inDevelopment = process.env.NODE_ENV === 'development' const inDevelopment = process.env.METAMASK_DEBUG || process.env.IN_TEST
let projectId = process.env.METAMETRICS_PROJECT_ID
if (!projectId) {
projectId = inDevelopment ? 1 : 2
}
const METAMETRICS_BASE_URL = 'https://chromeextensionmm.innocraft.cloud/piwik.php' const METAMETRICS_BASE_URL = 'https://chromeextensionmm.innocraft.cloud/piwik.php'
const METAMETRICS_REQUIRED_PARAMS = `?idsite=${inDevelopment ? 1 : 2}&rec=1&apiv=1` const METAMETRICS_REQUIRED_PARAMS = `?idsite=${projectId}&rec=1&apiv=1`
const METAMETRICS_BASE_FULL = METAMETRICS_BASE_URL + METAMETRICS_REQUIRED_PARAMS const METAMETRICS_BASE_FULL = METAMETRICS_BASE_URL + METAMETRICS_REQUIRED_PARAMS
const METAMETRICS_TRACKING_URL = inDevelopment const METAMETRICS_TRACKING_URL = inDevelopment
@ -18,7 +23,7 @@ const METAMETRICS_CUSTOM_GAS_LIMIT_CHANGE = 'gasLimitChange'
const METAMETRICS_CUSTOM_GAS_PRICE_CHANGE = 'gasPriceChange' const METAMETRICS_CUSTOM_GAS_PRICE_CHANGE = 'gasPriceChange'
const METAMETRICS_CUSTOM_FUNCTION_TYPE = 'functionType' const METAMETRICS_CUSTOM_FUNCTION_TYPE = 'functionType'
const METAMETRICS_CUSTOM_RECIPIENT_KNOWN = 'recipientKnown' const METAMETRICS_CUSTOM_RECIPIENT_KNOWN = 'recipientKnown'
const METAMETRICS_CUSTOM_CONFIRM_SCREEN_ORIGIN = 'origin' const METAMETRICS_REQUEST_ORIGIN = 'origin'
const METAMETRICS_CUSTOM_FROM_NETWORK = 'fromNetwork' const METAMETRICS_CUSTOM_FROM_NETWORK = 'fromNetwork'
const METAMETRICS_CUSTOM_TO_NETWORK = 'toNetwork' const METAMETRICS_CUSTOM_TO_NETWORK = 'toNetwork'
const METAMETRICS_CUSTOM_ERROR_FIELD = 'errorField' const METAMETRICS_CUSTOM_ERROR_FIELD = 'errorField'
@ -31,7 +36,7 @@ const METAMETRICS_CUSTOM_ASSET_SELECTED = 'assetSelected'
const customVariableNameIdMap = { const customVariableNameIdMap = {
[METAMETRICS_CUSTOM_FUNCTION_TYPE]: 1, [METAMETRICS_CUSTOM_FUNCTION_TYPE]: 1,
[METAMETRICS_CUSTOM_RECIPIENT_KNOWN]: 2, [METAMETRICS_CUSTOM_RECIPIENT_KNOWN]: 2,
[METAMETRICS_CUSTOM_CONFIRM_SCREEN_ORIGIN]: 3, [METAMETRICS_REQUEST_ORIGIN]: 3,
[METAMETRICS_CUSTOM_GAS_LIMIT_CHANGE]: 4, [METAMETRICS_CUSTOM_GAS_LIMIT_CHANGE]: 4,
[METAMETRICS_CUSTOM_GAS_PRICE_CHANGE]: 5, [METAMETRICS_CUSTOM_GAS_PRICE_CHANGE]: 5,
@ -69,7 +74,7 @@ const customDimensionsNameIdMap = {
function composeUrlRefParamAddition (previousPath, confirmTransactionOrigin) { function composeUrlRefParamAddition (previousPath, confirmTransactionOrigin) {
const externalOrigin = confirmTransactionOrigin && confirmTransactionOrigin !== 'metamask' const externalOrigin = confirmTransactionOrigin && confirmTransactionOrigin !== 'metamask'
return `&urlref=${externalOrigin ? 'EXTERNAL' : encodeURIComponent(previousPath.replace(/chrome-extension:\/\/\w+/, METAMETRICS_TRACKING_URL))}` return `&urlref=${externalOrigin ? 'EXTERNAL' : encodeURIComponent(`${METAMETRICS_TRACKING_URL}${previousPath}`)}`
} }
// composes query params of the form &dimension[0-999]=[value] // composes query params of the form &dimension[0-999]=[value]
@ -110,11 +115,10 @@ function composeParamAddition (paramValue, paramName) {
* @property {string} config.accountType The account type being used at the time of the event: 'hardware', 'imported' or 'default' * @property {string} config.accountType The account type being used at the time of the event: 'hardware', 'imported' or 'default'
* @property {number} config.numberOfTokens The number of tokens that the user has added at the time of the event * @property {number} config.numberOfTokens The number of tokens that the user has added at the time of the event
* @property {number} config.numberOfAccounts The number of accounts the user has added at the time of the event * @property {number} config.numberOfAccounts The number of accounts the user has added at the time of the event
* @property {string} config.previousPath The location path the user was on prior to the path they are on at the time of the event * @property {string} config.previousPath The pathname of the URL the user was on prior to the URL they are on at the time of the event
* @property {string} config.currentPath The location path the user is on at the time of the event * @property {string} config.currentPath The pathname of the URL the user is on at the time of the event
* @property {string} config.metaMetricsId A random id assigned to a user at the time of opting in to metametrics. A hexadecimal number * @property {string} config.metaMetricsId A random id assigned to a user at the time of opting in to metametrics. A hexadecimal number
* @property {string} config.confirmTransactionOrigin The origin on a transaction * @property {string} config.confirmTransactionOrigin The origin on a transaction
* @property {string} config.url The url to track an event at. Overrides `currentPath`
* @property {boolean} config.excludeMetaMetricsId Whether or not the tracked event data should be associated with a metametrics id * @property {boolean} config.excludeMetaMetricsId Whether or not the tracked event data should be associated with a metametrics id
* @property {boolean} config.isNewVisit Whether or not the event should be tracked as a new visit/user sessions * @property {boolean} config.isNewVisit Whether or not the event should be tracked as a new visit/user sessions
* @returns {string} - Returns a url to be passed to fetch to make the appropriate request to matomo. * @returns {string} - Returns a url to be passed to fetch to make the appropriate request to matomo.
@ -136,7 +140,6 @@ function composeUrl (config) {
currentPath, currentPath,
metaMetricsId, metaMetricsId,
confirmTransactionOrigin, confirmTransactionOrigin,
url: configUrl,
excludeMetaMetricsId, excludeMetaMetricsId,
isNewVisit, isNewVisit,
} = config } = config
@ -162,10 +165,10 @@ function composeUrl (config) {
numberOfTokens: (customVariables && customVariables.numberOfTokens) || numberOfTokens, numberOfTokens: (customVariables && customVariables.numberOfTokens) || numberOfTokens,
numberOfAccounts: (customVariables && customVariables.numberOfAccounts) || numberOfAccounts, numberOfAccounts: (customVariables && customVariables.numberOfAccounts) || numberOfAccounts,
}) : '' }) : ''
const url = configUrl || currentPath ? `&url=${encodeURIComponent(currentPath.replace(/chrome-extension:\/\/\w+/, METAMETRICS_TRACKING_URL))}` : '' const url = currentPath ? `&url=${encodeURIComponent(`${METAMETRICS_TRACKING_URL}${currentPath}`)}` : ''
const _id = metaMetricsId && !excludeMetaMetricsId ? `&_id=${metaMetricsId.slice(2, 18)}` : '' const _id = metaMetricsId && !excludeMetaMetricsId ? `&_id=${metaMetricsId.slice(2, 18)}` : ''
const rand = `&rand=${String(Math.random()).slice(2)}` const rand = `&rand=${String(Math.random()).slice(2)}`
const pv_id = ((url || currentPath) && `&pv_id=${ethUtil.bufferToHex(ethUtil.sha3(url || currentPath.match(/chrome-extension:\/\/\w+\/(.+)/)[0])).slice(2, 8)}`) || '' const pv_id = currentPath ? `&pv_id=${ethUtil.bufferToHex(ethUtil.sha3(currentPath)).slice(2, 8)}` : ''
const uid = metaMetricsId && !excludeMetaMetricsId const uid = metaMetricsId && !excludeMetaMetricsId
? `&uid=${metaMetricsId.slice(2, 18)}` ? `&uid=${metaMetricsId.slice(2, 18)}`
: excludeMetaMetricsId : excludeMetaMetricsId

View File

@ -0,0 +1,28 @@
import { useState, useCallback } from 'react'
import copyToClipboard from 'copy-to-clipboard'
import { useTimeout } from './useTimeout'
/**
* useCopyToClipboard
*
* @param {number} [delay=3000] - delay in ms
*
* @return {[boolean, Function]}
*/
const DEFAULT_DELAY = 3000
export function useCopyToClipboard (delay = DEFAULT_DELAY) {
const [copied, setCopied] = useState(false)
const startTimeout = useTimeout(() => setCopied(false), delay, false)
const handleCopy = useCallback(
(text) => {
setCopied(true)
startTimeout()
copyToClipboard(text)
},
[startTimeout],
)
return [copied, handleCopy]
}

View File

@ -0,0 +1,46 @@
import { useState, useEffect, useRef, useCallback } from 'react'
/**
* useTimeout
*
* @param {Function} cb - callback function inside setTimeout
* @param {number} delay - delay in ms
* @param {boolean} [immediate] - determines whether the timeout is invoked immediately
*
* @return {Function}
*/
export function useTimeout (cb, delay, immediate = true) {
const saveCb = useRef()
const [timeoutId, setTimeoutId] = useState(null)
useEffect(() => {
saveCb.current = cb
}, [cb])
useEffect(() => {
if (timeoutId !== 'start') {
return
}
const id = setTimeout(() => {
saveCb.current()
}, delay)
setTimeoutId(id)
return () => {
clearTimeout(timeoutId)
}
}, [delay, timeoutId])
const startTimeout = useCallback(() => {
clearTimeout(timeoutId)
setTimeoutId('start')
}, [timeoutId])
if (immediate) {
startTimeout()
}
return startTimeout
}

View File

@ -19,6 +19,7 @@ export default class ImportWithSeedPhrase extends PureComponent {
onSubmit: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired,
setSeedPhraseBackedUp: PropTypes.func, setSeedPhraseBackedUp: PropTypes.func,
initializeThreeBox: PropTypes.func, initializeThreeBox: PropTypes.func,
completeOnboarding: PropTypes.func,
} }
state = { state = {
@ -119,7 +120,7 @@ export default class ImportWithSeedPhrase extends PureComponent {
} }
const { password, seedPhrase } = this.state const { password, seedPhrase } = this.state
const { history, onSubmit, setSeedPhraseBackedUp, initializeThreeBox } = this.props const { history, onSubmit, setSeedPhraseBackedUp, initializeThreeBox, completeOnboarding } = this.props
try { try {
await onSubmit(password, this.parseSeedPhrase(seedPhrase)) await onSubmit(password, this.parseSeedPhrase(seedPhrase))
@ -131,7 +132,8 @@ export default class ImportWithSeedPhrase extends PureComponent {
}, },
}) })
setSeedPhraseBackedUp(true).then(() => { setSeedPhraseBackedUp(true).then(async () => {
await completeOnboarding()
initializeThreeBox() initializeThreeBox()
history.push(INITIALIZE_END_OF_FLOW_ROUTE) history.push(INITIALIZE_END_OF_FLOW_ROUTE)
}) })

View File

@ -3,12 +3,14 @@ import ImportWithSeedPhrase from './import-with-seed-phrase.component'
import { import {
setSeedPhraseBackedUp, setSeedPhraseBackedUp,
initializeThreeBox, initializeThreeBox,
setCompletedOnboarding,
} from '../../../../store/actions' } from '../../../../store/actions'
const mapDispatchToProps = (dispatch) => { const mapDispatchToProps = (dispatch) => {
return { return {
setSeedPhraseBackedUp: (seedPhraseBackupState) => dispatch(setSeedPhraseBackedUp(seedPhraseBackupState)), setSeedPhraseBackedUp: (seedPhraseBackupState) => dispatch(setSeedPhraseBackedUp(seedPhraseBackupState)),
initializeThreeBox: () => dispatch(initializeThreeBox()), initializeThreeBox: () => dispatch(initializeThreeBox()),
completeOnboarding: () => dispatch(setCompletedOnboarding()),
} }
} }

View File

@ -14,7 +14,6 @@ export default class EndOfFlowScreen extends PureComponent {
static propTypes = { static propTypes = {
history: PropTypes.object, history: PropTypes.object,
completeOnboarding: PropTypes.func,
completionMetaMetricsName: PropTypes.string, completionMetaMetricsName: PropTypes.string,
onboardingInitiator: PropTypes.exact({ onboardingInitiator: PropTypes.exact({
location: PropTypes.string, location: PropTypes.string,
@ -23,9 +22,8 @@ export default class EndOfFlowScreen extends PureComponent {
} }
onComplete = async () => { onComplete = async () => {
const { history, completeOnboarding, completionMetaMetricsName, onboardingInitiator } = this.props const { history, completionMetaMetricsName, onboardingInitiator } = this.props
await completeOnboarding()
this.context.metricsEvent({ this.context.metricsEvent({
eventOpts: { eventOpts: {
category: 'Onboarding', category: 'Onboarding',

View File

@ -1,6 +1,5 @@
import { connect } from 'react-redux' import { connect } from 'react-redux'
import EndOfFlow from './end-of-flow.component' import EndOfFlow from './end-of-flow.component'
import { setCompletedOnboarding } from '../../../store/actions'
import { getOnboardingInitiator } from '../../../selectors' import { getOnboardingInitiator } from '../../../selectors'
const firstTimeFlowTypeNameMap = { const firstTimeFlowTypeNameMap = {
@ -17,10 +16,4 @@ const mapStateToProps = (state) => {
} }
} }
const mapDispatchToProps = (dispatch) => { export default connect(mapStateToProps)(EndOfFlow)
return {
completeOnboarding: () => dispatch(setCompletedOnboarding()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(EndOfFlow)

View File

@ -12,7 +12,6 @@ describe('End of Flow Screen', function () {
history: { history: {
push: sinon.spy(), push: sinon.spy(),
}, },
completeOnboarding: sinon.spy(),
} }
beforeEach(function () { beforeEach(function () {
@ -30,7 +29,6 @@ describe('End of Flow Screen', function () {
endOfFlowButton.simulate('click') endOfFlowButton.simulate('click')
setImmediate(() => { setImmediate(() => {
assert(props.completeOnboarding.calledOnce)
assert(props.history.push.calledOnceWithExactly(DEFAULT_ROUTE)) assert(props.history.push.calledOnceWithExactly(DEFAULT_ROUTE))
done() done()
}) })

View File

@ -26,6 +26,7 @@ export default class ConfirmSeedPhrase extends PureComponent {
seedPhrase: PropTypes.string, seedPhrase: PropTypes.string,
initializeThreeBox: PropTypes.func, initializeThreeBox: PropTypes.func,
setSeedPhraseBackedUp: PropTypes.func, setSeedPhraseBackedUp: PropTypes.func,
completeOnboarding: PropTypes.func,
} }
state = { state = {
@ -66,6 +67,10 @@ export default class ConfirmSeedPhrase extends PureComponent {
exportAsFile('', this.props.seedPhrase, 'text/plain') exportAsFile('', this.props.seedPhrase, 'text/plain')
} }
setOnboardingCompleted = async () => {
await this.props.completeOnboarding()
}
handleSubmit = async () => { handleSubmit = async () => {
const { const {
history, history,
@ -86,8 +91,9 @@ export default class ConfirmSeedPhrase extends PureComponent {
}, },
}) })
setSeedPhraseBackedUp(true).then(() => { setSeedPhraseBackedUp(true).then(async () => {
initializeThreeBox() initializeThreeBox()
this.setOnboardingCompleted()
history.push(INITIALIZE_END_OF_FLOW_ROUTE) history.push(INITIALIZE_END_OF_FLOW_ROUTE)
}) })
} catch (error) { } catch (error) {

View File

@ -3,12 +3,14 @@ import ConfirmSeedPhrase from './confirm-seed-phrase.component'
import { import {
setSeedPhraseBackedUp, setSeedPhraseBackedUp,
initializeThreeBox, initializeThreeBox,
setCompletedOnboarding,
} from '../../../../store/actions' } from '../../../../store/actions'
const mapDispatchToProps = (dispatch) => { const mapDispatchToProps = (dispatch) => {
return { return {
setSeedPhraseBackedUp: (seedPhraseBackupState) => dispatch(setSeedPhraseBackedUp(seedPhraseBackupState)), setSeedPhraseBackedUp: (seedPhraseBackupState) => dispatch(setSeedPhraseBackedUp(seedPhraseBackupState)),
initializeThreeBox: () => dispatch(initializeThreeBox()), initializeThreeBox: () => dispatch(initializeThreeBox()),
completeOnboarding: () => dispatch(setCompletedOnboarding()),
} }
} }

View File

@ -142,6 +142,7 @@ describe('ConfirmSeedPhrase Component', function () {
history: { push: pushSpy }, history: { push: pushSpy },
setSeedPhraseBackedUp: () => Promise.resolve(), setSeedPhraseBackedUp: () => Promise.resolve(),
initializeThreeBox: initialize3BoxSpy, initializeThreeBox: initialize3BoxSpy,
completeOnboarding: sinon.spy(),
}, },
{ {
metricsEvent: metricsEventSpy, metricsEvent: metricsEventSpy,

View File

@ -98,11 +98,8 @@ export default class Routes extends Component {
this.props.history.listen((locationObj, action) => { this.props.history.listen((locationObj, action) => {
if (action === 'PUSH') { if (action === 'PUSH') {
pageChanged(locationObj.pathname) pageChanged(locationObj.pathname)
const url = `&url=${encodeURIComponent('http://www.metamask.io/metametrics' + locationObj.pathname)}`
this.context.metricsEvent({}, { this.context.metricsEvent({}, {
currentPath: '', currentPath: locationObj.pathname,
pathname: locationObj.pathname,
url,
pageOpts: { pageOpts: {
hideDimensions: true, hideDimensions: true,
}, },

View File

@ -106,7 +106,8 @@
height: 20px; height: 20px;
padding: 0; padding: 0;
background: none; background: none;
padding-left: 10px; padding-left: 0;
margin-left: 10px;
} }
} }

View File

@ -1,85 +1,102 @@
import React, { PureComponent } from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { Redirect } from 'react-router-dom' import { Redirect } from 'react-router-dom'
import Identicon from '../../../../components/ui/identicon' import Identicon from '../../../../components/ui/identicon'
import Copy from '../../../../components/ui/icon/copy-icon.component' import Copy from '../../../../components/ui/icon/copy-icon.component'
import Button from '../../../../components/ui/button/button.component' import Button from '../../../../components/ui/button/button.component'
import copyToClipboard from 'copy-to-clipboard'
import Tooltip from '../../../../components/ui/tooltip-v2'
import { useI18nContext } from '../../../../hooks/useI18nContext'
import { useCopyToClipboard } from '../../../../hooks/useCopyToClipboard'
function quadSplit (address) { function quadSplit (address) {
return '0x ' + address.slice(2).match(/.{1,4}/g).join(' ') return (
'0x ' +
address
.slice(2)
.match(/.{1,4}/g)
.join(' ')
)
} }
export default class ViewContact extends PureComponent { function ViewContact ({
history,
name,
address,
checkSummedAddress,
memo,
editRoute,
listRoute,
}) {
const t = useI18nContext()
const [copied, handleCopy] = useCopyToClipboard()
static contextTypes = { if (!address) {
t: PropTypes.func, return <Redirect to={{ pathname: listRoute }} />
} }
static propTypes = { return (
name: PropTypes.string, <div className="settings-page__content-row">
address: PropTypes.string, <div className="settings-page__content-item">
history: PropTypes.object, <div className="settings-page__header address-book__header">
checkSummedAddress: PropTypes.string, <Identicon address={address} diameter={60} />
memo: PropTypes.string, <div className="address-book__header__name">{name}</div>
editRoute: PropTypes.string, </div>
listRoute: PropTypes.string.isRequired, <div className="address-book__view-contact__group">
} <Button
type="secondary"
render () { onClick={() => {
const { t } = this.context history.push(`${editRoute}/${address}`)
const { history, name, address, checkSummedAddress, memo, editRoute, listRoute } = this.props }}
>
if (!address) { {t('edit')}
return <Redirect to={{ pathname: listRoute }} /> </Button>
} </div>
<div className="address-book__view-contact__group">
return ( <div className="address-book__view-contact__group__label">
<div className="settings-page__content-row"> {t('ethereumPublicAddress')}
<div className="settings-page__content-item">
<div className="settings-page__header address-book__header">
<Identicon address={address} diameter={60} />
<div className="address-book__header__name">{ name }</div>
</div> </div>
<div className="address-book__view-contact__group"> <div className="address-book__view-contact__group__value">
<Button <div className="address-book__view-contact__group__static-address">
type="secondary" {quadSplit(checkSummedAddress)}
onClick={() => {
history.push(`${editRoute}/${address}`)
}}
>
{t('edit')}
</Button>
</div>
<div className="address-book__view-contact__group">
<div className="address-book__view-contact__group__label">
{ t('ethereumPublicAddress') }
</div> </div>
<div className="address-book__view-contact__group__value"> <Tooltip
<div position="bottom"
className="address-book__view-contact__group__static-address" title={copied ? t('copiedExclamation') : t('copyToClipboard')}
> >
{ quadSplit(checkSummedAddress) }
</div>
<button <button
className="address-book__view-contact__group__static-address--copy-icon" className="address-book__view-contact__group__static-address--copy-icon"
onClick={() => copyToClipboard(checkSummedAddress)} onClick={() => {
handleCopy(checkSummedAddress)
}}
> >
<Copy size={20} color="#3098DC" /> <Copy size={20} color="#3098DC" />
</button> </button>
</div> </Tooltip>
</div> </div>
<div className="address-book__view-contact__group"> </div>
<div className="address-book__view-contact__group__label--capitalized"> <div className="address-book__view-contact__group">
{ t('memo') } <div className="address-book__view-contact__group__label--capitalized">
</div> {t('memo')}
<div className="address-book__view-contact__group__static-address"> </div>
{ memo } <div className="address-book__view-contact__group__static-address">
</div> {memo}
</div> </div>
</div> </div>
</div> </div>
) </div>
} )
} }
ViewContact.propTypes = {
name: PropTypes.string,
address: PropTypes.string,
history: PropTypes.object,
checkSummedAddress: PropTypes.string,
memo: PropTypes.string,
editRoute: PropTypes.string,
listRoute: PropTypes.string.isRequired,
}
export default React.memo(ViewContact)

View File

@ -76,12 +76,12 @@ export default class InfoTab extends PureComponent {
</div> </div>
<div className="info-tab__link-item"> <div className="info-tab__link-item">
<a <a
href="mailto:help@metamask.io?subject=Feedback" href="https://metamask.zendesk.com/hc/en-us/requests/new"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<span className="info-tab__link-text"> <span className="info-tab__link-text">
{ t('emailUs') } { t('contactUs') }
</span> </span>
</a> </a>
</div> </div>

View File

@ -168,7 +168,7 @@ export function createNewVault (password) {
export function verifyPassword (password) { export function verifyPassword (password) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
background.submitPassword(password, (error) => { background.verifyPassword(password, (error) => {
if (error) { if (error) {
return reject(error) return reject(error)
} }
@ -193,7 +193,7 @@ export function verifySeedPhrase () {
export function requestRevealSeedWords (password) { export function requestRevealSeedWords (password) {
return async (dispatch) => { return async (dispatch) => {
dispatch(showLoadingIndication()) dispatch(showLoadingIndication())
log.debug(`background.submitPassword`) log.debug(`background.verifyPassword`)
try { try {
await verifyPassword(password) await verifyPassword(password)

799
yarn.lock

File diff suppressed because it is too large Load Diff