1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 09:57:02 +01:00

Connections settings tab (#7013)

* Nix notification for Share Address

* Add Connections settings tab in place of privacy mode toggle

* Split ProviderApprovalController into two stores

* Remove privacyMode feature flag altogether

* Add migration to remove privacyMode feature flag
This commit is contained in:
Whymarrh Whitby 2019-08-15 18:37:18 -02:30 committed by GitHub
parent 4d9b095dd0
commit 247659ca65
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 711 additions and 506 deletions

View File

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "Režim súkromia"
},
"privacyModeDescription": {
"message": "Webové stránky musia požiadať o prístup k zobrazeniu informácií o vašom účte."
},
"exposeAccounts": { "exposeAccounts": {
"message": "Vystavte účty" "message": "Vystavte účty"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Schválené údaje webových stránek byly úspěšně zrušeny." "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": { "clearApprovalData": {
"message": "Jasné údaje o schválení" "message": "Jasné údaje o schválení"
}, },

View File

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "Datenschutzmodus"
},
"privacyModeDescription": {
"message": "Websites müssen Zugriff anfordern, um Ihre Kontoinformationen anzuzeigen."
},
"exposeAccounts": { "exposeAccounts": {
"message": "Expose Konten" "message": "Expose Konten"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Genehmigte Website-Daten wurden erfolgreich gelöscht." "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": { "clearApprovalData": {
"message": "Genehmigungsdaten löschen" "message": "Genehmigungsdaten löschen"
}, },

View File

@ -1,22 +1,4 @@
{ {
"shareAddress": {
"message": "Share Address"
},
"shareAddressToConnect": {
"message": "Share your address to connect to $1?"
},
"shareAddressInfo": {
"message": "Sharing your address with $1 will allow you to interact with this dapp. This permission is to protect your privacy by default. You may need to reload the dapp for the change to take effect."
},
"privacyModeDefault": {
"message": "Privacy Mode is now enabled by default"
},
"privacyMode": {
"message": "Privacy Mode"
},
"privacyModeDescription": {
"message": "Websites must request access to view your account information."
},
"exposeAccounts": { "exposeAccounts": {
"message": "Expose Accounts" "message": "Expose Accounts"
}, },
@ -32,20 +14,35 @@
"confirmClear": { "confirmClear": {
"message": "Are you sure you want to clear approved websites?" "message": "Are you sure you want to clear approved websites?"
}, },
"connections": {
"message": "Connections"
},
"connectionsSettingsDescription": {
"message": "Sites allowed to read your accounts"
},
"addSite": {
"message": "Add Site"
},
"addSiteDescription": {
"message": "Manually add a site to allow it access to your accounts, useful for older dapps"
},
"connected": {
"message": "Connected"
},
"connectedDescription": {
"message": "The list of sites allowed access to your addresses"
},
"privacyModeDefault": {
"message": "Privacy Mode is now enabled by default"
},
"contractInteraction": { "contractInteraction": {
"message": "Contract Interaction" "message": "Contract Interaction"
}, },
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Approved website data cleared successfully." "message": "Approved website data cleared successfully."
}, },
"approvalData": {
"message": "Privacy Data"
},
"approvalDataDescription": {
"message": "Clear privacy data so all websites must request access to view account information again."
},
"clearApprovalData": { "clearApprovalData": {
"message": "Clear Privacy Data" "message": "Remove all sites"
}, },
"reject": { "reject": {
"message": "Reject" "message": "Reject"

View File

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "Modo privado"
},
"privacyModeDescription": {
"message": "Los sitios web deben solicitar acceso para ver la información de su cuenta."
},
"exposeAccounts": { "exposeAccounts": {
"message": "Exponer cuentas" "message": "Exponer cuentas"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Los datos aprobados del sitio web se borraron con éxito." "message": "Los datos aprobados del sitio web se borraron con éxito."
}, },
"approvalData": {
"message": "Datos de aprobación"
},
"approvalDataDescription": {
"message": "Borrar la información privada de modo que todos los sitios deban volver a requerir acceso para acceder a los datos de la cuenta."
},
"clearApprovalData": { "clearApprovalData": {
"message": "Borrar datos de aprobación" "message": "Borrar datos de aprobación"
}, },

View File

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "Les sites Web doivent demander un accès pour afficher les informations de votre compte."
},
"privacyModeDescription": {
"message": "Les sites Web doivent demander un accès pour afficher les informations de votre compte."
},
"exposeAccounts": { "exposeAccounts": {
"message": "Exposer les comptes" "message": "Exposer les comptes"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Les données de site Web approuvées ont été supprimées." "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": { "clearApprovalData": {
"message": "Effacer les données d'approbation" "message": "Effacer les données d'approbation"
}, },

View File

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "गोपनीयता मोड"
},
"privacyModeDescription": {
"message": "वेबसाइटों को आपकी खाता जानकारी देखने के लिए पहुंच का अनुरोध करना होगा।"
},
"exposeAccounts": { "exposeAccounts": {
"message": "खातों का पर्दाफाश करें" "message": "खातों का पर्दाफाश करें"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "स्वीकृत वेबसाइट डेटा सफलतापूर्वक मंजूरी दे दी।" "message": "स्वीकृत वेबसाइट डेटा सफलतापूर्वक मंजूरी दे दी।"
}, },
"approvalData": {
"message": "स्वीकृति डेटा"
},
"approvalDataDescription": {
"message": "अनुमोदित वेबसाइट डेटा साफ़ करें ताकि सभी साइटों को फिर से अनुमोदन का अनुरोध करना होगा।"
},
"clearApprovalData": { "clearApprovalData": {
"message": "अनुमोदन डेटा साफ़ करें" "message": "अनुमोदन डेटा साफ़ करें"
}, },

View File

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "Mòd Privacy"
},
"privacyModeDescription": {
"message": "Sou sit entènèt yo dwe mande aksè pou wè enfòmasyon kont ou."
},
"exposeAccounts": { "exposeAccounts": {
"message": "Ekspoze Kont" "message": "Ekspoze Kont"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Done sou sit wèb apwouve yo te klarifye avèk siksè." "message": "Done sou sit wèb apwouve yo te klarifye avèk siksè."
}, },
"approvalData": {
"message": "Done sou vi prive"
},
"approvalDataDescription": {
"message": "Done sou vi prive klè pou tout sit entènèt yo dwe mande aksè pou wè enfòmasyon kont ankò."
},
"clearApprovalData": { "clearApprovalData": {
"message": "Klè Done sou vi prive" "message": "Klè Done sou vi prive"
}, },

View File

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "Modalità privacy"
},
"privacyModeDescription": {
"message": "I siti Web devono richiedere l'accesso per visualizzare le informazioni del tuo account."
},
"exposeAccounts": { "exposeAccounts": {
"message": "Esponi Accounts" "message": "Esponi Accounts"
}, },
@ -26,12 +20,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Dati del sito Web approvati cancellati correttamente." "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": { "clearApprovalData": {
"message": "Cancella i dati di approvazione" "message": "Cancella i dati di approvazione"
}, },

View File

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "プライバシーモード"
},
"privacyModeDescription": {
"message": "ウェブサイトはあなたのアカウント情報を閲覧するためのアクセスを要求する必要があります。"
},
"exposeAccounts": { "exposeAccounts": {
"message": "アカウントを公開する" "message": "アカウントを公開する"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "承認されたウェブサイトデータが正常に消去されました。" "message": "承認されたウェブサイトデータが正常に消去されました。"
}, },
"approvalData": {
"message": "承認データ"
},
"approvalDataDescription": {
"message": "承認されたウェブサイトのデータをクリアすると、すべてのサイトで承認を再度要求する必要があります"
},
"clearApprovalData": { "clearApprovalData": {
"message": "承認データのクリア" "message": "承認データのクリア"
}, },

