EIP-1102: updated implementation

feature/default_network_editable
bitpshr 6 years ago committed by Dan Finlay
parent 2d4ff1dd82
commit c76c9ca2c8
  1. 30
      app/_locales/cs/messages.json
  2. 30
      app/_locales/de/messages.json
  3. 30
      app/_locales/en/messages.json
  4. 30
      app/_locales/es/messages.json
  5. 30
      app/_locales/fr/messages.json
  6. 30
      app/_locales/hn/messages.json
  7. 30
      app/_locales/it/messages.json
  8. 30
      app/_locales/ja/messages.json
  9. 30
      app/_locales/ko/messages.json
  10. 30
      app/_locales/nl/messages.json
  11. 30
      app/_locales/ph/messages.json
  12. 30
      app/_locales/pt/messages.json
  13. 30
      app/_locales/ru/messages.json
  14. 32
      app/_locales/sl/messages.json
  15. 30
      app/_locales/th/messages.json
  16. 30
      app/_locales/tml/messages.json
  17. 30
      app/_locales/tr/messages.json
  18. 30
      app/_locales/vi/messages.json
  19. 30
      app/_locales/zh_CN/messages.json
  20. 30
      app/_locales/zh_TW/messages.json
  21. 5
      app/scripts/background.js
  22. 61
      app/scripts/contentscript.js
  23. 4
      app/scripts/controllers/preferences.js
  24. 84
      app/scripts/controllers/provider-approval.js
  25. 23
      app/scripts/inpage.js
  26. 21
      app/scripts/metamask-controller.js
  27. 12
      app/scripts/platforms/extension.js
  28. 10
      old-ui/app/app.js
  29. 25
      old-ui/app/config.js
  30. 64
      old-ui/app/provider-approval.js
  31. 14606
      package-lock.json
  32. 199
      test/e2e/beta/contract-test/contract.js
  33. 17
      test/e2e/beta/helpers.js
  34. 25
      test/e2e/beta/metamask-beta-ui.spec.js
  35. 31
      test/e2e/metamask.spec.js
  36. 4
      test/unit/app/controllers/preferences-controller-test.js
  37. 21
      ui/app/actions.js
  38. 39
      ui/app/components/modals/clear-approved-origins/clear-approved-origins.component.js
  39. 16
      ui/app/components/modals/clear-approved-origins/clear-approved-origins.container.js
  40. 1
      ui/app/components/modals/clear-approved-origins/index.js
  41. 14
      ui/app/components/modals/modal.js
  42. 1
      ui/app/components/page-container/index.scss
  43. 8
      ui/app/components/pages/home/home.component.js
  44. 2
      ui/app/components/pages/home/home.container.js
  45. 1
      ui/app/components/pages/provider-approval/index.js
  46. 35
      ui/app/components/pages/provider-approval/provider-approval.component.js
  47. 12
      ui/app/components/pages/provider-approval/provider-approval.container.js
  48. 32
      ui/app/components/pages/settings/settings-tab/settings-tab.component.js
  49. 1
      ui/app/components/pages/settings/settings-tab/settings-tab.container.js
  50. 2
      ui/app/css/itcss/components/pages/index.scss
  51. 11
      ui/app/css/itcss/components/pages/provider-approval.scss

