diff options
author | Frankie <frankie.diamond@gmail.com> | 2016-12-22 06:31:07 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-12-22 06:31:07 +0800 |
commit | 6f7c23fd28ef37ae51478b735c1f5888c97bbaf5 (patch) | |
tree | b927871ed00c33a66341a26de09f00cf006314b8 /app | |
parent | a85c691b71d5142d2412000930328fbe9161760a (diff) | |
parent | 73cdf0bfd49470bad1f0da4d0d894278c87af54e (diff) | |
download | tangerine-wallet-browser-6f7c23fd28ef37ae51478b735c1f5888c97bbaf5.tar tangerine-wallet-browser-6f7c23fd28ef37ae51478b735c1f5888c97bbaf5.tar.gz tangerine-wallet-browser-6f7c23fd28ef37ae51478b735c1f5888c97bbaf5.tar.bz2 tangerine-wallet-browser-6f7c23fd28ef37ae51478b735c1f5888c97bbaf5.tar.lz tangerine-wallet-browser-6f7c23fd28ef37ae51478b735c1f5888c97bbaf5.tar.xz tangerine-wallet-browser-6f7c23fd28ef37ae51478b735c1f5888c97bbaf5.tar.zst tangerine-wallet-browser-6f7c23fd28ef37ae51478b735c1f5888c97bbaf5.zip |
Merge branch 'dev' into TxManager
Diffstat (limited to 'app')
-rw-r--r-- | app/manifest.json | 2 | ||||
-rw-r--r-- | app/scripts/config.js | 3 | ||||
-rw-r--r-- | app/scripts/inpage.js | 18 | ||||
-rw-r--r-- | app/scripts/keyring-controller.js | 14 | ||||
-rw-r--r-- | app/scripts/keyrings/hd.js | 8 | ||||
-rw-r--r-- | app/scripts/lib/config-manager.js | 11 | ||||
-rw-r--r-- | app/scripts/lib/encryptor.js | 156 | ||||
-rw-r--r-- | app/scripts/lib/idStore-migrator.js | 43 | ||||
-rw-r--r-- | app/scripts/metamask-controller.js | 32 | ||||
-rw-r--r-- | app/scripts/notice-controller.js | 96 |
10 files changed, 191 insertions, 192 deletions
diff --git a/app/manifest.json b/app/manifest.json index b9d3735ad..61775ed93 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "2.13.10", + "version": "2.14.1", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", diff --git a/app/scripts/config.js b/app/scripts/config.js index 9a0f2a50b..e09206c5f 100644 --- a/app/scripts/config.js +++ b/app/scripts/config.js @@ -1,6 +1,5 @@ const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask' const TESTNET_RPC_URL = 'https://ropsten.infura.io/metamask' -const MORDEN_RPC_URL = 'https://morden.infura.io/metamask' const DEFAULT_RPC_URL = TESTNET_RPC_URL global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' @@ -11,6 +10,6 @@ module.exports = { default: DEFAULT_RPC_URL, mainnet: MAINET_RPC_URL, testnet: TESTNET_RPC_URL, - morden: MORDEN_RPC_URL, + morden: TESTNET_RPC_URL, }, } diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 68c6165c8..42332d92e 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -2,8 +2,8 @@ cleanContextForImports() require('web3/dist/web3.min.js') const LocalMessageDuplexStream = require('post-message-stream') -const PingStream = require('ping-pong-stream/ping') -const endOfStream = require('end-of-stream') +// const PingStream = require('ping-pong-stream/ping') +// const endOfStream = require('end-of-stream') const setupDappAutoReload = require('./lib/auto-reload.js') const MetamaskInpageProvider = require('./lib/inpage-provider.js') restoreContextAfterImports() @@ -40,13 +40,15 @@ reloadStream.once('data', triggerReload) // setup ping timeout autoreload // LocalMessageDuplexStream does not self-close, so reload if pingStream fails -var pingChannel = inpageProvider.multiStream.createStream('pingpong') -var pingStream = new PingStream({ objectMode: true }) +// var pingChannel = inpageProvider.multiStream.createStream('pingpong') +// var pingStream = new PingStream({ objectMode: true }) // wait for first successful reponse -metamaskStream.once('_data', function () { - pingStream.pipe(pingChannel).pipe(pingStream) -}) -endOfStream(pingStream, triggerReload) + +// disable pingStream until https://github.com/MetaMask/metamask-plugin/issues/746 is resolved more gracefully +// metamaskStream.once('data', function(){ +// pingStream.pipe(pingChannel).pipe(pingStream) +// }) +// endOfStream(pingStream, triggerReload) // set web3 defaultAcount inpageProvider.publicConfigStore.subscribe(function (state) { diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js index a58742228..6623419df 100644 --- a/app/scripts/keyring-controller.js +++ b/app/scripts/keyring-controller.js @@ -2,8 +2,9 @@ const ethUtil = require('ethereumjs-util') const bip39 = require('bip39') const EventEmitter = require('events').EventEmitter const filter = require('promise-filter') +const encryptor = require('browser-passworder') + const normalize = require('./lib/sig-util').normalize -const encryptor = require('./lib/encryptor') const messageManager = require('./lib/message-manager') const IdStoreMigrator = require('./lib/idStore-migrator') const BN = ethUtil.BN @@ -106,6 +107,7 @@ module.exports = class KeyringController extends EventEmitter { conversionDate: this.configManager.getConversionDate(), keyringTypes: this.keyringTypes.map(krt => krt.type), identities: this.identities, + lostAccounts: this.configManager.getLostAccounts(), } } @@ -430,11 +432,17 @@ module.exports = class KeyringController extends EventEmitter { // may be completed without interruption. migrateOldVaultIfAny (password) { const shouldMigrate = !!this.configManager.getWallet() && !this.configManager.getVault() + if (!shouldMigrate) { + return Promise.resolve() + } + return this.idStoreMigrator.migratedVaultForPassword(password) - .then((serialized) => { + .then((result) => { this.password = password - if (serialized && shouldMigrate) { + if (result && shouldMigrate) { + const { serialized, lostAccounts } = result + this.configManager.setLostAccounts(lostAccounts) return this.restoreKeyring(serialized) .then(keyring => keyring.getAccounts()) .then((accounts) => { diff --git a/app/scripts/keyrings/hd.js b/app/scripts/keyrings/hd.js index cfec56561..80b713b58 100644 --- a/app/scripts/keyrings/hd.js +++ b/app/scripts/keyrings/hd.js @@ -33,15 +33,15 @@ class HdKeyring extends EventEmitter { this.mnemonic = null this.root = null - if ('mnemonic' in opts) { + if (opts.mnemonic) { this._initFromMnemonic(opts.mnemonic) } - if ('numberOfAccounts' in opts) { - this.addAccounts(opts.numberOfAccounts) + if (opts.numberOfAccounts) { + return this.addAccounts(opts.numberOfAccounts) } - return Promise.resolve() + return Promise.resolve([]) } addAccounts (numberOfAccounts = 1) { diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index a32842cc7..c723655fa 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -380,3 +380,14 @@ ConfigManager.prototype.setGasMultiplier = function (gasMultiplier) { data.gasMultiplier = gasMultiplier this.setData(data) } + +ConfigManager.prototype.setLostAccounts = function (lostAccounts) { + var data = this.getData() + data.lostAccounts = lostAccounts + this.setData(data) +} + +ConfigManager.prototype.getLostAccounts = function () { + var data = this.getData() + return data.lostAccounts || [] +} diff --git a/app/scripts/lib/encryptor.js b/app/scripts/lib/encryptor.js deleted file mode 100644 index 4770d2f54..000000000 --- a/app/scripts/lib/encryptor.js +++ /dev/null @@ -1,156 +0,0 @@ -module.exports = { - - // Simple encryption methods: - encrypt, - decrypt, - - // More advanced encryption methods: - keyFromPassword, - encryptWithKey, - decryptWithKey, - - // Buffer <-> String methods - convertArrayBufferViewtoString, - convertStringToArrayBufferView, - - // Buffer <-> Hex string methods - serializeBufferForStorage, - serializeBufferFromStorage, - - // Buffer <-> base64 string methods - encodeBufferToBase64, - decodeBase64ToBuffer, - - generateSalt, -} - -// Takes a Pojo, returns cypher text. -function encrypt (password, dataObj) { - const salt = this.generateSalt() - - return keyFromPassword(password + salt) - .then(function (passwordDerivedKey) { - return encryptWithKey(passwordDerivedKey, dataObj) - }) - .then(function (payload) { - payload.salt = salt - return JSON.stringify(payload) - }) -} - -function encryptWithKey (key, dataObj) { - var data = JSON.stringify(dataObj) - var dataBuffer = convertStringToArrayBufferView(data) - var vector = global.crypto.getRandomValues(new Uint8Array(16)) - return global.crypto.subtle.encrypt({ - name: 'AES-GCM', - iv: vector, - }, key, dataBuffer).then(function (buf) { - var buffer = new Uint8Array(buf) - var vectorStr = encodeBufferToBase64(vector) - var vaultStr = encodeBufferToBase64(buffer) - return { - data: vaultStr, - iv: vectorStr, - } - }) -} - -// Takes encrypted text, returns the restored Pojo. -function decrypt (password, text) { - const payload = JSON.parse(text) - const salt = payload.salt - return keyFromPassword(password + salt) - .then(function (key) { - return decryptWithKey(key, payload) - }) -} - -function decryptWithKey (key, payload) { - const encryptedData = decodeBase64ToBuffer(payload.data) - const vector = decodeBase64ToBuffer(payload.iv) - return crypto.subtle.decrypt({name: 'AES-GCM', iv: vector}, key, encryptedData) - .then(function (result) { - const decryptedData = new Uint8Array(result) - const decryptedStr = convertArrayBufferViewtoString(decryptedData) - const decryptedObj = JSON.parse(decryptedStr) - return decryptedObj - }) - .catch(function (reason) { - throw new Error('Incorrect password') - }) -} - -function convertStringToArrayBufferView (str) { - var bytes = new Uint8Array(str.length) - for (var i = 0; i < str.length; i++) { - bytes[i] = str.charCodeAt(i) - } - - return bytes -} - -function convertArrayBufferViewtoString (buffer) { - var str = '' - for (var i = 0; i < buffer.byteLength; i++) { - str += String.fromCharCode(buffer[i]) - } - - return str -} - -function keyFromPassword (password) { - var passBuffer = convertStringToArrayBufferView(password) - return global.crypto.subtle.digest('SHA-256', passBuffer) - .then(function (passHash) { - return global.crypto.subtle.importKey('raw', passHash, {name: 'AES-GCM'}, false, ['encrypt', 'decrypt']) - }) -} - -function serializeBufferFromStorage (str) { - var stripStr = (str.slice(0, 2) === '0x') ? str.slice(2) : str - var buf = new Uint8Array(stripStr.length / 2) - for (var i = 0; i < stripStr.length; i += 2) { - var seg = stripStr.substr(i, 2) - buf[i / 2] = parseInt(seg, 16) - } - return buf -} - -// Should return a string, ready for storage, in hex format. -function serializeBufferForStorage (buffer) { - var result = '0x' - var len = buffer.length || buffer.byteLength - for (var i = 0; i < len; i++) { - result += unprefixedHex(buffer[i]) - } - return result -} - -function unprefixedHex (num) { - var hex = num.toString(16) - while (hex.length < 2) { - hex = '0' + hex - } - return hex -} - -function encodeBufferToBase64 (buf) { - var b64encoded = btoa(String.fromCharCode.apply(null, buf)) - return b64encoded -} - -function decodeBase64ToBuffer (base64) { - var buf = new Uint8Array(atob(base64).split('') - .map(function (c) { - return c.charCodeAt(0) - })) - return buf -} - -function generateSalt (byteCount = 32) { - var view = new Uint8Array(byteCount) - global.crypto.getRandomValues(view) - var b64encoded = btoa(String.fromCharCode.apply(null, view)) - return b64encoded -} diff --git a/app/scripts/lib/idStore-migrator.js b/app/scripts/lib/idStore-migrator.js index 40b08efee..2e9418376 100644 --- a/app/scripts/lib/idStore-migrator.js +++ b/app/scripts/lib/idStore-migrator.js @@ -1,5 +1,8 @@ const IdentityStore = require('./idStore') - +const HdKeyring = require('../keyrings/hd') +const sigUtil = require('./sig-util') +const normalize = sigUtil.normalize +const denodeify = require('denodeify') module.exports = class IdentityStoreMigrator { @@ -23,15 +26,13 @@ module.exports = class IdentityStoreMigrator { return Promise.resolve(null) } - return new Promise((resolve, reject) => { - this.idStore.submitPassword(password, (err) => { - if (err) return reject(err) - try { - resolve(this.serializeVault()) - } catch (e) { - reject(e) - } - }) + const idStore = this.idStore + const submitPassword = denodeify(idStore.submitPassword.bind(idStore)) + + return submitPassword(password) + .then(() => { + const serialized = this.serializeVault() + return this.checkForLostAccounts(serialized) }) } @@ -45,6 +46,28 @@ module.exports = class IdentityStoreMigrator { } } + checkForLostAccounts (serialized) { + const hd = new HdKeyring() + return hd.deserialize(serialized.data) + .then((hexAccounts) => { + const newAccounts = hexAccounts.map(normalize) + const oldAccounts = this.idStore._getAddresses().map(normalize) + const lostAccounts = oldAccounts.reduce((result, account) => { + if (newAccounts.includes(account)) { + return result + } else { + result.push(account) + return result + } + }, []) + + return { + serialized, + lostAccounts, + } + }) + } + hasOldVault () { const wallet = this.configManager.getWallet() return wallet diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 7798a6a60..125753063 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -2,6 +2,7 @@ const extend = require('xtend') const EthStore = require('eth-store') const MetaMaskProvider = require('web3-provider-engine/zero.js') const KeyringController = require('./keyring-controller') +const NoticeController = require('./notice-controller') const messageManager = require('./lib/message-manager') const TxManager = require('./transaction-manager') const HostStore = require('./lib/remote-store.js').HostStore @@ -23,6 +24,13 @@ module.exports = class MetamaskController { txManager: this.txManager, getNetwork: this.getStateNetwork.bind(this), }) + // notices + this.noticeController = new NoticeController({ + configManager: this.configManager, + }) + this.noticeController.updateNoticesList() + // to be uncommented when retrieving notices from a remote server. + // this.noticeController.startPolling() this.provider = this.initializeProvider(opts) this.ethStore = new EthStore(this.provider) this.keyringController.setStore(this.ethStore) @@ -57,12 +65,15 @@ module.exports = class MetamaskController { this.configManager.getConfig(), this.keyringController.getState(), this.txManager.getState() + this.noticeController.getState() ) } getApi () { const keyringController = this.keyringController const txManager = this.txManager + const noticeController = this.noticeController + return { getState: (cb) => { cb(null, this.getState()) }, setRpcTarget: this.setRpcTarget.bind(this), @@ -101,6 +112,9 @@ module.exports = class MetamaskController { buyEth: this.buyEth.bind(this), // shapeshift createShapeShiftTx: this.createShapeShiftTx.bind(this), + // notices + checkNotices: noticeController.updateNoticesList.bind(noticeController), + markNoticeRead: noticeController.markNoticeRead.bind(noticeController), } } @@ -292,7 +306,7 @@ module.exports = class MetamaskController { setTOSHash (hash) { try { this.configManager.setTOSHash(hash) - } catch (e) { + } catch (err) { console.error('Error in setting terms of service hash.') } } @@ -304,17 +318,19 @@ module.exports = class MetamaskController { this.resetDisclaimer() this.setTOSHash(global.TOS_HASH) } - } catch (e) { + } catch (err) { console.error('Error in checking TOS change.') } } + // disclaimer + agreeToDisclaimer (cb) { try { this.configManager.setConfirmedDisclaimer(true) cb() - } catch (e) { - cb(e) + } catch (err) { + cb(err) } } @@ -337,8 +353,8 @@ module.exports = class MetamaskController { conversionDate: this.configManager.getConversionDate(), } cb(data) - } catch (e) { - cb(null, e) + } catch (err) { + cb(null, err) } } @@ -411,8 +427,8 @@ module.exports = class MetamaskController { try { this.configManager.setGasMultiplier(gasMultiplier) cb() - } catch (e) { - cb(e) + } catch (err) { + cb(err) } } diff --git a/app/scripts/notice-controller.js b/app/scripts/notice-controller.js new file mode 100644 index 000000000..00c87c670 --- /dev/null +++ b/app/scripts/notice-controller.js @@ -0,0 +1,96 @@ +const EventEmitter = require('events').EventEmitter +const hardCodedNotices = require('../../development/notices.json') + +module.exports = class NoticeController extends EventEmitter { + + constructor (opts) { + super() + this.configManager = opts.configManager + this.noticePoller = null + } + + getState () { + var lastUnreadNotice = this.getLatestUnreadNotice() + + return { + lastUnreadNotice: lastUnreadNotice, + noActiveNotices: !lastUnreadNotice, + } + } + + getNoticesList () { + var data = this.configManager.getData() + if ('noticesList' in data) { + return data.noticesList + } else { + return [] + } + } + + setNoticesList (list) { + var data = this.configManager.getData() + data.noticesList = list + this.configManager.setData(data) + return Promise.resolve(true) + } + + markNoticeRead (notice, cb) { + cb = cb || function (err) { if (err) throw err } + try { + var notices = this.getNoticesList() + var id = notice.id + notices[id].read = true + this.setNoticesList(notices) + const latestNotice = this.getLatestUnreadNotice() + cb(null, latestNotice) + } catch (err) { + cb(err) + } + } + + updateNoticesList () { + return this._retrieveNoticeData().then((newNotices) => { + var oldNotices = this.getNoticesList() + var combinedNotices = this._mergeNotices(oldNotices, newNotices) + return Promise.resolve(this.setNoticesList(combinedNotices)) + }) + } + + getLatestUnreadNotice () { + var notices = this.getNoticesList() + var filteredNotices = notices.filter((notice) => { + return notice.read === false + }) + return filteredNotices[filteredNotices.length - 1] + } + + startPolling () { + if (this.noticePoller) { + clearInterval(this.noticePoller) + } + this.noticePoller = setInterval(() => { + this.noticeController.updateNoticesList() + }, 300000) + } + + _mergeNotices (oldNotices, newNotices) { + var noticeMap = this._mapNoticeIds(oldNotices) + newNotices.forEach((notice) => { + if (noticeMap.indexOf(notice.id) === -1) { + oldNotices.push(notice) + } + }) + return oldNotices + } + + _mapNoticeIds (notices) { + return notices.map((notice) => notice.id) + } + + _retrieveNoticeData () { + // Placeholder for the API. + return Promise.resolve(hardCodedNotices) + } + + +} |