View File

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "개인 정보 보호 모드"
},
"privacyModeDescription": {
"message": "웹 사이트는 계정 정보를 볼 수있는 액세스 권한을 요청해야합니다."
},
"exposeAccounts": { "exposeAccounts": {
"message": "계정 노출" "message": "계정 노출"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "승인 된 웹 사이트 데이터가 성공적으로 삭제되었습니다." "message": "승인 된 웹 사이트 데이터가 성공적으로 삭제되었습니다."
}, },
"approvalData": {
"message": "승인 데이터"
},
"approvalDataDescription": {
"message": "승인 된 웹 사이트 데이터를 삭제하여 모든 사이트에서 승인을 다시 요청해야합니다."
},
"clearApprovalData": { "clearApprovalData": {
"message": "승인 데이터 삭제" "message": "승인 데이터 삭제"
}, },

View File

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "Privacy-modus"
},
"privacyModeDescription": {
"message": "Websites moeten toegang vragen om uw accountgegevens te bekijken."
},
"exposeAccounts": { "exposeAccounts": {
"message": "Expose Accounts" "message": "Expose Accounts"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Goedgekeurde websitegegevens zijn met succes gewist." "message": "Goedgekeurde websitegegevens zijn met succes gewist."
}, },
"approvalData": {
"message": "Goedkeuringsgegevens"
},
"approvalDataDescription": {
"message": "Goedgekeurde websitegegevens wissen zodat alle sites opnieuw goedkeuring moeten aanvragen."
},
"clearApprovalData": { "clearApprovalData": {
"message": "Gegevens over goedkeuring wissen" "message": "Gegevens over goedkeuring wissen"
}, },

View File

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "Mode ng Privacy"
},
"privacyModeDescription": {
"message": "Dapat humiling ng access ang mga website upang tingnan ang impormasyon ng iyong account."
},
"exposeAccounts": { "exposeAccounts": {
"message": "Ilantad ang Mga Account" "message": "Ilantad ang Mga Account"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Matagumpay na na-clear ang data ng aprubadong website." "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": { "clearApprovalData": {
"message": "Tanggalin ang data ng pag-apruba" "message": "Tanggalin ang data ng pag-apruba"
}, },

View File

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "Modo de privacidade"
},
"privacyModeDescription": {
"message": "Os sites devem solicitar acesso para visualizar as informações da sua conta."
},
"exposeAccounts": { "exposeAccounts": {
"message": "Expor contas" "message": "Expor contas"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Dados aprovados do website foram limpos com sucesso." "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": { "clearApprovalData": {
"message": "Limpar dados de aprovação" "message": "Limpar dados de aprovação"
}, },

View File

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "Режим конфиденциальности"
},
"privacyModeDescription": {
"message": "Веб-сайты должны запрашивать доступ для просмотра информации об учетной записи."
},
"exposeAccounts": { "exposeAccounts": {
"message": "Открыть счета" "message": "Открыть счета"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Утвержденные данные веб-сайта успешно удалены." "message": "Утвержденные данные веб-сайта успешно удалены."
}, },
"approvalData": {
"message": "Данные об утверждении"
},
"approvalDataDescription": {
"message": "Очистите утвержденные данные веб-сайта, чтобы все сайты снова запросили подтверждение."
},
"clearApprovalData": { "clearApprovalData": {
"message": "Четкие данные об утверждении" "message": "Четкие данные об утверждении"
}, },

View File

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "Režim súkromia"
},
"privacyModeDescription": {
"message": "Webové stránky musia požiadať o prístup k zobrazeniu informácií o vašom účte."
},
"exposeAccounts": { "exposeAccounts": {
"message": "Vystavte účty" "message": "Vystavte účty"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Schválené údaje webových stránek byly úspěšně zrušeny." "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": { "clearApprovalData": {
"message": "Jasné údaje o schválení" "message": "Jasné údaje o schválení"
}, },

View File

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "Zasebnostni način"
},
"privacyModeDescription": {
"message": "Spletne strani morajo zahtevati dovoljenje za ogled podatkov o vašem računu."
},
"privacyNotice": { "privacyNotice": {
"message": "Obvestilo o zasebnosti" "message": "Obvestilo o zasebnosti"
}, },
@ -26,12 +20,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Odobrene spletne strani uspešno počiščene." "message": "Odobrene spletne strani uspešno počiščene."
}, },
"approvalData": {
"message": "Podatki o odobritvi"
},
"approvalDataDescription": {
"message": "Počistite seznam odobrenih spletnih strani, tako da bodo morale ponovno zahtevati odobritev."
},
"clearApprovalData": { "clearApprovalData": {
"message": "Počisti podatke o odobritvi" "message": "Počisti podatke o odobritvi"
}, },

View File

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "โหมดความเป็นส่วนตัว"
},
"privacyModeDescription": {
"message": "เว็บไซต์ต้องขอเข้าถึงเพื่อดูข้อมูลบัญชีของคุณ"
},
"exposeAccounts": { "exposeAccounts": {
"message": "เปิดเผยบัญชี" "message": "เปิดเผยบัญชี"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "อนุมัติข้อมูลเว็บไซต์ที่ได้รับอนุมัติแล้ว" "message": "อนุมัติข้อมูลเว็บไซต์ที่ได้รับอนุมัติแล้ว"
}, },
"approvalData": {
"message": "ข้อมูลการอนุมัติ"
},
"approvalDataDescription": {
"message": "ล้างข้อมูลเว็บไซต์ที่ได้รับการอนุมัติเพื่อให้ทุกไซต์ต้องขออนุมัติอีกครั้ง"
},
"clearApprovalData": { "clearApprovalData": {
"message": "ล้างข้อมูลการอนุมัติ" "message": "ล้างข้อมูลการอนุมัติ"
}, },

View File

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "தனியுரிமை முறை"
},
"privacyModeDescription": {
"message": "உங்கள் கணக்குத் தகவலை பார்வையிட வலைத்தளங்கள் அணுகலைக் கோர வேண்டும்."
},
"exposeAccounts": { "exposeAccounts": {
"message": "கணக்குகளை அம்பலப்படுத்துங்கள்" "message": "கணக்குகளை அம்பலப்படுத்துங்கள்"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "அங்கீகரிக்கப்பட்ட வலைத்தள தரவு வெற்றிகரமாக அழிக்கப்பட்டது." "message": "அங்கீகரிக்கப்பட்ட வலைத்தள தரவு வெற்றிகரமாக அழிக்கப்பட்டது."
}, },
"approvalData": {
"message": "ஒப்புதல் தரவு"
},
"approvalDataDescription": {
"message": "அங்கீகரிக்கப்பட்ட வலைத்தள தரவை அழிக்கவும், அனைத்து தளங்களும் ஒப்புதல் மீண்டும் கோர வேண்டும்."
},
"clearApprovalData": { "clearApprovalData": {
"message": "ஒப்புதல் தரவை அழி" "message": "ஒப்புதல் தரவை அழி"
}, },

View File

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "Gizlilik modu"
},
"privacyModeDescription": {
"message": "Web siteleri, hesap bilgilerinizi görmek için erişim istemek zorundadır."
},
"exposeAccounts": { "exposeAccounts": {
"message": "Hesaplarıığa Çıkar" "message": "Hesaplarıığa Çıkar"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Onaylanan web sitesi verileri başarıyla temizlendi." "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": { "clearApprovalData": {
"message": "Onay verilerini temizle" "message": "Onay verilerini temizle"
}, },

