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

feature/default_network_editable
Whymarrh Whitby 5 years ago committed by GitHub
parent 4d88e1cf86
commit e9a63d5d5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      app/_locales/en/messages.json
  2. 7
      app/images/icons/connect.svg
  3. 5
      app/images/icons/info.svg
  4. 8
      app/scripts/contentscript.js
  5. 8
      app/scripts/controllers/preferences.js
  6. 74
      app/scripts/controllers/provider-approval.js
  7. 8
      app/scripts/metamask-controller.js
  8. 33
      app/scripts/migrations/034.js
  9. 77
      app/scripts/popup-core.js
  10. 125
      app/scripts/ui.js
  11. 1
      package.json
  12. 110
      ui/app/components/app/home-notification/home-notification.component.js
  13. 1
      ui/app/components/app/home-notification/index.js
  14. 106
      ui/app/components/app/home-notification/index.scss
  15. 2
      ui/app/components/app/index.scss
  16. 3
      ui/app/components/app/transaction-list/transaction-list.component.js
  17. 12
      ui/app/components/app/transaction-view/transaction-view.component.js
  18. 60
      ui/app/pages/home/home.component.js
  19. 26
      ui/app/pages/home/home.container.js
  20. 14
      ui/app/store/actions.js
  21. 1
      ui/index.js

@ -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": { "privacyMode": {
"message": "Privacy Mode" "message": "Privacy Mode"
}, },

@ -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

@ -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

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

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

@ -18,12 +18,12 @@ class ProviderApprovalController extends SafeEventEmitter {
*/ */
constructor ({ closePopup, keyringController, openPopup, preferencesController } = {}) { constructor ({ closePopup, keyringController, openPopup, preferencesController } = {}) {
super() super()
this.approvedOrigins = {}
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.store = new ObservableStore({
approvedOrigins: {},
providerRequests: [], providerRequests: [],
}) })
} }
@ -45,7 +45,7 @@ class ProviderApprovalController extends SafeEventEmitter {
} }
// register the provider request // register the provider request
const metadata = await getSiteMetadata(origin) 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 // wait for resolution of request
const approved = await new Promise(resolve => this.once(`resolvedRequest:${origin}`, ({ approved }) => resolve(approved))) const approved = await new Promise(resolve => this.once(`resolvedRequest:${origin}`, ({ approved }) => resolve(approved)))
if (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} siteTitle - The title of the document requesting full provider access
* @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, force, tabID) { _handleProviderRequest (origin, siteTitle, siteImage) {
this.store.updateState({ providerRequests: [{ origin, siteTitle, siteImage, tabID }] }) this.store.updateState({ providerRequests: [{ origin, siteTitle, siteImage }] })
const isUnlocked = this.keyringController.memStore.getState().isUnlocked 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 return
} }
this.openPopup && this.openPopup() this.openPopup && this.openPopup()
@ -78,11 +78,19 @@ class ProviderApprovalController extends SafeEventEmitter {
* @param {string} origin - origin of the domain that had provider access approved * @param {string} origin - origin of the domain that had provider access approved
*/ */
approveProviderRequestByOrigin (origin) { approveProviderRequestByOrigin (origin) {
this.closePopup && this.closePopup() if (this.closePopup) {
const requests = this.store.getState().providerRequests this.closePopup()
const providerRequests = requests.filter(request => request.origin !== origin) }
this.store.updateState({ providerRequests })
this.approvedOrigins[origin] = true 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 }) 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 * @param {string} origin - origin of the domain that had provider access approved
*/ */
rejectProviderRequestByOrigin (origin) { rejectProviderRequestByOrigin (origin) {
this.closePopup && this.closePopup() if (this.closePopup) {
const requests = this.store.getState().providerRequests this.closePopup()
const providerRequests = requests.filter(request => request.origin !== origin) }
this.store.updateState({ providerRequests })
delete this.approvedOrigins[origin] 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 }) 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 * Clears any cached approvals for user-approved origins
*/ */
clearApprovedOrigins () { clearApprovedOrigins () {
this.approvedOrigins = {} this.store.updateState({
approvedOrigins: {},
})
} }
/** /**
@ -115,8 +154,7 @@ class ProviderApprovalController extends SafeEventEmitter {
*/ */
shouldExposeAccounts (origin) { shouldExposeAccounts (origin) {
const privacyMode = this.preferencesController.getFeatureFlags().privacyMode const privacyMode = this.preferencesController.getFeatureFlags().privacyMode
const result = !privacyMode || Boolean(this.approvedOrigins[origin]) return !privacyMode || Boolean(this.store.getState().approvedOrigins[origin])
return result
} }
} }

