aboutsummaryrefslogtreecommitdiffstats
path: root/app/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'app/scripts')
-rw-r--r--app/scripts/background.js2
-rw-r--r--app/scripts/contentscript.js10
-rw-r--r--app/scripts/controllers/computed-balances.js2
-rw-r--r--app/scripts/controllers/detect-tokens.js4
-rw-r--r--app/scripts/controllers/network/contract-addresses.js8
-rw-r--r--app/scripts/controllers/onboarding.js43
-rw-r--r--app/scripts/controllers/preferences.js58
-rw-r--r--app/scripts/controllers/provider-approval.js97
-rw-r--r--app/scripts/controllers/transactions/index.js26
-rw-r--r--app/scripts/controllers/transactions/lib/tx-state-history-helper.js8
-rw-r--r--app/scripts/controllers/transactions/lib/util.js10
-rw-r--r--app/scripts/controllers/transactions/pending-tx-tracker.js4
-rw-r--r--app/scripts/controllers/transactions/tx-state-manager.js10
-rw-r--r--app/scripts/edge-encryptor.js92
-rw-r--r--app/scripts/inpage.js91
-rw-r--r--app/scripts/lib/account-tracker.js18
-rw-r--r--app/scripts/lib/backend-metametrics.js10
-rw-r--r--app/scripts/lib/createStreamSink.js13
-rw-r--r--app/scripts/lib/ens-ipfs/setup.js2
-rw-r--r--app/scripts/lib/message-manager.js2
-rw-r--r--app/scripts/lib/personal-message-manager.js2
-rw-r--r--app/scripts/metamask-controller.js202
-rw-r--r--app/scripts/migrations/018.js6
-rw-r--r--app/scripts/migrations/019.js10
-rw-r--r--app/scripts/migrations/024.js2
-rw-r--r--app/scripts/migrations/031.js10
-rw-r--r--app/scripts/migrations/034.js33
-rw-r--r--app/scripts/migrations/035.js28
-rw-r--r--app/scripts/phishing-detect.js6
-rw-r--r--app/scripts/platforms/extension.js16
-rw-r--r--app/scripts/popup-core.js77
-rw-r--r--app/scripts/ui.js129
32 files changed, 604 insertions, 427 deletions
diff --git a/app/scripts/background.js b/app/scripts/background.js
index cca0d4709..8e65bd5a4 100644
--- a/app/scripts/background.js
+++ b/app/scripts/background.js
@@ -116,7 +116,6 @@ setupMetamaskMeshMetrics()
* @property {boolean} useBlockie - Indicates preferred user identicon format. True for blockie, false for Jazzicon.
* @property {Object} featureFlags - An object for optional feature flags.
* @property {string} networkEndpointType - TODO: Document
- * @property {boolean} isRevealingSeedWords - True if seed words are currently being recovered, and should be shown to user.
* @property {boolean} welcomeScreen - True if welcome screen should be shown.
* @property {string} currentLocale - A locale string matching the user's preferred display language.
* @property {Object} provider - The current selected network provider.
@@ -253,7 +252,6 @@ function setupController (initState, initLangCode) {
const controller = new MetamaskController({
// User confirmation callbacks:
showUnconfirmedMessage: triggerUi,
- unlockAccountMessage: triggerUi,
showUnapprovedTx: triggerUi,
openPopup: openPopup,
closePopup: notificationManager.closePopup.bind(notificationManager),
diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js
index 0c55ae39f..7415c5fe9 100644
--- a/app/scripts/contentscript.js
+++ b/app/scripts/contentscript.js
@@ -10,7 +10,7 @@ const extension = require('extensionizer')
const PortStream = require('extension-port-stream')
const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'inpage.js')).toString()
-const inpageSuffix = '//# sourceURL=' + extension.extension.getURL('inpage.js') + '\n'
+const inpageSuffix = '//# sourceURL=' + extension.runtime.getURL('inpage.js') + '\n'
const inpageBundle = inpageContent + inpageSuffix
// Eventually this streaming injection could be replaced with:
@@ -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/computed-balances.js b/app/scripts/controllers/computed-balances.js
index e04ce2ef7..caa061df4 100644
--- a/app/scripts/controllers/computed-balances.js
+++ b/app/scripts/controllers/computed-balances.js
@@ -65,7 +65,7 @@ class ComputedbalancesController {
syncAllAccountsFromStore (store) {
const upstream = Object.keys(store.accounts)
const balances = Object.keys(this.balances)
- .map(address => this.balances[address])
+ .map(address => this.balances[address])
// Follow new addresses
for (const address in balances) {
diff --git a/app/scripts/controllers/detect-tokens.js b/app/scripts/controllers/detect-tokens.js
index e6940c613..e6e993073 100644
--- a/app/scripts/controllers/detect-tokens.js
+++ b/app/scripts/controllers/detect-tokens.js
@@ -47,14 +47,14 @@ class DetectTokensController {
}
tokensToDetect.forEach((tokenAddress, index) => {
const balance = result[index]
- if (!balance.isZero()) {
+ if (balance && !balance.isZero()) {
this._preferences.addToken(tokenAddress, contracts[tokenAddress].symbol, contracts[tokenAddress].decimals)
}
})
})
}
- /**
+ /**
* Find if selectedAddress has tokens with contract in contractAddress.
*
* @param {string} contractAddress Hex address of the token contract to explore.
diff --git a/app/scripts/controllers/network/contract-addresses.js b/app/scripts/controllers/network/contract-addresses.js
index 5cd7da1d0..f9385accd 100644
--- a/app/scripts/controllers/network/contract-addresses.js
+++ b/app/scripts/controllers/network/contract-addresses.js
@@ -4,8 +4,8 @@ const SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN = '0xb8e671734ce5c8d7dfbbea5574fa4cf3
const SINGLE_CALL_BALANCES_ADDRESS_KOVAN = '0xb1d3fbb2f83aecd196f474c16ca5d9cffa0d0ffc'
module.exports = {
- SINGLE_CALL_BALANCES_ADDRESS,
- SINGLE_CALL_BALANCES_ADDRESS_RINKEBY,
- SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN,
- SINGLE_CALL_BALANCES_ADDRESS_KOVAN,
+ SINGLE_CALL_BALANCES_ADDRESS,
+ SINGLE_CALL_BALANCES_ADDRESS_RINKEBY,
+ SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN,
+ SINGLE_CALL_BALANCES_ADDRESS_KOVAN,
}
diff --git a/app/scripts/controllers/onboarding.js b/app/scripts/controllers/onboarding.js
new file mode 100644
index 000000000..18fec4993
--- /dev/null
+++ b/app/scripts/controllers/onboarding.js
@@ -0,0 +1,43 @@
+const ObservableStore = require('obs-store')
+const extend = require('xtend')
+
+/**
+ * @typedef {Object} InitState
+ * @property {Boolean} seedPhraseBackedUp Indicates whether the user has completed the seed phrase backup challenge
+ */
+
+/**
+ * @typedef {Object} OnboardingOptions
+ * @property {InitState} initState The initial controller state
+ */
+
+/**
+ * Controller responsible for maintaining
+ * a cache of account balances in local storage
+ */
+class OnboardingController {
+ /**
+ * Creates a new controller instance
+ *
+ * @param {OnboardingOptions} [opts] Controller configuration parameters
+ */
+ constructor (opts = {}) {
+ const initState = extend({
+ seedPhraseBackedUp: null,
+ }, opts.initState)
+ this.store = new ObservableStore(initState)
+ }
+
+ setSeedPhraseBackedUp (newSeedPhraseBackUpState) {
+ this.store.updateState({
+ seedPhraseBackedUp: newSeedPhraseBackUpState,
+ })
+ }
+
+ getSeedPhraseBackedUp () {
+ return this.store.getState().seedPhraseBackedUp
+ }
+
+}
+
+module.exports = OnboardingController
diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js
index acf952bb1..d480834f5 100644
--- a/app/scripts/controllers/preferences.js
+++ b/app/scripts/controllers/preferences.js
@@ -49,13 +49,12 @@ class PreferencesController {
currentLocale: opts.initLangCode,
identities: {},
lostIdentities: {},
- seedWords: null,
forgottenPassword: false,
preferences: {
useNativeCurrencyAsPrimaryCurrency: true,
},
completedOnboarding: false,
- completedUiMigration: true,
+ migratedPrivacyMode: false,
metaMetricsId: null,
metaMetricsSendCount: 0,
}, opts.initState)
@@ -70,7 +69,7 @@ class PreferencesController {
return this.setFeatureFlag(key, value)
}
}
-// PUBLIC METHODS
+ // PUBLIC METHODS
/**
* Sets the {@code forgottenPassword} state property
@@ -81,14 +80,6 @@ class PreferencesController {
}
/**
- * Sets the {@code seedWords} seed words
- * @param {string|null} seedWords the seed words
- */
- setSeedWords (seedWords) {
- this.store.updateState({ seedWords })
- }
-
- /**
* Setter for the `useBlockie` property
*
* @param {boolean} val Whether or not the user prefers blockie indicators
@@ -139,9 +130,9 @@ class PreferencesController {
* @param {String} type Indicates the type of first time flow - create or import - the user wishes to follow
*
*/
- setFirstTimeFlowType (type) {
- this.store.updateState({ firstTimeFlowType: type })
- }
+ setFirstTimeFlowType (type) {
+ this.store.updateState({ firstTimeFlowType: type })
+ }
getSuggestedTokens () {
@@ -503,22 +494,22 @@ class PreferencesController {
* @returns {Promise<array>} Promise resolving to updated frequentRpcList.
*
*/
- addToFrequentRpcList (url, chainId, ticker = 'ETH', nickname = '', rpcPrefs = {}) {
- const rpcList = this.getFrequentRpcListDetail()
- const index = rpcList.findIndex((element) => { return element.rpcUrl === url })
- if (index !== -1) {
- rpcList.splice(index, 1)
- }
- if (url !== 'http://localhost:8545') {
- let checkedChainId
- if (!!chainId && !Number.isNaN(parseInt(chainId))) {
- checkedChainId = chainId
- }
- rpcList.push({ rpcUrl: url, chainId: checkedChainId, ticker, nickname, rpcPrefs })
+ addToFrequentRpcList (url, chainId, ticker = 'ETH', nickname = '', rpcPrefs = {}) {
+ const rpcList = this.getFrequentRpcListDetail()
+ const index = rpcList.findIndex((element) => { return element.rpcUrl === url })
+ if (index !== -1) {
+ rpcList.splice(index, 1)
+ }
+ if (url !== 'http://localhost:8545') {
+ let checkedChainId
+ if (!!chainId && !Number.isNaN(parseInt(chainId))) {
+ checkedChainId = chainId
}
- this.store.updateState({ frequentRpcListDetail: rpcList })
- return Promise.resolve(rpcList)
+ rpcList.push({ rpcUrl: url, chainId: checkedChainId, ticker, nickname, rpcPrefs })
}
+ this.store.updateState({ frequentRpcListDetail: rpcList })
+ return Promise.resolve(rpcList)
+ }
/**
* Removes custom RPC url from state.
@@ -613,12 +604,11 @@ class PreferencesController {
return Promise.resolve(true)
}
- /**
- * Sets the {@code completedUiMigration} state to {@code true}, indicating that the user has completed the UI switch.
- */
- completeUiMigration () {
- this.store.updateState({ completedUiMigration: true })
- return Promise.resolve(true)
+ unsetMigratedPrivacyMode () {
+ this.store.updateState({
+ migratedPrivacyMode: false,
+ })
+ return Promise.resolve()
}
//
diff --git a/app/scripts/controllers/provider-approval.js b/app/scripts/controllers/provider-approval.js
index 06c499780..5d565c385 100644
--- a/app/scripts/controllers/provider-approval.js
+++ b/app/scripts/controllers/provider-approval.js
@@ -18,12 +18,13 @@ 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: {},
+ dismissedOrigins: {},
providerRequests: [],
})
}
@@ -45,7 +46,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 +64,12 @@ 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) {
+ const { approvedOrigins, dismissedOrigins } = this.store.getState()
+ const originAlreadyHandled = approvedOrigins[origin] || dismissedOrigins[origin]
+ if (originAlreadyHandled && this.caching && isUnlocked) {
return
}
this.openPopup && this.openPopup()
@@ -78,11 +81,27 @@ 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, dismissedOrigins, providerRequests } = this.store.getState()
+
+ let _dismissedOrigins = dismissedOrigins
+ if (dismissedOrigins[origin]) {
+ _dismissedOrigins = Object.assign({}, dismissedOrigins)
+ delete _dismissedOrigins[origin]
+ }
+
+ const remainingProviderRequests = providerRequests.filter(request => request.origin !== origin)
+ this.store.updateState({
+ approvedOrigins: {
+ ...approvedOrigins,
+ [origin]: true,
+ },
+ dismissedOrigins: _dismissedOrigins,
+ providerRequests: remainingProviderRequests,
+ })
this.emit(`resolvedRequest:${origin}`, { approved: true })
}
@@ -92,19 +111,62 @@ 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, dismissedOrigins } = 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,
+ dismissedOrigins: {
+ ...dismissedOrigins,
+ [origin]: true,
+ },
+ })
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, dismissedOrigins, providerRequests } = this.store.getState()
+ const remainingProviderRequests = providerRequests.filter(request => request.origin !== origin)
+
+ let _dismissedOrigins = dismissedOrigins
+ if (dismissedOrigins[origin]) {
+ _dismissedOrigins = Object.assign({}, dismissedOrigins)
+ delete _dismissedOrigins[origin]
+ }
+
+ this.store.updateState({
+ approvedOrigins: {
+ ...approvedOrigins,
+ [origin]: true,
+ },
+ dismissedOrigins: _dismissedOrigins,
+ 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 +177,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/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js
index c4371c25b..a33b46851 100644
--- a/app/scripts/controllers/transactions/index.js
+++ b/app/scripts/controllers/transactions/index.js
@@ -129,7 +129,7 @@ class TransactionController extends EventEmitter {
}
}
-/**
+ /**
Adds a tx to the txlist
@emits ${txMeta.id}:unapproved
*/
@@ -220,7 +220,7 @@ class TransactionController extends EventEmitter {
return txMeta
}
-/**
+ /**
adds the tx gas defaults: gas && gasPrice
@param txMeta {Object} - the txMeta object
@returns {Promise<object>} resolves with txMeta
@@ -495,9 +495,9 @@ class TransactionController extends EventEmitter {
this.txStateManager.updateTx(txMeta, 'transactions#setTxHash')
}
-//
-// PRIVATE METHODS
-//
+ //
+ // PRIVATE METHODS
+ //
/** maps methods for convenience*/
_mapMethods () {
/** @returns the state in transaction controller */
@@ -537,14 +537,14 @@ class TransactionController extends EventEmitter {
loadingDefaults: true,
}).forEach((tx) => {
this.addTxGasDefaults(tx)
- .then((txMeta) => {
- txMeta.loadingDefaults = false
- this.txStateManager.updateTx(txMeta, 'transactions: gas estimation for tx on boot')
- }).catch((error) => {
- tx.loadingDefaults = false
- this.txStateManager.updateTx(tx, 'failed to estimate gas during boot cleanup.')
- this.txStateManager.setTxStatusFailed(tx.id, error)
- })
+ .then((txMeta) => {
+ txMeta.loadingDefaults = false
+ this.txStateManager.updateTx(txMeta, 'transactions: gas estimation for tx on boot')
+ }).catch((error) => {
+ tx.loadingDefaults = false
+ this.txStateManager.updateTx(tx, 'failed to estimate gas during boot cleanup.')
+ this.txStateManager.setTxStatusFailed(tx.id, error)
+ })
})
this.txStateManager.getFilteredTxList({
diff --git a/app/scripts/controllers/transactions/lib/tx-state-history-helper.js b/app/scripts/controllers/transactions/lib/tx-state-history-helper.js
index 4562568e9..76fc5c35b 100644
--- a/app/scripts/controllers/transactions/lib/tx-state-history-helper.js
+++ b/app/scripts/controllers/transactions/lib/tx-state-history-helper.js
@@ -17,10 +17,10 @@ function migrateFromSnapshotsToDiffs (longHistory) {
return (
longHistory
// convert non-initial history entries into diffs
- .map((entry, index) => {
- if (index === 0) return entry
- return generateHistoryEntry(longHistory[index - 1], entry)
- })
+ .map((entry, index) => {
+ if (index === 0) return entry
+ return generateHistoryEntry(longHistory[index - 1], entry)
+ })
)
}
diff --git a/app/scripts/controllers/transactions/lib/util.js b/app/scripts/controllers/transactions/lib/util.js
index 5a8a0cefe..0d2ddddef 100644
--- a/app/scripts/controllers/transactions/lib/util.js
+++ b/app/scripts/controllers/transactions/lib/util.js
@@ -26,7 +26,7 @@ const normalizers = {
gasPrice: gasPrice => addHexPrefix(gasPrice),
}
- /**
+/**
normalizes txParams
@param txParams {object}
@returns {object} normalized txParams
@@ -40,7 +40,7 @@ function normalizeTxParams (txParams, LowerCase) {
return normalizedTxParams
}
- /**
+/**
validates txParams
@param txParams {object}
*/
@@ -59,7 +59,7 @@ function validateTxParams (txParams) {
}
}
- /**
+/**
validates the from field in txParams
@param txParams {object}
*/
@@ -68,7 +68,7 @@ function validateFrom (txParams) {
if (!isValidAddress(txParams.from)) throw new Error('Invalid from address')
}
- /**
+/**
validates the to field in txParams
@param txParams {object}
*/
@@ -85,7 +85,7 @@ function validateRecipient (txParams) {
return txParams
}
- /**
+/**
@returns an {array} of states that can be considered final
*/
function getFinalStates () {
diff --git a/app/scripts/controllers/transactions/pending-tx-tracker.js b/app/scripts/controllers/transactions/pending-tx-tracker.js
index bc11f6633..1ef3be36e 100644
--- a/app/scripts/controllers/transactions/pending-tx-tracker.js
+++ b/app/scripts/controllers/transactions/pending-tx-tracker.js
@@ -186,7 +186,7 @@ class PendingTransactionTracker extends EventEmitter {
this.emit('tx:warning', txMeta, err)
}
}
- /**
+ /**
checks to see if if the tx's nonce has been used by another transaction
@param txMeta {Object} - txMeta object
@emits tx:dropped
@@ -198,7 +198,7 @@ class PendingTransactionTracker extends EventEmitter {
const nextNonce = await this.query.getTransactionCount(from)
const { blockNumber } = await this.query.getTransactionByHash(hash) || {}
if (!blockNumber && parseInt(nextNonce) > parseInt(nonce)) {
- return true
+ return true
}
return false
}
diff --git a/app/scripts/controllers/transactions/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js
index 2aa28c270..a91b59918 100644
--- a/app/scripts/controllers/transactions/tx-state-manager.js
+++ b/app/scripts/controllers/transactions/tx-state-manager.js
@@ -34,7 +34,7 @@ class TransactionStateManager extends EventEmitter {
this.store = new ObservableStore(
extend({
transactions: [],
- }, initState))
+ }, initState))
this.txHistoryLimit = txHistoryLimit
this.getNetwork = getNetwork
}
@@ -245,7 +245,7 @@ class TransactionStateManager extends EventEmitter {
})
}
-/**
+ /**
@param opts {object} - an object of fields to search for eg:<br>
let <code>thingsToLookFor = {<br>
to: '0x0..',<br>
@@ -403,9 +403,9 @@ class TransactionStateManager extends EventEmitter {
// Update state
this._saveTxList(otherAccountTxs)
}
-//
-// PRIVATE METHODS
-//
+ //
+ // PRIVATE METHODS
+ //
// STATUS METHODS
// statuses:
diff --git a/app/scripts/edge-encryptor.js b/app/scripts/edge-encryptor.js
index dcb06873b..012672ed2 100644
--- a/app/scripts/edge-encryptor.js
+++ b/app/scripts/edge-encryptor.js
@@ -14,23 +14,23 @@ class EdgeEncryptor {
* @returns {Promise<string>} Promise resolving to an object with ciphertext
*/
encrypt (password, dataObject) {
- var salt = this._generateSalt()
- return this._keyFromPassword(password, salt)
- .then(function (key) {
- var data = JSON.stringify(dataObject)
- var dataBuffer = Unibabel.utf8ToBuffer(data)
- var vector = global.crypto.getRandomValues(new Uint8Array(16))
- var resultbuffer = asmcrypto.AES_GCM.encrypt(dataBuffer, key, vector)
+ var salt = this._generateSalt()
+ return this._keyFromPassword(password, salt)
+ .then(function (key) {
+ var data = JSON.stringify(dataObject)
+ var dataBuffer = Unibabel.utf8ToBuffer(data)
+ var vector = global.crypto.getRandomValues(new Uint8Array(16))
+ var resultbuffer = asmcrypto.AES_GCM.encrypt(dataBuffer, key, vector)
- var buffer = new Uint8Array(resultbuffer)
- var vectorStr = Unibabel.bufferToBase64(vector)
- var vaultStr = Unibabel.bufferToBase64(buffer)
- return JSON.stringify({
- data: vaultStr,
- iv: vectorStr,
- salt: salt,
- })
- })
+ var buffer = new Uint8Array(resultbuffer)
+ var vectorStr = Unibabel.bufferToBase64(vector)
+ var vaultStr = Unibabel.bufferToBase64(buffer)
+ return JSON.stringify({
+ data: vaultStr,
+ iv: vectorStr,
+ salt: salt,
+ })
+ })
}
/**
@@ -41,25 +41,25 @@ class EdgeEncryptor {
* @returns {Promise<Object>} Promise resolving to copy of decrypted object
*/
decrypt (password, text) {
- const payload = JSON.parse(text)
- const salt = payload.salt
- return this._keyFromPassword(password, salt)
- .then(function (key) {
- const encryptedData = Unibabel.base64ToBuffer(payload.data)
- const vector = Unibabel.base64ToBuffer(payload.iv)
- return new Promise((resolve, reject) => {
- var result
- try {
- result = asmcrypto.AES_GCM.decrypt(encryptedData, key, vector)
- } catch (err) {
- return reject(new Error('Incorrect password'))
- }
- const decryptedData = new Uint8Array(result)
- const decryptedStr = Unibabel.bufferToUtf8(decryptedData)
- const decryptedObj = JSON.parse(decryptedStr)
- resolve(decryptedObj)
- })
- })
+ const payload = JSON.parse(text)
+ const salt = payload.salt
+ return this._keyFromPassword(password, salt)
+ .then(function (key) {
+ const encryptedData = Unibabel.base64ToBuffer(payload.data)
+ const vector = Unibabel.base64ToBuffer(payload.iv)
+ return new Promise((resolve, reject) => {
+ var result
+ try {
+ result = asmcrypto.AES_GCM.decrypt(encryptedData, key, vector)
+ } catch (err) {
+ return reject(new Error('Incorrect password'))
+ }
+ const decryptedData = new Uint8Array(result)
+ const decryptedStr = Unibabel.bufferToUtf8(decryptedData)
+ const decryptedObj = JSON.parse(decryptedStr)
+ resolve(decryptedObj)
+ })
+ })
}
/**
@@ -72,12 +72,14 @@ class EdgeEncryptor {
*/
_keyFromPassword (password, salt) {
- var passBuffer = Unibabel.utf8ToBuffer(password)
- var saltBuffer = Unibabel.base64ToBuffer(salt)
- return new Promise((resolve) => {
- var key = asmcrypto.PBKDF2_HMAC_SHA256.bytes(passBuffer, saltBuffer, 10000)
- resolve(key)
- })
+ var passBuffer = Unibabel.utf8ToBuffer(password)
+ var saltBuffer = Unibabel.base64ToBuffer(salt)
+ const iterations = 10000
+ const length = 32 // SHA256 hash size
+ return new Promise((resolve) => {
+ var key = asmcrypto.Pbkdf2HmacSha256(passBuffer, saltBuffer, iterations, length)
+ resolve(key)
+ })
}
/**
@@ -87,10 +89,10 @@ class EdgeEncryptor {
* @returns {string} Randomized base64 encoded data
*/
_generateSalt (byteCount = 32) {
- var view = new Uint8Array(byteCount)
- global.crypto.getRandomValues(view)
- var b64encoded = btoa(String.fromCharCode.apply(null, view))
- return b64encoded
+ var view = new Uint8Array(byteCount)
+ global.crypto.getRandomValues(view)
+ var b64encoded = btoa(String.fromCharCode.apply(null, view))
+ return b64encoded
}
}
diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js
index fb16dd43d..a94787b05 100644
--- a/app/scripts/inpage.js
+++ b/app/scripts/inpage.js
@@ -1,4 +1,36 @@
/*global Web3*/
+
+
+// need to make sure we aren't affected by overlapping namespaces
+// and that we dont affect the app with our namespace
+// mostly a fix for web3's BigNumber if AMD's "define" is defined...
+let __define
+
+/**
+ * Caches reference to global define object and deletes it to
+ * avoid conflicts with other global define objects, such as
+ * AMD's define function
+ */
+const cleanContextForImports = () => {
+ __define = global.define
+ try {
+ global.define = undefined
+ } catch (_) {
+ console.warn('MetaMask - global.define could not be deleted.')
+ }
+}
+
+/**
+ * Restores global define object from cached reference
+ */
+const restoreContextAfterImports = () => {
+ try {
+ global.define = __define
+ } catch (_) {
+ console.warn('MetaMask - global.define could not be overwritten.')
+ }
+}
+
cleanContextForImports()
require('web3/dist/web3.min.js')
const log = require('loglevel')
@@ -46,6 +78,20 @@ inpageProvider.enable = function ({ force } = {}) {
// this will be default true so it does not break any old apps.
inpageProvider.autoRefreshOnNetworkChange = true
+
+// publicConfig isn't populated until we get a message from background.
+// Using this getter will ensure the state is available
+const getPublicConfigWhenReady = async () => {
+ const store = inpageProvider.publicConfigStore
+ let state = store.getState()
+ // if state is missing, wait for first update
+ if (!state.networkVersion) {
+ state = await new Promise(resolve => store.once('update', resolve))
+ console.log('new state', state)
+ }
+ return state
+}
+
// add metamask-specific convenience methods
inpageProvider._metamask = new Proxy({
/**
@@ -87,21 +133,8 @@ inpageProvider._metamask = new Proxy({
},
})
-// publicConfig isn't populated until we get a message from background.
-// Using this getter will ensure the state is available
-async function getPublicConfigWhenReady () {
- const store = inpageProvider.publicConfigStore
- let state = store.getState()
- // if state is missing, wait for first update
- if (!state.networkVersion) {
- state = await new Promise(resolve => store.once('update', resolve))
- console.log('new state', state)
- }
- return state
-}
-
// Work around for web3@1.0 deleting the bound `sendAsync` but not the unbound
-// `sendAsync` method on the prototype, causing `this` reference issues with drizzle
+// `sendAsync` method on the prototype, causing `this` reference issues
const proxiedInpageProvider = new Proxy(inpageProvider, {
// straight up lie that we deleted the property so that it doesnt
// throw an error in strict mode
@@ -161,33 +194,3 @@ inpageProvider.publicConfigStore.subscribe(function (state) {
window.postMessage('onboardingcomplete', '*')
}
})
-
-// need to make sure we aren't affected by overlapping namespaces
-// and that we dont affect the app with our namespace
-// mostly a fix for web3's BigNumber if AMD's "define" is defined...
-let __define
-
-/**
- * Caches reference to global define object and deletes it to
- * avoid conflicts with other global define objects, such as
- * AMD's define function
- */
-function cleanContextForImports () {
- __define = global.define
- try {
- global.define = undefined
- } catch (_) {
- console.warn('MetaMask - global.define could not be deleted.')
- }
-}
-
-/**
- * Restores global define object from cached reference
- */
-function restoreContextAfterImports () {
- try {
- global.define = __define
- } catch (_) {
- console.warn('MetaMask - global.define could not be overwritten.')
- }
-}
diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js
index 24c5ef7ee..1cbf867cb 100644
--- a/app/scripts/lib/account-tracker.js
+++ b/app/scripts/lib/account-tracker.js
@@ -183,23 +183,23 @@ class AccountTracker {
switch (currentNetwork) {
case MAINNET_CODE:
- await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS)
- break
+ await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS)
+ break
case RINKEYBY_CODE:
- await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS_RINKEBY)
- break
+ await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS_RINKEBY)
+ break
case ROPSTEN_CODE:
- await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN)
- break
+ await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN)
+ break
case KOVAN_CODE:
- await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS_KOVAN)
- break
+ await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS_KOVAN)
+ break
default:
- await Promise.all(addresses.map(this._updateAccount.bind(this)))
+ await Promise.all(addresses.map(this._updateAccount.bind(this)))
}
}
diff --git a/app/scripts/lib/backend-metametrics.js b/app/scripts/lib/backend-metametrics.js
index e3c163c1a..ad7874ead 100644
--- a/app/scripts/lib/backend-metametrics.js
+++ b/app/scripts/lib/backend-metametrics.js
@@ -15,11 +15,11 @@ function backEndMetaMetricsEvent (metaMaskState, eventData) {
const stateEventData = getMetaMetricState({ metamask: metaMaskState })
if (stateEventData.participateInMetaMetrics) {
- sendMetaMetricsEvent({
- ...stateEventData,
- ...eventData,
- url: METAMETRICS_TRACKING_URL + '/backend',
- })
+ sendMetaMetricsEvent({
+ ...stateEventData,
+ ...eventData,
+ url: METAMETRICS_TRACKING_URL + '/backend',
+ })
}
}
diff --git a/app/scripts/lib/createStreamSink.js b/app/scripts/lib/createStreamSink.js
index b93dbc089..eb9d0bd1a 100644
--- a/app/scripts/lib/createStreamSink.js
+++ b/app/scripts/lib/createStreamSink.js
@@ -1,13 +1,6 @@
const WritableStream = require('readable-stream').Writable
const promiseToCallback = require('promise-to-callback')
-module.exports = createStreamSink
-
-
-function createStreamSink (asyncWriteFn, _opts) {
- return new AsyncWritableStream(asyncWriteFn, _opts)
-}
-
class AsyncWritableStream extends WritableStream {
constructor (asyncWriteFn, _opts) {
@@ -22,3 +15,9 @@ class AsyncWritableStream extends WritableStream {
}
}
+
+function createStreamSink (asyncWriteFn, _opts) {
+ return new AsyncWritableStream(asyncWriteFn, _opts)
+}
+
+module.exports = createStreamSink
diff --git a/app/scripts/lib/ens-ipfs/setup.js b/app/scripts/lib/ens-ipfs/setup.js
index 82679db5d..86f3e7d47 100644
--- a/app/scripts/lib/ens-ipfs/setup.js
+++ b/app/scripts/lib/ens-ipfs/setup.js
@@ -51,6 +51,8 @@ function setupEnsIpfsResolver ({ provider }) {
}
} else if (type === 'swarm-ns') {
url = `https://swarm-gateways.net/bzz:/${hash}${path}${search || ''}`
+ } else if (type === 'onion' || type === 'onion3') {
+ url = `http://${hash}.onion${path}${search || ''}`
}
} catch (err) {
console.warn(err)
diff --git a/app/scripts/lib/message-manager.js b/app/scripts/lib/message-manager.js
index ac41de523..898378389 100644
--- a/app/scripts/lib/message-manager.js
+++ b/app/scripts/lib/message-manager.js
@@ -61,7 +61,7 @@ module.exports = class MessageManager extends EventEmitter {
*/
getUnapprovedMsgs () {
return this.messages.filter(msg => msg.status === 'unapproved')
- .reduce((result, msg) => { result[msg.id] = msg; return result }, {})
+ .reduce((result, msg) => { result[msg.id] = msg; return result }, {})
}
/**
diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js
index 7c13e521a..b5e28be13 100644
--- a/app/scripts/lib/personal-message-manager.js
+++ b/app/scripts/lib/personal-message-manager.js
@@ -64,7 +64,7 @@ module.exports = class PersonalMessageManager extends EventEmitter {
*/
getUnapprovedMsgs () {
return this.messages.filter(msg => msg.status === 'unapproved')
- .reduce((result, msg) => { result[msg.id] = msg; return result }, {})
+ .reduce((result, msg) => { result[msg.id] = msg; return result }, {})
}
/**
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index 0b6f5fcb5..d999bb790 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -28,6 +28,7 @@ const PreferencesController = require('./controllers/preferences')
const AppStateController = require('./controllers/app-state')
const InfuraController = require('./controllers/infura')
const CachedBalancesController = require('./controllers/cached-balances')
+const OnboardingController = require('./controllers/onboarding')
const RecentBlocksController = require('./controllers/recent-blocks')
const MessageManager = require('./lib/message-manager')
const PersonalMessageManager = require('./lib/personal-message-manager')
@@ -68,7 +69,7 @@ module.exports = class MetamaskController extends EventEmitter {
* @constructor
* @param {Object} opts
*/
- constructor (opts) {
+ constructor (opts) {
super()
this.defaultMaxListeners = 20
@@ -158,6 +159,10 @@ module.exports = class MetamaskController extends EventEmitter {
initState: initState.CachedBalancesController,
})
+ this.onboardingController = new OnboardingController({
+ initState: initState.OnboardingController,
+ })
+
// ensure accountTracker updates balances after network change
this.networkController.on('networkDidChange', () => {
this.accountTracker._updateAccounts()
@@ -262,6 +267,7 @@ module.exports = class MetamaskController extends EventEmitter {
NetworkController: this.networkController.store,
InfuraController: this.infuraController.store,
CachedBalancesController: this.cachedBalancesController.store,
+ OnboardingController: this.onboardingController.store,
})
this.memStore = new ComposableObservableStore(null, {
@@ -283,6 +289,7 @@ module.exports = class MetamaskController extends EventEmitter {
ShapeshiftController: this.shapeshiftController,
InfuraController: this.infuraController.store,
ProviderApprovalController: this.providerApprovalController.store,
+ OnboardingController: this.onboardingController.store,
})
this.memStore.subscribe(this.sendUpdate.bind(this))
}
@@ -362,9 +369,9 @@ module.exports = class MetamaskController extends EventEmitter {
return publicConfigStore
}
-//=============================================================================
-// EXPOSED TO THE UI SUBSYSTEM
-//=============================================================================
+ //=============================================================================
+ // EXPOSED TO THE UI SUBSYSTEM
+ //=============================================================================
/**
* The metamask-state of the various controllers, made available to the UI
@@ -398,6 +405,7 @@ module.exports = class MetamaskController extends EventEmitter {
const txController = this.txController
const networkController = this.networkController
const providerApprovalController = this.providerApprovalController
+ const onboardingController = this.onboardingController
return {
// etc
@@ -420,9 +428,7 @@ module.exports = class MetamaskController extends EventEmitter {
// primary HD keyring management
addNewAccount: nodeify(this.addNewAccount, this),
- placeSeedWords: this.placeSeedWords.bind(this),
verifySeedPhrase: nodeify(this.verifySeedPhrase, this),
- clearSeedWordCache: this.clearSeedWordCache.bind(this),
resetAccount: nodeify(this.resetAccount, this),
removeAccount: nodeify(this.removeAccount, this),
importAccountWithStrategy: nodeify(this.importAccountWithStrategy, this),
@@ -454,15 +460,16 @@ module.exports = class MetamaskController extends EventEmitter {
setAccountLabel: nodeify(preferencesController.setAccountLabel, preferencesController),
setFeatureFlag: nodeify(preferencesController.setFeatureFlag, preferencesController),
setPreference: nodeify(preferencesController.setPreference, preferencesController),
- completeUiMigration: nodeify(preferencesController.completeUiMigration, preferencesController),
completeOnboarding: nodeify(preferencesController.completeOnboarding, preferencesController),
addKnownMethodData: nodeify(preferencesController.addKnownMethodData, preferencesController),
+ unsetMigratedPrivacyMode: nodeify(preferencesController.unsetMigratedPrivacyMode, preferencesController),
// BlacklistController
whitelistPhishingDomain: this.whitelistPhishingDomain.bind(this),
// AddressController
setAddressBook: this.addressBookController.set.bind(this.addressBookController),
+ removeFromAddressBook: this.addressBookController.delete.bind(this.addressBookController),
// AppStateController
setLastActiveTime: nodeify(this.appStateController.setLastActiveTime, this.appStateController),
@@ -500,14 +507,18 @@ 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),
+
+ // onboarding controller
+ setSeedPhraseBackedUp: nodeify(onboardingController.setSeedPhraseBackedUp, onboardingController),
}
}
-//=============================================================================
-// VAULT / KEYRING RELATED METHODS
-//=============================================================================
+ //=============================================================================
+ // VAULT / KEYRING RELATED METHODS
+ //=============================================================================
/**
* Creates a new Vault and create a new keychain.
@@ -617,7 +628,7 @@ module.exports = class MetamaskController extends EventEmitter {
* with the mobile client for syncing purposes
* @returns Promise<Object> Parts of the state that we want to syncx
*/
- async fetchInfoToSync () {
+ async fetchInfoToSync () {
// Preferences
const {
accountTokens,
@@ -746,14 +757,14 @@ module.exports = class MetamaskController extends EventEmitter {
const keyring = await this.getKeyringForDevice(deviceName, hdPath)
let accounts = []
switch (page) {
- case -1:
- accounts = await keyring.getPreviousPage()
- break
- case 1:
- accounts = await keyring.getNextPage()
- break
- default:
- accounts = await keyring.getFirstPage()
+ case -1:
+ accounts = await keyring.getPreviousPage()
+ break
+ case 1:
+ accounts = await keyring.getNextPage()
+ break
+ default:
+ accounts = await keyring.getFirstPage()
}
// Merge with existing accounts
@@ -810,7 +821,7 @@ module.exports = class MetamaskController extends EventEmitter {
const { identities } = this.preferencesController.store.getState()
return { ...keyState, identities }
- }
+ }
//
@@ -846,26 +857,6 @@ module.exports = class MetamaskController extends EventEmitter {
}
/**
- * Adds the current vault's seed words to the UI's state tree.
- *
- * Used when creating a first vault, to allow confirmation.
- * Also used when revealing the seed words in the confirmation view.
- *
- * @param {Function} cb - A callback called on completion.
- */
- placeSeedWords (cb) {
-
- this.verifySeedPhrase()
- .then((seedWords) => {
- this.preferencesController.setSeedWords(seedWords)
- return cb(null, seedWords)
- })
- .catch((err) => {
- return cb(err)
- })
- }
-
- /**
* Verifies the validity of the current vault's seed phrase.
*
* Validity: seed phrase restores the accounts belonging to the current vault.
@@ -899,18 +890,6 @@ module.exports = class MetamaskController extends EventEmitter {
}
/**
- * Remove the primary account seed phrase from the UI's state tree.
- *
- * The seed phrase remains available in the background process.
- *
- * @param {function} cb Callback function called with the current address.
- */
- clearSeedWordCache (cb) {
- this.preferencesController.setSeedWords(null)
- cb(null, this.preferencesController.getSelectedAddress())
- }
-
- /**
* Clears the transaction history, to allow users to force-reset their nonces.
* Mostly used in development environments, when networks are restarted with
* the same network ID.
@@ -1009,16 +988,16 @@ module.exports = class MetamaskController extends EventEmitter {
// sets the status op the message to 'approved'
// and removes the metamaskId for signing
return this.messageManager.approveMessage(msgParams)
- .then((cleanMsgParams) => {
+ .then((cleanMsgParams) => {
// signs the message
- return this.keyringController.signMessage(cleanMsgParams)
- })
- .then((rawSig) => {
+ return this.keyringController.signMessage(cleanMsgParams)
+ })
+ .then((rawSig) => {
// tells the listener that the message has been signed
// and can be returned to the dapp
- this.messageManager.setMsgStatusSigned(msgId, rawSig)
- return this.getState()
- })
+ this.messageManager.setMsgStatusSigned(msgId, rawSig)
+ return this.getState()
+ })
}
/**
@@ -1067,16 +1046,16 @@ module.exports = class MetamaskController extends EventEmitter {
// sets the status op the message to 'approved'
// and removes the metamaskId for signing
return this.personalMessageManager.approveMessage(msgParams)
- .then((cleanMsgParams) => {
+ .then((cleanMsgParams) => {
// signs the message
- return this.keyringController.signPersonalMessage(cleanMsgParams)
- })
- .then((rawSig) => {
+ return this.keyringController.signPersonalMessage(cleanMsgParams)
+ })
+ .then((rawSig) => {
// tells the listener that the message has been signed
// and can be returned to the dapp
- this.personalMessageManager.setMsgStatusSigned(msgId, rawSig)
- return this.getState()
- })
+ this.personalMessageManager.setMsgStatusSigned(msgId, rawSig)
+ return this.getState()
+ })
}
/**
@@ -1176,7 +1155,7 @@ module.exports = class MetamaskController extends EventEmitter {
restoreOldVaultAccounts (migratorOutput) {
const { serialized } = migratorOutput
return this.keyringController.restoreKeyring(serialized)
- .then(() => migratorOutput)
+ .then(() => migratorOutput)
}
/**
@@ -1219,9 +1198,9 @@ module.exports = class MetamaskController extends EventEmitter {
})
}
-//=============================================================================
-// END (VAULT / KEYRING RELATED METHODS)
-//=============================================================================
+ //=============================================================================
+ // END (VAULT / KEYRING RELATED METHODS)
+ //=============================================================================
/**
* Allows a user to try to speed up a transaction by retrying it
@@ -1270,9 +1249,9 @@ module.exports = class MetamaskController extends EventEmitter {
})
}
-//=============================================================================
-// PASSWORD MANAGEMENT
-//=============================================================================
+ //=============================================================================
+ // PASSWORD MANAGEMENT
+ //=============================================================================
/**
* Allows a user to begin the seed phrase recovery process.
@@ -1294,9 +1273,9 @@ module.exports = class MetamaskController extends EventEmitter {
cb()
}
-//=============================================================================
-// SETUP
-//=============================================================================
+ //=============================================================================
+ // SETUP
+ //=============================================================================
/**
* Used to create a multiplexed stream for connecting to an untrusted context
@@ -1319,6 +1298,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)
}
/**
@@ -1392,6 +1373,32 @@ module.exports = class MetamaskController extends EventEmitter {
* @param {string} origin - The URI of the requesting resource.
*/
setupProviderConnection (outStream, origin, publicApi) {
+ const getSiteMetadata = publicApi && publicApi.getSiteMetadata
+ const engine = this.setupProviderEngine(origin, getSiteMetadata)
+
+ // setup connection
+ const providerStream = createEngineStream({ engine })
+
+ pump(
+ outStream,
+ providerStream,
+ outStream,
+ (err) => {
+ // cleanup filter polyfill middleware
+ engine._middleware.forEach((mid) => {
+ if (mid.destroy && typeof mid.destroy === 'function') {
+ mid.destroy()
+ }
+ })
+ if (err) log.error(err)
+ }
+ )
+ }
+
+ /**
+ * A method for creating a provider that is safely restricted for the requesting domain.
+ **/
+ setupProviderEngine (origin, getSiteMetadata) {
// setup json rpc engine stack
const engine = new RpcEngine()
const provider = this.provider
@@ -1399,6 +1406,7 @@ module.exports = class MetamaskController extends EventEmitter {
// create filter polyfill middleware
const filterMiddleware = createFilterMiddleware({ provider, blockTracker })
+
// create subscription polyfill middleware
const subscriptionManager = createSubscriptionManager({ provider, blockTracker })
subscriptionManager.events.on('notification', (message) => engine.emit('notification', message))
@@ -1414,24 +1422,11 @@ module.exports = class MetamaskController extends EventEmitter {
// requestAccounts
engine.push(this.providerApprovalController.createMiddleware({
origin,
- getSiteMetadata: publicApi && publicApi.getSiteMetadata,
+ getSiteMetadata,
}))
// forward to metamask primary provider
engine.push(providerAsMiddleware(provider))
-
- // setup connection
- const providerStream = createEngineStream({ engine })
-
- pump(
- outStream,
- providerStream,
- outStream,
- (err) => {
- // cleanup filter polyfill middleware
- filterMiddleware.destroy()
- if (err) log.error(err)
- }
- )
+ return engine
}
/**
@@ -1485,6 +1480,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)()
@@ -1551,13 +1550,13 @@ module.exports = class MetamaskController extends EventEmitter {
return GWEI_BN
}
return block.gasPrices
- .map(hexPrefix => hexPrefix.substr(2))
- .map(hex => new BN(hex, 16))
- .sort((a, b) => {
- return a.gt(b) ? 1 : -1
- })[0]
+ .map(hexPrefix => hexPrefix.substr(2))
+ .map(hex => new BN(hex, 16))
+ .sort((a, b) => {
+ return a.gt(b) ? 1 : -1
+ })[0]
})
- .map(number => number.div(GWEI_BN).toNumber())
+ .map(number => number.div(GWEI_BN).toNumber())
const percentileNum = percentile(65, lowestPrices)
const percentileNumBn = new BN(percentileNum)
@@ -1577,9 +1576,9 @@ module.exports = class MetamaskController extends EventEmitter {
return pendingNonce
}
-//=============================================================================
-// CONFIG
-//=============================================================================
+ //=============================================================================
+ // CONFIG
+ //=============================================================================
// Log blocks
@@ -1774,7 +1773,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.tokenRatesController.isActive = active
}
- /**
+ /**
* Creates RPC engine middleware for processing eth_signTypedData requests
*
* @param {Object} req - request object
@@ -1798,3 +1797,4 @@ module.exports = class MetamaskController extends EventEmitter {
return this.keyringController.setLocked()
}
}
+
diff --git a/app/scripts/migrations/018.js b/app/scripts/migrations/018.js
index ffbf24a4b..f6442dacc 100644
--- a/app/scripts/migrations/018.js
+++ b/app/scripts/migrations/018.js
@@ -43,9 +43,9 @@ function transformState (state) {
const newHistory = (
txStateHistoryHelper.migrateFromSnapshotsToDiffs(txMeta.history)
// remove empty diffs
- .filter((entry) => {
- return !Array.isArray(entry) || entry.length > 0
- })
+ .filter((entry) => {
+ return !Array.isArray(entry) || entry.length > 0
+ })
)
txMeta.history = newHistory
return txMeta
diff --git a/app/scripts/migrations/019.js b/app/scripts/migrations/019.js
index ce5da6859..7b726c3e8 100644
--- a/app/scripts/migrations/019.js
+++ b/app/scripts/migrations/019.js
@@ -38,13 +38,13 @@ function transformState (state) {
if (txMeta.status !== 'submitted') return txMeta
const confirmedTxs = txList.filter((tx) => tx.status === 'confirmed')
- .filter((tx) => tx.txParams.from === txMeta.txParams.from)
- .filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from)
+ .filter((tx) => tx.txParams.from === txMeta.txParams.from)
+ .filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from)
const highestConfirmedNonce = getHighestNonce(confirmedTxs)
const pendingTxs = txList.filter((tx) => tx.status === 'submitted')
- .filter((tx) => tx.txParams.from === txMeta.txParams.from)
- .filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from)
+ .filter((tx) => tx.txParams.from === txMeta.txParams.from)
+ .filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from)
const highestContinuousNonce = getHighestContinuousFrom(pendingTxs, highestConfirmedNonce)
const maxNonce = Math.max(highestContinuousNonce, highestConfirmedNonce)
@@ -78,7 +78,7 @@ function getHighestContinuousFrom (txList, startPoint) {
function getHighestNonce (txList) {
const nonces = txList.map((txMeta) => {
- const nonce = txMeta.txParams.nonce
+ const nonce = txMeta.txParams.nonce
return parseInt(nonce || '0x0', 16)
})
const highestNonce = Math.max.apply(null, nonces)
diff --git a/app/scripts/migrations/024.js b/app/scripts/migrations/024.js
index 6239bab13..5ffaea377 100644
--- a/app/scripts/migrations/024.js
+++ b/app/scripts/migrations/024.js
@@ -32,7 +32,7 @@ function transformState (state) {
txMeta.status === 'unapproved' &&
txMeta.txParams &&
txMeta.txParams.from
- ) {
+ ) {
txMeta.txParams.from = txMeta.txParams.from.toLowerCase()
}
return txMeta
diff --git a/app/scripts/migrations/031.js b/app/scripts/migrations/031.js
index 98d182828..9c8cbeb09 100644
--- a/app/scripts/migrations/031.js
+++ b/app/scripts/migrations/031.js
@@ -2,14 +2,14 @@
const version = 31
const clone = require('clone')
- /*
+/*
* The purpose of this migration is to properly set the completedOnboarding flag baesd on the state
* of the KeyringController.
*/
module.exports = {
version,
- migrate: async function (originalVersionedData) {
+ migrate: async function (originalVersionedData) {
const versionedData = clone(originalVersionedData)
versionedData.meta.version = version
const state = versionedData.data
@@ -19,13 +19,13 @@ module.exports = {
},
}
- function transformState (state) {
+function transformState (state) {
const { KeyringController, PreferencesController } = state
- if (KeyringController && PreferencesController) {
+ if (KeyringController && PreferencesController) {
const { vault } = KeyringController
PreferencesController.completedOnboarding = Boolean(vault)
}
- return state
+ return state
}
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/migrations/035.js b/app/scripts/migrations/035.js
new file mode 100644
index 000000000..02b01f588
--- /dev/null
+++ b/app/scripts/migrations/035.js
@@ -0,0 +1,28 @@
+// next version number
+const version = 35
+
+/*
+
+Removes the deprecated 'seedWords' state
+
+*/
+
+const clone = require('clone')
+
+module.exports = {
+ version,
+
+ migrate: async function (originalVersionedData) {
+ const versionedData = clone(originalVersionedData)
+ versionedData.meta.version = version
+ versionedData.data = transformState(versionedData.data)
+ return versionedData
+ },
+}
+
+function transformState (state) {
+ if (state.PreferencesController && state.PreferencesController.seedWords !== undefined) {
+ delete state.PreferencesController.seedWords
+ }
+ return state
+}
diff --git a/app/scripts/phishing-detect.js b/app/scripts/phishing-detect.js
index b30c76b72..266e4fc31 100644
--- a/app/scripts/phishing-detect.js
+++ b/app/scripts/phishing-detect.js
@@ -37,11 +37,11 @@ function start () {
function setupControllerConnection (connectionStream, cb) {
const eventEmitter = new EventEmitter()
- const accountManagerDnode = dnode({
+ const metaMaskControllerDnode = dnode({
sendUpdate (state) {
eventEmitter.emit('update', state)
},
})
- connectionStream.pipe(accountManagerDnode).pipe(connectionStream)
- accountManagerDnode.once('remote', (accountManager) => cb(null, accountManager))
+ connectionStream.pipe(metaMaskControllerDnode).pipe(connectionStream)
+ metaMaskControllerDnode.once('remote', (backgroundConnection) => cb(null, backgroundConnection))
}
diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js
index 0c2d222b8..43820515d 100644
--- a/app/scripts/platforms/extension.js
+++ b/app/scripts/platforms/extension.js
@@ -68,7 +68,7 @@ class ExtensionPlatform {
const nonce = parseInt(txMeta.txParams.nonce, 16)
const title = 'Confirmed transaction'
- const message = `Transaction ${nonce} confirmed! View on EtherScan`
+ const message = `Transaction ${nonce} confirmed! View on Etherscan`
this._showNotification(title, message, url)
}
@@ -84,20 +84,20 @@ class ExtensionPlatform {
extension.notifications.create(
url,
{
- 'type': 'basic',
- 'title': title,
- 'iconUrl': extension.extension.getURL('../../images/icon-64.png'),
- 'message': message,
+ 'type': 'basic',
+ 'title': title,
+ 'iconUrl': extension.extension.getURL('../../images/icon-64.png'),
+ 'message': message,
})
}
_subscribeToNotificationClicked () {
- if (!extension.notifications.onClicked.hasListener(this._viewOnEtherScan)) {
- extension.notifications.onClicked.addListener(this._viewOnEtherScan)
+ if (!extension.notifications.onClicked.hasListener(this._viewOnEtherscan)) {
+ extension.notifications.onClicked.addListener(this._viewOnEtherscan)
}
}
- _viewOnEtherScan (txId) {
+ _viewOnEtherscan (txId) {
if (txId.startsWith('http://')) {
extension.tabs.create({ url: txId })
}
diff --git a/app/scripts/popup-core.js b/app/scripts/popup-core.js
deleted file mode 100644
index e964d001d..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),
- (accountManager, cb) => launchMetamaskUi({ container, accountManager }, 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 accountManagerDnode = Dnode({
- sendUpdate: function (state) {
- eventEmitter.emit('update', state)
- },
- })
- connectionStream.pipe(accountManagerDnode).pipe(connectionStream)
- accountManagerDnode.once('remote', function (accountManager) {
- // setup push events
- accountManager.on = eventEmitter.on.bind(eventEmitter)
- cb(null, accountManager)
- })
-}
diff --git a/app/scripts/ui.js b/app/scripts/ui.js
index 67ec6bf06..a99da37a0 100644
--- a/app/scripts/ui.js
+++ b/app/scripts/ui.js
@@ -1,14 +1,19 @@
-const injectCss = require('inject-css')
-const NewMetaMaskUiCss = require('../../ui/css')
-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)
@@ -41,22 +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
- }
-
- injectCss(NewMetaMaskUiCss())
- })
-
+ const activeTab = await queryCurrentActiveTab(windowType)
+ initializeUiWithTab(activeTab)
function closePopupIfOpen (windowType) {
if (windowType !== ENVIRONMENT_TYPE_NOTIFICATION) {
@@ -65,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 { hostname: origin, protocol } = url ? urlUtil.parse(url) : {}
+ resolve({
+ title, origin, protocol, 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)
+ })
}