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

Default Privacy Mode to ON, allow force sharing address (#6904)

This commit is contained in:
Whymarrh Whitby 2019-08-01 10:54:33 -02:30 committed by GitHub
parent 4d88e1cf86
commit e9a63d5d5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 576 additions and 117 deletions

View File

@ -1,4 +1,16 @@
{
"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."
},
"privacyModeDefault": {
"message": "Privacy Mode is now enabled by default"
},
"privacyMode": {
"message": "Privacy Mode"
},

View File

@ -0,0 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.00002 9.57037C8.93767 9.57037 9.69778 8.81026 9.69778 7.8726C9.69778 6.93495 8.93767 6.17484 8.00002 6.17484C7.06236 6.17484 6.30225 6.93495 6.30225 7.8726C6.30225 8.81026 7.06236 9.57037 8.00002 9.57037Z" fill="white"/>
<path d="M11.0582 11.6586C10.872 11.6586 10.6857 11.5876 10.5437 11.4455C10.2595 11.1614 10.2595 10.7007 10.5437 10.4165C11.2232 9.73704 11.5975 8.83356 11.5975 7.87259C11.5975 6.91161 11.2232 6.00813 10.5437 5.32865C10.2595 5.04448 10.2595 4.58381 10.5437 4.29964C10.8278 4.01554 11.2886 4.01554 11.5727 4.29964C12.527 5.25398 13.0527 6.52293 13.0527 7.87259C13.0527 9.22224 12.527 10.4912 11.5727 11.4455C11.4306 11.5876 11.2444 11.6586 11.0582 11.6586Z" fill="white"/>
<path d="M4.94175 11.6586C4.75553 11.6586 4.56929 11.5876 4.42724 11.4455C3.4729 10.4912 2.94727 9.22224 2.94727 7.87259C2.94727 6.52293 3.4729 5.25398 4.42724 4.29964C4.71135 4.01554 5.17215 4.01554 5.45626 4.29964C5.74043 4.58381 5.74043 5.04448 5.45626 5.32865C4.77672 6.00813 4.4025 6.91161 4.4025 7.87259C4.4025 8.83356 4.77672 9.73704 5.45626 10.4165C5.74043 10.7007 5.74043 11.1614 5.45626 11.4455C5.3142 11.5876 5.12798 11.6586 4.94175 11.6586Z" fill="white"/>
<path d="M13.1451 13.7453C12.9589 13.7453 12.7727 13.6742 12.6306 13.5322C12.3464 13.248 12.3464 12.7873 12.6306 12.5031C15.1839 9.94985 15.1839 5.79538 12.6306 3.24209C12.3464 2.95792 12.3464 2.49725 12.6306 2.21308C12.9147 1.92897 13.3755 1.92897 13.6596 2.21308C16.7803 5.33374 16.7803 10.4115 13.6596 13.5322C13.5176 13.6742 13.3313 13.7453 13.1451 13.7453Z" fill="white"/>
<path d="M2.855 13.7453C2.66878 13.7453 2.48255 13.6742 2.3405 13.5322C-0.780166 10.4115 -0.780166 5.33374 2.3405 2.21308C2.62461 1.92897 3.08541 1.92897 3.36951 2.21308C3.65368 2.49725 3.65368 2.95792 3.36951 3.24209C0.816221 5.79538 0.816221 9.94985 3.36951 12.5031C3.65368 12.7873 3.65368 13.248 3.36951 13.5322C3.22745 13.6742 3.04123 13.7453 2.855 13.7453Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.99984 2.00001C4.68613 2.00001 1.99984 4.6863 1.99984 8C1.99984 11.3137 4.68613 14 7.99984 14C11.3135 14 13.9998 11.3137 13.9998 8C13.9998 4.6863 11.3135 2.00001 7.99984 2.00001ZM0.666504 8C0.666504 3.94992 3.94975 0.666672 7.99984 0.666672C12.0499 0.666672 15.3332 3.94992 15.3332 8C15.3332 12.0501 12.0499 15.3333 7.99984 15.3333C3.94975 15.3333 0.666504 12.0501 0.666504 8Z" fill="#6A737D"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.99984 7.33334C8.36803 7.33334 8.6665 7.63181 8.6665 8V10.6667C8.6665 11.0349 8.36803 11.3333 7.99984 11.3333C7.63165 11.3333 7.33317 11.0349 7.33317 10.6667V8C7.33317 7.63181 7.63165 7.33334 7.99984 7.33334Z" fill="#6A737D"/>
<path d="M8.6665 5.33334C8.6665 5.70153 8.36803 6 7.99984 6C7.63165 6 7.33317 5.70153 7.33317 5.33334C7.33317 4.96515 7.63165 4.66667 7.99984 4.66667C8.36803 4.66667 8.6665 4.96515 8.6665 5.33334Z" fill="#6A737D"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -114,6 +114,7 @@ function forwardTrafficBetweenMuxers (channelName, muxA, muxB) {
async function setupPublicApi (outStream) {
const api = {
forceReloadSite: (cb) => cb(null, forceReloadSite()),
getSiteMetadata: (cb) => cb(null, getSiteMetadata()),
}
const dnode = Dnode(api)
@ -306,3 +307,10 @@ async function domIsReady () {
// wait for load
await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve, { once: true }))
}
/**
* Reloads the site
*/
function forceReloadSite () {
window.location.reload()
}

View File

@ -54,6 +54,7 @@ class PreferencesController {
useNativeCurrencyAsPrimaryCurrency: true,
},
completedOnboarding: false,
migratedPrivacyMode: false,
metaMetricsId: null,
metaMetricsSendCount: 0,
}, opts.initState)
@ -603,6 +604,13 @@ class PreferencesController {
return Promise.resolve(true)
}
unsetMigratedPrivacyMode () {
this.store.updateState({
migratedPrivacyMode: false,
})
return Promise.resolve()
}
//
// PRIVATE METHODS
//