View File

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "Chế độ riêng tư"
},
"privacyModeDescription": {
"message": "Trang web phải yêu cầu quyền truy cập để xem thông tin tài khoản của bạn."
},
"exposeAccounts": { "exposeAccounts": {
"message": "Hiển thị tài khoản" "message": "Hiển thị tài khoản"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Đã xóa thành công dữ liệu trang web được phê duyệt." "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": { "clearApprovalData": {
"message": "Xóa dữ liệu phê duyệt" "message": "Xóa dữ liệu phê duyệt"
}, },

View File

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "隐私模式"
},
"privacyModeDescription": {
"message": "网站必须请求访问权限才能查看您的帐户信息。"
},
"exposeAccounts": { "exposeAccounts": {
"message": "公开账户" "message": "公开账户"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "已批准的网站数据已成功清除。" "message": "已批准的网站数据已成功清除。"
}, },
"approvalData": {
"message": "审批数据"
},
"approvalDataDescription": {
"message": "清除已批准的网站数据,以便所有网站都必须再次申请"
},
"clearApprovalData": { "clearApprovalData": {
"message": "清除批准数据" "message": "清除批准数据"
}, },

View File

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "隱私模式"
},
"privacyModeDescription": {
"message": "網站必須請求訪問權限才能查看您的帳戶資訊"
},
"exposeAccounts": { "exposeAccounts": {
"message": "公開賬戶" "message": "公開賬戶"
}, },
@ -23,12 +17,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "已批准的網站紀錄已成功清除。" "message": "已批准的網站紀錄已成功清除。"
}, },
"approvalData": {
"message": "審核紀錄"
},
"approvalDataDescription": {
"message": "清除之前已批准過的網站審核紀錄,所有網站都必須再次申請"
},
"clearApprovalData": { "clearApprovalData": {
"message": "清除批准數據" "message": "清除批准數據"
}, },

View File

