aboutsummaryrefslogtreecommitdiffstats
path: root/app/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'app/scripts')
-rw-r--r--app/scripts/contentscript.js8
-rw-r--r--app/scripts/controllers/preferences.js8
-rw-r--r--app/scripts/controllers/provider-approval.js74
-rw-r--r--app/scripts/metamask-controller.js8
-rw-r--r--app/scripts/migrations/034.js33
-rw-r--r--app/scripts/popup-core.js77
-rw-r--r--app/scripts/ui.js125
7 files changed, 221 insertions, 112 deletions
diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js
index db4d5fd63..7415c5fe9 100644
--- a/app/scripts/contentscript.js
+++ b/app/scripts/contentscript.js
@@ -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()
+}
diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js
index 24df29c1d..d480834f5 100644
--- a/app/scripts/controllers/preferences.js
+++ b/app/scripts/controllers/preferences.js
@@ -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
//
diff --git a/app/scripts/controllers/provider-approval.js b/app/scripts/controllers/provider-approval.js
index 06c499780..00ec0aea1 100644
--- a/app/scripts/controllers/provider-approval.js
+++ b/app/scripts/controllers/provider-approval.js
@@ -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])
}
}
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index 26dde8288..158fb3079 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -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)()
diff --git a/app/scripts/migrations/034.js b/app/scripts/migrations/034.js
new file mode 100644
index 000000000..7c852de96
--- /dev/null
+++ b/app/scripts/migrations/034.js
@@ -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
+}
diff --git a/app/scripts/popup-core.js b/app/scripts/popup-core.js
deleted file mode 100644
index c08e9fa24..000000000
--- a/app/scripts/popup-core.js
+++ /dev/null
@@ -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)
- })
-}
diff --git a/app/scripts/ui.js b/app/scripts/ui.js
index 2dde14b48..a1f904f61 100644
--- a/app/scripts/ui.js
+++ b/app/scripts/ui.js
@@ -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)
+ })
}