aboutsummaryrefslogtreecommitdiffstats
path: root/app/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'app/scripts')
-rw-r--r--app/scripts/background.js37
-rw-r--r--app/scripts/controllers/transactions.js4
-rw-r--r--app/scripts/edge-encryptor.js69
-rw-r--r--app/scripts/lib/config-manager.js11
-rw-r--r--app/scripts/lib/nonce-tracker.js2
-rw-r--r--app/scripts/lib/reportFailedTxToSentry.js38
-rw-r--r--app/scripts/lib/setupMetamaskMeshMetrics.js9
-rw-r--r--app/scripts/lib/setupRaven.js (renamed from app/scripts/setupRaven.js)4
-rw-r--r--app/scripts/lib/tx-gas-utils.js5
-rw-r--r--app/scripts/lib/tx-state-manager.js11
-rw-r--r--app/scripts/metamask-controller.js38
-rw-r--r--app/scripts/migrations/021.js34
-rw-r--r--app/scripts/migrations/index.js1
-rw-r--r--app/scripts/popup.js2
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()