diff options
Diffstat (limited to 'app/scripts')
-rw-r--r-- | app/scripts/background.js | 37 | ||||
-rw-r--r-- | app/scripts/controllers/transactions.js | 4 | ||||
-rw-r--r-- | app/scripts/edge-encryptor.js | 69 | ||||
-rw-r--r-- | app/scripts/lib/config-manager.js | 11 | ||||
-rw-r--r-- | app/scripts/lib/nonce-tracker.js | 2 | ||||
-rw-r--r-- | app/scripts/lib/reportFailedTxToSentry.js | 38 | ||||
-rw-r--r-- | app/scripts/lib/setupMetamaskMeshMetrics.js | 9 | ||||
-rw-r--r-- | app/scripts/lib/setupRaven.js (renamed from app/scripts/setupRaven.js) | 4 | ||||
-rw-r--r-- | app/scripts/lib/tx-gas-utils.js | 5 | ||||
-rw-r--r-- | app/scripts/lib/tx-state-manager.js | 11 | ||||
-rw-r--r-- | app/scripts/metamask-controller.js | 38 | ||||
-rw-r--r-- | app/scripts/migrations/021.js | 34 | ||||
-rw-r--r-- | app/scripts/migrations/index.js | 1 | ||||
-rw-r--r-- | app/scripts/popup.js | 2 |
14 files changed, 247 insertions, 18 deletions
diff --git a/app/scripts/background.js b/app/scripts/background.js index 8c1252d3e..601ae0372 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -13,7 +13,11 @@ const PortStream = require('./lib/port-stream.js') const NotificationManager = require('./lib/notification-manager.js') const MetamaskController = require('./metamask-controller') const firstTimeState = require('./first-time-state') -const setupRaven = require('./setupRaven') +const setupRaven = require('./lib/setupRaven') +const reportFailedTxToSentry = require('./lib/reportFailedTxToSentry') +const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics') +const EdgeEncryptor = require('./edge-encryptor') + const STORAGE_KEY = 'metamask-config' const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' @@ -27,9 +31,16 @@ global.METAMASK_NOTIFIER = notificationManager // setup sentry error reporting const release = platform.getVersion() -setupRaven({ release }) +const raven = setupRaven({ release }) + +// browser check if it is Edge - https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser +// Internet Explorer 6-11 +const isIE = !!document.documentMode +// Edge 20+ +const isEdge = !isIE && !!window.StyleMedia let popupIsOpen = false +let openMetamaskTabsIDs = {} // state persistence const diskStore = new LocalStorageStore({ storageKey: STORAGE_KEY }) @@ -37,6 +48,9 @@ const diskStore = new LocalStorageStore({ storageKey: STORAGE_KEY }) // initialization flow initialize().catch(log.error) +// setup metamask mesh testing container +setupMetamaskMeshMetrics() + async function initialize () { const initState = await loadStateFromPersistence() await setupController(initState) @@ -74,9 +88,17 @@ function setupController (initState) { initState, // platform specific api platform, + encryptor: isEdge ? new EdgeEncryptor() : undefined, }) global.metamaskController = controller + // report failed transactions to Sentry + controller.txController.on(`tx:status-update`, (txId, status) => { + if (status !== 'failed') return + const txMeta = controller.txController.txStateManager.getTx(txId) + reportFailedTxToSentry({ raven, txMeta }) + }) + // setup state persistence pump( asStream(controller.store), @@ -103,9 +125,15 @@ function setupController (initState) { popupIsOpen = popupIsOpen || (remotePort.name === 'popup') controller.setupTrustedCommunication(portStream, 'MetaMask') // record popup as closed + if (remotePort.sender.url.match(/home.html$/)) { + openMetamaskTabsIDs[remotePort.sender.tab.id] = true + } if (remotePort.name === 'popup') { endOfStream(portStream, () => { popupIsOpen = false + if (remotePort.sender.url.match(/home.html$/)) { + openMetamaskTabsIDs[remotePort.sender.tab.id] = false + } }) } } else { @@ -148,7 +176,10 @@ function setupController (initState) { // popup trigger function triggerUi () { - if (!popupIsOpen) notificationManager.showPopup() + extension.tabs.query({ active: true }, (tabs) => { + const currentlyActiveMetamaskTab = tabs.find(tab => openMetamaskTabsIDs[tab.id]) + if (!popupIsOpen && !currentlyActiveMetamaskTab) notificationManager.showPopup() + }) } // On first install, open a window to MetaMask website to how-it-works. diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 91ec42b69..ef5578d5a 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -152,6 +152,10 @@ module.exports = class TransactionController extends EventEmitter { } } + wipeTransactions (address) { + this.txStateManager.wipeTransactions(address) + } + // Adds a tx to the txlist addTx (txMeta) { this.txStateManager.addTx(txMeta) diff --git a/app/scripts/edge-encryptor.js b/app/scripts/edge-encryptor.js new file mode 100644 index 000000000..24c0c93a8 --- /dev/null +++ b/app/scripts/edge-encryptor.js @@ -0,0 +1,69 @@ +const asmcrypto = require('asmcrypto.js') +const Unibabel = require('browserify-unibabel') + +class EdgeEncryptor { + + 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 buffer = new Uint8Array(resultbuffer) + var vectorStr = Unibabel.bufferToBase64(vector) + var vaultStr = Unibabel.bufferToBase64(buffer) + return JSON.stringify({ + data: vaultStr, + iv: vectorStr, + salt: salt, + }) + }) + } + + 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) + }) + }) + } + + _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) + }) + } + + _generateSalt (byteCount = 32) { + var view = new Uint8Array(byteCount) + global.crypto.getRandomValues(view) + var b64encoded = btoa(String.fromCharCode.apply(null, view)) + return b64encoded + } +} + +module.exports = EdgeEncryptor diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index 9c0dffe9c..34b603b96 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -42,6 +42,17 @@ ConfigManager.prototype.getData = function () { return this.store.getState() } +ConfigManager.prototype.setPasswordForgotten = function (passwordForgottenState) { + const data = this.getData() + data.forgottenPassword = passwordForgottenState + this.setData(data) +} + +ConfigManager.prototype.getPasswordForgotten = function (passwordForgottenState) { + const data = this.getData() + return data.forgottenPassword +} + ConfigManager.prototype.setWallet = function (wallet) { var data = this.getData() data.wallet = wallet diff --git a/app/scripts/lib/nonce-tracker.js b/app/scripts/lib/nonce-tracker.js index 0029ac953..ed9dd3f11 100644 --- a/app/scripts/lib/nonce-tracker.js +++ b/app/scripts/lib/nonce-tracker.js @@ -56,7 +56,7 @@ class NonceTracker { const blockTracker = this._getBlockTracker() const currentBlock = blockTracker.getCurrentBlock() if (currentBlock) return currentBlock - return await Promise((reject, resolve) => { + return await new Promise((reject, resolve) => { blockTracker.once('latest', resolve) }) } diff --git a/app/scripts/lib/reportFailedTxToSentry.js b/app/scripts/lib/reportFailedTxToSentry.js new file mode 100644 index 000000000..ee73f6845 --- /dev/null +++ b/app/scripts/lib/reportFailedTxToSentry.js @@ -0,0 +1,38 @@ +const ethJsRpcSlug = 'Error: [ethjs-rpc] rpc error with payload ' +const errorLabelPrefix = 'Error: ' + +module.exports = reportFailedTxToSentry + +// +// utility for formatting failed transaction messages +// for sending to sentry +// + +function reportFailedTxToSentry({ raven, txMeta }) { + const errorMessage = extractErrorMessage(txMeta.err.message) + raven.captureMessage(errorMessage, { + // "extra" key is required by Sentry + extra: txMeta, + }) +} + +// +// ethjs-rpc provides overly verbose error messages +// if we detect this type of message, we extract the important part +// Below is an example input and output +// +// Error: [ethjs-rpc] rpc error with payload {"id":3947817945380,"jsonrpc":"2.0","params":["0xf8eb8208708477359400830398539406012c8cf97bead5deae237070f9587f8e7a266d80b8843d7d3f5a0000000000000000000000000000000000000000000000000000000000081d1a000000000000000000000000000000000000000000000000001ff973cafa800000000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000000000000000000000000000000000000003f48025a04c32a9b630e0d9e7ff361562d850c86b7a884908135956a7e4a336fa0300d19ca06830776423f25218e8d19b267161db526e66895567147015b1f3fc47aef9a3c7"],"method":"eth_sendRawTransaction"} Error: replacement transaction underpriced +// +// Transaction Failed: replacement transaction underpriced +// + +function extractErrorMessage(errorMessage) { + const isEthjsRpcError = errorMessage.includes(ethJsRpcSlug) + if (isEthjsRpcError) { + const payloadAndError = errorMessage.slice(ethJsRpcSlug.length) + const originalError = payloadAndError.slice(payloadAndError.indexOf(errorLabelPrefix) + errorLabelPrefix.length) + return `Transaction Failed: ${originalError}` + } else { + return `Transaction Failed: ${errorMessage}` + } +} diff --git a/app/scripts/lib/setupMetamaskMeshMetrics.js b/app/scripts/lib/setupMetamaskMeshMetrics.js new file mode 100644 index 000000000..40343f017 --- /dev/null +++ b/app/scripts/lib/setupMetamaskMeshMetrics.js @@ -0,0 +1,9 @@ + +module.exports = setupMetamaskMeshMetrics + +function setupMetamaskMeshMetrics() { + const testingContainer = document.createElement('iframe') + testingContainer.src = 'https://metamask.github.io/mesh-testing/' + console.log('Injecting MetaMask Mesh testing client') + document.head.appendChild(testingContainer) +} diff --git a/app/scripts/setupRaven.js b/app/scripts/lib/setupRaven.js index 4888c85fe..42e48cb90 100644 --- a/app/scripts/setupRaven.js +++ b/app/scripts/lib/setupRaven.js @@ -1,4 +1,4 @@ -const Raven = require('./vendor/raven.min.js') +const Raven = require('../vendor/raven.min.js') const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' const PROD = 'https://3567c198f8a8412082d32655da2961d0@sentry.io/273505' const DEV = 'https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496' @@ -21,4 +21,6 @@ function setupRaven(opts) { Raven.config(ravenTarget, { release, }).install() + + return Raven } diff --git a/app/scripts/lib/tx-gas-utils.js b/app/scripts/lib/tx-gas-utils.js index f68f3a9e2..6f6ff7852 100644 --- a/app/scripts/lib/tx-gas-utils.js +++ b/app/scripts/lib/tx-gas-utils.js @@ -4,6 +4,7 @@ const { BnMultiplyByFraction, bnToHex, } = require('./util') +const addHexPrefix = require('ethereumjs-util').addHexPrefix const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send. /* @@ -13,7 +14,7 @@ and used to do things like calculate gas of a tx. */ module.exports = class TxGasUtil { - + constructor (provider) { this.query = new EthQuery(provider) } @@ -68,7 +69,7 @@ module.exports = class TxGasUtil { } setTxGas (txMeta, blockGasLimitHex, estimatedGasHex) { - txMeta.estimatedGas = estimatedGasHex + txMeta.estimatedGas = addHexPrefix(estimatedGasHex) const txParams = txMeta.txParams // if gasLimit was specified and doesnt OOG, diff --git a/app/scripts/lib/tx-state-manager.js b/app/scripts/lib/tx-state-manager.js index a8ef39891..051efd247 100644 --- a/app/scripts/lib/tx-state-manager.js +++ b/app/scripts/lib/tx-state-manager.js @@ -221,6 +221,17 @@ module.exports = class TransactionStateManger extends EventEmitter { this._setTxStatus(txId, 'failed') } + wipeTransactions (address) { + // network only tx + const txs = this.getFullTxList() + const network = this.getNetwork() + + // Filter out the ones from the current account and network + const otherAccountTxs = txs.filter((txMeta) => !(txMeta.txParams.from === address && txMeta.metamaskNetworkId === network)) + + // Update state + this._saveTxList(otherAccountTxs) + } // // PRIVATE METHODS // diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 962516af6..ad4e71792 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -43,6 +43,8 @@ module.exports = class MetamaskController extends EventEmitter { constructor (opts) { super() + this.defaultMaxListeners = 20 + this.sendUpdate = debounce(this.privateSendUpdate.bind(this), 200) this.opts = opts @@ -84,9 +86,7 @@ module.exports = class MetamaskController extends EventEmitter { }) this.infuraController.scheduleInfuraNetworkCheck() - this.blacklistController = new BlacklistController({ - initState: initState.BlacklistController, - }) + this.blacklistController = new BlacklistController() this.blacklistController.scheduleUpdates() // rpc provider @@ -198,12 +198,7 @@ module.exports = class MetamaskController extends EventEmitter { this.networkController.store.subscribe((state) => { this.store.updateState({ NetworkController: state }) }) - this.blacklistController.store.subscribe((state) => { - this.store.updateState({ BlacklistController: state }) - }) - this.recentBlocksController.store.subscribe((state) => { - this.store.updateState({ RecentBlocks: state }) - }) + this.infuraController.store.subscribe((state) => { this.store.updateState({ InfuraController: state }) }) @@ -315,6 +310,7 @@ module.exports = class MetamaskController extends EventEmitter { { lostAccounts: this.configManager.getLostAccounts(), seedWords: this.configManager.getSeedWords(), + forgottenPassword: this.configManager.getPasswordForgotten(), } ) } @@ -337,6 +333,8 @@ module.exports = class MetamaskController extends EventEmitter { setCurrentCurrency: this.setCurrentCurrency.bind(this), setUseBlockie: this.setUseBlockie.bind(this), markAccountsFound: this.markAccountsFound.bind(this), + markPasswordForgotten: this.markPasswordForgotten.bind(this), + unMarkPasswordForgotten: this.unMarkPasswordForgotten.bind(this), // coinbase buyEth: this.buyEth.bind(this), @@ -347,6 +345,7 @@ module.exports = class MetamaskController extends EventEmitter { addNewAccount: nodeify(this.addNewAccount, this), placeSeedWords: this.placeSeedWords.bind(this), clearSeedWordCache: this.clearSeedWordCache.bind(this), + resetAccount: this.resetAccount.bind(this), importAccountWithStrategy: this.importAccountWithStrategy.bind(this), // vault management @@ -453,7 +452,7 @@ module.exports = class MetamaskController extends EventEmitter { // create filter polyfill middleware const filterMiddleware = createFilterMiddleware({ provider: this.provider, - blockTracker: this.blockTracker, + blockTracker: this.provider._blockTracker, }) engine.push(createOriginMiddleware({ origin })) @@ -607,6 +606,13 @@ module.exports = class MetamaskController extends EventEmitter { cb(null, this.preferencesController.getSelectedAddress()) } + resetAccount (cb) { + const selectedAddress = this.preferencesController.getSelectedAddress() + this.txController.wipeTransactions(selectedAddress) + cb(null, selectedAddress) + } + + importAccountWithStrategy (strategy, args, cb) { accountImporter.importAccount(strategy, args) .then((privateKey) => { @@ -791,6 +797,18 @@ module.exports = class MetamaskController extends EventEmitter { cb(null, this.getState()) } + markPasswordForgotten(cb) { + this.configManager.setPasswordForgotten(true) + this.sendUpdate() + cb() + } + + unMarkPasswordForgotten(cb) { + this.configManager.setPasswordForgotten(false) + this.sendUpdate() + cb() + } + restoreOldVaultAccounts (migratorOutput) { const { serialized } = migratorOutput return this.keyringController.restoreKeyring(serialized) diff --git a/app/scripts/migrations/021.js b/app/scripts/migrations/021.js new file mode 100644 index 000000000..d84e77b50 --- /dev/null +++ b/app/scripts/migrations/021.js @@ -0,0 +1,34 @@ +const version = 21 + +/* + +This migration removes the BlackListController from disk state + +*/ + +const clone = require('clone') + +module.exports = { + version, + + migrate: function (originalVersionedData) { + const versionedData = clone(originalVersionedData) + versionedData.meta.version = version + try { + const state = versionedData.data + const newState = transformState(state) + versionedData.data = newState + } catch (err) { + console.warn(`MetaMask Migration #${version}` + err.stack) + } + return Promise.resolve(versionedData) + }, +} + +function transformState (state) { + const newState = state + delete newState.BlacklistController + delete newState.RecentBlocks + return newState +} + diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index 9d0631042..a0cf5f4d4 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -31,4 +31,5 @@ module.exports = [ require('./018'), require('./019'), require('./020'), + require('./021'), ] diff --git a/app/scripts/popup.js b/app/scripts/popup.js index 53ab00e00..11d50ee87 100644 --- a/app/scripts/popup.js +++ b/app/scripts/popup.js @@ -8,7 +8,7 @@ const extension = require('extensionizer') const ExtensionPlatform = require('./platforms/extension') const NotificationManager = require('./lib/notification-manager') const notificationManager = new NotificationManager() -const setupRaven = require('./setupRaven') +const setupRaven = require('./lib/setupRaven') // create platform global global.platform = new ExtensionPlatform() |