diff options
Merge branch 'develop' of github.com:MetaMask/metamask-extension into network-remove-provider-engine
Diffstat (limited to 'app')
-rw-r--r-- | app/_locales/en/messages.json | 23 | ||||
-rw-r--r-- | app/images/check-icon.svg | 17 | ||||
-rw-r--r-- | app/manifest.json | 5 | ||||
-rw-r--r-- | app/scripts/background.js | 14 | ||||
-rw-r--r-- | app/scripts/controllers/preferences.js | 80 | ||||
-rw-r--r-- | app/scripts/controllers/transactions/index.js | 8 | ||||
-rw-r--r-- | app/scripts/controllers/transactions/tx-state-manager.js | 21 | ||||
-rw-r--r-- | app/scripts/controllers/user-actions.js | 17 | ||||
-rw-r--r-- | app/scripts/lib/cleanErrorStack.js | 24 | ||||
-rw-r--r-- | app/scripts/lib/createErrorMiddleware.js | 1 | ||||
-rw-r--r-- | app/scripts/lib/diagnostics-reporter.js | 71 | ||||
-rw-r--r-- | app/scripts/lib/get-first-preferred-lang-code.js | 11 | ||||
-rw-r--r-- | app/scripts/metamask-controller.js | 39 | ||||
-rw-r--r-- | app/scripts/migrations/026.js | 2 |
14 files changed, 305 insertions, 28 deletions
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index fa01fea24..40d362f51 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -402,6 +402,9 @@ "infoHelp": { "message": "Info & Help" }, + "initialTransactionConfirmed": { + "message": "Your initial transaction was confirmed by the network. Click OK to go back." + }, "insufficientFunds": { "message": "Insufficient funds." }, @@ -520,6 +523,9 @@ "networks": { "message": "Networks" }, + "nevermind": { + "message": "Nevermind" + }, "newAccount": { "message": "New Account" }, @@ -634,9 +640,15 @@ "rejected": { "message": "Rejected" }, + "reset": { + "message": "Reset" + }, "resetAccount": { "message": "Reset Account" }, + "resetAccountDescription": { + "message": "Resetting your account will clear your transaction history." + }, "restoreFromSeed": { "message": "Restore account?" }, @@ -676,6 +688,9 @@ "ropsten": { "message": "Ropsten Test Network" }, + "rpc": { + "message": "Custom RPC" + }, "currentRpc": { "message": "Current RPC" }, @@ -701,10 +716,10 @@ "save": { "message": "Save" }, - "reprice_title": { - "message": "Reprice Transaction" + "speedUpTitle": { + "message": "Speed Up Transaction" }, - "reprice_subtitle": { + "speedUpSubtitle": { "message": "Increase your gas price to attempt to overwrite and speed up your transaction" }, "saveAsCsvFile": { @@ -891,7 +906,7 @@ "message": "Welcome to the New UI (Beta)" }, "uiWelcomeMessage": { - "message": "You are now using the new Metamask UI. Take a look around, try out new features like sending tokens, and let us know if you have any issues." + "message": "You are now using the new Metamask UI." }, "unapproved": { "message": "Unapproved" diff --git a/app/images/check-icon.svg b/app/images/check-icon.svg new file mode 100644 index 000000000..cafa864e5 --- /dev/null +++ b/app/images/check-icon.svg @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <!-- Generator: sketchtool 50.2 (55047) - http://www.bohemiancoding.com/sketch --> + <title>76BCDB09-52B0-41CB-908F-12F9087A2F1B</title> + <desc>Created with sketchtool.</desc> + <defs></defs> + <g id="Confirm-TX-screen" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> + <g id="confirmed-alert" transform="translate(-144.000000, -53.000000)" stroke="#61BA00" stroke-width="4"> + <g id="Group-17-Copy" transform="translate(22.000000, 20.000000)"> + <g id="check-icon" transform="translate(124.000000, 35.000000)"> + <circle id="Oval-5" cx="48" cy="48" r="48"></circle> + <polyline id="Path-3" stroke-linecap="round" points="29.76 52.8 41.0023819 64.32 71.04 34.56"></polyline> + </g> + </g> + </g> + </g> +</svg>
\ No newline at end of file diff --git a/app/manifest.json b/app/manifest.json index 3e5eed205..c1f26d2ea 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "4.6.0", + "version": "4.7.2", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", @@ -67,6 +67,9 @@ "externally_connectable": { "matches": [ "https://metamask.io/*" + ], + "ids": [ + "*" ] } }
\ No newline at end of file diff --git a/app/scripts/background.js b/app/scripts/background.js index 686296329..56e190f97 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -309,6 +309,7 @@ function setupController (initState, initLangCode) { // connect to other contexts // extension.runtime.onConnect.addListener(connectRemote) + extension.runtime.onConnectExternal.addListener(connectExternal) const metamaskInternalProcessHash = { [ENVIRONMENT_TYPE_POPUP]: true, @@ -335,9 +336,9 @@ function setupController (initState, initLangCode) { function connectRemote (remotePort) { const processName = remotePort.name const isMetaMaskInternalProcess = metamaskInternalProcessHash[processName] - const portStream = new PortStream(remotePort) if (isMetaMaskInternalProcess) { + const portStream = new PortStream(remotePort) // communication with popup controller.isClientOpen = true controller.setupTrustedCommunication(portStream, 'MetaMask') @@ -370,12 +371,17 @@ function setupController (initState, initLangCode) { }) } } else { - // communication with page - const originDomain = urlUtil.parse(remotePort.sender.url).hostname - controller.setupUntrustedCommunication(portStream, originDomain) + connectExternal(remotePort) } } + // communication with page or other extension + function connectExternal(remotePort) { + const originDomain = urlUtil.parse(remotePort.sender.url).hostname + const portStream = new PortStream(remotePort) + controller.setupUntrustedCommunication(portStream, originDomain) + } + // // User Interface setup // diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index a4ff1207e..a5d8cc27b 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -2,6 +2,7 @@ const ObservableStore = require('obs-store') const normalizeAddress = require('eth-sig-util').normalize const extend = require('xtend') + class PreferencesController { /** @@ -28,7 +29,11 @@ class PreferencesController { featureFlags: {}, currentLocale: opts.initLangCode, identities: {}, + lostIdentities: {}, }, opts.initState) + + this.diagnostics = opts.diagnostics + this.store = new ObservableStore(initState) } // PUBLIC METHODS @@ -63,6 +68,13 @@ class PreferencesController { this.store.updateState({ currentLocale: key }) } + /** + * Updates identities to only include specified addresses. Removes identities + * not included in addresses array + * + * @param {string[]} addresses An array of hex addresses + * + */ setAddresses (addresses) { const oldIdentities = this.store.getState().identities const identities = addresses.reduce((ids, address, index) => { @@ -74,6 +86,68 @@ class PreferencesController { } /** + * Adds addresses to the identities object without removing identities + * + * @param {string[]} addresses An array of hex addresses + * + */ + addAddresses (addresses) { + const identities = this.store.getState().identities + addresses.forEach((address) => { + // skip if already exists + if (identities[address]) return + // add missing identity + const identityCount = Object.keys(identities).length + identities[address] = { name: `Account ${identityCount + 1}`, address } + }) + this.store.updateState({ identities }) + } + + /* + * Synchronizes identity entries with known accounts. + * Removes any unknown identities, and returns the resulting selected address. + * + * @param {Array<string>} addresses known to the vault. + * @returns {Promise<string>} selectedAddress the selected address. + */ + syncAddresses (addresses) { + let { identities, lostIdentities } = this.store.getState() + + let newlyLost = {} + Object.keys(identities).forEach((identity) => { + if (!addresses.includes(identity)) { + newlyLost[identity] = identities[identity] + delete identities[identity] + } + }) + + // Identities are no longer present. + if (Object.keys(newlyLost).length > 0) { + + // Notify our servers: + if (this.diagnostics) this.diagnostics.reportOrphans(newlyLost) + + // store lost accounts + for (let key in newlyLost) { + lostIdentities[key] = newlyLost[key] + } + } + + this.store.updateState({ identities, lostIdentities }) + this.addAddresses(addresses) + + // If the selected account is no longer valid, + // select an arbitrary other account: + let selected = this.getSelectedAddress() + if (!addresses.includes(selected)) { + selected = addresses[0] + this.setSelectedAddress(selected) + } + + return selected + } + + /** * Setter for the `selectedAddress` property * * @param {string} _address A new hex address for an account @@ -111,7 +185,7 @@ class PreferencesController { /** * Adds a new token to the token array, or updates the token if passed an address that already exists. * Modifies the existing tokens array from the store. All objects in the tokens array array AddedToken objects. - * @see AddedToken {@link AddedToken} + * @see AddedToken {@link AddedToken} * * @param {string} rawAddress Hex address of the token contract. May or may not be a checksum address. * @param {string} symbol The symbol of the token @@ -197,7 +271,7 @@ class PreferencesController { } /** - * Setter for the `currentAccountTab` property + * Setter for the `currentAccountTab` property * * @param {string} currentAccountTab Specifies the new tab to be marked as current * @returns {Promise<void>} Promise resolves with undefined @@ -215,7 +289,7 @@ class PreferencesController { * The returned list will have a max length of 2. If the _url currently exists it the list, it will be moved to the * end of the list. The current list is modified and returned as a promise. * - * @param {string} _url The rpc url to add to the frequentRpcList. + * @param {string} _url The rpc url to add to the frequentRpcList. * @returns {Promise<array>} The updated frequentRpcList. * */ diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 71e7ea920..79241ed1a 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -8,6 +8,7 @@ const TxGasUtil = require('./tx-gas-utils') const PendingTransactionTracker = require('./pending-tx-tracker') const NonceTracker = require('./nonce-tracker') const txUtils = require('./lib/util') +const cleanErrorStack = require('../../lib/cleanErrorStack') const log = require('loglevel') /** @@ -123,6 +124,7 @@ class TransactionController extends EventEmitter { @param txParams {object} - txParams for the transaction @param opts {object} - with the key origin to put the origin on the txMeta */ + async newUnapprovedTransaction (txParams, opts = {}) { log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`) const initialTxMeta = await this.addUnapprovedTransaction(txParams) @@ -135,11 +137,11 @@ class TransactionController extends EventEmitter { case 'submitted': return resolve(finishedTxMeta.hash) case 'rejected': - return reject(new Error('MetaMask Tx Signature: User denied transaction signature.')) + return reject(cleanErrorStack(new Error('MetaMask Tx Signature: User denied transaction signature.'))) case 'failed': - return reject(new Error(finishedTxMeta.err.message)) + return reject(cleanErrorStack(new Error(finishedTxMeta.err.message))) default: - return reject(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`)) + return reject(cleanErrorStack(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`))) } }) }) diff --git a/app/scripts/controllers/transactions/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js index 00e837571..0aae4774b 100644 --- a/app/scripts/controllers/transactions/tx-state-manager.js +++ b/app/scripts/controllers/transactions/tx-state-manager.js @@ -2,6 +2,7 @@ const extend = require('xtend') const EventEmitter = require('events') const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') +const log = require('loglevel') const txStateHistoryHelper = require('./lib/tx-state-history-helper') const createId = require('../../lib/random-id') const { getFinalStates } = require('./lib/util') @@ -398,13 +399,19 @@ class TransactionStateManager extends EventEmitter { _setTxStatus (txId, status) { const txMeta = this.getTx(txId) txMeta.status = status - this.emit(`${txMeta.id}:${status}`, txId) - this.emit(`tx:status-update`, txId, status) - if (['submitted', 'rejected', 'failed'].includes(status)) { - this.emit(`${txMeta.id}:finished`, txMeta) - } - this.updateTx(txMeta, `txStateManager: setting status to ${status}`) - this.emit('update:badge') + setTimeout(() => { + try { + this.updateTx(txMeta, `txStateManager: setting status to ${status}`) + this.emit(`${txMeta.id}:${status}`, txId) + this.emit(`tx:status-update`, txId, status) + if (['submitted', 'rejected', 'failed'].includes(status)) { + this.emit(`${txMeta.id}:finished`, txMeta) + } + this.emit('update:badge') + } catch (error) { + log.error(error) + } + }) } /** diff --git a/app/scripts/controllers/user-actions.js b/app/scripts/controllers/user-actions.js new file mode 100644 index 000000000..f777054b8 --- /dev/null +++ b/app/scripts/controllers/user-actions.js @@ -0,0 +1,17 @@ +const MessageManager = require('./lib/message-manager') +const PersonalMessageManager = require('./lib/personal-message-manager') +const TypedMessageManager = require('./lib/typed-message-manager') + +class UserActionController { + + constructor (opts = {}) { + + this.messageManager = new MessageManager() + this.personalMessageManager = new PersonalMessageManager() + this.typedMessageManager = new TypedMessageManager() + + } + +} + +module.exports = UserActionController diff --git a/app/scripts/lib/cleanErrorStack.js b/app/scripts/lib/cleanErrorStack.js new file mode 100644 index 000000000..fe1bfb0ce --- /dev/null +++ b/app/scripts/lib/cleanErrorStack.js @@ -0,0 +1,24 @@ +/** + * Returns error without stack trace for better UI display + * @param {Error} err - error + * @returns {Error} Error with clean stack trace. + */ +function cleanErrorStack(err){ + var name = err.name + name = (name === undefined) ? 'Error' : String(name) + + var msg = err.message + msg = (msg === undefined) ? '' : String(msg) + + if (name === '') { + err.stack = err.message + } else if (msg === '') { + err.stack = err.name + } else { + err.stack = err.name + ': ' + err.message + } + + return err +} + +module.exports = cleanErrorStack diff --git a/app/scripts/lib/createErrorMiddleware.js b/app/scripts/lib/createErrorMiddleware.js index baed99e45..c70beddfd 100644 --- a/app/scripts/lib/createErrorMiddleware.js +++ b/app/scripts/lib/createErrorMiddleware.js @@ -59,6 +59,7 @@ function createErrorMiddleware ({ override = true } = {}) { if (!error) { return done() } sanitizeRPCError(error) log.error(`MetaMask - RPC Error: ${error.message}`, error) + done() }) } } diff --git a/app/scripts/lib/diagnostics-reporter.js b/app/scripts/lib/diagnostics-reporter.js new file mode 100644 index 000000000..aa4ca6e26 --- /dev/null +++ b/app/scripts/lib/diagnostics-reporter.js @@ -0,0 +1,71 @@ +class DiagnosticsReporter { + + constructor ({ firstTimeInfo, version }) { + this.firstTimeInfo = firstTimeInfo + this.version = version + } + + async reportOrphans(orphans) { + try { + return await this.submit({ + accounts: Object.keys(orphans), + metadata: { + type: 'orphans', + }, + }) + } catch (err) { + console.error('DiagnosticsReporter - "reportOrphans" encountered an error:') + console.error(err) + } + } + + async reportMultipleKeyrings(rawKeyrings) { + try { + const keyrings = await Promise.all(rawKeyrings.map(async (keyring, index) => { + return { + index, + type: keyring.type, + accounts: await keyring.getAccounts(), + } + })) + return await this.submit({ + accounts: [], + metadata: { + type: 'keyrings', + keyrings, + }, + }) + } catch (err) { + console.error('DiagnosticsReporter - "reportMultipleKeyrings" encountered an error:') + console.error(err) + } + } + + async submit (message) { + try { + // add metadata + message.metadata.version = this.version + message.metadata.firstTimeInfo = this.firstTimeInfo + return await postData(message) + } catch (err) { + console.error('DiagnosticsReporter - "submit" encountered an error:') + throw err + } + } + +} + +function postData(data) { + const uri = 'https://diagnostics.metamask.io/v1/orphanedAccounts' + return fetch(uri, { + body: JSON.stringify(data), // must match 'Content-Type' header + credentials: 'same-origin', // include, same-origin, *omit + headers: { + 'content-type': 'application/json', + }, + method: 'POST', // *GET, POST, PUT, DELETE, etc. + mode: 'cors', // no-cors, cors, *same-origin + }) +} + +module.exports = DiagnosticsReporter diff --git a/app/scripts/lib/get-first-preferred-lang-code.js b/app/scripts/lib/get-first-preferred-lang-code.js index 5473fccf0..1e6a83ba6 100644 --- a/app/scripts/lib/get-first-preferred-lang-code.js +++ b/app/scripts/lib/get-first-preferred-lang-code.js @@ -2,6 +2,12 @@ const extension = require('extensionizer') const promisify = require('pify') const allLocales = require('../../_locales/index.json') +const isSupported = extension.i18n && extension.i18n.getAcceptLanguages +const getPreferredLocales = isSupported ? promisify( + extension.i18n.getAcceptLanguages, + { errorFirst: false } +) : async () => [] + const existingLocaleCodes = allLocales.map(locale => locale.code.toLowerCase().replace('_', '-')) /** @@ -12,10 +18,7 @@ const existingLocaleCodes = allLocales.map(locale => locale.code.toLowerCase().r * */ async function getFirstPreferredLangCode () { - const userPreferredLocaleCodes = await promisify( - extension.i18n.getAcceptLanguages, - { errorFirst: false } - )() + const userPreferredLocaleCodes = await getPreferredLocales() const firstPreferredLangCode = userPreferredLocaleCodes .map(code => code.toLowerCase()) .find(code => existingLocaleCodes.includes(code)) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 2e74b3a20..b206c6b67 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -45,6 +45,8 @@ const BN = require('ethereumjs-util').BN const GWEI_BN = new BN('1000000000') const percentile = require('percentile') const seedPhraseVerifier = require('./lib/seed-phrase-verifier') +const cleanErrorStack = require('./lib/cleanErrorStack') +const DiagnosticsReporter = require('./lib/diagnostics-reporter') const log = require('loglevel') module.exports = class MetamaskController extends EventEmitter { @@ -63,6 +65,12 @@ module.exports = class MetamaskController extends EventEmitter { const initState = opts.initState || {} this.recordFirstTimeInfo(initState) + // metamask diagnostics reporter + this.diagnostics = opts.diagnostics || new DiagnosticsReporter({ + firstTimeInfo: initState.firstTimeInfo, + version, + }) + // platform-specific api this.platform = opts.platform @@ -84,6 +92,7 @@ module.exports = class MetamaskController extends EventEmitter { this.preferencesController = new PreferencesController({ initState: initState.PreferencesController, initLangCode: opts.initLangCode, + diagnostics: this.diagnostics, }) // currency controller @@ -139,6 +148,8 @@ module.exports = class MetamaskController extends EventEmitter { const address = addresses[0] this.preferencesController.setSelectedAddress(address) } + // ensure preferences + identities controller know about all addresses + this.preferencesController.addAddresses(addresses) this.accountTracker.syncWithAddresses(addresses) }) @@ -348,7 +359,7 @@ module.exports = class MetamaskController extends EventEmitter { importAccountWithStrategy: nodeify(this.importAccountWithStrategy, this), // vault management - submitPassword: nodeify(keyringController.submitPassword, keyringController), + submitPassword: nodeify(this.submitPassword, this), // network management setProviderType: nodeify(networkController.setProviderType, networkController), @@ -450,7 +461,11 @@ module.exports = class MetamaskController extends EventEmitter { async createNewVaultAndRestore (password, seed) { const release = await this.createVaultMutex.acquire() try { + // clear known identities + this.preferencesController.setAddresses([]) + // create new vault const vault = await this.keyringController.createNewVaultAndRestore(password, seed) + // set new identities const accounts = await this.keyringController.getAccounts() this.preferencesController.setAddresses(accounts) this.selectFirstIdentity() @@ -462,6 +477,28 @@ module.exports = class MetamaskController extends EventEmitter { } } + /* + * Submits the user's password and attempts to unlock the vault. + * Also synchronizes the preferencesController, to ensure its schema + * is up to date with known accounts once the vault is decrypted. + * + * @param {string} password - The user's password + * @returns {Promise<object>} - The keyringController update. + */ + async submitPassword (password) { + await this.keyringController.submitPassword(password) + const accounts = await this.keyringController.getAccounts() + + // verify keyrings + const nonSimpleKeyrings = this.keyringController.keyrings.filter(keyring => keyring.type !== 'Simple Key Pair') + if (nonSimpleKeyrings.length > 1 && this.diagnostics) { + await this.diagnostics.reportMultipleKeyrings(nonSimpleKeyrings) + } + + await this.preferencesController.syncAddresses(accounts) + return this.keyringController.fullUpdate() + } + /** * @type Identity * @property {string} name - The account nickname. diff --git a/app/scripts/migrations/026.js b/app/scripts/migrations/026.js index 1b8a91a45..4e907e09c 100644 --- a/app/scripts/migrations/026.js +++ b/app/scripts/migrations/026.js @@ -27,7 +27,7 @@ module.exports = { function transformState (state) { if (!state.KeyringController || !state.PreferencesController) { - return + return state } if (!state.KeyringController.walletNicknames) { |