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:
parent
4d88e1cf86
commit
e9a63d5d5b
@ -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"
|
||||
},
|
||||
|
7
app/images/icons/connect.svg
Normal file
7
app/images/icons/connect.svg
Normal 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 |
5
app/images/icons/info.svg
Normal file
5
app/images/icons/info.svg
Normal 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 |
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
//
|
||||
|
@ -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])
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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)()
|
||||
|
33
app/scripts/migrations/034.js
Normal file
33
app/scripts/migrations/034.js
Normal 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
|
||||
}
|
@ -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 { 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)
|
||||
})
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
}
|
1
ui/app/components/app/home-notification/index.js
Normal file
1
ui/app/components/app/home-notification/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './home-notification.component'
|
106
ui/app/components/app/home-notification/index.scss
Normal file
106
ui/app/components/app/home-notification/index.scss
Normal 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;
|
||||
}
|
||||
}
|
@ -79,3 +79,5 @@
|
||||
@import 'gas-customization/gas-price-button-group/index';
|
||||
|
||||
@import '../ui/toggle-button/index';
|
||||
|
||||
@import 'home-notification/index';
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
@ -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>
|
||||
)
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user