diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index f34a22bd5..95c9efeeb 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -32,14 +32,11 @@
"reject": {
"message": "Reject"
},
- "providerAPIRequest": {
- "message": "Ethereum API Request"
- },
- "reviewProviderRequest": {
- "message": "Please review this Ethereum API request."
+ "providerRequest": {
+ "message": "$1 would like to connect to your account"
},
"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."
+ "message": "This site is requesting access to view your current account address. Always make sure you trust the sites you interact with."
},
"accept": {
"message": "Accept"
@@ -212,6 +209,9 @@
"connect": {
"message": "Connect"
},
+ "connectRequest": {
+ "message": "Connect Request"
+ },
"connecting": {
"message": "Connecting..."
},
diff --git a/app/images/mm-secure.svg b/app/images/mm-secure.svg
new file mode 100644
index 000000000..1345b75b2
--- /dev/null
+++ b/app/images/mm-secure.svg
@@ -0,0 +1,7 @@
+
+
+
diff --git a/app/images/provider-approval-check.svg b/app/images/provider-approval-check.svg
new file mode 100644
index 000000000..c3df71f59
--- /dev/null
+++ b/app/images/provider-approval-check.svg
@@ -0,0 +1,20 @@
+
+
+
+
diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js
index fa8b3207f..1cdc85945 100644
--- a/app/scripts/contentscript.js
+++ b/app/scripts/contentscript.js
@@ -126,6 +126,8 @@ function listenForProviderRequest () {
extension.runtime.sendMessage({
action: 'init-provider-request',
origin: source.location.hostname,
+ siteImage: getSiteIcon(source),
+ siteTitle: getSiteName(source),
})
break
case 'ETHEREUM_IS_APPROVED':
@@ -285,3 +287,31 @@ function redirectToPhishingWarning () {
href: window.location.href,
})}`
}
+
+function getSiteName (window) {
+ const document = window.document
+ const siteName = document.querySelector('head > meta[property="og:site_name"]')
+ if (siteName) {
+ return siteName.content
+ }
+
+ return document.title
+}
+
+function getSiteIcon (window) {
+ const document = window.document
+
+ // Use the site's favicon if it exists
+ const shortcutIcon = document.querySelector('head > link[rel="shortcut icon"]')
+ if (shortcutIcon) {
+ return shortcutIcon.href
+ }
+
+ // Search through available icons in no particular order
+ const icon = Array.from(document.querySelectorAll('head > link[rel="icon"]')).find((icon) => Boolean(icon.href))
+ if (icon) {
+ return icon.href
+ }
+
+ return null
+}
diff --git a/app/scripts/controllers/provider-approval.js b/app/scripts/controllers/provider-approval.js
index 728361c79..f2d40e67d 100644
--- a/app/scripts/controllers/provider-approval.js
+++ b/app/scripts/controllers/provider-approval.js
@@ -24,31 +24,35 @@ class ProviderApprovalController {
this.publicConfigStore = publicConfigStore
this.store = new ObservableStore()
- platform && platform.addMessageListener && platform.addMessageListener(({ action = '', origin }) => {
- switch (action) {
- case 'init-provider-request':
- this._handleProviderRequest(origin)
- break
- case 'init-is-approved':
- this._handleIsApproved(origin)
- break
- case 'init-is-unlocked':
- this._handleIsUnlocked()
- break
- case 'init-privacy-request':
- this._handlePrivacyRequest()
- break
- }
- })
+ if (platform && platform.addMessageListener) {
+ platform.addMessageListener(({ action = '', origin, siteTitle, siteImage }) => {
+ switch (action) {
+ case 'init-provider-request':
+ this._handleProviderRequest(origin, siteTitle, siteImage)
+ break
+ case 'init-is-approved':
+ this._handleIsApproved(origin)
+ break
+ case 'init-is-unlocked':
+ this._handleIsUnlocked()
+ break
+ case 'init-privacy-request':
+ this._handlePrivacyRequest()
+ break
+ }
+ })
+ }
}
/**
* Called when a tab requests access to a full Ethereum provider API
*
* @param {string} origin - Origin of the window requesting full provider access
+ * @param {string} siteTitle - The title of the document requesting full provider access
+ * @param {string} siteImage - The icon of the window requesting full provider access
*/
- _handleProviderRequest (origin) {
- this.store.updateState({ providerRequests: [{ origin }] })
+ _handleProviderRequest (origin, siteTitle, siteImage) {
+ this.store.updateState({ providerRequests: [{ origin, siteTitle, siteImage }] })
const isUnlocked = this.keyringController.memStore.getState().isUnlocked
if (this.isApproved(origin) && this.caching && isUnlocked) {
this.approveProviderRequest(origin)
diff --git a/test/e2e/beta/metamask-beta-ui.spec.js b/test/e2e/beta/metamask-beta-ui.spec.js
index c8bf5f4ff..5887d0293 100644
--- a/test/e2e/beta/metamask-beta-ui.spec.js
+++ b/test/e2e/beta/metamask-beta-ui.spec.js
@@ -459,7 +459,7 @@ describe('MetaMask', function () {
dapp = windowHandles.find(handle => handle !== extension && handle !== popup)
await delay(regularDelayMs)
- const approveButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Approve')]`), 10000)
+ const approveButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`))
await approveButton.click()
})
diff --git a/ui/app/components/index.scss b/ui/app/components/index.scss
index 72de6cb93..e27b0f182 100644
--- a/ui/app/components/index.scss
+++ b/ui/app/components/index.scss
@@ -32,6 +32,8 @@
@import './pages/index';
+@import './provider-page-container/index';
+
@import './selected-account/index';
@import './sender-to-recipient/index';
diff --git a/ui/app/components/network-display/index.scss b/ui/app/components/network-display/index.scss
index 2085cff67..e9f2f2057 100644
--- a/ui/app/components/network-display/index.scss
+++ b/ui/app/components/network-display/index.scss
@@ -3,11 +3,14 @@
display: flex;
align-items: center;
justify-content: flex-start;
- background-color: lighten(rgb(125, 128, 130), 45%);
padding: 0 10px;
border-radius: 4px;
height: 25px;
+ &--colored {
+ background-color: lighten(rgb(125, 128, 130), 45%);
+ }
+
&--mainnet {
background-color: lighten($blue-lagoon, 68%);
}
diff --git a/ui/app/components/network-display/network-display.component.js b/ui/app/components/network-display/network-display.component.js
index 82f9ff9c3..22d617099 100644
--- a/ui/app/components/network-display/network-display.component.js
+++ b/ui/app/components/network-display/network-display.component.js
@@ -16,7 +16,12 @@ const networkToClassHash = {
}
export default class NetworkDisplay extends Component {
+ static defaultProps = {
+ colored: true,
+ }
+
static propTypes = {
+ colored: PropTypes.bool,
network: PropTypes.string,
provider: PropTypes.object,
}
@@ -41,14 +46,16 @@ export default class NetworkDisplay extends Component {
}
render () {
- const { network, provider: { type, nickname } } = this.props
+ const { colored, network, provider: { type, nickname } } = this.props
const networkClass = networkToClassHash[network]
return (
-
+
{
networkClass
?
diff --git a/ui/app/components/pages/home/home.component.js b/ui/app/components/pages/home/home.component.js
index 7b64ebc4e..b9ec3c258 100644
--- a/ui/app/components/pages/home/home.component.js
+++ b/ui/app/components/pages/home/home.component.js
@@ -67,7 +67,9 @@ export default class Home extends PureComponent {
}
if (providerRequests && providerRequests.length > 0) {
- return
+ return (
+
+ )
}
return (
diff --git a/ui/app/components/pages/provider-approval/provider-approval.component.js b/ui/app/components/pages/provider-approval/provider-approval.component.js
index 67e8bdd4c..da98bc3fc 100644
--- a/ui/app/components/pages/provider-approval/provider-approval.component.js
+++ b/ui/app/components/pages/provider-approval/provider-approval.component.js
@@ -1,12 +1,12 @@
-import PageContainerContent from '../../page-container'
import PropTypes from 'prop-types'
import React, { Component } from 'react'
+import ProviderPageContainer from '../../provider-page-container'
export default class ProviderApproval extends Component {
static propTypes = {
- approveProviderRequest: PropTypes.func,
- origin: PropTypes.string,
- rejectProviderRequest: PropTypes.func,
+ approveProviderRequest: PropTypes.func.isRequired,
+ providerRequest: PropTypes.object.isRequired,
+ rejectProviderRequest: PropTypes.func.isRequired,
};
static contextTypes = {
@@ -14,22 +14,15 @@ export default class ProviderApproval extends Component {
};
render () {
- const { approveProviderRequest, origin, rejectProviderRequest } = this.props
+ const { approveProviderRequest, providerRequest, rejectProviderRequest } = this.props
return (
-
- {this.context.t('providerRequestInfo')}
- {origin}
-
- )}
- submitText={this.context.t('approve')}
- cancelText={this.context.t('reject')}
- onSubmit={() => { approveProviderRequest(origin) }}
- onCancel={() => { rejectProviderRequest(origin) }}
- onClose={() => { rejectProviderRequest(origin) }} />
+
)
}
}
diff --git a/ui/app/components/provider-page-container/index.js b/ui/app/components/provider-page-container/index.js
new file mode 100644
index 000000000..927c35940
--- /dev/null
+++ b/ui/app/components/provider-page-container/index.js
@@ -0,0 +1,3 @@
+export {default} from './provider-page-container.component'
+export {default as ProviderPageContainerContent} from './provider-page-container-content'
+export {default as ProviderPageContainerHeader} from './provider-page-container-header'
diff --git a/ui/app/components/provider-page-container/index.scss b/ui/app/components/provider-page-container/index.scss
new file mode 100644
index 000000000..a67d7f427
--- /dev/null
+++ b/ui/app/components/provider-page-container/index.scss
@@ -0,0 +1,120 @@
+.provider-approval-container {
+ display: flex;
+
+ &__header {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ border-bottom: 1px solid $geyser;
+ padding: 9px;
+ }
+
+ &__content {
+ display: flex;
+ overflow-y: auto;
+ flex: 1;
+ flex-direction: column;
+ justify-content: space-between;
+ color: #7C808E;
+
+ h1, h2 {
+ color: #4A4A4A;
+ display: flex;
+ justify-content: center;
+ text-align: center;
+ }
+
+ h2 {
+ font-size: 16px;
+ line-height: 18px;
+ padding: 20px;
+ }
+
+ h1 {
+ font-size: 22px;
+ line-height: 26px;
+ padding: 20px;
+ }
+
+ p {
+ padding: 0 40px;
+ text-align: center;
+ font-size: 12px;
+ line-height: 18px;
+ }
+
+ a, a:hover {
+ color: $dodger-blue;
+ }
+
+ .provider-approval-visual {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-evenly;
+ position: relative;
+ margin: 0 32px;
+
+ section {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ flex: 1;
+ }
+
+ h1 {
+ font-size: 14px;
+ line-height: 18px;
+ padding: 8px 0 0;
+ }
+
+ h2 {
+ font-size: 10px;
+ line-height: 14px;
+ padding: 0;
+ color: #A2A4AC;
+ }
+
+ &__check {
+ width: 40px;
+ height: 40px;
+ background: white url("/images/provider-approval-check.svg") no-repeat;
+ margin-top: 14px;
+ }
+
+ &__identicon {
+ width: 64px;
+ height: 64px;
+
+ &--default {
+ background-color: lightgray;
+ width: 64px;
+ height: 64px;
+ border-radius: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: bold;
+ }
+ }
+
+ &:before {
+ border-top: 2px dashed #CDD1E4;
+ content: "";
+ margin: 0 auto;
+ position: absolute;
+ top: 32px;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ width: 65%;
+ z-index: -1;
+ }
+ }
+
+ .secure-badge {
+ display: flex;
+ justify-content: center;
+ padding: 25px;
+ }
+ }
+}
diff --git a/ui/app/components/provider-page-container/provider-page-container-content/index.js b/ui/app/components/provider-page-container/provider-page-container-content/index.js
new file mode 100644
index 000000000..73e491adc
--- /dev/null
+++ b/ui/app/components/provider-page-container/provider-page-container-content/index.js
@@ -0,0 +1 @@
+export {default} from './provider-page-container-content.container'
diff --git a/ui/app/components/provider-page-container/provider-page-container-content/provider-page-container-content.component.js b/ui/app/components/provider-page-container/provider-page-container-content/provider-page-container-content.component.js
new file mode 100644
index 000000000..20fad5c6d
--- /dev/null
+++ b/ui/app/components/provider-page-container/provider-page-container-content/provider-page-container-content.component.js
@@ -0,0 +1,71 @@
+import PropTypes from 'prop-types'
+import React, {PureComponent} from 'react'
+import Identicon from '../../identicon'
+
+export default class ProviderPageContainerContent extends PureComponent {
+ static propTypes = {
+ origin: PropTypes.string.isRequired,
+ selectedIdentity: PropTypes.string.isRequired,
+ siteImage: PropTypes.string,
+ siteTitle: PropTypes.string.isRequired,
+ }
+
+ static contextTypes = {
+ t: PropTypes.func,
+ };
+
+ renderConnectVisual = () => {
+ const { origin, selectedIdentity, siteImage, siteTitle } = this.props
+
+ return (
+
+
+ {siteImage ? (
+
+ ) : (
+
+ {siteTitle.charAt(0).toUpperCase()}
+
+ )}
+ {siteTitle}
+ {origin}
+
+
+
+
+ {selectedIdentity.name}
+
+
+ )
+ }
+
+ render () {
+ const { siteTitle } = this.props
+ const { t } = this.context
+
+ return (
+
+
+ {t('connectRequest')}
+ {this.renderConnectVisual()}
+ {t('providerRequest', [siteTitle])}
+
+ {t('providerRequestInfo')}
+
+ {t('learnMore')}.
+
+
+
+
+
+
+ )
+ }
+}
diff --git a/ui/app/components/provider-page-container/provider-page-container-content/provider-page-container-content.container.js b/ui/app/components/provider-page-container/provider-page-container-content/provider-page-container-content.container.js
new file mode 100644
index 000000000..3ea1ce20e
--- /dev/null
+++ b/ui/app/components/provider-page-container/provider-page-container-content/provider-page-container-content.container.js
@@ -0,0 +1,11 @@
+import { connect } from 'react-redux'
+import ProviderPageContainerContent from './provider-page-container-content.component'
+import { getSelectedIdentity } from '../../../selectors'
+
+const mapStateToProps = (state) => {
+ return {
+ selectedIdentity: getSelectedIdentity(state),
+ }
+}
+
+export default connect(mapStateToProps)(ProviderPageContainerContent)
diff --git a/ui/app/components/provider-page-container/provider-page-container-header/index.js b/ui/app/components/provider-page-container/provider-page-container-header/index.js
new file mode 100644
index 000000000..430627d3a
--- /dev/null
+++ b/ui/app/components/provider-page-container/provider-page-container-header/index.js
@@ -0,0 +1 @@
+export {default} from './provider-page-container-header.component'
diff --git a/ui/app/components/provider-page-container/provider-page-container-header/provider-page-container-header.component.js b/ui/app/components/provider-page-container/provider-page-container-header/provider-page-container-header.component.js
new file mode 100644
index 000000000..41bf6c3dd
--- /dev/null
+++ b/ui/app/components/provider-page-container/provider-page-container-header/provider-page-container-header.component.js
@@ -0,0 +1,12 @@
+import React, {PureComponent} from 'react'
+import NetworkDisplay from '../../network-display'
+
+export default class ProviderPageContainerHeader extends PureComponent {
+ render () {
+ return (
+
+
+
+ )
+ }
+}
diff --git a/ui/app/components/provider-page-container/provider-page-container.component.js b/ui/app/components/provider-page-container/provider-page-container.component.js
new file mode 100644
index 000000000..902733616
--- /dev/null
+++ b/ui/app/components/provider-page-container/provider-page-container.component.js
@@ -0,0 +1,50 @@
+import PropTypes from 'prop-types'
+import React, {PureComponent} from 'react'
+import { ProviderPageContainerContent, ProviderPageContainerHeader } from './'
+import { PageContainerFooter } from '../page-container'
+
+export default class ProviderPageContainer extends PureComponent {
+ static propTypes = {
+ approveProviderRequest: PropTypes.func.isRequired,
+ origin: PropTypes.string.isRequired,
+ rejectProviderRequest: PropTypes.func.isRequired,
+ siteImage: PropTypes.string,
+ siteTitle: PropTypes.string.isRequired,
+ };
+
+ static contextTypes = {
+ t: PropTypes.func,
+ };
+
+ onCancel = () => {
+ const { origin, rejectProviderRequest } = this.props
+ rejectProviderRequest(origin)
+ }
+
+ onSubmit = () => {
+ const { approveProviderRequest, origin } = this.props
+ approveProviderRequest(origin)
+ }
+
+ render () {
+ const {origin, siteImage, siteTitle} = this.props
+
+ return (
+
+
+
+
this.onCancel()}
+ cancelText={this.context.t('cancel')}
+ onSubmit={() => this.onSubmit()}
+ submitText={this.context.t('connect')}
+ submitButtonType="confirm"
+ />
+
+ )
+ }
+}