@ -454,6 +454,7 @@ module.exports = class MetamaskController extends EventEmitter {
setPreference: nodeify(preferencesController.setPreference, preferencesController), setPreference: nodeify(preferencesController.setPreference, preferencesController),
completeOnboarding: nodeify(preferencesController.completeOnboarding, preferencesController), completeOnboarding: nodeify(preferencesController.completeOnboarding, preferencesController),
addKnownMethodData: nodeify(preferencesController.addKnownMethodData, preferencesController), addKnownMethodData: nodeify(preferencesController.addKnownMethodData, preferencesController),
unsetMigratedPrivacyMode: nodeify(preferencesController.unsetMigratedPrivacyMode, preferencesController),
// BlacklistController // BlacklistController
whitelistPhishingDomain: this.whitelistPhishingDomain.bind(this), whitelistPhishingDomain: this.whitelistPhishingDomain.bind(this),
@ -498,6 +499,7 @@ 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),
} }
} }
@ -1285,6 +1287,8 @@ module.exports = class MetamaskController extends EventEmitter {
const publicApi = this.setupPublicApi(mux.createStream('publicApi'), originDomain) const publicApi = this.setupPublicApi(mux.createStream('publicApi'), originDomain)
this.setupProviderConnection(mux.createStream('provider'), originDomain, publicApi) this.setupProviderConnection(mux.createStream('provider'), originDomain, publicApi)
this.setupPublicConfig(mux.createStream('publicConfig'), originDomain) 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 = { const publicApi = {
// wrap with an await remote // wrap with an await remote
forceReloadSite: async () => {
const remote = await getRemote()
return await pify(remote.forceReloadSite)()
},
getSiteMetadata: async () => { getSiteMetadata: async () => {
const remote = await getRemote() const remote = await getRemote()
return await pify(remote.getSiteMetadata)() return await pify(remote.getSiteMetadata)()

@ -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
}

@ -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)
})
}

@ -1,12 +1,19 @@
const startPopup = require('./popup-core')
const PortStream = require('extension-port-stream') const PortStream = require('extension-port-stream')
const { getEnvironmentType } = require('./lib/util') 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 extension = require('extensionizer')
const ExtensionPlatform = require('./platforms/extension') const ExtensionPlatform = require('./platforms/extension')
const NotificationManager = require('./lib/notification-manager') const NotificationManager = require('./lib/notification-manager')
const notificationManager = new NotificationManager() const notificationManager = new NotificationManager()
const setupSentry = require('./lib/setupSentry') 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') const log = require('loglevel')
start().catch(log.error) start().catch(log.error)
@ -39,20 +46,8 @@ async function start () {
const extensionPort = extension.runtime.connect({ name: windowType }) const extensionPort = extension.runtime.connect({ name: windowType })
const connectionStream = new PortStream(extensionPort) const connectionStream = new PortStream(extensionPort)
// start ui const activeTab = await queryCurrentActiveTab(windowType)
const container = document.getElementById('app-content') initializeUiWithTab(activeTab)
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
}
})
function closePopupIfOpen (windowType) { function closePopupIfOpen (windowType) {
if (windowType !== ENVIRONMENT_TYPE_NOTIFICATION) { 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.innerHTML = '<div class="critical-error">The MetaMask app failed to load: please open and close MetaMask again to restart.</div>'
container.style.height = '80px' container.style.height = '80px'
log.error(err.stack) log.error(err.stack)
throw err 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)
})
} }

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

@ -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>
)
}
}

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

@ -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;
}
}

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

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

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

@ -2,6 +2,7 @@ import React, { PureComponent } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import Media from 'react-media' import Media from 'react-media'
import { Redirect } from 'react-router-dom' import { Redirect } from 'react-router-dom'
import HomeNotification from '../../components/app/home-notification'
import WalletView from '../../components/app/wallet-view' import WalletView from '../../components/app/wallet-view'
import TransactionView from '../../components/app/transaction-view' import TransactionView from '../../components/app/transaction-view'
import ProviderApproval from '../provider-approval' import ProviderApproval from '../provider-approval'
@ -13,12 +14,30 @@ import {
} from '../../helpers/constants/routes' } from '../../helpers/constants/routes'
export default class Home extends PureComponent { export default class Home extends PureComponent {
static contextTypes = {
t: PropTypes.func,
}
static defaultProps = {
activeTab: null,
unsetMigratedPrivacyMode: null,
forceApproveProviderRequestByOrigin: null,
}
static propTypes = { static propTypes = {
activeTab: PropTypes.shape({
title: PropTypes.string.isRequired,
url: PropTypes.string.isRequired,
}),
history: PropTypes.object, history: PropTypes.object,
forgottenPassword: PropTypes.bool, forgottenPassword: PropTypes.bool,
suggestedTokens: PropTypes.object, suggestedTokens: PropTypes.object,
unconfirmedTransactionsCount: PropTypes.number, unconfirmedTransactionsCount: PropTypes.number,
providerRequests: PropTypes.array, providerRequests: PropTypes.array,
showPrivacyModeNotification: PropTypes.bool.isRequired,
unsetMigratedPrivacyMode: PropTypes.func,
viewingUnconnectedDapp: PropTypes.bool.isRequired,
forceApproveProviderRequestByOrigin: PropTypes.func,
} }
componentWillMount () { componentWillMount () {
@ -45,10 +64,16 @@ export default class Home extends PureComponent {
} }
render () { render () {
const { t } = this.context
const { const {
activeTab,
forgottenPassword, forgottenPassword,
providerRequests, providerRequests,
history, history,
showPrivacyModeNotification,
unsetMigratedPrivacyMode,
viewingUnconnectedDapp,
forceApproveProviderRequestByOrigin,
} = this.props } = this.props
if (forgottenPassword) { if (forgottenPassword) {
@ -68,7 +93,40 @@ export default class Home extends PureComponent {
query="(min-width: 576px)" query="(min-width: 576px)"
render={() => <WalletView />} 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>
</div> </div>
) )

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

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

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

Loading…
Cancel
Save