@ -1,4 +1,34 @@
{ {
"confirmClear": {
"message": "Naozaj chcete vymazať schválené webové stránky?"
},
"clearApprovalDataSuccess": {
"message": "Schválené údaje webových stránek byly úspěšně zrušeny."
},
"approvalData": {
"message": "Údaje o schválení"
},
"approvalDataDescription": {
"message": "Vymažte schválené údaje webových stránek, aby všechny weby znovu požádaly o schválení."
},
"clearApprovalData": {
"message": "Jasné údaje o schválení"
},
"approve": {
"message": "Schválit"
},
"reject": {
"message": "Odmítnout"
},
"providerAPIRequest": {
"message": "Požadavek API Ethereum"
},
"reviewProviderRequest": {
"message": "Přečtěte si prosím tuto žádost API Ethereum."
},
"providerRequestInfo": {
"message": "Níže uvedená doména se pokouší požádat o přístup k API Ethereum, aby mohla komunikovat s blokádou Ethereum. Před schválením přístupu Ethereum vždy zkontrolujte, zda jste na správném místě."
},
"accept": { "accept": {
"message": "Přijmout" "message": "Přijmout"
}, },

@ -1,4 +1,34 @@
{ {
"confirmClear": {
"message": "Möchten Sie die genehmigten Websites wirklich löschen?"
},
"clearApprovalDataSuccess": {
"message": "Genehmigte Website-Daten wurden erfolgreich gelöscht."
},
"approvalData": {
"message": "Genehmigungsdaten"
},
"approvalDataDescription": {
"message": "Löschen Sie die genehmigten Website-Daten, damit alle Websites erneut eine Genehmigung anfordern müssen."
},
"clearApprovalData": {
"message": "Genehmigungsdaten löschen"
},
"approve": {
"message": "Genehmigen"
},
"reject": {
"message": "Ablehnen"
},
"providerAPIRequest": {
"message": "Web3-API-Anfrage"
},
"reviewProviderRequest": {
"message": "Bitte lesen Sie diese Ethereum-API-Anfrage."
},
"providerRequestInfo": {
"message": "Die unten aufgeführte Domäne versucht, Zugriff auf die Ethereum-API anzufordern, damit sie mit der Ethereum-Blockchain interagieren kann. Überprüfen Sie immer, dass Sie sich auf der richtigen Website befinden, bevor Sie den Web3-Zugriff genehmigen."
},
"accept": { "accept": {
"message": "Annehmen" "message": "Annehmen"
}, },

@ -1,4 +1,34 @@
{ {
"confirmClear": {
"message": "Are you sure you want to clear approved websites?"
},
"clearApprovalDataSuccess": {
"message": "Approved website data cleared successfully."
},
"approvalData": {
"message": "Approval Data"
},
"approvalDataDescription": {
"message": "Clear approved website data so all sites must request approval again."
},
"clearApprovalData": {
"message": "Clear Approval Data"
},
"approve": {
"message": "Approve"
},
"reject": {
"message": "Reject"
},
"providerAPIRequest": {
"message": "Ethereum API Request"
},
"reviewProviderRequest": {
"message": "Please review this Ethereum API request."
},
"providerRequestInfo": {
"message": "The domain listed below is requesting access to the Ethereum blockchain and to view your current account. Always double check that you're on the correct site before approving access."
},
"accept": { "accept": {
"message": "Accept" "message": "Accept"
}, },

@ -1,4 +1,34 @@
{ {
"confirmClear": {
"message": "¿Seguro que quieres borrar los sitios web aprobados?"
},
"clearApprovalDataSuccess": {
"message": "Los datos aprobados del sitio web se borraron con éxito."
},
"approvalData": {
"message": "Datos de aprobación"
},
"approvalDataDescription": {
"message": "Borre los datos del sitio web aprobado para que todos los sitios deban volver a solicitar la aprobación."
},
"clearApprovalData": {
"message": "Borrar datos de aprobación"
},
"approve": {
"message": "Aprobar"
},
"reject": {
"message": "Rechazar"
},
"providerAPIRequest": {
"message": "Solicitud de API Web3"
},
"reviewProviderRequest": {
"message": "Por favor, revise esta solicitud API Ethereum."
},
"providerRequestInfo": {
"message": "El dominio que se muestra a continuación intenta solicitar acceso a la API Ethereum para que pueda interactuar con la cadena de bloques de Ethereum. Siempre verifique que esté en el sitio correcto antes de aprobar el acceso Ethereum."
},
"accept": { "accept": {
"message": "Aceptar" "message": "Aceptar"
}, },

@ -1,4 +1,34 @@
{ {
"confirmClear": {
"message": "Êtes-vous sûr de vouloir supprimer les sites Web approuvés?"
},
"clearApprovalDataSuccess": {
"message": "Les données de site Web approuvées ont été supprimées."
},
"approvalData": {
"message": "Données d'approbation"
},
"approvalDataDescription": {
"message": "Effacer les données de site Web approuvées afin que tous les sites doivent à nouveau demander l'approbation."
},
"clearApprovalData": {
"message": "Effacer les données d'approbation"
},
"approve": {
"message": "Approuver"
},
"reject": {
"message": "Rejeter"
},
"providerAPIRequest": {
"message": "Demande d'API Web3"
},
"reviewProviderRequest": {
"message": "Veuillez consulter cette demande d'API Ethereum."
},
"providerRequestInfo": {
"message": "Le domaine répertorié ci-dessous tente de demander l'accès à l'API Ethereum pour pouvoir interagir avec la chaîne de blocs Ethereum. Vérifiez toujours que vous êtes sur le bon site avant d'autoriser l'accès à Ethereum."
},
"accept": { "accept": {
"message": "Accepter" "message": "Accepter"
}, },

@ -1,4 +1,34 @@
{ {
"confirmClear": {
"message": "क आप वकई अनित वबसइट करनहत?"
},
"clearApprovalDataSuccess": {
"message": "सत वबसइट ड सफलतवक म।"
},
"approvalData": {
"message": "सि"
},
"approvalDataDescription": {
"message": "अनित वबसइट ड करि सभइटिर स अनदन क अनध करन।"
},
"clearApprovalData": {
"message": "अनदन ड कर"
},
"approve": {
"message": "मर"
},
"reject": {
"message": "असर"
},
"providerAPIRequest": {
"message": "वब 3 एपआई अनध"
},
"reviewProviderRequest": {
"message": "कपय इस वब 3 एपआई अनध क सम कर।"
},
"providerRequestInfo": {
"message": "नबदध डन वब 3 एपआई तक पहच क अनध करनरयस कर रहि यह एथियम बकचन सतचत कर सक। वब 3 एकस क पहल हम सहच करि आप सहइट पर ह।"
},
"accept": { "accept": {
"message": "सर कर" "message": "सर कर"
}, },

@ -1,4 +1,34 @@
{ {
"confirmClear": {
"message": "Sei sicuro di voler cancellare i siti Web approvati?"
},
"clearApprovalDataSuccess": {
"message": "Dati del sito Web approvati cancellati correttamente."
},
"approvalData": {
"message": "Dati di approvazione"
},
"approvalDataDescription": {
"message": "Cancella i dati del sito web approvati, quindi tutti i siti devono richiedere nuovamente l'approvazione."
},
"clearApprovalData": {
"message": "Cancella i dati di approvazione"
},
"approve": {
"message": "Approvare"
},
"reject": {
"message": "Rifiutare"
},
"providerAPIRequest": {
"message": "Richiesta API Web3"
},
"reviewProviderRequest": {
"message": "Si prega di rivedere questa richiesta API Ethereum."
},
"providerRequestInfo": {
"message": "Il dominio elencato di seguito sta tentando di richiedere l'accesso all'API Ethereum in modo che possa interagire con la blockchain di Ethereum. Controlla sempre di essere sul sito corretto prima di approvare l'accesso a Ethereum."
},
"accept": { "accept": {
"message": "Accetta" "message": "Accetta"
}, },

@ -1,4 +1,34 @@
{ {
"confirmClear": {
"message": "承認されたウェブサイトをクリアしてもよろしいですか?"
},
"clearApprovalDataSuccess": {
"message": "承認されたウェブサイトデータが正常に消去されました。"
},
"approvalData": {
"message": "承認データ"
},
"approvalDataDescription": {
"message": "承認されたウェブサイトのデータをクリアすると、すべてのサイトで承認を再度要求する必要があります"
},
"clearApprovalData": {
"message": "承認データのクリア"
},
"approve": {
"message": "承認する"
},
"reject": {
"message": "拒否"
},
"providerAPIRequest": {
"message": "Web3 APIリクエスト"
},
"reviewProviderRequest": {
"message": "このEthereum APIリクエストを確認してください。"
},
"providerRequestInfo": {
"message": "下記のドメインは、Ethereumブロックチェーンとやり取りできるようにEthereum APIへのアクセスをリクエストしようとしています。 Web3アクセスを承認する前に、正しいサイトにいることを常に確認してください。"
},
"accept": { "accept": {
"message": "承認" "message": "承認"
}, },

@ -1,4 +1,34 @@
{ {
"confirmClear": {
"message": "승인 된 웹 사이트를 삭제 하시겠습니까?"
},
"clearApprovalDataSuccess": {
"message": "승인 된 웹 사이트 데이터가 성공적으로 삭제되었습니다."
},
"approvalData": {
"message": "승인 데이터"
},
"approvalDataDescription": {
"message": "승인 된 웹 사이트 데이터를 삭제하여 모든 사이트에서 승인을 다시 요청해야합니다."
},
"clearApprovalData": {
"message": "승인 데이터 삭제"
},
"approve": {
"message": "승인하다"
},
"reject": {
"message": "받지 않다"
},
"providerAPIRequest": {
"message": "Web3 API 요청"
},
"reviewProviderRequest": {
"message": "이 Ethereum API 요청을 검토하십시오."
},
"providerRequestInfo": {
"message": "아래 나열된 도메인은 Web3 API에 대한 액세스를 요청하여 Ethereum 블록 체인과 상호 작용할 수 있습니다. Ethereum 액세스를 승인하기 전에 항상 올바른 사이트에 있는지 다시 확인하십시오."
},
"accept": { "accept": {
"message": "수락" "message": "수락"
}, },

@ -1,4 +1,34 @@
{ {
"confirmClear": {
"message": "Weet je zeker dat je goedgekeurde websites wilt wissen?"
},
"clearApprovalDataSuccess": {
"message": "Goedgekeurde websitegegevens zijn met succes gewist."
},
"approvalData": {
"message": "Goedkeuringsgegevens"
},
"approvalDataDescription": {
"message": "Goedgekeurde websitegegevens wissen zodat alle sites opnieuw goedkeuring moeten aanvragen."
},
"clearApprovalData": {
"message": "Gegevens over goedkeuring wissen"
},
"approve": {
"message": "Goedkeuren"
},
"reject": {
"message": "Afwijzen"
},
"providerAPIRequest": {
"message": "Web3 API-aanvraag"
},
"reviewProviderRequest": {
"message": "Bekijk deze Ethereum API-aanvraag."
},
"providerRequestInfo": {
"message": "Het onderstaande domein probeert toegang tot de Ethereum API te vragen zodat deze kan communiceren met de Ethereum-blockchain. Controleer altijd eerst of u op de juiste site bent voordat u Ethereum-toegang goedkeurt."
},
"accept": { "accept": {
"message": "Aanvaarden" "message": "Aanvaarden"
}, },

@ -1,4 +1,34 @@
{ {
"confirmClear": {
"message": "Sigurado ka bang gusto mong i-clear ang mga naaprubahang website?"
},
"clearApprovalDataSuccess": {
"message": "Matagumpay na na-clear ang data ng aprubadong website."
},
"approvalData": {
"message": "Data ng Pag-apruba"
},
"approvalDataDescription": {
"message": "I-clear ang naaprubahang data ng website upang ang lahat ng site ay dapat humiling muli ng pag-apruba"
},
"clearApprovalData": {
"message": "Tanggalin ang data ng pag-apruba"
},
"approve": {
"message": "Aprubahan"
},
"reject": {
"message": "Tanggihan"
},
"providerAPIRequest": {
"message": "Kahilingan sa Web3 API"
},
"reviewProviderRequest": {
"message": "Mangyaring suriin ang kahilingan sa Ethereum API na ito."
},
"providerRequestInfo": {
"message": "Ang domain na nakalista sa ibaba ay sinusubukang humiling ng access sa Ethereum API upang maaari itong makipag-ugnayan sa Ethereum blockchain. Laging i-double check na ikaw ay nasa tamang site bago aprubahan ang Ethereum access."
},
"accept": { "accept": {
"message": "Tanggapin" "message": "Tanggapin"
}, },

@ -1,4 +1,34 @@
{ {
"confirmClear": {
"message": "Tem certeza de que deseja limpar sites aprovados?"
},
"clearApprovalDataSuccess": {
"message": "Dados aprovados do website foram limpos com sucesso."
},
"approvalData": {
"message": "Dados de aprovação"
},
"approvalDataDescription": {
"message": "Limpe os dados aprovados do website para que todos os sites solicitem aprovação novamente."
},
"clearApprovalData": {
"message": "Limpar dados de aprovação"
},
"approve": {
"message": "Aprovar"
},
"reject": {
"message": "Rejeitar"
},
"providerAPIRequest": {
"message": "Solicitação de API do Web3"
},
"reviewProviderRequest": {
"message": "Por favor, revise esta solicitação da API da Ethereum."
},
"providerRequestInfo": {
"message": "O domínio listado abaixo está tentando solicitar acesso à API Ethereum para que ele possa interagir com o blockchain Ethereum. Sempre verifique se você está no site correto antes de aprovar o acesso à Ethereum."
},
"accept": { "accept": {
"message": "Aceitar" "message": "Aceitar"
}, },

@ -1,4 +1,34 @@
{ {
"confirmClear": {
"message": "Вы уверены, что хотите очистить утвержденные веб-сайты?Tem certeza de que deseja limpar sites aprovados?"
},
"clearApprovalDataSuccess": {
"message": "Утвержденные данные веб-сайта успешно удалены."
},
"approvalData": {
"message": "Данные об утверждении"
},
"approvalDataDescription": {
"message": "Очистите утвержденные данные веб-сайта, чтобы все сайты снова запросили подтверждение."
},
"clearApprovalData": {
"message": "Четкие данные об утверждении"
},
"approve": {
"message": "Одобрить"
},
"reject": {
"message": "Отклонить"
},
"providerAPIRequest": {
"message": "Запрос API Web3"
},
"reviewProviderRequest": {
"message": "Просмотрите этот запрос API Ethereum."
},
"providerRequestInfo": {
"message": "Домен, указанный ниже, пытается запросить доступ к API-интерфейсу Ethereum, чтобы он мог взаимодействовать с блокчейном Ethereum. Всегда проверяйте, что вы находитесь на правильном сайте, прежде чем одобрять доступ к веб-сайту."
},
"accept": { "accept": {
"message": "Принять" "message": "Принять"
}, },

@ -1,4 +1,5 @@
{ {
<<<<<<< HEAD
"privacyMode": { "privacyMode": {
"message": "Način zasebnosti" "message": "Način zasebnosti"
}, },
@ -37,6 +38,37 @@
}, },
"providerRequestInfo": { "providerRequestInfo": {
"message": "Domena zahteva dostop do verige blokov in ogled vašega računa. Pred potrditvjo vedno preverite ali ste na želeni spletni strani." "message": "Domena zahteva dostop do verige blokov in ogled vašega računa. Pred potrditvjo vedno preverite ali ste na želeni spletni strani."
=======
"confirmClear": {
"message": "Naozaj chcete vymazať schválené webové stránky?"
},
"clearApprovalDataSuccess": {
"message": "Schválené webové stránky boli úspešne odstránené."
},
"approvalData": {
"message": "Údaje o schválení"
},
"approvalDataDescription": {
"message": "Vymažte schválené údaje webových stránok, aby sa všetky stránky opäť požiadali o schválenie."
},
"clearApprovalData": {
"message": "Jasné údaje o schválení"
},
"approve": {
"message": "Schvaľovať"
},
"reject": {
"message": "Odmietnuť"
},
"providerAPIRequest": {
"message": "Žiadosť API Web3"
},
"reviewProviderRequest": {
"message": "Prečítajte si túto žiadosť rozhrania API Ethereum."
},
"providerRequestInfo": {
"message": "Doména uvedená nižšie sa pokúša požiadať o prístup k rozhraniu API Ethereum, aby mohla komunikovať s blokom Ethereum. Pred schválením prístupu na Ethereum vždy skontrolujte, či ste na správnom mieste."
>>>>>>> EIP-1102: updated implementation
}, },
"accept": { "accept": {
"message": "Potrdi" "message": "Potrdi"

@ -1,4 +1,34 @@
{ {
"confirmClear": {
"message": "คณแนใจหรอไมาตองการลางเวบไซตานการอน"
},
"clearApprovalDataSuccess": {
"message": "อนอมลเวบไซตไดบอนแลว"
},
"approvalData": {
"message": "ขอมลการอน"
},
"approvalDataDescription": {
"message": "ลางขอมลเวบไซตไดบการอนเพอใหกไซตองขออนกครง"
},
"clearApprovalData": {
"message": "ลางขอมลการอน"
},
"approve": {
"message": "อน"
},
"reject": {
"message": "ปฏเสธ"
},
"providerAPIRequest": {
"message": "คำขอ Web3 API"
},
"reviewProviderRequest": {
"message": "โปรดอานคำขอ Ethereum API น"
},
"providerRequestInfo": {
"message": "โดเมนทแสดงดานลางกำลงพยายามขอเขาถง API ของ Ethereum เพอใหสามารถโตตอบกบบลอค Ethereum ได ตรวจสอบวาคณอยในไซตกตองกอนทจะอนการเขาถง Ethereum เสมอ"
},
"accept": { "accept": {
"message": "ยอมรบ" "message": "ยอมรบ"
}, },

@ -1,4 +1,34 @@
{ {
"confirmClear": {
"message": "அஙகரிகபபடட வலதளஙகளிசயமக நக விிகள?"
},
"clearApprovalDataSuccess": {
"message": "அஙகரிகபபடட வலதள தரவிகரமக அழிகபபடடத."
},
"approvalData": {
"message": "ஒபதல தரவ"
},
"approvalDataDescription": {
"message": "அஙகரிகபபடட வலதள தரவ அழிகவ, அன தளஙகள ஒபதலர வ."
},
"clearApprovalData": {
"message": "ஒபதல தரவ அழி"
},
"approve": {
"message": "ஒபதல"
},
"reject": {
"message": "நிகரி"
},
"providerAPIRequest": {
"message": "Web3 API கி"
},
"reviewProviderRequest": {
"message": "இநத வல 3 API கி மதியவ."
},
"providerRequestInfo": {
"message": "க படியலிடபபடள ட Web3 ஏபிஐ அணகலவதறயறிிறத, எனவ இத Ethereum blockchain உடனடரள மி. Web3 அண அஙகரிபதற சரின தளதி இரபத எப இர சரிகவ."
},
"accept": { "accept": {
"message": "ஏறகவ" "message": "ஏறகவ"
}, },

@ -1,4 +1,34 @@
{ {
"confirmClear": {
"message": "Onaylanmış web sitelerini silmek istediğinizden emin misiniz?"
},
"clearApprovalDataSuccess": {
"message": "Onaylanan web sitesi verileri başarıyla temizlendi."
},
"approvalData": {
"message": "Onay Verileri"
},
"approvalDataDescription": {
"message": "Onaylanan web sitesi verilerini temizle, tüm sitelerin tekrar onay isteğinde bulunması gerekir."
},
"clearApprovalData": {
"message": "Onay verilerini temizle"
},
"approve": {
"message": "Onaylamak"
},
"reject": {
"message": "Reddetmek"
},
"providerAPIRequest": {
"message": "Web3 API İsteği"
},
"reviewProviderRequest": {
"message": "Lütfen bu Ethereum API isteğini inceleyin."
},
"providerRequestInfo": {
"message": "Aşağıda listelenen etki alanı, Ethereum API'sine erişim talep etmeye çalışmaktadır, böylece Ethereum blockchain ile etkileşime girebilir. Web3 erişimini onaylamadan önce her zaman doğru sitede olduğunuzu kontrol edin."
},
"accept": { "accept": {
"message": "Kabul et" "message": "Kabul et"
}, },

@ -1,4 +1,34 @@
{ {
"confirmClear": {
"message": "Bạn có chắc chắn muốn xóa các trang web được phê duyệt không?"
},
"clearApprovalDataSuccess": {
"message": "Đã xóa thành công dữ liệu trang web được phê duyệt."
},
"approvalData": {
"message": "Dữ liệu phê duyệt"
},
"approvalDataDescription": {
"message": "Xóa dữ liệu trang web được phê duyệt để tất cả các trang web phải yêu cầu phê duyệt lại."
},
"clearApprovalData": {
"message": "Xóa dữ liệu phê duyệt"
},
"approve": {
"message": "Phê duyệt"
},
"reject": {
"message": "Từ chối"
},
"providerAPIRequest": {
"message": "Yêu cầu API Web3"
},
"reviewProviderRequest": {
"message": "Vui lòng xem lại yêu cầu API Ethereum này."
},
"providerRequestInfo": {
"message": "Miền được liệt kê bên dưới đang cố gắng yêu cầu quyền truy cập vào API Ethereum để nó có thể tương tác với chuỗi khối Ethereum. Luôn kiểm tra kỹ xem bạn có đang ở đúng trang web trước khi phê duyệt quyền truy cập Ethereum hay không."
},
"accept": { "accept": {
"message": "Chấp nhận" "message": "Chấp nhận"
}, },

@ -1,4 +1,34 @@
{ {
"confirmClear": {
"message": "您确定要清除已批准的网站吗?"
},
"clearApprovalDataSuccess": {
"message": "已批准的网站数据已成功清除。"
},
"approvalData": {
"message": "审批数据"
},
"approvalDataDescription": {
"message": "清除已批准的网站数据,以便所有网站都必须再次申请"
},
"clearApprovalData": {
"message": "清除批准数据"
},
"approve": {
"message": "批准"
},
"reject": {
"message": "拒绝"
},
"providerAPIRequest": {
"message": "Web3 API请求"
},
"reviewProviderRequest": {
"message": "请查看此Ethereum API请求。"
},
"providerRequestInfo": {
"message": "下面列出的域正在尝试请求访问Ethereum API,以便它可以与以太坊区块链进行交互。在批准Ethereum访问之前,请务必仔细检查您是否在正确的站点上。"
},
"accept": { "accept": {
"message": "接受" "message": "接受"
}, },

@ -1,4 +1,34 @@
{ {
"confirmClear": {
"message": "您確定要清除已批准的網站嗎?"
},
"clearApprovalDataSuccess": {
"message": "已批准的網站數據已成功清除。"
},
"approvalData": {
"message": "審批數據"
},
"approvalDataDescription": {
"message": "清除已批准的網站數據,以便所有網站都必須再次申請"
},
"clearApprovalData": {
"message": "清除批准數據"
},
"approve": {
"message": "批准"
},
"reject": {
"message": "拒絕"
},
"providerAPIRequest": {
"message": "Web3 API請求"
},
"reviewProviderRequest": {
"message": "請查看此Ethereum API請求。"
},
"providerRequestInfo": {
"message": "下面列出的域正在嘗試請求訪問Ethereum API,以便它可以與以太坊區塊鏈進行交互。在批准Ethereum訪問之前,請務必仔細檢查您是否在正確的站點上。"
},
"accept": { "accept": {
"message": "接受" "message": "接受"
}, },

@ -256,7 +256,8 @@ function setupController (initState, initLangCode) {
showUnconfirmedMessage: triggerUi, showUnconfirmedMessage: triggerUi,
unlockAccountMessage: triggerUi, unlockAccountMessage: triggerUi,
showUnapprovedTx: triggerUi, showUnapprovedTx: triggerUi,
showWatchAssetUi: showWatchAssetUi, openPopup: openPopup,
closePopup: notificationManager.closePopup.bind(notificationManager),
// initial state // initial state
initState, initState,
// initial locale code // initial locale code
@ -447,7 +448,7 @@ function triggerUi () {
* Opens the browser popup for user confirmation of watchAsset * Opens the browser popup for user confirmation of watchAsset
* then it waits until user interact with the UI * then it waits until user interact with the UI
*/ */
function showWatchAssetUi () { function openPopup () {
triggerUi() triggerUi()
return new Promise( return new Promise(
(resolve) => { (resolve) => {

@ -11,6 +11,7 @@ const PortStream = require('extension-port-stream')
const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'inpage.js')).toString() const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'inpage.js')).toString()
const inpageSuffix = '//# sourceURL=' + extension.extension.getURL('inpage.js') + '\n' const inpageSuffix = '//# sourceURL=' + extension.extension.getURL('inpage.js') + '\n'
const inpageBundle = inpageContent + inpageSuffix const inpageBundle = inpageContent + inpageSuffix
let originApproved = false
// Eventually this streaming injection could be replaced with: // Eventually this streaming injection could be replaced with:
// https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Language_Bindings/Components.utils.exportFunction // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Language_Bindings/Components.utils.exportFunction
@ -20,24 +21,24 @@ const inpageBundle = inpageContent + inpageSuffix
// MetaMask will be much faster loading and performant on Firefox. // MetaMask will be much faster loading and performant on Firefox.
if (shouldInjectWeb3()) { if (shouldInjectWeb3()) {
setupInjection() injectScript(inpageBundle)
setupStreams() setupStreams()
listenForProviderRequest()
} }
/** /**
* Creates a script tag that injects inpage.js * Injects a script tag into the current document
*
* @param {string} content - Code to be executed in the current document
*/ */
function setupInjection () { function injectScript (content) {
try { try {
// inject in-page script const container = document.head || document.documentElement
var scriptTag = document.createElement('script') const scriptTag = document.createElement('script')
scriptTag.textContent = inpageBundle scriptTag.textContent = content
scriptTag.onload = function () { this.parentNode.removeChild(this) }
var container = document.head || document.documentElement
// append as first child
container.insertBefore(scriptTag, container.children[0]) container.insertBefore(scriptTag, container.children[0])
} catch (e) { } catch (e) {
console.error('Metamask injection failed.', e) console.error('Metamask script injection failed.', e)
} }
} }
@ -54,6 +55,16 @@ function setupStreams () {
const pluginPort = extension.runtime.connect({ name: 'contentscript' }) const pluginPort = extension.runtime.connect({ name: 'contentscript' })
const pluginStream = new PortStream(pluginPort) const pluginStream = new PortStream(pluginPort)
// Until this origin is approved, cut-off publicConfig stream writes at the content
// script level so malicious sites can't snoop on the currently-selected address
pageStream._write = function (data, encoding, cb) {
if (typeof data === 'object' && data.name && data.name === 'publicConfig' && !originApproved) {
cb()
return
}
LocalMessageDuplexStream.prototype._write.apply(pageStream, arguments)
}
// forward communication plugin->inpage // forward communication plugin->inpage
pump( pump(
pageStream, pageStream,
@ -97,6 +108,36 @@ function setupStreams () {
mux.ignoreStream('publicConfig') mux.ignoreStream('publicConfig')
} }
/**
* Establishes listeners for requests to fully-enable the provider from the dapp context
* and for full-provider approvals and rejections from the background script context. Dapps
* should not post messages directly and should instead call provider.enable(), which
* handles posting these messages automatically.
*/
function listenForProviderRequest () {
window.addEventListener('message', (event) => {
if (event.source !== window) { return }
if (!event.data || !event.data.type || event.data.type !== 'ETHEREUM_ENABLE_PROVIDER') { return }
extension.runtime.sendMessage({
action: 'init-provider-request',
origin: event.source.location.hostname,
})
})
extension.runtime.onMessage.addListener(({ action }) => {
if (!action) { return }
switch (action) {
case 'approve-provider-request':
originApproved = true
injectScript(`window.dispatchEvent(new CustomEvent('ethereumprovider', { detail: {}}))`)
break
case 'reject-provider-request':
injectScript(`window.dispatchEvent(new CustomEvent('ethereumprovider', { detail: { error: 'User rejected provider access' }}))`)
break
}
})
}
/** /**
* Error handler for page to plugin stream disconnections * Error handler for page to plugin stream disconnections

@ -46,7 +46,7 @@ class PreferencesController {
this.diagnostics = opts.diagnostics this.diagnostics = opts.diagnostics
this.network = opts.network this.network = opts.network
this.store = new ObservableStore(initState) this.store = new ObservableStore(initState)
this.showWatchAssetUi = opts.showWatchAssetUi this.openPopup = opts.openPopup
this._subscribeProviderType() this._subscribeProviderType()
} }
// PUBLIC METHODS // PUBLIC METHODS
@ -567,7 +567,7 @@ class PreferencesController {
} }
const tokenOpts = { rawAddress, decimals, symbol, image } const tokenOpts = { rawAddress, decimals, symbol, image }
this.addSuggestedERC20Asset(tokenOpts) this.addSuggestedERC20Asset(tokenOpts)
return this.showWatchAssetUi().then(() => { return this.openPopup().then(() => {
const tokenAddresses = this.getTokens().filter(token => token.address === normalizeAddress(rawAddress)) const tokenAddresses = this.getTokens().filter(token => token.address === normalizeAddress(rawAddress))
return tokenAddresses.length > 0 return tokenAddresses.length > 0
}) })

@ -0,0 +1,84 @@
const ObservableStore = require('obs-store')
/**
* A controller that services user-approved requests for a full Ethereum provider API
*/
class ProviderApprovalController {
/**
* Creates a ProviderApprovalController
*
* @param {Object} [config] - Options to configure controller
*/
constructor ({ closePopup, openPopup, platform, publicConfigStore } = {}) {
this.store = new ObservableStore()
this.closePopup = closePopup
this.openPopup = openPopup
this.platform = platform
this.publicConfigStore = publicConfigStore
this.approvedOrigins = {}
platform && platform.addMessageListener && platform.addMessageListener(({ action, origin }) => {
action && action === 'init-provider-request' && this.handleProviderRequest(origin)
})
}
/**
* Called when a tab requests access to a full Ethereum provider API
*
* @param {string} origin - Origin of the window requesting full provider access
*/
handleProviderRequest (origin) {
this.store.updateState({ providerRequests: [{ origin }] })
if (this.approvedOrigins[origin]) {
this.approveProviderRequest(origin)
return
}
this.openPopup && this.openPopup()
}
/**
* Called when a user approves access to a full Ethereum provider API
*
* @param {string} origin - Origin of the target window to approve provider access
*/
approveProviderRequest (origin) {
this.closePopup && this.closePopup()
const requests = this.store.getState().providerRequests || []
this.platform && this.platform.sendMessage({ action: 'approve-provider-request' }, { active: true })
this.publicConfigStore.emit('update', this.publicConfigStore.getState())
const providerRequests = requests.filter(request => request.origin !== origin)
this.store.updateState({ providerRequests })
this.approvedOrigins[origin] = true
}
/**
* Called when a tab rejects access to a full Ethereum provider API
*
* @param {string} origin - Origin of the target window to reject provider access
*/
rejectProviderRequest (origin) {
this.closePopup && this.closePopup()
const requests = this.store.getState().providerRequests || []
this.platform && this.platform.sendMessage({ action: 'reject-provider-request' }, { active: true })
const providerRequests = requests.filter(request => request.origin !== origin)
this.store.updateState({ providerRequests })
}
/**
* Clears any cached approvals for user-approved origins
*/
clearApprovedOrigins () {
this.approvedOrigins = {}
}
/**
* Determines if a given origin has been approved
*
* @param {string} origin - Domain origin to check for approval status
* @returns {boolean} - True if the origin has been approved
*/
isApproved (origin) {
return this.approvedOrigins[origin]
}
}
module.exports = ProviderApprovalController

@ -31,19 +31,18 @@ var inpageProvider = new MetamaskInpageProvider(metamaskStream)
inpageProvider.setMaxListeners(100) inpageProvider.setMaxListeners(100)
// Augment the provider with its enable method // Augment the provider with its enable method
inpageProvider.enable = function (options = {}) { inpageProvider.enable = function () {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (options.mockRejection) { window.addEventListener('ethereumprovider', ({ detail }) => {
reject('User rejected account access') if (typeof detail.error !== 'undefined') {
} else { reject(detail.error)
inpageProvider.sendAsync({ method: 'eth_accounts', params: [] }, (error, response) => { } else {
if (error) { inpageProvider.publicConfigStore.once('update', () => {
reject(error) resolve(inpageProvider.send({ method: 'eth_accounts' }).result)
} else { })
resolve(response.result) }
} })
}) window.postMessage({ type: 'ETHEREUM_ENABLE_PROVIDER' }, '*')
}
}) })
} }

@ -37,6 +37,7 @@ const TransactionController = require('./controllers/transactions')
const BalancesController = require('./controllers/computed-balances') const BalancesController = require('./controllers/computed-balances')
const TokenRatesController = require('./controllers/token-rates') const TokenRatesController = require('./controllers/token-rates')
const DetectTokensController = require('./controllers/detect-tokens') const DetectTokensController = require('./controllers/detect-tokens')
const ProviderApprovalController = require('./controllers/provider-approval')
const nodeify = require('./lib/nodeify') const nodeify = require('./lib/nodeify')
const accountImporter = require('./account-import-strategies') const accountImporter = require('./account-import-strategies')
const getBuyEthUrl = require('./lib/buy-eth-url') const getBuyEthUrl = require('./lib/buy-eth-url')
@ -89,7 +90,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.preferencesController = new PreferencesController({ this.preferencesController = new PreferencesController({
initState: initState.PreferencesController, initState: initState.PreferencesController,
initLangCode: opts.initLangCode, initLangCode: opts.initLangCode,
showWatchAssetUi: opts.showWatchAssetUi, openPopup: opts.openPopup,
network: this.networkController, network: this.networkController,
}) })
@ -219,6 +220,13 @@ module.exports = class MetamaskController extends EventEmitter {
this.typedMessageManager = new TypedMessageManager({ networkController: this.networkController }) this.typedMessageManager = new TypedMessageManager({ networkController: this.networkController })
this.publicConfigStore = this.initPublicConfigStore() this.publicConfigStore = this.initPublicConfigStore()
this.providerApprovalController = new ProviderApprovalController({
closePopup: opts.closePopup,
openPopup: opts.openPopup,
platform: opts.platform,
publicConfigStore: this.publicConfigStore,
})
this.store.updateStructure({ this.store.updateStructure({
TransactionController: this.txController.store, TransactionController: this.txController.store,
KeyringController: this.keyringController.store, KeyringController: this.keyringController.store,
@ -248,6 +256,7 @@ module.exports = class MetamaskController extends EventEmitter {
NoticeController: this.noticeController.memStore, NoticeController: this.noticeController.memStore,
ShapeshiftController: this.shapeshiftController.store, ShapeshiftController: this.shapeshiftController.store,
InfuraController: this.infuraController.store, InfuraController: this.infuraController.store,
ProviderApprovalController: this.providerApprovalController.store,
}) })
this.memStore.subscribe(this.sendUpdate.bind(this)) this.memStore.subscribe(this.sendUpdate.bind(this))
} }
@ -263,7 +272,10 @@ module.exports = class MetamaskController extends EventEmitter {
}, },
version, version,
// account mgmt // account mgmt
getAccounts: async () => { getAccounts: async ({ origin }) => {
// Expose no accounts if this origin has not been approved, preventing
// account-requring RPC methods from completing successfully
if (origin !== 'MetaMask' && !this.providerApprovalController.isApproved(origin)) { return [] }
const isUnlocked = this.keyringController.memStore.getState().isUnlocked const isUnlocked = this.keyringController.memStore.getState().isUnlocked
const selectedAddress = this.preferencesController.getSelectedAddress() const selectedAddress = this.preferencesController.getSelectedAddress()
// only show address if account is unlocked // only show address if account is unlocked
@ -349,6 +361,7 @@ module.exports = class MetamaskController extends EventEmitter {
const noticeController = this.noticeController const noticeController = this.noticeController
const addressBookController = this.addressBookController const addressBookController = this.addressBookController
const networkController = this.networkController const networkController = this.networkController
const providerApprovalController = this.providerApprovalController
return { return {
// etc // etc
@ -437,6 +450,10 @@ module.exports = class MetamaskController extends EventEmitter {
// notices // notices
checkNotices: noticeController.updateNoticesList.bind(noticeController), checkNotices: noticeController.updateNoticesList.bind(noticeController),
markNoticeRead: noticeController.markNoticeRead.bind(noticeController), markNoticeRead: noticeController.markNoticeRead.bind(noticeController),
approveProviderRequest: providerApprovalController.approveProviderRequest.bind(providerApprovalController),
clearApprovedOrigins: providerApprovalController.clearApprovedOrigins.bind(providerApprovalController),
rejectProviderRequest: providerApprovalController.rejectProviderRequest.bind(providerApprovalController),
} }
} }

@ -57,6 +57,18 @@ class ExtensionPlatform {
} }
} }
addMessageListener (cb) {
extension.runtime.onMessage.addListener(cb)
}
sendMessage (message, query = {}) {
extension.tabs.query(query, tabs => {
tabs.forEach(tab => {
extension.tabs.sendMessage(tab.id, message)
})
})
}
_showConfirmedTransaction (txMeta) { _showConfirmedTransaction (txMeta) {
this._subscribeToNotificationClicked() this._subscribeToNotificationClicked()

@ -33,6 +33,7 @@ const BuyView = require('./components/buy-button-subview')
const HDCreateVaultComplete = require('./keychains/hd/create-vault-complete') const HDCreateVaultComplete = require('./keychains/hd/create-vault-complete')
const HDRestoreVaultScreen = require('./keychains/hd/restore-vault') const HDRestoreVaultScreen = require('./keychains/hd/restore-vault')
const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation') const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation')
const ProviderApproval = require('./provider-approval')
module.exports = connect(mapStateToProps)(App) module.exports = connect(mapStateToProps)(App)
@ -49,6 +50,7 @@ function mapStateToProps (state) {
noActiveNotices, noActiveNotices,
seedWords, seedWords,
featureFlags, featureFlags,
providerRequests,
} = state.metamask } = state.metamask
const selected = address || Object.keys(accounts)[0] const selected = address || Object.keys(accounts)[0]
@ -75,6 +77,7 @@ function mapStateToProps (state) {
lostAccounts: state.metamask.lostAccounts, lostAccounts: state.metamask.lostAccounts,
frequentRpcListDetail: state.metamask.frequentRpcListDetail || [], frequentRpcListDetail: state.metamask.frequentRpcListDetail || [],
featureFlags, featureFlags,
providerRequests,
suggestedTokens: state.metamask.suggestedTokens, suggestedTokens: state.metamask.suggestedTokens,
// state needed to get account dropdown temporarily rendering from app bar // state needed to get account dropdown temporarily rendering from app bar
@ -147,7 +150,7 @@ App.prototype.renderLoadingIndicator = function ({ isLoading, isLoadingNetwork,
App.prototype.renderPrimary = function () { App.prototype.renderPrimary = function () {
log.debug('rendering primary') log.debug('rendering primary')
var props = this.props var props = this.props
const {isMascara, isOnboarding} = props const {isMascara, isOnboarding, providerRequests} = props
if (isMascara && isOnboarding) { if (isMascara && isOnboarding) {
return h(MascaraFirstTime) return h(MascaraFirstTime)
@ -215,6 +218,11 @@ App.prototype.renderPrimary = function () {
return h(HDCreateVaultComplete, {key: 'HDCreateVaultComplete'}) return h(HDCreateVaultComplete, {key: 'HDCreateVaultComplete'})
} }
if (providerRequests && providerRequests.length > 0) {
log.debug('rendering provider API approval screen')
return h(ProviderApproval, { origin: providerRequests[0].origin })
}
// show current view // show current view
switch (props.currentView.name) { switch (props.currentView.name) {

@ -200,6 +200,31 @@ ConfigScreen.prototype.render = function () {
h('hr.horizontal-line'), h('hr.horizontal-line'),
h('div', {
style: {
marginTop: '20px',
},
}, [
h('p', {
style: {
fontFamily: 'Montserrat Light',
fontSize: '13px',
},
}, 'Clear approved website data so all sites must request approval again.'),
h('br'),
h('button', {
style: {
alignSelf: 'center',
},
onClick (event) {
event.preventDefault()
state.dispatch(actions.clearApprovedOrigins())
},
}, 'Clear approval data'),
]),
h('hr.horizontal-line'),
h('div', { h('div', {
style: { style: {
marginTop: '20px', marginTop: '20px',

@ -0,0 +1,64 @@
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import { approveProviderRequest, rejectProviderRequest } from '../../ui/app/actions'
import { connect } from 'react-redux'
class ProviderApproval extends Component {
render () {
const { approveProviderRequest, origin, rejectProviderRequest } = this.props
return (
<div className="flex-column flex-grow">
<style dangerouslySetInnerHTML={{__html: `
.provider_approval_actions {
display: flex;
justify-content: flex-end;
margin: 14px 25px;
}
.provider_approval_actions button {
margin-left: 10px;
text-transform: uppercase;
}
.provider_approval_content {
padding: 0 25px;
}
.provider_approval_origin {
font-weight: bold;
margin: 14px 0;
}
`}} />
<div className="section-title flex-row flex-center">
<i
className="fa fa-arrow-left fa-lg cursor-pointer"
onClick={() => { rejectProviderRequest(origin) }} />
<h2 className="page-subtitle">Web3 API Request</h2>
</div>
<div className="provider_approval_content">
{"The domain listed below is requesting access to the Ethereum blockchain and to view your current account. Always double check that you're on the correct site before approving access."}
<div className="provider_approval_origin">{origin}</div>
</div>
<div className="provider_approval_actions">
<button
className="btn-green"
onClick={() => { approveProviderRequest(origin) }}>APPROVE</button>
<button
className="cancel btn-red"
onClick={() => { rejectProviderRequest(origin) }}>REJECT</button>
</div>
</div>
)
}
}
ProviderApproval.propTypes = {
approveProviderRequest: PropTypes.func,
origin: PropTypes.string,
rejectProviderRequest: PropTypes.func,
}
function mapDispatchToProps (dispatch) {
return {
approveProviderRequest: origin => dispatch(approveProviderRequest(origin)),
rejectProviderRequest: origin => dispatch(rejectProviderRequest(origin)),
}
}
module.exports = connect(null, mapDispatchToProps)(ProviderApproval)

14606
package-lock.json generated

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -14,6 +14,7 @@ module.exports = {
loadExtension, loadExtension,
openNewPage, openNewPage,
switchToWindowWithTitle, switchToWindowWithTitle,
switchToWindowWithUrlThatMatches,
verboseReportOnFailure, verboseReportOnFailure,
waitUntilXWindowHandles, waitUntilXWindowHandles,
} }
@ -130,3 +131,19 @@ async function assertElementNotPresent (webdriver, driver, by) {
} }
assert.ok(!dataTab, 'Found element that should not be present') assert.ok(!dataTab, 'Found element that should not be present')
} }
async function switchToWindowWithUrlThatMatches (driver, regexp, windowHandles) {
if (!windowHandles) {
windowHandles = await driver.getAllWindowHandles()
} else if (windowHandles.length === 0) {
throw new Error('No window that matches: ' + regexp)
}
const firstHandle = windowHandles[0]
await driver.switchTo().window(firstHandle)
const windowUrl = await driver.getCurrentUrl()
if (windowUrl.match(regexp)) {
return firstHandle
} else {
return await switchToWindowWithUrlThatMatches(driver, regexp, windowHandles.slice(1))
}
}

@ -426,24 +426,37 @@ describe('MetaMask', function () {
}) })
describe('Send ETH from dapp', () => { describe('Send ETH from dapp', () => {
let windowHandles
let extension
let popup
let dapp
it('starts a send transaction inside the dapp', async () => { it('starts a send transaction inside the dapp', async () => {
await openNewPage(driver, 'http://127.0.0.1:8080/') await openNewPage(driver, 'http://127.0.0.1:8080/')
await delay(regularDelayMs) await delay(regularDelayMs)
await waitUntilXWindowHandles(driver, 2) await waitUntilXWindowHandles(driver, 3)
let windowHandles = await driver.getAllWindowHandles() windowHandles = await driver.getAllWindowHandles()
const extension = windowHandles[0]
const dapp = windowHandles[1] extension = windowHandles[0]
popup = await switchToWindowWithTitle(driver, 'MetaMask Notification', windowHandles)
dapp = windowHandles.find(handle => handle !== extension && handle !== popup)
await delay(regularDelayMs)
const approveButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Approve')]`), 10000)
await approveButton.click()
})
it('initiates a send from the dapp', async () => {
await driver.switchTo().window(dapp) await driver.switchTo().window(dapp)
await delay(regularDelayMs) await delay(regularDelayMs)
const send3eth = await findElement(driver, By.xpath(`//button[contains(text(), 'Send')]`), 10000) const send3eth = await findElement(driver, By.xpath(`//button[contains(text(), 'Send')]`), 10000)
await send3eth.click() await send3eth.click()
await delay(regularDelayMs) await delay(5000)
windowHandles = await driver.getAllWindowHandles() windowHandles = await driver.getAllWindowHandles()
await driver.switchTo().window(windowHandles[2]) await switchToWindowWithTitle(driver, 'MetaMask Notification', windowHandles)
await delay(regularDelayMs) await delay(regularDelayMs)
await assertElementNotPresent(webdriver, driver, By.xpath(`//li[contains(text(), 'Data')]`)) await assertElementNotPresent(webdriver, driver, By.xpath(`//li[contains(text(), 'Data')]`))

@ -2,6 +2,11 @@ const path = require('path')
const assert = require('assert') const assert = require('assert')
const { By, Key, until } = require('selenium-webdriver') const { By, Key, until } = require('selenium-webdriver')
const { delay, createModifiedTestBuild, setupBrowserAndExtension, verboseReportOnFailure } = require('./func') const { delay, createModifiedTestBuild, setupBrowserAndExtension, verboseReportOnFailure } = require('./func')
const {
closeAllWindowHandlesExcept,
switchToWindowWithTitle,
switchToWindowWithUrlThatMatches,
} = require('./beta/helpers')
describe('Metamask popup page', function () { describe('Metamask popup page', function () {
const browser = process.env.SELENIUM_BROWSER const browser = process.env.SELENIUM_BROWSER
@ -183,6 +188,7 @@ describe('Metamask popup page', function () {
}) })
it('restores from seed phrase', async function () { it('restores from seed phrase', async function () {
await delay(1000)
const restoreSeedLink = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div.flex-row.flex-center.flex-grow > p')) const restoreSeedLink = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div.flex-row.flex-center.flex-grow > p'))
assert.equal(await restoreSeedLink.getText(), 'Restore from seed phrase') assert.equal(await restoreSeedLink.getText(), 'Restore from seed phrase')
await restoreSeedLink.click() await restoreSeedLink.click()
@ -201,10 +207,10 @@ describe('Metamask popup page', function () {
}) })
it('balance renders', async function () { it('balance renders', async function () {
await delay(500) await delay(1000)
const balance = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div.flex-row > div.ether-balance.ether-balance-amount > div > div > div:nth-child(1) > div:nth-child(1)')) const balance = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div.flex-row > div.ether-balance.ether-balance-amount > div > div > div:nth-child(1) > div:nth-child(1)'))
assert.equal(await balance.getText(), '100.000') assert.equal(await balance.getText(), '100.000')
await delay(200) await delay(1000)
}) })
it('sends transaction', async function () { it('sends transaction', async function () {
@ -242,12 +248,31 @@ describe('Metamask popup page', function () {
}) })
describe('Token Factory', function () { describe('Token Factory', function () {
let windowHandles
let extension
let dapp
it('navigates to token factory', async function () { it('navigates to token factory', async function () {
await driver.get('http://tokenfactory.surge.sh/') await driver.get('http://token-factory-1102.now.sh')
await delay(7000)
windowHandles = await driver.getAllWindowHandles()
dapp = await switchToWindowWithTitle(driver, 'Token Factory', windowHandles)
await delay(400)
extension = await switchToWindowWithUrlThatMatches(driver, /notification.html/, windowHandles)
await delay(400)
await closeAllWindowHandlesExcept(driver, [extension, dapp])
await switchToWindowWithUrlThatMatches(driver, /notification.html/, [extension, dapp])
const approveButton = await driver.wait(until.elementLocated(By.xpath(`//button[contains(text(), 'APPROVE')]`)), 10000)
await approveButton.click()
}) })
it('navigates to create token contract link', async function () { it('navigates to create token contract link', async function () {
await delay(400)
await switchToWindowWithTitle(driver, 'Token Factory', windowHandles)
await delay(400)
const createToken = await driver.findElement(By.css('#bs-example-navbar-collapse-1 > ul > li:nth-child(3) > a')) const createToken = await driver.findElement(By.css('#bs-example-navbar-collapse-1 > ul > li:nth-child(3) > a'))
await createToken.click() await createToken.click()
}) })

@ -418,7 +418,7 @@ describe('preferences controller', function () {
req.params.options = { address, symbol, decimals, image } req.params.options = { address, symbol, decimals, image }
sandbox.stub(preferencesController, '_validateERC20AssetParams').returns(true) sandbox.stub(preferencesController, '_validateERC20AssetParams').returns(true)
preferencesController.showWatchAssetUi = async () => {} preferencesController.openPopup = async () => {}
await preferencesController._handleWatchAssetERC20(req.params.options) await preferencesController._handleWatchAssetERC20(req.params.options)
const suggested = preferencesController.getSuggestedTokens() const suggested = preferencesController.getSuggestedTokens()
@ -438,7 +438,7 @@ describe('preferences controller', function () {
req.params.options = { address, symbol, decimals, image } req.params.options = { address, symbol, decimals, image }
sandbox.stub(preferencesController, '_validateERC20AssetParams').returns(true) sandbox.stub(preferencesController, '_validateERC20AssetParams').returns(true)
preferencesController.showWatchAssetUi = async () => { preferencesController.openPopup = async () => {
await preferencesController.addToken(address, symbol, decimals, image) await preferencesController.addToken(address, symbol, decimals, image)
} }

@ -325,6 +325,9 @@ var actions = {
clearPendingTokens, clearPendingTokens,
createCancelTransaction, createCancelTransaction,
approveProviderRequest,
rejectProviderRequest,
clearApprovedOrigins,
} }
module.exports = actions module.exports = actions
@ -2484,3 +2487,21 @@ function setPendingTokens (pendingTokens) {
payload: tokens, payload: tokens,
} }
} }
function approveProviderRequest (origin) {
return (dispatch) => {
background.approveProviderRequest(origin)
}
}
function rejectProviderRequest (origin) {
return (dispatch) => {
background.rejectProviderRequest(origin)
}
}
function clearApprovedOrigins () {
return (dispatch) => {
background.clearApprovedOrigins()
}
}

@ -0,0 +1,39 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import Modal, { ModalContent } from '../../modal'
export default class ClearApprovedOrigins extends PureComponent {
static propTypes = {
hideModal: PropTypes.func.isRequired,
clearApprovedOrigins: PropTypes.func.isRequired,
}
static contextTypes = {
t: PropTypes.func,
}
handleClear = () => {
const { clearApprovedOrigins, hideModal } = this.props
clearApprovedOrigins()
hideModal()
}
render () {
const { t } = this.context
return (
<Modal
onSubmit={this.handleClear}
onCancel={() => this.props.hideModal()}
submitText={t('ok')}
cancelText={t('nevermind')}
submitType="secondary"
>
<ModalContent
title={t('clearApprovalData')}
description={t('confirmClear')}
/>
</Modal>
)
}
}

@ -0,0 +1,16 @@
import { connect } from 'react-redux'
import { compose } from 'recompose'
import withModalProps from '../../../higher-order-components/with-modal-props'
import ClearApprovedOriginsComponent from './clear-approved-origins.component'
import { clearApprovedOrigins } from '../../../actions'
const mapDispatchToProps = dispatch => {
return {
clearApprovedOrigins: () => dispatch(clearApprovedOrigins()),
}
}
export default compose(
withModalProps,
connect(null, mapDispatchToProps)
)(ClearApprovedOriginsComponent)

@ -0,0 +1 @@
export { default } from './clear-approved-origins.container'

@ -28,6 +28,7 @@ import ConfirmCustomizeGasModal from './customize-gas'
import CancelTransaction from './cancel-transaction' import CancelTransaction from './cancel-transaction'
import WelcomeBeta from './welcome-beta' import WelcomeBeta from './welcome-beta'
import RejectTransactions from './reject-transactions' import RejectTransactions from './reject-transactions'
import ClearApprovedOrigins from './clear-approved-origins'
const modalContainerBaseStyle = { const modalContainerBaseStyle = {
transform: 'translate3d(-50%, 0, 0px)', transform: 'translate3d(-50%, 0, 0px)',
@ -212,6 +213,19 @@ const MODALS = {
}, },
}, },
CLEAR_APPROVED_ORIGINS: {
contents: h(ClearApprovedOrigins),
mobileModalStyle: {
...modalContainerMobileStyle,
},
laptopModalStyle: {
...modalContainerLaptopStyle,
},
contentStyle: {
borderRadius: '8px',
},
},
OLD_UI_NOTIFICATION_MODAL: { OLD_UI_NOTIFICATION_MODAL: {
contents: [ contents: [
h(NotifcationModal, { h(NotifcationModal, {

@ -101,6 +101,7 @@
font-size: 2rem; font-size: 2rem;
font-weight: 500; font-weight: 500;
line-height: 2rem; line-height: 2rem;
margin-right: 1.5rem;
} }
&__subtitle { &__subtitle {

@ -4,6 +4,8 @@ import Media from 'react-media'
import { Redirect } from 'react-router-dom' import { Redirect } from 'react-router-dom'
import WalletView from '../../wallet-view' import WalletView from '../../wallet-view'
import TransactionView from '../../transaction-view' import TransactionView from '../../transaction-view'
import ProviderApproval from '../provider-approval'
import { import {
INITIALIZE_BACKUP_PHRASE_ROUTE, INITIALIZE_BACKUP_PHRASE_ROUTE,
RESTORE_VAULT_ROUTE, RESTORE_VAULT_ROUTE,
@ -21,6 +23,7 @@ export default class Home extends PureComponent {
seedWords: PropTypes.string, seedWords: PropTypes.string,
suggestedTokens: PropTypes.object, suggestedTokens: PropTypes.object,
unconfirmedTransactionsCount: PropTypes.number, unconfirmedTransactionsCount: PropTypes.number,
providerRequests: PropTypes.array,
} }
componentDidMount () { componentDidMount () {
@ -46,6 +49,7 @@ export default class Home extends PureComponent {
lostAccounts, lostAccounts,
forgottenPassword, forgottenPassword,
seedWords, seedWords,
providerRequests,
} = this.props } = this.props
// notices // notices
@ -62,6 +66,10 @@ export default class Home extends PureComponent {
return <Redirect to={{ pathname: RESTORE_VAULT_ROUTE }} /> return <Redirect to={{ pathname: RESTORE_VAULT_ROUTE }} />
} }
if (providerRequests && providerRequests.length > 0) {
return <ProviderApproval origin={providerRequests[0].origin} />
}
return ( return (
<div className="main-container"> <div className="main-container">
<div className="account-and-transaction-details"> <div className="account-and-transaction-details">

@ -11,6 +11,7 @@ const mapStateToProps = state => {
lostAccounts, lostAccounts,
seedWords, seedWords,
suggestedTokens, suggestedTokens,
providerRequests,
} = metamask } = metamask
const { forgottenPassword } = appState const { forgottenPassword } = appState
@ -21,6 +22,7 @@ const mapStateToProps = state => {
seedWords, seedWords,
suggestedTokens, suggestedTokens,
unconfirmedTransactionsCount: unconfirmedTransactionsCountSelector(state), unconfirmedTransactionsCount: unconfirmedTransactionsCountSelector(state),
providerRequests,
} }
} }

@ -0,0 +1 @@
export { default } from './provider-approval.container'

@ -0,0 +1,35 @@
import PageContainerContent from '../../page-container'
import PropTypes from 'prop-types'
import React, { Component } from 'react'
export default class ProviderApproval extends Component {
static propTypes = {
approveProviderRequest: PropTypes.func,
origin: PropTypes.string,
rejectProviderRequest: PropTypes.func,
};
static contextTypes = {
t: PropTypes.func,
};
render () {
const { approveProviderRequest, origin, rejectProviderRequest } = this.props
return (
<PageContainerContent
title={this.context.t('providerAPIRequest')}
subtitle={this.context.t('reviewProviderRequest')}
contentComponent={(
<div className="provider_approval_content">
{this.context.t('providerRequestInfo')}
<div className="provider_approval_origin">{origin}</div>
</div>
)}
submitText={this.context.t('approve')}
cancelText={this.context.t('reject')}
onSubmit={() => { approveProviderRequest(origin) }}
onCancel={() => { rejectProviderRequest(origin) }}
onClose={() => { rejectProviderRequest(origin) }} />
)
}
}

@ -0,0 +1,12 @@
import { connect } from 'react-redux'
import ProviderApproval from './provider-approval.component'
import { approveProviderRequest, rejectProviderRequest } from '../../../actions'
function mapDispatchToProps (dispatch) {
return {
approveProviderRequest: origin => dispatch(approveProviderRequest(origin)),
rejectProviderRequest: origin => dispatch(rejectProviderRequest(origin)),
}
}
export default connect(null, mapDispatchToProps)(ProviderApproval)

@ -45,6 +45,7 @@ export default class SettingsTab extends PureComponent {
displayWarning: PropTypes.func, displayWarning: PropTypes.func,
revealSeedConfirmation: PropTypes.func, revealSeedConfirmation: PropTypes.func,
setFeatureFlagToBeta: PropTypes.func, setFeatureFlagToBeta: PropTypes.func,
showClearApprovalModal: PropTypes.func,
showResetAccountConfirmationModal: PropTypes.func, showResetAccountConfirmationModal: PropTypes.func,
warning: PropTypes.string, warning: PropTypes.string,
history: PropTypes.object, history: PropTypes.object,
@ -276,6 +277,36 @@ export default class SettingsTab extends PureComponent {
) )
} }
renderClearApproval () {
const { t } = this.context
const { showClearApprovalModal } = this.props
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('approvalData') }</span>
<span className="settings-page__content-description">
{ t('approvalDataDescription') }
</span>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<Button
type="secondary"
large
className="settings-tab__button--orange"
onClick={event => {
event.preventDefault()
showClearApprovalModal()
}}
>
{ t('clearApprovalData') }
</Button>
</div>
</div>
</div>
)
}
renderSeedWords () { renderSeedWords () {
const { t } = this.context const { t } = this.context
const { history } = this.props const { history } = this.props
@ -473,6 +504,7 @@ export default class SettingsTab extends PureComponent {
{ this.renderNewRpcUrl() } { this.renderNewRpcUrl() }
{ this.renderStateLogs() } { this.renderStateLogs() }
{ this.renderSeedWords() } { this.renderSeedWords() }
{ this.renderClearApproval() }
{ !isMascara && this.renderOldUI() } { !isMascara && this.renderOldUI() }
{ this.renderResetAccount() } { this.renderResetAccount() }
{ this.renderBlockieOptIn() } { this.renderBlockieOptIn() }

@ -59,6 +59,7 @@ const mapDispatchToProps = dispatch => {
setUseNativeCurrencyAsPrimaryCurrencyPreference: value => { setUseNativeCurrencyAsPrimaryCurrencyPreference: value => {
return dispatch(setUseNativeCurrencyAsPrimaryCurrencyPreference(value)) return dispatch(setUseNativeCurrencyAsPrimaryCurrencyPreference(value))
}, },
showClearApprovalModal: () => dispatch(showModal({ name: 'CLEAR_APPROVED_ORIGINS' })),
} }
} }

@ -1 +1,3 @@
@import './reveal-seed.scss'; @import './reveal-seed.scss';
@import './provider-approval.scss';

@ -0,0 +1,11 @@
.provider_approval_content {
height: auto;
overflow: auto;
padding: 16px;
}
.provider_approval_origin {
font-weight: 999;
margin-top: 16px;
word-wrap: break-word;
}
Loading…
Cancel
Save