View File

@ -18,12 +18,12 @@ class ProviderApprovalController extends SafeEventEmitter {
*/
constructor ({ closePopup, keyringController, openPopup, preferencesController } = {}) {
super()
this.approvedOrigins = {}
this.closePopup = closePopup
this.keyringController = keyringController
this.openPopup = openPopup
this.preferencesController = preferencesController
this.store = new ObservableStore({
approvedOrigins: {},
providerRequests: [],
})
}
@ -45,7 +45,7 @@ class ProviderApprovalController extends SafeEventEmitter {
}
// register the provider request
const metadata = await getSiteMetadata(origin)
this._handleProviderRequest(origin, metadata.name, metadata.icon, false, null)
this._handleProviderRequest(origin, metadata.name, metadata.icon)
// wait for resolution of request
const approved = await new Promise(resolve => this.once(`resolvedRequest:${origin}`, ({ approved }) => resolve(approved)))
if (approved) {
@ -63,10 +63,10 @@ class ProviderApprovalController extends SafeEventEmitter {
* @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, siteTitle, siteImage, force, tabID) {
this.store.updateState({ providerRequests: [{ origin, siteTitle, siteImage, tabID }] })
_handleProviderRequest (origin, siteTitle, siteImage) {
this.store.updateState({ providerRequests: [{ origin, siteTitle, siteImage }] })
const isUnlocked = this.keyringController.memStore.getState().isUnlocked
if (!force && this.approvedOrigins[origin] && this.caching && isUnlocked) {
if (this.store.getState().approvedOrigins[origin] && this.caching && isUnlocked) {
return
}
this.openPopup && this.openPopup()
@ -78,11 +78,19 @@ class ProviderApprovalController extends SafeEventEmitter {
* @param {string} origin - origin of the domain that had provider access approved
*/
approveProviderRequestByOrigin (origin) {
this.closePopup && this.closePopup()
const requests = this.store.getState().providerRequests
const providerRequests = requests.filter(request => request.origin !== origin)
this.store.updateState({ providerRequests })
this.approvedOrigins[origin] = true
if (this.closePopup) {
this.closePopup()
}
const { approvedOrigins, providerRequests } = this.store.getState()
const remainingProviderRequests = providerRequests.filter(request => request.origin !== origin)
this.store.updateState({
approvedOrigins: {
...approvedOrigins,
[origin]: true,
},
providerRequests: remainingProviderRequests,
})
this.emit(`resolvedRequest:${origin}`, { approved: true })
}
@ -92,19 +100,50 @@ class ProviderApprovalController extends SafeEventEmitter {
* @param {string} origin - origin of the domain that had provider access approved
*/
rejectProviderRequestByOrigin (origin) {
this.closePopup && this.closePopup()
const requests = this.store.getState().providerRequests
const providerRequests = requests.filter(request => request.origin !== origin)
this.store.updateState({ providerRequests })
delete this.approvedOrigins[origin]
if (this.closePopup) {
this.closePopup()
}
const { approvedOrigins, providerRequests } = this.store.getState()
const remainingProviderRequests = providerRequests.filter(request => request.origin !== origin)
// We're cloning and deleting keys here because we don't want to keep unneeded keys
const _approvedOrigins = Object.assign({}, approvedOrigins)
delete _approvedOrigins[origin]
this.store.putState({
approvedOrigins: _approvedOrigins,
providerRequests: remainingProviderRequests,
})
this.emit(`resolvedRequest:${origin}`, { approved: false })
}
/**
* Silently approves access to a full Ethereum provider API for the origin
*
* @param {string} origin - origin of the domain that had provider access approved
*/
forceApproveProviderRequestByOrigin (origin) {
const { approvedOrigins, providerRequests } = this.store.getState()
const remainingProviderRequests = providerRequests.filter(request => request.origin !== origin)
this.store.updateState({
approvedOrigins: {
...approvedOrigins,
[origin]: true,
},
providerRequests: remainingProviderRequests,
})
this.emit(`forceResolvedRequest:${origin}`, { approved: true, forced: true })
}
/**
* Clears any cached approvals for user-approved origins
*/
clearApprovedOrigins () {
this.approvedOrigins = {}
this.store.updateState({
approvedOrigins: {},
})
}
/**
@ -115,8 +154,7 @@ class ProviderApprovalController extends SafeEventEmitter {
*/
shouldExposeAccounts (origin) {
const privacyMode = this.preferencesController.getFeatureFlags().privacyMode
const result = !privacyMode || Boolean(this.approvedOrigins[origin])
return result
return !privacyMode || Boolean(this.store.getState().approvedOrigins[origin])
}
}

View File

@ -454,6 +454,7 @@ module.exports = class MetamaskController extends EventEmitter {
setPreference: nodeify(preferencesController.setPreference, preferencesController),
completeOnboarding: nodeify(preferencesController.completeOnboarding, preferencesController),
addKnownMethodData: nodeify(preferencesController.addKnownMethodData, preferencesController),
unsetMigratedPrivacyMode: nodeify(preferencesController.unsetMigratedPrivacyMode, preferencesController),
// BlacklistController
whitelistPhishingDomain: this.whitelistPhishingDomain.bind(this),
@ -498,6 +499,7 @@ module.exports = class MetamaskController extends EventEmitter {
// provider approval
approveProviderRequestByOrigin: providerApprovalController.approveProviderRequestByOrigin.bind(providerApprovalController),
rejectProviderRequestByOrigin: providerApprovalController.rejectProviderRequestByOrigin.bind(providerApprovalController),
forceApproveProviderRequestByOrigin: providerApprovalController.forceApproveProviderRequestByOrigin.bind(providerApprovalController),
clearApprovedOrigins: providerApprovalController.clearApprovedOrigins.bind(providerApprovalController),
}
}
@ -1285,6 +1287,8 @@ module.exports = class MetamaskController extends EventEmitter {
const publicApi = this.setupPublicApi(mux.createStream('publicApi'), originDomain)
this.setupProviderConnection(mux.createStream('provider'), originDomain, publicApi)
this.setupPublicConfig(mux.createStream('publicConfig'), originDomain)
this.providerApprovalController.on(`forceResolvedRequest:${originDomain}`, publicApi.forceReloadSite)
}
/**
@ -1465,6 +1469,10 @@ module.exports = class MetamaskController extends EventEmitter {
const publicApi = {
// wrap with an await remote
forceReloadSite: async () => {
const remote = await getRemote()
return await pify(remote.forceReloadSite)()
},
getSiteMetadata: async () => {
const remote = await getRemote()
return await pify(remote.getSiteMetadata)()

View File

@ -0,0 +1,33 @@
const version = 34
const clone = require('clone')
/**
* The purpose of this migration is to enable the {@code privacyMode} feature flag and set the user as being migrated
* if it was {@code false}.
*/
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 (!featureFlags.privacyMode && typeof PreferencesController.migratedPrivacyMode === 'undefined') {
// Mark the state has being migrated and enable Privacy Mode
PreferencesController.migratedPrivacyMode = true
featureFlags.privacyMode = true
}
}
return state
}

View File

@ -1,77 +0,0 @@
const {EventEmitter} = require('events')
const async = require('async')
const Dnode = require('dnode')
const Eth = require('ethjs')
const EthQuery = require('eth-query')
const launchMetamaskUi = require('../../ui')
const StreamProvider = require('web3-stream-provider')
const {setupMultiplex} = require('./lib/stream-utils.js')
module.exports = initializePopup
/**
* Asynchronously initializes the MetaMask popup UI
*
* @param {{ container: Element, connectionStream: * }} config Popup configuration object
* @param {Function} cb Called when initialization is complete
*/
function initializePopup ({ container, connectionStream }, cb) {
// setup app
async.waterfall([
(cb) => connectToAccountManager(connectionStream, cb),
(backgroundConnection, cb) => launchMetamaskUi({ container, backgroundConnection }, cb),
], cb)
}
/**
* Establishes streamed connections to background scripts and a Web3 provider
*
* @param {PortDuplexStream} connectionStream PortStream instance establishing a background connection
* @param {Function} cb Called when controller connection is established
*/
function connectToAccountManager (connectionStream, cb) {
// setup communication with background
// setup multiplexing
const mx = setupMultiplex(connectionStream)
// connect features
setupControllerConnection(mx.createStream('controller'), cb)
setupWeb3Connection(mx.createStream('provider'))
}
/**
* Establishes a streamed connection to a Web3 provider
*
* @param {PortDuplexStream} connectionStream PortStream instance establishing a background connection
*/
function setupWeb3Connection (connectionStream) {
const providerStream = new StreamProvider()
providerStream.pipe(connectionStream).pipe(providerStream)
connectionStream.on('error', console.error.bind(console))
providerStream.on('error', console.error.bind(console))
global.ethereumProvider = providerStream
global.ethQuery = new EthQuery(providerStream)
global.eth = new Eth(providerStream)
}
/**
* Establishes a streamed connection to the background account manager
*
* @param {PortDuplexStream} connectionStream PortStream instance establishing a background connection
* @param {Function} cb Called when the remote account manager connection is established
*/
function setupControllerConnection (connectionStream, cb) {
// this is a really sneaky way of adding EventEmitter api
// to a bi-directional dnode instance
const eventEmitter = new EventEmitter()
const backgroundDnode = Dnode({
sendUpdate: function (state) {
eventEmitter.emit('update', state)
},
})
connectionStream.pipe(backgroundDnode).pipe(connectionStream)
backgroundDnode.once('remote', function (backgroundConnection) {
// setup push events
backgroundConnection.on = eventEmitter.on.bind(eventEmitter)
cb(null, backgroundConnection)
})
}

View File

@ -1,12 +1,19 @@
const startPopup = require('./popup-core')
const PortStream = require('extension-port-stream')
const { getEnvironmentType } = require('./lib/util')
const { ENVIRONMENT_TYPE_NOTIFICATION, ENVIRONMENT_TYPE_FULLSCREEN } = require('./lib/enums')
const { ENVIRONMENT_TYPE_NOTIFICATION, ENVIRONMENT_TYPE_FULLSCREEN, ENVIRONMENT_TYPE_POPUP } = require('./lib/enums')
const extension = require('extensionizer')
const ExtensionPlatform = require('./platforms/extension')
const NotificationManager = require('./lib/notification-manager')
const notificationManager = new NotificationManager()
const setupSentry = require('./lib/setupSentry')
const {EventEmitter} = require('events')
const Dnode = require('dnode')
const Eth = require('ethjs')
const EthQuery = require('eth-query')
const urlUtil = require('url')
const launchMetaMaskUi = require('../../ui')
const StreamProvider = require('web3-stream-provider')
const {setupMultiplex} = require('./lib/stream-utils.js')
const log = require('loglevel')
start().catch(log.error)
@ -39,20 +46,8 @@ async function start () {
const extensionPort = extension.runtime.connect({ name: windowType })
const connectionStream = new PortStream(extensionPort)
// start ui
const container = document.getElementById('app-content')
startPopup({ container, connectionStream }, (err, store) => {
if (err) return displayCriticalError(err)
const state = store.getState()
const { metamask: { completedOnboarding } = {} } = state
if (!completedOnboarding && windowType !== ENVIRONMENT_TYPE_FULLSCREEN) {
global.platform.openExtensionInBrowser()
return
}
})
const activeTab = await queryCurrentActiveTab(windowType)
initializeUiWithTab(activeTab)
function closePopupIfOpen (windowType) {
if (windowType !== ENVIRONMENT_TYPE_NOTIFICATION) {
@ -61,11 +56,107 @@ async function start () {
}
}
function displayCriticalError (err) {
function displayCriticalError (container, err) {
container.innerHTML = '<div class="critical-error">The MetaMask app failed to load: please open and close MetaMask again to restart.</div>'
container.style.height = '80px'
log.error(err.stack)
throw err
}
function initializeUiWithTab (tab) {
const container = document.getElementById('app-content')
initializeUi(tab, container, connectionStream, (err, store) => {
if (err) {
return displayCriticalError(container, err)
}
const state = store.getState()
const { metamask: { completedOnboarding } = {} } = state
if (!completedOnboarding && windowType !== ENVIRONMENT_TYPE_FULLSCREEN) {
global.platform.openExtensionInBrowser()
}
})
}
}
async function queryCurrentActiveTab (windowType) {
return new Promise((resolve) => {
// At the time of writing we only have the `activeTab` permission which means
// that this query will only succeed in the popup context (i.e. after a "browserAction")
if (windowType !== ENVIRONMENT_TYPE_POPUP) {
resolve({})
return
}
extension.tabs.query({active: true, currentWindow: true}, (tabs) => {
const [activeTab] = tabs
const {title, url} = activeTab
const origin = url ? urlUtil.parse(url).hostname : null
resolve({
title, origin, url,
})
})
})
}
function initializeUi (activeTab, container, connectionStream, cb) {
connectToAccountManager(connectionStream, (err, backgroundConnection) => {
if (err) {
return cb(err)
}
launchMetaMaskUi({
activeTab,
container,
backgroundConnection,
}, cb)
})
}
/**
* Establishes a connection to the background and a Web3 provider
*
* @param {PortDuplexStream} connectionStream PortStream instance establishing a background connection
* @param {Function} cb Called when controller connection is established
*/
function connectToAccountManager (connectionStream, cb) {
const mx = setupMultiplex(connectionStream)
setupControllerConnection(mx.createStream('controller'), cb)
setupWeb3Connection(mx.createStream('provider'))
}
/**
* Establishes a streamed connection to a Web3 provider
*
* @param {PortDuplexStream} connectionStream PortStream instance establishing a background connection
*/
function setupWeb3Connection (connectionStream) {
const providerStream = new StreamProvider()
providerStream.pipe(connectionStream).pipe(providerStream)
connectionStream.on('error', console.error.bind(console))
providerStream.on('error', console.error.bind(console))
global.ethereumProvider = providerStream
global.ethQuery = new EthQuery(providerStream)
global.eth = new Eth(providerStream)
}
/**
* Establishes a streamed connection to the background account manager
*
* @param {PortDuplexStream} connectionStream PortStream instance establishing a background connection
* @param {Function} cb Called when the remote account manager connection is established
*/
function setupControllerConnection (connectionStream, cb) {
const eventEmitter = new EventEmitter()
const backgroundDnode = Dnode({
sendUpdate: function (state) {
eventEmitter.emit('update', state)
},
})
connectionStream.pipe(backgroundDnode).pipe(connectionStream)
backgroundDnode.once('remote', function (backgroundConnection) {
backgroundConnection.on = eventEmitter.on.bind(eventEmitter)
cb(null, backgroundConnection)
})
}

View File

@ -50,7 +50,6 @@
"@zxing/library": "^0.8.0",
"abi-decoder": "^1.2.0",
"asmcrypto.js": "^2.3.2",
"async": "^2.5.0",
"await-semaphore": "^0.1.1",
"babel-runtime": "^6.23.0",
"bignumber.js": "^4.1.0",

View File

@ -0,0 +1,110 @@
import React, { PureComponent } from 'react'
import {Tooltip as ReactTippy} from 'react-tippy'
import PropTypes from 'prop-types'
import Button from '../../ui/button'
export default class HomeNotification extends PureComponent {
static contextTypes = {
metricsEvent: PropTypes.func,
}
static defaultProps = {
onAccept: null,
ignoreText: null,
onIgnore: null,
infoText: null,
}
static propTypes = {
acceptText: PropTypes.string.isRequired,
onAccept: PropTypes.func,
ignoreText: PropTypes.string,
onIgnore: PropTypes.func,
descriptionText: PropTypes.string.isRequired,
infoText: PropTypes.string,
}
handleAccept = () => {
this.props.onAccept()
}
handleIgnore = () => {
this.props.onIgnore()
}
render () {
const { descriptionText, acceptText, onAccept, ignoreText, onIgnore, infoText } = this.props
return (
<div className="home-notification">
<div className="home-notification__header">
<div className="home-notification__header-container">
<img
className="home-notification__icon"
alt=""
src="images/icons/connect.svg"
/>
<div className="home-notification__text">
{ descriptionText }
</div>
</div>
{
infoText ? (
<ReactTippy
style={{
display: 'flex',
}}
html={(
<p className="home-notification-tooltip__content">
{infoText}
</p>
)}
offset={-36}
distance={36}
animation="none"
position="top"
arrow
theme="info"
>
<img
alt=""
src="images/icons/info.svg"
/>
</ReactTippy>
) : (
null
)
}
</div>
<div className="home-notification__buttons">
{
(onAccept && acceptText) ? (
<Button
type="primary"
className="home-notification__accept-button"
onClick={this.handleAccept}
>
{ acceptText }
</Button>
) : (
null
)
}
{
(onIgnore && ignoreText) ? (
<Button
type="secondary"
className="home-notification__ignore-button"
onClick={this.handleIgnore}
>
{ ignoreText }
</Button>
) : (
null
)
}
</div>
</div>
)
}
}

View File

@ -0,0 +1 @@
export { default } from './home-notification.component'

View File

@ -0,0 +1,106 @@
.tippy-tooltip.info-theme {
background: rgba(36, 41, 46, 0.9);
color: $white;
border-radius: 8px;
}
.home-notification {
background: rgba(36, 41, 46, 0.9);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.12);
border-radius: 8px;
height: 116px;
padding: 16px;
margin: 8px;
display: flex;
flex-flow: column;
justify-content: space-between;
&__header-container {
display: flex;
}
&__header {
display: flex;
align-items: center;
justify-content: space-between;
}
&__text {
font-family: Roboto, 'sans-serif';
font-style: normal;
font-weight: normal;
font-size: 12px;
color: $white;
margin-left: 10px;
margin-right: 8px;
}
.fa-info-circle {
color: #6A737D;
}
&__ignore-button {
border: 2px solid #6A737D;
box-sizing: border-box;
border-radius: 6px;
color: $white;
background-color: inherit;
height: 34px;
width: 155px;
padding: 0;
&:hover {
border-color: #6A737D;
background-color: #6A737D;
}
&:active {
background-color: #141618;
}
}
&__accept-button {
border: 2px solid #6A737D;
box-sizing: border-box;
border-radius: 6px;
color: $white;
background-color: inherit;
height: 34px;
width: 155px;
padding: 0;
&:hover {
border-color: #6A737D;
background-color: #6A737D;
}
&:active {
background-color: #141618;
}
}
&__buttons {
display: flex;
width: 100%;
justify-content: space-between;
flex-direction: row-reverse;
}
}
.home-notification-tooltip {
&__tooltip-container {
display: flex;
}
&__content {
font-family: Roboto, 'sans-serif';
font-style: normal;
font-weight: normal;
font-size: 12px;
color: $white;
text-align: left;
display: inline-block;
width: 200px;
}
}

View File

@ -79,3 +79,5 @@
@import 'gas-customization/gas-price-button-group/index';
@import '../ui/toggle-button/index';
@import 'home-notification/index';

View File

@ -10,11 +10,13 @@ export default class TransactionList extends PureComponent {
}
static defaultProps = {
children: null,
pendingTransactions: [],
completedTransactions: [],
}
static propTypes = {
children: PropTypes.node,
pendingTransactions: PropTypes.array,
completedTransactions: PropTypes.array,
selectedToken: PropTypes.object,
@ -120,6 +122,7 @@ export default class TransactionList extends PureComponent {
return (
<div className="transaction-list">
{ this.renderTransactions() }
{ this.props.children }
</div>
)
}

View File

@ -10,6 +10,14 @@ export default class TransactionView extends PureComponent {
t: PropTypes.func,
}
static propTypes = {
children: PropTypes.node,
}
static defaultProps = {
children: null,
}
render () {
return (
<div className="transaction-view">
@ -20,7 +28,9 @@ export default class TransactionView extends PureComponent {
<div className="transaction-view__balance-wrapper">
<TransactionViewBalance />
</div>
<TransactionList />
<TransactionList>
{ this.props.children }
</TransactionList>
</div>
)
}

View File

@ -2,6 +2,7 @@ import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import Media from 'react-media'
import { Redirect } from 'react-router-dom'
import HomeNotification from '../../components/app/home-notification'
import WalletView from '../../components/app/wallet-view'
import TransactionView from '../../components/app/transaction-view'
import ProviderApproval from '../provider-approval'
@ -13,12 +14,30 @@ import {
} from '../../helpers/constants/routes'
export default class Home extends PureComponent {
static contextTypes = {
t: PropTypes.func,
}
static defaultProps = {
activeTab: null,
unsetMigratedPrivacyMode: null,
forceApproveProviderRequestByOrigin: null,
}
static propTypes = {
activeTab: PropTypes.shape({
title: PropTypes.string.isRequired,
url: PropTypes.string.isRequired,
}),
history: PropTypes.object,
forgottenPassword: PropTypes.bool,
suggestedTokens: PropTypes.object,
unconfirmedTransactionsCount: PropTypes.number,
providerRequests: PropTypes.array,
showPrivacyModeNotification: PropTypes.bool.isRequired,
unsetMigratedPrivacyMode: PropTypes.func,
viewingUnconnectedDapp: PropTypes.bool.isRequired,
forceApproveProviderRequestByOrigin: PropTypes.func,
}
componentWillMount () {
@ -45,10 +64,16 @@ export default class Home extends PureComponent {
}
render () {
const { t } = this.context
const {
activeTab,
forgottenPassword,
providerRequests,
history,
showPrivacyModeNotification,
unsetMigratedPrivacyMode,
viewingUnconnectedDapp,
forceApproveProviderRequestByOrigin,
} = this.props
if (forgottenPassword) {
@ -68,7 +93,40 @@ export default class Home extends PureComponent {
query="(min-width: 576px)"
render={() => <WalletView />}
/>
{ !history.location.pathname.match(/^\/confirm-transaction/) ? <TransactionView /> : null }
{ !history.location.pathname.match(/^\/confirm-transaction/)
? (
<TransactionView>
{
showPrivacyModeNotification
? (
<HomeNotification
descriptionText={t('privacyModeDefault')}
acceptText={t('learnMore')}
onAccept={() => {
window.open('https://medium.com/metamask/42549d4870fa', '_blank', 'noopener')
unsetMigratedPrivacyMode()
}}
/>
)
: null
}
{
viewingUnconnectedDapp
? (
<HomeNotification
descriptionText={t('shareAddressToConnect', [activeTab.origin])}
acceptText={t('shareAddress')}
onAccept={() => {
forceApproveProviderRequestByOrigin(activeTab.origin)
}}
infoText={t('shareAddressInfo', [activeTab.origin])}
/>
)
: null
}
</TransactionView>
)
: null }
</div>
</div>
)

View File

@ -3,26 +3,48 @@ import { compose } from 'recompose'
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
import { unconfirmedTransactionsCountSelector } from '../../selectors/confirm-transaction'
import {
forceApproveProviderRequestByOrigin,
unsetMigratedPrivacyMode,
} from '../../store/actions'
import { getEnvironmentType } from '../../../../app/scripts/lib/util'
import { ENVIRONMENT_TYPE_POPUP } from '../../../../app/scripts/lib/enums'
const mapStateToProps = state => {
const { metamask, appState } = state
const { activeTab, metamask, appState } = state
const {
approvedOrigins,
lostAccounts,
suggestedTokens,
providerRequests,
migratedPrivacyMode,
featureFlags: {
privacyMode,
} = {},
} = metamask
const { forgottenPassword } = appState
const isUnconnected = Boolean(activeTab && privacyMode && !approvedOrigins[activeTab.origin])
const isPopup = getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP
return {
lostAccounts,
forgottenPassword,
suggestedTokens,
unconfirmedTransactionsCount: unconfirmedTransactionsCountSelector(state),
providerRequests,
showPrivacyModeNotification: migratedPrivacyMode,
activeTab,
viewingUnconnectedDapp: isUnconnected && isPopup,
}
}
const mapDispatchToProps = (dispatch) => ({
unsetMigratedPrivacyMode: () => dispatch(unsetMigratedPrivacyMode()),
forceApproveProviderRequestByOrigin: (origin) => dispatch(forceApproveProviderRequestByOrigin(origin)),
})
export default compose(
withRouter,
connect(mapStateToProps)
connect(mapStateToProps, mapDispatchToProps)
)(Home)

View File

@ -324,6 +324,7 @@ var actions = {
setUseNativeCurrencyAsPrimaryCurrencyPreference,
setShowFiatConversionOnTestnetsPreference,
setAutoLogoutTimeLimit,
unsetMigratedPrivacyMode,
// Onboarding
setCompletedOnboarding,
@ -348,6 +349,7 @@ var actions = {
approveProviderRequestByOrigin,
rejectProviderRequestByOrigin,
forceApproveProviderRequestByOrigin,
clearApprovedOrigins,
setFirstTimeFlowType,
@ -2637,6 +2639,12 @@ function approveProviderRequestByOrigin (origin) {
}
}
function forceApproveProviderRequestByOrigin (origin) {
return () => {
background.forceApproveProviderRequestByOrigin(origin)
}
}
function rejectProviderRequestByOrigin (origin) {
return () => {
background.rejectProviderRequestByOrigin(origin)
@ -2758,3 +2766,9 @@ function getTokenParams (tokenAddress) {
})
}
}
function unsetMigratedPrivacyMode () {
return () => {
background.unsetMigratedPrivacyMode()
}
}

View File

@ -34,6 +34,7 @@ async function startApp (metamaskState, backgroundConnection, opts) {
const enLocaleMessages = await fetchLocale('en')
const store = configureStore({
activeTab: opts.activeTab,
// metamaskState represents the cross-tab state
metamask: metamaskState,