diff options
Merge branch 'master' into i18n-translator-redux
Diffstat (limited to 'app/scripts')
-rw-r--r-- | app/scripts/README.md | 14 | ||||
-rw-r--r-- | app/scripts/background.js | 9 | ||||
-rw-r--r-- | app/scripts/controllers/README.md | 4 | ||||
-rw-r--r-- | app/scripts/lib/notification-manager.js | 11 | ||||
-rw-r--r-- | app/scripts/metamask-controller.js | 599 | ||||
-rw-r--r-- | app/scripts/migrations/README.md | 5 | ||||
-rw-r--r-- | app/scripts/popup.js | 1 |
7 files changed, 392 insertions, 251 deletions
diff --git a/app/scripts/README.md b/app/scripts/README.md new file mode 100644 index 000000000..f5a907244 --- /dev/null +++ b/app/scripts/README.md @@ -0,0 +1,14 @@ +# Main MetaMask Code + +This folder contains the core-code. + +Currently, it is organized mostly based on file category, like: + +controllers, migrations, lib + +## Ongoing Task + +Refactor code-structure, thus the subsystems are reflected on the filesystem. + +### Examples + diff --git a/app/scripts/background.js b/app/scripts/background.js index ef5513ec7..8bd7766ad 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -42,6 +42,7 @@ const isIE = !!document.documentMode const isEdge = !isIE && !!window.StyleMedia let popupIsOpen = false +let notificationIsOpen = false let openMetamaskTabsIDs = {} // state persistence @@ -165,6 +166,11 @@ function setupController (initState) { } }) } + if (remotePort.name === 'notification') { + endOfStream(portStream, () => { + notificationIsOpen = false + }) + } } else { // communication with page const originDomain = urlUtil.parse(remotePort.sender.url).hostname @@ -207,7 +213,8 @@ function setupController (initState) { function triggerUi () { extension.tabs.query({ active: true }, (tabs) => { const currentlyActiveMetamaskTab = tabs.find(tab => openMetamaskTabsIDs[tab.id]) - if (!popupIsOpen && !currentlyActiveMetamaskTab) notificationManager.showPopup() + if (!popupIsOpen && !currentlyActiveMetamaskTab && !notificationIsOpen) notificationManager.showPopup() + notificationIsOpen = true }) } diff --git a/app/scripts/controllers/README.md b/app/scripts/controllers/README.md new file mode 100644 index 000000000..392c0457d --- /dev/null +++ b/app/scripts/controllers/README.md @@ -0,0 +1,4 @@ +# Controllers + +Different controllers (in the sense of *VC *View-Controller). + diff --git a/app/scripts/lib/notification-manager.js b/app/scripts/lib/notification-manager.js index adaf60c65..1fcb7cf69 100644 --- a/app/scripts/lib/notification-manager.js +++ b/app/scripts/lib/notification-manager.js @@ -13,11 +13,12 @@ class NotificationManager { this._getPopup((err, popup) => { if (err) throw err + // Bring focus to chrome popup if (popup) { - // bring focus to existing popup + // bring focus to existing chrome popup extension.windows.update(popup.id, { focused: true }) } else { - // create new popup + // create new notification popup extension.windows.create({ url: 'notification.html', type: 'popup', @@ -29,6 +30,7 @@ class NotificationManager { } closePopup () { + // closes notification popup this._getPopup((err, popup) => { if (err) throw err if (!popup) return @@ -60,9 +62,8 @@ class NotificationManager { _getPopupIn (windows) { return windows ? windows.find((win) => { - return (win && win.type === 'popup' && - win.height === height && - win.width === width) + // Returns notification popup + return (win && win.type === 'popup') }) : null } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 4ff08e029..bd092cea0 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1,3 +1,9 @@ +/** + * @file The central metamask controller. Aggregates other controllers and exports an api. + * @copyright Copyright (c) 2018 MetaMask + * @license MIT + */ + const EventEmitter = require('events') const extend = require('xtend') const pump = require('pump') @@ -41,7 +47,11 @@ const seedPhraseVerifier = require('./lib/seed-phrase-verifier') module.exports = class MetamaskController extends EventEmitter { - constructor (opts) { + /** + * @constructor + * @param {Object} opts + */ + constructor (opts) { super() this.defaultMaxListeners = 20 @@ -223,10 +233,9 @@ module.exports = class MetamaskController extends EventEmitter { this.infuraController.store.subscribe(sendUpdate) } - // - // Constructor helpers - // - + /** + * Constructor helper: initialize a provider. + */ initializeProvider () { const providerOpts = { static: { @@ -257,6 +266,9 @@ module.exports = class MetamaskController extends EventEmitter { return providerProxy } + /** + * Constructor helper: initialize a public config store. + */ initPublicConfigStore () { // get init state const publicConfigStore = new ObservableStore() @@ -278,10 +290,15 @@ module.exports = class MetamaskController extends EventEmitter { return publicConfigStore } - // - // State Management - // +//============================================================================= +// EXPOSED TO THE UI SUBSYSTEM +//============================================================================= + /** + * The metamask-state of the various controllers, made available to the UI + * + * @returns {Object} status + */ getState () { const wallet = this.configManager.getWallet() const vault = this.keyringController.store.getState().vault @@ -316,10 +333,11 @@ module.exports = class MetamaskController extends EventEmitter { ) } - // - // Remote Features - // - + /** + * Returns an api-object which is consumed by the UI + * + * @returns {Object} + */ getApi () { const keyringController = this.keyringController const preferencesController = this.preferencesController @@ -401,127 +419,24 @@ module.exports = class MetamaskController extends EventEmitter { } } - setupUntrustedCommunication (connectionStream, originDomain) { - // Check if new connection is blacklisted - if (this.blacklistController.checkForPhishing(originDomain)) { - log.debug('MetaMask - sending phishing warning for', originDomain) - this.sendPhishingWarning(connectionStream, originDomain) - return - } - - // setup multiplexing - const mux = setupMultiplex(connectionStream) - // connect features - this.setupProviderConnection(mux.createStream('provider'), originDomain) - this.setupPublicConfig(mux.createStream('publicConfig')) - } - - setupTrustedCommunication (connectionStream, originDomain) { - // setup multiplexing - const mux = setupMultiplex(connectionStream) - // connect features - this.setupControllerConnection(mux.createStream('controller')) - this.setupProviderConnection(mux.createStream('provider'), originDomain) - } - - sendPhishingWarning (connectionStream, hostname) { - const mux = setupMultiplex(connectionStream) - const phishingStream = mux.createStream('phishing') - phishingStream.write({ hostname }) - } - - setupControllerConnection (outStream) { - const api = this.getApi() - const dnode = Dnode(api) - pump( - outStream, - dnode, - outStream, - (err) => { - if (err) log.error(err) - } - ) - dnode.on('remote', (remote) => { - // push updates to popup - const sendUpdate = remote.sendUpdate.bind(remote) - this.on('update', sendUpdate) - }) - } - - setupProviderConnection (outStream, origin) { - // setup json rpc engine stack - const engine = new RpcEngine() - - // create filter polyfill middleware - const filterMiddleware = createFilterMiddleware({ - provider: this.provider, - blockTracker: this.provider._blockTracker, - }) - - engine.push(createOriginMiddleware({ origin })) - engine.push(createLoggerMiddleware({ origin })) - engine.push(filterMiddleware) - engine.push(createProviderMiddleware({ provider: this.provider })) - - // setup connection - const providerStream = createEngineStream({ engine }) - pump( - outStream, - providerStream, - outStream, - (err) => { - // cleanup filter polyfill middleware - filterMiddleware.destroy() - if (err) log.error(err) - } - ) - } - - setupPublicConfig (outStream) { - pump( - asStream(this.publicConfigStore), - outStream, - (err) => { - if (err) log.error(err) - } - ) - } - privateSendUpdate () { - this.emit('update', this.getState()) - } - getGasPrice () { - const { recentBlocksController } = this - const { recentBlocks } = recentBlocksController.store.getState() - - // Return 1 gwei if no blocks have been observed: - if (recentBlocks.length === 0) { - return '0x' + GWEI_BN.toString(16) - } - - const lowestPrices = recentBlocks.map((block) => { - if (!block.gasPrices || block.gasPrices.length < 1) { - 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(number => number.div(GWEI_BN).toNumber()) - - const percentileNum = percentile(50, lowestPrices) - const percentileNumBn = new BN(percentileNum) - return '0x' + percentileNumBn.mul(GWEI_BN).toString(16) - } - - // - // Vault Management - // +//============================================================================= +// VAULT / KEYRING RELATED METHODS +//============================================================================= + /** + * Creates a new Vault(?) and create a new keychain(?) + * + * A vault is ... + * + * A keychain is ... + * + * + * @param {} password + * + * @returns {} vault + */ async createNewVaultAndKeychain (password) { const release = await this.createVaultMutex.acquire() let vault @@ -545,6 +460,11 @@ module.exports = class MetamaskController extends EventEmitter { return vault } + /** + * Create a new Vault and restore an existent keychain + * @param {} password + * @param {} seed + */ async createNewVaultAndRestore (password, seed) { const release = await this.createVaultMutex.acquire() try { @@ -558,16 +478,28 @@ module.exports = class MetamaskController extends EventEmitter { } } + /** + * Retrieves the first Identiy from the passed Vault and selects the related address + * + * An Identity is ... + * + * @param {} vault + */ selectFirstIdentity (vault) { const { identities } = vault const address = Object.keys(identities)[0] this.preferencesController.setSelectedAddress(address) } - // + // ? // Opinionated Keyring Management // + /** + * Adds a new account to ... + * + * @returns {} keyState + */ async addNewAccount () { const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0] if (!primaryKeyring) { @@ -589,10 +521,12 @@ module.exports = class MetamaskController extends EventEmitter { return keyState } - // 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. + /** + * 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. + */ placeSeedWords (cb) { this.verifySeedPhrase() @@ -605,10 +539,13 @@ module.exports = class MetamaskController extends EventEmitter { }) } - // Verifies the current vault's seed words if they can restore the - // accounts belonging to the current vault. - // - // Called when the first account is created and on unlocking the vault. + /** + * Verifies the validity of the current vault's seed phrase. + * + * Validity: seed phrase restores the accounts belonging to the current vault. + * + * Called when the first account is created and on unlocking the vault. + */ async verifySeedPhrase () { const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0] @@ -633,22 +570,33 @@ module.exports = class MetamaskController extends EventEmitter { } } - // ClearSeedWordCache - // - // Removes the primary account's seed words from the UI's state tree, - // ensuring they are only ever available in the background process. + /** + * Remove the primary account seed phrase from the UI's state tree. + * + * The seed phrase remains available in the background process. + * + */ clearSeedWordCache (cb) { this.configManager.setSeedWords(null) cb(null, this.preferencesController.getSelectedAddress()) } - + + /** + * ? + */ resetAccount (cb) { const selectedAddress = this.preferencesController.getSelectedAddress() this.txController.wipeTransactions(selectedAddress) cb(null, selectedAddress) } - + /** + * Imports an account ... ? + * + * @param {} strategy + * @param {} args + * @param {} cb + */ importAccountWithStrategy (strategy, args, cb) { accountImporter.importAccount(strategy, args) .then((privateKey) => { @@ -660,11 +608,150 @@ module.exports = class MetamaskController extends EventEmitter { .catch((reason) => { cb(reason) }) } + // --------------------------------------------------------------------------- + // Identity Management (sign) - // - // Identity Management - // - // + /** + * @param {} msgParams + * @param {} cb + */ + signMessage (msgParams, cb) { + log.info('MetaMaskController - signMessage') + const msgId = msgParams.metamaskId + + // sets the status op the message to 'approved' + // and removes the metamaskId for signing + return this.messageManager.approveMessage(msgParams) + .then((cleanMsgParams) => { + // signs the message + 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() + }) + } + + // Prefixed Style Message Signing Methods: + + /** + * + * @param {} msgParams + * @param {} cb + */ + approvePersonalMessage (msgParams, cb) { + const msgId = this.personalMessageManager.addUnapprovedMessage(msgParams) + this.sendUpdate() + this.opts.showUnconfirmedMessage() + this.personalMessageManager.once(`${msgId}:finished`, (data) => { + switch (data.status) { + case 'signed': + return cb(null, data.rawSig) + case 'rejected': + return cb(new Error('MetaMask Message Signature: User denied transaction signature.')) + default: + return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) + } + }) + } + + /** + * @param {} msgParams + */ + signPersonalMessage (msgParams) { + log.info('MetaMaskController - signPersonalMessage') + const msgId = msgParams.metamaskId + // sets the status op the message to 'approved' + // and removes the metamaskId for signing + return this.personalMessageManager.approveMessage(msgParams) + .then((cleanMsgParams) => { + // signs the message + 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() + }) + } + + /** + * @param {} msgParams + */ + signTypedMessage (msgParams) { + log.info('MetaMaskController - signTypedMessage') + const msgId = msgParams.metamaskId + // sets the status op the message to 'approved' + // and removes the metamaskId for signing + return this.typedMessageManager.approveMessage(msgParams) + .then((cleanMsgParams) => { + // signs the message + return this.keyringController.signTypedMessage(cleanMsgParams) + }) + .then((rawSig) => { + // tells the listener that the message has been signed + // and can be returned to the dapp + this.typedMessageManager.setMsgStatusSigned(msgId, rawSig) + return this.getState() + }) + } + + // --------------------------------------------------------------------------- + // Account Restauration + + /** + * ? + * + * @param {} migratorOutput + */ + restoreOldVaultAccounts (migratorOutput) { + const { serialized } = migratorOutput + return this.keyringController.restoreKeyring(serialized) + .then(() => migratorOutput) + } + + /** + * ? + * + * @param {} migratorOutput + */ + restoreOldLostAccounts (migratorOutput) { + const { lostAccounts } = migratorOutput + if (lostAccounts) { + this.configManager.setLostAccounts(lostAccounts.map(acct => acct.address)) + return this.importLostAccounts(migratorOutput) + } + return Promise.resolve(migratorOutput) + } + + /** + * Import (lost) Accounts + * + * @param {Object} {lostAccounts} @Array accounts <{ address, privateKey }> + * + * Uses the array's private keys to create a new Simple Key Pair keychain + * and add it to the keyring controller. + */ + importLostAccounts ({ lostAccounts }) { + const privKeys = lostAccounts.map(acct => acct.privateKey) + return this.keyringController.restoreKeyring({ + type: 'Simple Key Pair', + data: privKeys, + }) + } + +//============================================================================= +// END (VAULT / KEYRING RELATED METHODS) +//============================================================================= + +// + +//============================================================================= +// MESSAGES +//============================================================================= async retryTransaction (txId, cb) { await this.txController.retryTransaction(txId) @@ -731,85 +818,13 @@ module.exports = class MetamaskController extends EventEmitter { }) } - signMessage (msgParams, cb) { - log.info('MetaMaskController - signMessage') - const msgId = msgParams.metamaskId - - // sets the status op the message to 'approved' - // and removes the metamaskId for signing - return this.messageManager.approveMessage(msgParams) - .then((cleanMsgParams) => { - // signs the message - 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() - }) - } - cancelMessage (msgId, cb) { const messageManager = this.messageManager messageManager.rejectMsg(msgId) if (cb && typeof cb === 'function') { cb(null, this.getState()) } - } - - // Prefixed Style Message Signing Methods: - approvePersonalMessage (msgParams, cb) { - const msgId = this.personalMessageManager.addUnapprovedMessage(msgParams) - this.sendUpdate() - this.opts.showUnconfirmedMessage() - this.personalMessageManager.once(`${msgId}:finished`, (data) => { - switch (data.status) { - case 'signed': - return cb(null, data.rawSig) - case 'rejected': - return cb(new Error('MetaMask Message Signature: User denied transaction signature.')) - default: - return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) - } - }) - } - - signPersonalMessage (msgParams) { - log.info('MetaMaskController - signPersonalMessage') - const msgId = msgParams.metamaskId - // sets the status op the message to 'approved' - // and removes the metamaskId for signing - return this.personalMessageManager.approveMessage(msgParams) - .then((cleanMsgParams) => { - // signs the message - 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() - }) - } - - signTypedMessage (msgParams) { - log.info('MetaMaskController - signTypedMessage') - const msgId = msgParams.metamaskId - // sets the status op the message to 'approved' - // and removes the metamaskId for signing - return this.typedMessageManager.approveMessage(msgParams) - .then((cleanMsgParams) => { - // signs the message - return this.keyringController.signTypedMessage(cleanMsgParams) - }) - .then((rawSig) => { - // tells the listener that the message has been signed - // and can be returned to the dapp - this.typedMessageManager.setMsgStatusSigned(msgId, rawSig) - return this.getState() - }) - } + } cancelPersonalMessage (msgId, cb) { const messageManager = this.personalMessageManager @@ -845,36 +860,130 @@ module.exports = class MetamaskController extends EventEmitter { cb() } - restoreOldVaultAccounts (migratorOutput) { - const { serialized } = migratorOutput - return this.keyringController.restoreKeyring(serialized) - .then(() => migratorOutput) - } +//============================================================================= +// SETUP +//============================================================================= - restoreOldLostAccounts (migratorOutput) { - const { lostAccounts } = migratorOutput - if (lostAccounts) { - this.configManager.setLostAccounts(lostAccounts.map(acct => acct.address)) - return this.importLostAccounts(migratorOutput) + setupUntrustedCommunication (connectionStream, originDomain) { + // Check if new connection is blacklisted + if (this.blacklistController.checkForPhishing(originDomain)) { + log.debug('MetaMask - sending phishing warning for', originDomain) + this.sendPhishingWarning(connectionStream, originDomain) + return } - return Promise.resolve(migratorOutput) + + // setup multiplexing + const mux = setupMultiplex(connectionStream) + // connect features + this.setupProviderConnection(mux.createStream('provider'), originDomain) + this.setupPublicConfig(mux.createStream('publicConfig')) } - // IMPORT LOST ACCOUNTS - // @Object with key lostAccounts: @Array accounts <{ address, privateKey }> - // Uses the array's private keys to create a new Simple Key Pair keychain - // and add it to the keyring controller. - importLostAccounts ({ lostAccounts }) { - const privKeys = lostAccounts.map(acct => acct.privateKey) - return this.keyringController.restoreKeyring({ - type: 'Simple Key Pair', - data: privKeys, + setupTrustedCommunication (connectionStream, originDomain) { + // setup multiplexing + const mux = setupMultiplex(connectionStream) + // connect features + this.setupControllerConnection(mux.createStream('controller')) + this.setupProviderConnection(mux.createStream('provider'), originDomain) + } + + sendPhishingWarning (connectionStream, hostname) { + const mux = setupMultiplex(connectionStream) + const phishingStream = mux.createStream('phishing') + phishingStream.write({ hostname }) + } + + setupControllerConnection (outStream) { + const api = this.getApi() + const dnode = Dnode(api) + pump( + outStream, + dnode, + outStream, + (err) => { + if (err) log.error(err) + } + ) + dnode.on('remote', (remote) => { + // push updates to popup + const sendUpdate = remote.sendUpdate.bind(remote) + this.on('update', sendUpdate) }) } - // - // config - // + setupProviderConnection (outStream, origin) { + // setup json rpc engine stack + const engine = new RpcEngine() + + // create filter polyfill middleware + const filterMiddleware = createFilterMiddleware({ + provider: this.provider, + blockTracker: this.provider._blockTracker, + }) + + engine.push(createOriginMiddleware({ origin })) + engine.push(createLoggerMiddleware({ origin })) + engine.push(filterMiddleware) + engine.push(createProviderMiddleware({ provider: this.provider })) + + // setup connection + const providerStream = createEngineStream({ engine }) + pump( + outStream, + providerStream, + outStream, + (err) => { + // cleanup filter polyfill middleware + filterMiddleware.destroy() + if (err) log.error(err) + } + ) + } + + setupPublicConfig (outStream) { + pump( + asStream(this.publicConfigStore), + outStream, + (err) => { + if (err) log.error(err) + } + ) + } + + privateSendUpdate () { + this.emit('update', this.getState()) + } + + getGasPrice () { + const { recentBlocksController } = this + const { recentBlocks } = recentBlocksController.store.getState() + + // Return 1 gwei if no blocks have been observed: + if (recentBlocks.length === 0) { + return '0x' + GWEI_BN.toString(16) + } + + const lowestPrices = recentBlocks.map((block) => { + if (!block.gasPrices || block.gasPrices.length < 1) { + 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(number => number.div(GWEI_BN).toNumber()) + + const percentileNum = percentile(50, lowestPrices) + const percentileNumBn = new BN(percentileNum) + return '0x' + percentileNumBn.mul(GWEI_BN).toString(16) + } + +//============================================================================= +// CONFIG +//============================================================================= // Log blocks diff --git a/app/scripts/migrations/README.md b/app/scripts/migrations/README.md new file mode 100644 index 000000000..3a67b08e1 --- /dev/null +++ b/app/scripts/migrations/README.md @@ -0,0 +1,5 @@ +# Migrations + +Data (user data, config files etc.) is migrated from one version to another. + +Migrations are called by {} from {} during {}.
\ No newline at end of file diff --git a/app/scripts/popup.js b/app/scripts/popup.js index 6ef0be1ff..13c7ac5ec 100644 --- a/app/scripts/popup.js +++ b/app/scripts/popup.js @@ -69,6 +69,7 @@ async function start() { function closePopupIfOpen (windowType) { if (windowType !== 'notification') { + // should close only chrome popup notificationManager.closePopup() } } |