@ -409,7 +409,7 @@ function setupController (initState, initLangCode) {
controller.messageManager.on('updateBadge', updateBadge) controller.messageManager.on('updateBadge', updateBadge)
controller.personalMessageManager.on('updateBadge', updateBadge) controller.personalMessageManager.on('updateBadge', updateBadge)
controller.typedMessageManager.on('updateBadge', updateBadge) controller.typedMessageManager.on('updateBadge', updateBadge)
controller.providerApprovalController.store.on('update', updateBadge) controller.providerApprovalController.memStore.on('update', updateBadge)
/** /**
* Updates the Web Extension's "badge" number, on the little fox in the toolbar. * Updates the Web Extension's "badge" number, on the little fox in the toolbar.
@ -421,7 +421,7 @@ function setupController (initState, initLangCode) {
const unapprovedMsgCount = controller.messageManager.unapprovedMsgCount const unapprovedMsgCount = controller.messageManager.unapprovedMsgCount
const unapprovedPersonalMsgs = controller.personalMessageManager.unapprovedPersonalMsgCount const unapprovedPersonalMsgs = controller.personalMessageManager.unapprovedPersonalMsgCount
const unapprovedTypedMsgs = controller.typedMessageManager.unapprovedTypedMessagesCount const unapprovedTypedMsgs = controller.typedMessageManager.unapprovedTypedMessagesCount
const pendingProviderRequests = controller.providerApprovalController.store.getState().providerRequests.length const pendingProviderRequests = controller.providerApprovalController.memStore.getState().providerRequests.length
const count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgs + unapprovedTypedMsgs + pendingProviderRequests const count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgs + unapprovedTypedMsgs + pendingProviderRequests
if (count) { if (count) {
label = String(count) label = String(count)

View File

@ -41,7 +41,6 @@ class PreferencesController {
// for convenient testing of pre-release features, and should never // for convenient testing of pre-release features, and should never
// perform sensitive operations. // perform sensitive operations.
featureFlags: { featureFlags: {
privacyMode: true,
}, },
knownMethodData: {}, knownMethodData: {},
participateInMetaMetrics: null, participateInMetaMetrics: null,

View File

@ -6,27 +6,23 @@ const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware
* A controller that services user-approved requests for a full Ethereum provider API * A controller that services user-approved requests for a full Ethereum provider API
*/ */
class ProviderApprovalController extends SafeEventEmitter { class ProviderApprovalController extends SafeEventEmitter {
/**
* Determines if caching is enabled
*/
caching = true
/** /**
* Creates a ProviderApprovalController * Creates a ProviderApprovalController
* *
* @param {Object} [config] - Options to configure controller * @param {Object} [config] - Options to configure controller
*/ */
constructor ({ closePopup, keyringController, openPopup, preferencesController } = {}) { constructor ({ closePopup, initState, keyringController, openPopup, preferencesController } = {}) {
super() super()
this.closePopup = closePopup this.closePopup = closePopup
this.keyringController = keyringController this.keyringController = keyringController
this.openPopup = openPopup this.openPopup = openPopup
this.preferencesController = preferencesController this.preferencesController = preferencesController
this.store = new ObservableStore({ this.memStore = new ObservableStore({
approvedOrigins: {},
dismissedOrigins: {},
providerRequests: [], providerRequests: [],
}) })
const defaultState = { approvedOrigins: {} }
this.store = new ObservableStore(Object.assign(defaultState, initState))
} }
/** /**
@ -65,11 +61,17 @@ class ProviderApprovalController extends SafeEventEmitter {
* @param {string} siteImage - The icon of the window requesting full provider access * @param {string} siteImage - The icon of the window requesting full provider access
*/ */
_handleProviderRequest (origin, siteTitle, siteImage) { _handleProviderRequest (origin, siteTitle, siteImage) {
this.store.updateState({ providerRequests: [{ origin, siteTitle, siteImage }] }) const { providerRequests } = this.memStore.getState()
this.memStore.updateState({
providerRequests: [
...providerRequests,
{ origin, siteTitle, siteImage },
],
})
const isUnlocked = this.keyringController.memStore.getState().isUnlocked const isUnlocked = this.keyringController.memStore.getState().isUnlocked
const { approvedOrigins, dismissedOrigins } = this.store.getState() const { approvedOrigins } = this.store.getState()
const originAlreadyHandled = approvedOrigins[origin] || dismissedOrigins[origin] const originAlreadyHandled = approvedOrigins[origin]
if (originAlreadyHandled && this.caching && isUnlocked) { if (originAlreadyHandled && isUnlocked) {
return return
} }
this.openPopup && this.openPopup() this.openPopup && this.openPopup()
@ -85,23 +87,20 @@ class ProviderApprovalController extends SafeEventEmitter {
this.closePopup() this.closePopup()
} }
const { approvedOrigins, dismissedOrigins, providerRequests } = this.store.getState() const { approvedOrigins } = this.store.getState()
const { providerRequests } = this.memStore.getState()
let _dismissedOrigins = dismissedOrigins const providerRequest = providerRequests.find((request) => request.origin === origin)
if (dismissedOrigins[origin]) {
_dismissedOrigins = Object.assign({}, dismissedOrigins)
delete _dismissedOrigins[origin]
}
const remainingProviderRequests = providerRequests.filter(request => request.origin !== origin) const remainingProviderRequests = providerRequests.filter(request => request.origin !== origin)
this.store.updateState({ this.store.updateState({
approvedOrigins: { approvedOrigins: {
...approvedOrigins, ...approvedOrigins,
[origin]: true, [origin]: {
siteTitle: providerRequest ? providerRequest.siteTitle : null,
siteImage: providerRequest ? providerRequest.siteImage : null,
},
}, },
dismissedOrigins: _dismissedOrigins,
providerRequests: remainingProviderRequests,
}) })
this.memStore.updateState({ providerRequests: remainingProviderRequests })
this.emit(`resolvedRequest:${origin}`, { approved: true }) this.emit(`resolvedRequest:${origin}`, { approved: true })
} }
@ -115,51 +114,21 @@ class ProviderApprovalController extends SafeEventEmitter {
this.closePopup() this.closePopup()
} }
const { approvedOrigins, providerRequests, dismissedOrigins } = this.store.getState() const { approvedOrigins } = this.store.getState()
const { providerRequests } = this.memStore.getState()
const remainingProviderRequests = providerRequests.filter(request => request.origin !== origin) const remainingProviderRequests = providerRequests.filter(request => request.origin !== origin)
// We're cloning and deleting keys here because we don't want to keep unneeded keys // We're cloning and deleting keys here because we don't want to keep unneeded keys
const _approvedOrigins = Object.assign({}, approvedOrigins) const _approvedOrigins = Object.assign({}, approvedOrigins)
delete _approvedOrigins[origin] delete _approvedOrigins[origin]
this.store.putState({ this.store.putState({ approvedOrigins: _approvedOrigins })
approvedOrigins: _approvedOrigins, this.memStore.putState({ providerRequests: remainingProviderRequests })
providerRequests: remainingProviderRequests,
dismissedOrigins: {
...dismissedOrigins,
[origin]: true,
},
})
this.emit(`resolvedRequest:${origin}`, { approved: false }) this.emit(`resolvedRequest:${origin}`, { approved: false })
} }
/** /**
* Silently approves access to a full Ethereum provider API for the origin * Clears any approvals for user-approved origins
*
* @param {string} origin - origin of the domain that had provider access approved
*/
forceApproveProviderRequestByOrigin (origin) {
const { approvedOrigins, dismissedOrigins, providerRequests } = this.store.getState()
const remainingProviderRequests = providerRequests.filter(request => request.origin !== origin)
let _dismissedOrigins = dismissedOrigins
if (dismissedOrigins[origin]) {
_dismissedOrigins = Object.assign({}, dismissedOrigins)
delete _dismissedOrigins[origin]
}
this.store.updateState({
approvedOrigins: {
...approvedOrigins,
[origin]: true,
},
dismissedOrigins: _dismissedOrigins,
providerRequests: remainingProviderRequests,
})
}
/**
* Clears any cached approvals for user-approved origins
*/ */
clearApprovedOrigins () { clearApprovedOrigins () {
this.store.updateState({ this.store.updateState({
@ -174,10 +143,17 @@ class ProviderApprovalController extends SafeEventEmitter {
* @returns {boolean} - True if the origin has been approved * @returns {boolean} - True if the origin has been approved
*/ */
shouldExposeAccounts (origin) { shouldExposeAccounts (origin) {
const privacyMode = this.preferencesController.getFeatureFlags().privacyMode return Boolean(this.store.getState().approvedOrigins[origin])
return !privacyMode || Boolean(this.store.getState().approvedOrigins[origin])
} }
/**
* Returns a merged state representation
* @return {object}
* @private
*/
_getMergedState () {
return Object.assign({}, this.memStore.getState(), this.store.getState())
}
} }
module.exports = ProviderApprovalController module.exports = ProviderApprovalController

View File

@ -251,6 +251,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.providerApprovalController = new ProviderApprovalController({ this.providerApprovalController = new ProviderApprovalController({
closePopup: opts.closePopup, closePopup: opts.closePopup,
initState: initState.ProviderApprovalController,
keyringController: this.keyringController, keyringController: this.keyringController,
openPopup: opts.openPopup, openPopup: opts.openPopup,
preferencesController: this.preferencesController, preferencesController: this.preferencesController,
@ -268,6 +269,7 @@ module.exports = class MetamaskController extends EventEmitter {
InfuraController: this.infuraController.store, InfuraController: this.infuraController.store,
CachedBalancesController: this.cachedBalancesController.store, CachedBalancesController: this.cachedBalancesController.store,
OnboardingController: this.onboardingController.store, OnboardingController: this.onboardingController.store,
ProviderApprovalController: this.providerApprovalController.store,
}) })
this.memStore = new ComposableObservableStore(null, { this.memStore = new ComposableObservableStore(null, {
@ -288,8 +290,10 @@ module.exports = class MetamaskController extends EventEmitter {
CurrencyController: this.currencyRateController, CurrencyController: this.currencyRateController,
ShapeshiftController: this.shapeshiftController, ShapeshiftController: this.shapeshiftController,
InfuraController: this.infuraController.store, InfuraController: this.infuraController.store,
ProviderApprovalController: this.providerApprovalController.store,
OnboardingController: this.onboardingController.store, OnboardingController: this.onboardingController.store,
// ProviderApprovalController
ProviderApprovalController: this.providerApprovalController.store,
ProviderApprovalControllerMemStore: this.providerApprovalController.memStore,
}) })
this.memStore.subscribe(this.sendUpdate.bind(this)) this.memStore.subscribe(this.sendUpdate.bind(this))
} }
@ -503,7 +507,6 @@ module.exports = class MetamaskController extends EventEmitter {
// provider approval // provider approval
approveProviderRequestByOrigin: providerApprovalController.approveProviderRequestByOrigin.bind(providerApprovalController), approveProviderRequestByOrigin: providerApprovalController.approveProviderRequestByOrigin.bind(providerApprovalController),
rejectProviderRequestByOrigin: providerApprovalController.rejectProviderRequestByOrigin.bind(providerApprovalController), rejectProviderRequestByOrigin: providerApprovalController.rejectProviderRequestByOrigin.bind(providerApprovalController),
forceApproveProviderRequestByOrigin: providerApprovalController.forceApproveProviderRequestByOrigin.bind(providerApprovalController),
clearApprovedOrigins: providerApprovalController.clearApprovedOrigins.bind(providerApprovalController), clearApprovedOrigins: providerApprovalController.clearApprovedOrigins.bind(providerApprovalController),
// onboarding controller // onboarding controller

View File

@ -0,0 +1,30 @@
const version = 36
const clone = require('clone')
/**
* The purpose of this migration is to remove the {@code privacyMode} feature flag.
*/
module.exports = {
version,
migrate: async function (originalVersionedData) {
const versionedData = clone(originalVersionedData)
versionedData.meta.version = version
const state = versionedData.data
versionedData.data = transformState(state)
return versionedData
},
}
function transformState (state) {
const { PreferencesController } = state
if (PreferencesController) {
const featureFlags = PreferencesController.featureFlags || {}
if (typeof featureFlags.privacyMode !== 'undefined') {
delete featureFlags.privacyMode
}
}
return state
}

View File

@ -46,4 +46,5 @@ module.exports = [
require('./033'), require('./033'),
require('./034'), require('./034'),
require('./035'), require('./035'),
require('./036'),
] ]

View File

@ -0,0 +1,260 @@
const assert = require('assert')
const sinon = require('sinon')
const ProviderApprovalController = require('../../../../app/scripts/controllers/provider-approval')
const mockLockedKeyringController = {
memStore: {
getState: () => ({
isUnlocked: false,
}),
},
}
const mockUnlockedKeyringController = {
memStore: {
getState: () => ({
isUnlocked: true,
}),
},
}
describe('ProviderApprovalController', () => {
describe('#_handleProviderRequest', () => {
it('should add a pending provider request when unlocked', () => {
const controller = new ProviderApprovalController({
keyringController: mockUnlockedKeyringController,
})
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
assert.deepEqual(controller._getMergedState(), {
approvedOrigins: {},
providerRequests: [{
origin: 'example.com',
siteTitle: 'Example',
siteImage: 'https://example.com/logo.svg',
}],
})
})
it('should add a pending provider request when locked', () => {
const controller = new ProviderApprovalController({
keyringController: mockLockedKeyringController,
})
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
assert.deepEqual(controller._getMergedState(), {
approvedOrigins: {},
providerRequests: [{
origin: 'example.com',
siteTitle: 'Example',
siteImage: 'https://example.com/logo.svg',
}],
})
})
it('should add a 2nd pending provider request when unlocked', () => {
const controller = new ProviderApprovalController({
keyringController: mockUnlockedKeyringController,
})
controller._handleProviderRequest('example1.com', 'Example 1', 'https://example1.com/logo.svg')
controller._handleProviderRequest('example2.com', 'Example 2', 'https://example2.com/logo.svg')
assert.deepEqual(controller._getMergedState(), {
approvedOrigins: {},
providerRequests: [{
origin: 'example1.com',
siteTitle: 'Example 1',
siteImage: 'https://example1.com/logo.svg',
}, {
origin: 'example2.com',
siteTitle: 'Example 2',
siteImage: 'https://example2.com/logo.svg',
}],
})
})
it('should add a 2nd pending provider request when locked', () => {
const controller = new ProviderApprovalController({
keyringController: mockLockedKeyringController,
})
controller._handleProviderRequest('example1.com', 'Example 1', 'https://example1.com/logo.svg')
controller._handleProviderRequest('example2.com', 'Example 2', 'https://example2.com/logo.svg')
assert.deepEqual(controller._getMergedState(), {
approvedOrigins: {},
providerRequests: [{
origin: 'example1.com',
siteTitle: 'Example 1',
siteImage: 'https://example1.com/logo.svg',
}, {
origin: 'example2.com',
siteTitle: 'Example 2',
siteImage: 'https://example2.com/logo.svg',
}],
})
})
it('should call openPopup when unlocked and when given', () => {
const openPopup = sinon.spy()
const controller = new ProviderApprovalController({
openPopup,
keyringController: mockUnlockedKeyringController,
})
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
assert.ok(openPopup.calledOnce)
})
it('should call openPopup when locked and when given', () => {
const openPopup = sinon.spy()
const controller = new ProviderApprovalController({
openPopup,
keyringController: mockLockedKeyringController,
})
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
assert.ok(openPopup.calledOnce)
})
it('should NOT call openPopup when unlocked and when the domain has already been approved', () => {
const openPopup = sinon.spy()
const controller = new ProviderApprovalController({
openPopup,
keyringController: mockUnlockedKeyringController,
})
controller.store.updateState({
approvedOrigins: {
'example.com': {
siteTitle: 'Example',
siteImage: 'https://example.com/logo.svg',
},
},
})
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
assert.ok(openPopup.notCalled)
})
})
describe('#approveProviderRequestByOrigin', () => {
it('should mark the origin as approved and remove the provider request', () => {
const controller = new ProviderApprovalController({
keyringController: mockUnlockedKeyringController,
})
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
controller.approveProviderRequestByOrigin('example.com')
assert.deepEqual(controller._getMergedState(), {
providerRequests: [],
approvedOrigins: {
'example.com': {
siteTitle: 'Example',
siteImage: 'https://example.com/logo.svg',
},
},
})
})
it('should mark the origin as approved and multiple requests for the same domain', () => {
const controller = new ProviderApprovalController({
keyringController: mockUnlockedKeyringController,
})
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
controller.approveProviderRequestByOrigin('example.com')
assert.deepEqual(controller._getMergedState(), {
providerRequests: [],
approvedOrigins: {
'example.com': {
siteTitle: 'Example',
siteImage: 'https://example.com/logo.svg',
},
},
})
})
it('should mark the origin as approved without a provider request', () => {
const controller = new ProviderApprovalController({
keyringController: mockUnlockedKeyringController,
})
controller.approveProviderRequestByOrigin('example.com')
assert.deepEqual(controller._getMergedState(), {
providerRequests: [],
approvedOrigins: {
'example.com': {
siteTitle: null,
siteImage: null,
},
},
})
})
})
describe('#rejectProviderRequestByOrigin', () => {
it('should remove the origin from approved', () => {
const controller = new ProviderApprovalController({
keyringController: mockUnlockedKeyringController,
})
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
controller.approveProviderRequestByOrigin('example.com')
controller.rejectProviderRequestByOrigin('example.com')
assert.deepEqual(controller._getMergedState(), {
providerRequests: [],
approvedOrigins: {},
})
})
it('should reject the origin even without a pending request', () => {
const controller = new ProviderApprovalController({
keyringController: mockUnlockedKeyringController,
})
controller.rejectProviderRequestByOrigin('example.com')
assert.deepEqual(controller._getMergedState(), {
providerRequests: [],
approvedOrigins: {},
})
})
})
describe('#clearApprovedOrigins', () => {
it('should clear the approved origins', () => {
const controller = new ProviderApprovalController({
keyringController: mockUnlockedKeyringController,
})
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
controller.approveProviderRequestByOrigin('example.com')
controller.clearApprovedOrigins()
assert.deepEqual(controller._getMergedState(), {
providerRequests: [],
approvedOrigins: {},
})
})
})
describe('#shouldExposeAccounts', () => {
it('should return true for an approved origin', () => {
const controller = new ProviderApprovalController({
keyringController: mockUnlockedKeyringController,
})
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
controller.approveProviderRequestByOrigin('example.com')
assert.ok(controller.shouldExposeAccounts('example.com'))
})
it('should return false for an origin not yet approved', () => {
const controller = new ProviderApprovalController({
keyringController: mockUnlockedKeyringController,
})
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
controller.approveProviderRequestByOrigin('example.com')
assert.ok(!controller.shouldExposeAccounts('bad.website'))
})
})
})

View File

@ -0,0 +1,119 @@
const assert = require('assert')
const migration36 = require('../../../app/scripts/migrations/036')
describe('migration #36', () => {
it('should update the version metadata', (done) => {
const oldStorage = {
'meta': {
'version': 35,
},
'data': {},
}
migration36.migrate(oldStorage)
.then((newStorage) => {
assert.deepEqual(newStorage.meta, {
'version': 36,
})
done()
})
.catch(done)
})
it('should remove privacyMode if featureFlags.privacyMode was false', (done) => {
const oldStorage = {
'meta': {},
'data': {
'PreferencesController': {
'featureFlags': {
'privacyMode': false,
},
},
},
}
migration36.migrate(oldStorage)
.then((newStorage) => {
assert.deepEqual(newStorage.data.PreferencesController, {
'featureFlags': {
},
})
done()
})
.catch(done)
})
it('should remove privacyMode if featureFlags.privacyMode was true', (done) => {
const oldStorage = {
'meta': {},
'data': {
'PreferencesController': {
'featureFlags': {
'privacyMode': true,
},
},
},
}
migration36.migrate(oldStorage)
.then((newStorage) => {
assert.deepEqual(newStorage.data.PreferencesController, {
'featureFlags': {
},
})
done()
})
.catch(done)
})
it('should NOT change any state if privacyMode does not exist', (done) => {
const oldStorage = {
'meta': {},
'data': {
'PreferencesController': {
'migratedPrivacyMode': true,
'featureFlags': {
},
},
},
}
migration36.migrate(oldStorage)
.then((newStorage) => {
assert.deepEqual(newStorage.data, oldStorage.data)
done()
})
.catch(done)
})
it('should NOT change any state if PreferencesController is missing', (done) => {
const oldStorage = {
'meta': {},
'data': {},
}
migration36.migrate(oldStorage)
.then((newStorage) => {
assert.deepEqual(newStorage.data, oldStorage.data)
done()
})
.catch(done)
})
it('should NOT change any state if featureFlags is missing', (done) => {
const oldStorage = {
'meta': {},
'data': {
'PreferencesController': {
},
},
}
migration36.migrate(oldStorage)
.then((newStorage) => {
assert.deepEqual(newStorage.data, oldStorage.data)
done()
})
.catch(done)
})
})

View File

@ -3,6 +3,7 @@ const UNLOCK_ROUTE = '/unlock'
const LOCK_ROUTE = '/lock' const LOCK_ROUTE = '/lock'
const SETTINGS_ROUTE = '/settings' const SETTINGS_ROUTE = '/settings'
const GENERAL_ROUTE = '/settings/general' const GENERAL_ROUTE = '/settings/general'
const CONNECTIONS_ROUTE = '/settings/connections'
const ADVANCED_ROUTE = '/settings/advanced' const ADVANCED_ROUTE = '/settings/advanced'
const SECURITY_ROUTE = '/settings/security' const SECURITY_ROUTE = '/settings/security'
const ABOUT_US_ROUTE = '/settings/about-us' const ABOUT_US_ROUTE = '/settings/about-us'
@ -82,6 +83,7 @@ module.exports = {
ADVANCED_ROUTE, ADVANCED_ROUTE,
SECURITY_ROUTE, SECURITY_ROUTE,
GENERAL_ROUTE, GENERAL_ROUTE,
CONNECTIONS_ROUTE,
ABOUT_US_ROUTE, ABOUT_US_ROUTE,
CONTACT_LIST_ROUTE, CONTACT_LIST_ROUTE,
CONTACT_EDIT_ROUTE, CONTACT_EDIT_ROUTE,
@ -93,4 +95,3 @@ module.exports = {
NETWORKS_ROUTE, NETWORKS_ROUTE,
INITIALIZE_BACKUP_SEED_PHRASE_ROUTE, INITIALIZE_BACKUP_SEED_PHRASE_ROUTE,
} }

View File

@ -21,18 +21,10 @@ export default class Home extends PureComponent {
} }
static defaultProps = { static defaultProps = {
activeTab: {},
unsetMigratedPrivacyMode: null, unsetMigratedPrivacyMode: null,
forceApproveProviderRequestByOrigin: null,
} }
static propTypes = { static propTypes = {
activeTab: PropTypes.shape({
origin: PropTypes.string,
protocol: PropTypes.string,
title: PropTypes.string,
url: PropTypes.string,
}),
history: PropTypes.object, history: PropTypes.object,
forgottenPassword: PropTypes.bool, forgottenPassword: PropTypes.bool,
suggestedTokens: PropTypes.object, suggestedTokens: PropTypes.object,
@ -40,10 +32,7 @@ export default class Home extends PureComponent {
providerRequests: PropTypes.array, providerRequests: PropTypes.array,
showPrivacyModeNotification: PropTypes.bool.isRequired, showPrivacyModeNotification: PropTypes.bool.isRequired,
unsetMigratedPrivacyMode: PropTypes.func, unsetMigratedPrivacyMode: PropTypes.func,
viewingUnconnectedDapp: PropTypes.bool.isRequired,
forceApproveProviderRequestByOrigin: PropTypes.func,
shouldShowSeedPhraseReminder: PropTypes.bool, shouldShowSeedPhraseReminder: PropTypes.bool,
rejectProviderRequestByOrigin: PropTypes.func,
isPopup: PropTypes.bool, isPopup: PropTypes.bool,
} }
@ -73,16 +62,12 @@ export default class Home extends PureComponent {
render () { render () {
const { t } = this.context const { t } = this.context
const { const {
activeTab,
forgottenPassword, forgottenPassword,
providerRequests, providerRequests,
history, history,
showPrivacyModeNotification, showPrivacyModeNotification,
unsetMigratedPrivacyMode, unsetMigratedPrivacyMode,
viewingUnconnectedDapp,
forceApproveProviderRequestByOrigin,
shouldShowSeedPhraseReminder, shouldShowSeedPhraseReminder,
rejectProviderRequestByOrigin,
isPopup, isPopup,
} = this.props } = this.props
@ -120,20 +105,6 @@ export default class Home extends PureComponent {
key="home-privacyModeDefault" key="home-privacyModeDefault"
/>, />,
}, },
{
shouldBeRendered: viewingUnconnectedDapp,
component: <HomeNotification
descriptionText={t('shareAddressToConnect', [activeTab.origin])}
acceptText={t('shareAddress')}
onAccept={() => {
forceApproveProviderRequestByOrigin(activeTab.origin)
}}
ignoreText={t('dismiss')}
onIgnore={() => rejectProviderRequestByOrigin(activeTab.origin)}
infoText={t('shareAddressInfo', [activeTab.origin])}
key="home-shareAddressToConnect"
/>,
},
{ {
shouldBeRendered: shouldShowSeedPhraseReminder, shouldBeRendered: shouldShowSeedPhraseReminder,
component: <HomeNotification component: <HomeNotification

View File

@ -5,39 +5,23 @@ import { withRouter } from 'react-router-dom'
import { unconfirmedTransactionsCountSelector } from '../../selectors/confirm-transaction' import { unconfirmedTransactionsCountSelector } from '../../selectors/confirm-transaction'
import { getCurrentEthBalance } from '../../selectors/selectors' import { getCurrentEthBalance } from '../../selectors/selectors'
import { import {
forceApproveProviderRequestByOrigin,
unsetMigratedPrivacyMode, unsetMigratedPrivacyMode,
rejectProviderRequestByOrigin,
} from '../../store/actions' } from '../../store/actions'
import { getEnvironmentType } from '../../../../app/scripts/lib/util' import { getEnvironmentType } from '../../../../app/scripts/lib/util'
import { ENVIRONMENT_TYPE_POPUP } from '../../../../app/scripts/lib/enums' import { ENVIRONMENT_TYPE_POPUP } from '../../../../app/scripts/lib/enums'
const activeTabDappProtocols = ['http:', 'https:', 'dweb:', 'ipfs:', 'ipns:', 'ssb:']
const mapStateToProps = state => { const mapStateToProps = state => {
const { activeTab, metamask, appState } = state const { metamask, appState } = state
const { const {
approvedOrigins,
dismissedOrigins,
suggestedTokens, suggestedTokens,
providerRequests, providerRequests,
migratedPrivacyMode, migratedPrivacyMode,
featureFlags: {
privacyMode,
} = {},
seedPhraseBackedUp, seedPhraseBackedUp,
tokens, tokens,
} = metamask } = metamask
const accountBalance = getCurrentEthBalance(state) const accountBalance = getCurrentEthBalance(state)
const { forgottenPassword } = appState const { forgottenPassword } = appState
const isUnconnected = Boolean(
activeTab &&
activeTabDappProtocols.includes(activeTab.protocol) &&
privacyMode &&
!approvedOrigins[activeTab.origin] &&
!dismissedOrigins[activeTab.origin]
)
const isPopup = getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP const isPopup = getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP
return { return {
@ -46,8 +30,6 @@ const mapStateToProps = state => {
unconfirmedTransactionsCount: unconfirmedTransactionsCountSelector(state), unconfirmedTransactionsCount: unconfirmedTransactionsCountSelector(state),
providerRequests, providerRequests,
showPrivacyModeNotification: migratedPrivacyMode, showPrivacyModeNotification: migratedPrivacyMode,
activeTab,
viewingUnconnectedDapp: isUnconnected && isPopup,
shouldShowSeedPhraseReminder: !seedPhraseBackedUp && (parseInt(accountBalance, 16) > 0 || tokens.length > 0), shouldShowSeedPhraseReminder: !seedPhraseBackedUp && (parseInt(accountBalance, 16) > 0 || tokens.length > 0),
isPopup, isPopup,
} }
@ -55,8 +37,6 @@ const mapStateToProps = state => {
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
unsetMigratedPrivacyMode: () => dispatch(unsetMigratedPrivacyMode()), unsetMigratedPrivacyMode: () => dispatch(unsetMigratedPrivacyMode()),
forceApproveProviderRequestByOrigin: (origin) => dispatch(forceApproveProviderRequestByOrigin(origin)),
rejectProviderRequestByOrigin: origin => dispatch(rejectProviderRequestByOrigin(origin)),
}) })
export default compose( export default compose(

View File

@ -0,0 +1,31 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
export default class ConnectedSiteRow extends PureComponent {
static defaultProps = {
siteTitle: null,
siteImage: null,
onDelete: () => {},
}
static propTypes = {
siteTitle: PropTypes.string,
siteImage: PropTypes.string,
origin: PropTypes.string.isRequired,
onDelete: PropTypes.func,
}
render () {
const {
origin,
onDelete,
} = this.props
return (
<div className="connected-site-row">
<div className="connected-site-row__origin">{origin}</div>
<div className="connected-site-row__delete" onClick={onDelete}><i className="fa fa-trash" /></div>
</div>
)
}
}

View File

@ -0,0 +1 @@
export { default } from './connected-site-row.component'

View File

@ -0,0 +1,14 @@
.connected-site-row {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
&__origin {
font-family: monospace;
}
&__delete {
padding: 8px;
}
}

View File

@ -0,0 +1,133 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import ConnectedSiteEntry from './connected-site-row'
import TextField from '../../../components/ui/text-field'
import Button from '../../../components/ui/button'
export default class ConnectionsTab extends PureComponent {
static contextTypes = {
t: PropTypes.func,
metricsEvent: PropTypes.func,
}
static defaultProps = {
activeTab: {},
}
static propTypes = {
activeTab: PropTypes.object,
approvedOrigins: PropTypes.object.isRequired,
approveProviderRequestByOrigin: PropTypes.func.isRequired,
rejectProviderRequestByOrigin: PropTypes.func.isRequired,
showClearApprovalModal: PropTypes.func.isRequired,
}
state = {
input: this.props.activeTab.origin || '',
}
handleAddOrigin = () => {
const newOrigin = this.state.input
this.setState({
input: '',
}, () => {
if (newOrigin && newOrigin.trim()) {
this.props.approveProviderRequestByOrigin(newOrigin)
}
})
}
renderNewOriginInput () {
const { t } = this.context
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('addSite') }</span>
<div className="settings-page__content-description">
{ t('addSiteDescription') }
</div>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<TextField
type="text"
value={this.state.input}
onChange={e => this.setState({ input: e.target.value })}
fullWidth
margin="dense"
min={0}
/>
<button
className="button btn-primary settings-tab__rpc-save-button"
onClick={this.handleAddOrigin}
>
{ t('connect') }
</button>
</div>
</div>
</div>
)
}
renderApprovedOriginsList () {
const { t } = this.context
const { approvedOrigins, rejectProviderRequestByOrigin, showClearApprovalModal } = this.props
const approvedEntries = Object.entries(approvedOrigins)
const approvalListEmpty = approvedEntries.length === 0
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('connected') }</span>
<span className="settings-page__content-description">
{ t('connectedDescription') }
</span>
</div>
<div className="settings-page__content-item">
{
approvalListEmpty
? <div><i className="fa fa-ban" /></div>
: null
}
{
approvedEntries.map(([origin, { siteTitle, siteImage }]) => (
<ConnectedSiteEntry
key={origin}
origin={origin}
siteTitle={siteTitle}
siteImage={siteImage}
onDelete={() => {
rejectProviderRequestByOrigin(origin)
}}
/>
))
}
</div>
<div className="settings-page__content-item-col">
<Button
disabled={approvalListEmpty}
type="warning"
large
className="settings-tab__button--orange"
onClick={event => {
event.preventDefault()
showClearApprovalModal()
}}
>
{ t('clearApprovalData') }
</Button>
</div>
</div>
)
}
render () {
return (
<div className="settings-page__body">
{ this.renderNewOriginInput() }
{ this.renderApprovedOriginsList() }
</div>
)
}
}

View File

@ -0,0 +1,39 @@
import ConnectionsTab from './connections-tab.component'
import { compose } from 'recompose'
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
import {
approveProviderRequestByOrigin,
rejectProviderRequestByOrigin,
showModal,
} from '../../../store/actions'
export const mapStateToProps = state => {
const {
activeTab,
metamask,
} = state
const {
approvedOrigins,
} = metamask
return {
activeTab,
approvedOrigins,
}
}
export const mapDispatchToProps = dispatch => {
return {
approveProviderRequestByOrigin: (origin) => dispatch(approveProviderRequestByOrigin(origin)),
rejectProviderRequestByOrigin: (origin) => dispatch(rejectProviderRequestByOrigin(origin)),
showClearApprovalModal: () => dispatch(showModal({
name: 'CLEAR_APPROVED_ORIGINS',
})),
}
}
export default compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(ConnectionsTab)

View File

@ -0,0 +1 @@
export { default } from './connections-tab.container'

View File

@ -0,0 +1 @@
@import './connected-site-row/index';

View File

@ -4,6 +4,8 @@
@import 'settings-tab/index'; @import 'settings-tab/index';
@import 'connections-tab/index';
@import 'contact-list-tab/index'; @import 'contact-list-tab/index';
.settings-page { .settings-page {

View File

@ -1,6 +1,5 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { exportAsFile } from '../../../helpers/utils/util'
import ToggleButton from '../../../components/ui/toggle-button' import ToggleButton from '../../../components/ui/toggle-button'
import { REVEAL_SEED_ROUTE } from '../../../helpers/constants/routes' import { REVEAL_SEED_ROUTE } from '../../../helpers/constants/routes'
import Button from '../../../components/ui/button' import Button from '../../../components/ui/button'
@ -12,11 +11,8 @@ export default class SecurityTab extends PureComponent {
} }
static propTypes = { static propTypes = {
setPrivacyMode: PropTypes.func,
privacyMode: PropTypes.bool,
displayWarning: PropTypes.func, displayWarning: PropTypes.func,
revealSeedConfirmation: PropTypes.func, revealSeedConfirmation: PropTypes.func,
showClearApprovalModal: PropTypes.func,
warning: PropTypes.string, warning: PropTypes.string,
history: PropTypes.object, history: PropTypes.object,
mobileSync: PropTypes.bool, mobileSync: PropTypes.bool,
@ -24,71 +20,6 @@ export default class SecurityTab extends PureComponent {
setParticipateInMetaMetrics: PropTypes.func, setParticipateInMetaMetrics: PropTypes.func,
} }
renderStateLogs () {
const { t } = this.context
const { displayWarning } = this.props
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('stateLogs') }</span>
<span className="settings-page__content-description">
{ t('stateLogsDescription') }
</span>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<Button
type="secondary"
large
onClick={() => {
window.logStateString((err, result) => {
if (err) {
displayWarning(t('stateLogError'))
} else {
exportAsFile('MetaMask State Logs.json', result)
}
})
}}
>
{ t('downloadStateLogs') }
</Button>
</div>
</div>
</div>
)
}
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="warning"
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
@ -123,32 +54,6 @@ export default class SecurityTab extends PureComponent {
) )
} }
renderPrivacyOptIn () {
const { t } = this.context
const { privacyMode, setPrivacyMode } = this.props
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('privacyMode') }</span>
<div className="settings-page__content-description">
{ t('privacyModeDescription') }
</div>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<ToggleButton
value={privacyMode}
onToggle={value => setPrivacyMode(!value)}
offLabel={t('off')}
onLabel={t('on')}
/>
</div>
</div>
</div>
)
}
renderMetaMetricsOptIn () { renderMetaMetricsOptIn () {
const { t } = this.context const { t } = this.context
const { participateInMetaMetrics, setParticipateInMetaMetrics } = this.props const { participateInMetaMetrics, setParticipateInMetaMetrics } = this.props
@ -181,8 +86,6 @@ export default class SecurityTab extends PureComponent {
return ( return (
<div className="settings-page__body"> <div className="settings-page__body">
{ warning && <div className="settings-tab__error">{ warning }</div> } { warning && <div className="settings-tab__error">{ warning }</div> }
{ this.renderPrivacyOptIn() }
{ this.renderClearApproval() }
{ this.renderSeedWords() } { this.renderSeedWords() }
{ this.renderMetaMetricsOptIn() } { this.renderMetaMetricsOptIn() }
</div> </div>

View File

@ -5,23 +5,17 @@ import { withRouter } from 'react-router-dom'
import { import {
displayWarning, displayWarning,
revealSeedConfirmation, revealSeedConfirmation,
setFeatureFlag,
showModal,
setParticipateInMetaMetrics, setParticipateInMetaMetrics,
} from '../../../store/actions' } from '../../../store/actions'
const mapStateToProps = state => { const mapStateToProps = state => {
const { appState: { warning }, metamask } = state const { appState: { warning }, metamask } = state
const { const {
featureFlags: {
privacyMode,
} = {},
participateInMetaMetrics, participateInMetaMetrics,
} = metamask } = metamask
return { return {
warning, warning,
privacyMode,
participateInMetaMetrics, participateInMetaMetrics,
} }
} }
@ -30,8 +24,6 @@ const mapDispatchToProps = dispatch => {
return { return {
displayWarning: warning => dispatch(displayWarning(warning)), displayWarning: warning => dispatch(displayWarning(warning)),
revealSeedConfirmation: () => dispatch(revealSeedConfirmation()), revealSeedConfirmation: () => dispatch(revealSeedConfirmation()),
setPrivacyMode: enabled => dispatch(setFeatureFlag('privacyMode', enabled)),
showClearApprovalModal: () => dispatch(showModal({ name: 'CLEAR_APPROVED_ORIGINS' })),
setParticipateInMetaMetrics: (val) => dispatch(setParticipateInMetaMetrics(val)), setParticipateInMetaMetrics: (val) => dispatch(setParticipateInMetaMetrics(val)),
} }
} }

View File

@ -4,6 +4,7 @@ import { Switch, Route, matchPath, withRouter } from 'react-router-dom'
import TabBar from '../../components/app/tab-bar' import TabBar from '../../components/app/tab-bar'
import c from 'classnames' import c from 'classnames'
import SettingsTab from './settings-tab' import SettingsTab from './settings-tab'
import ConnectionsTab from './connections-tab'
import NetworksTab from './networks-tab' import NetworksTab from './networks-tab'
import AdvancedTab from './advanced-tab' import AdvancedTab from './advanced-tab'
import InfoTab from './info-tab' import InfoTab from './info-tab'
@ -14,6 +15,7 @@ import {
ADVANCED_ROUTE, ADVANCED_ROUTE,
SECURITY_ROUTE, SECURITY_ROUTE,
GENERAL_ROUTE, GENERAL_ROUTE,
CONNECTIONS_ROUTE,
ABOUT_US_ROUTE, ABOUT_US_ROUTE,
SETTINGS_ROUTE, SETTINGS_ROUTE,
NETWORKS_ROUTE, NETWORKS_ROUTE,
@ -148,6 +150,7 @@ class SettingsPage extends PureComponent {
<TabBar <TabBar
tabs={[ tabs={[
{ content: t('general'), description: t('generalSettingsDescription'), key: GENERAL_ROUTE }, { content: t('general'), description: t('generalSettingsDescription'), key: GENERAL_ROUTE },
{ content: t('connections'), description: t('connectionsSettingsDescription'), key: CONNECTIONS_ROUTE },
{ content: t('advanced'), description: t('advancedSettingsDescription'), key: ADVANCED_ROUTE }, { content: t('advanced'), description: t('advancedSettingsDescription'), key: ADVANCED_ROUTE },
{ content: t('contacts'), description: t('contactsSettingsDescription'), key: CONTACT_LIST_ROUTE }, { content: t('contacts'), description: t('contactsSettingsDescription'), key: CONTACT_LIST_ROUTE },
{ content: t('securityAndPrivacy'), description: t('securitySettingsDescription'), key: SECURITY_ROUTE }, { content: t('securityAndPrivacy'), description: t('securitySettingsDescription'), key: SECURITY_ROUTE },
@ -173,6 +176,11 @@ class SettingsPage extends PureComponent {
path={GENERAL_ROUTE} path={GENERAL_ROUTE}
component={SettingsTab} component={SettingsTab}
/> />
<Route
exact
path={CONNECTIONS_ROUTE}
component={ConnectionsTab}
/>
<Route <Route
exact exact
path={ABOUT_US_ROUTE} path={ABOUT_US_ROUTE}

View File

@ -8,6 +8,7 @@ import { ENVIRONMENT_TYPE_POPUP } from '../../../../app/scripts/lib/enums'
import { getEnvironmentType } from '../../../../app/scripts/lib/util' import { getEnvironmentType } from '../../../../app/scripts/lib/util'
import { import {
CONNECTIONS_ROUTE,
ADVANCED_ROUTE, ADVANCED_ROUTE,
SECURITY_ROUTE, SECURITY_ROUTE,
GENERAL_ROUTE, GENERAL_ROUTE,
@ -24,6 +25,7 @@ import {
const ROUTES_TO_I18N_KEYS = { const ROUTES_TO_I18N_KEYS = {
[GENERAL_ROUTE]: 'general', [GENERAL_ROUTE]: 'general',
[CONNECTIONS_ROUTE]: 'connections',
[ADVANCED_ROUTE]: 'advanced', [ADVANCED_ROUTE]: 'advanced',
[SECURITY_ROUTE]: 'securityAndPrivacy', [SECURITY_ROUTE]: 'securityAndPrivacy',
[ABOUT_US_ROUTE]: 'about', [ABOUT_US_ROUTE]: 'about',

View File

@ -349,7 +349,6 @@ var actions = {
approveProviderRequestByOrigin, approveProviderRequestByOrigin,
rejectProviderRequestByOrigin, rejectProviderRequestByOrigin,
forceApproveProviderRequestByOrigin,
clearApprovedOrigins, clearApprovedOrigins,
setFirstTimeFlowType, setFirstTimeFlowType,
@ -2650,12 +2649,6 @@ function approveProviderRequestByOrigin (origin) {
} }
} }
function forceApproveProviderRequestByOrigin (origin) {
return () => {
background.forceApproveProviderRequestByOrigin(origin)
}
}
function rejectProviderRequestByOrigin (origin) { function rejectProviderRequestByOrigin (origin) {
return () => { return () => {
background.rejectProviderRequestByOrigin(origin) background.rejectProviderRequestByOrigin(origin)