diff options
Diffstat (limited to 'app/scripts/lib')
-rw-r--r-- | app/scripts/lib/config-manager.js | 156 | ||||
-rw-r--r-- | app/scripts/lib/controllers/currency.js | 70 | ||||
-rw-r--r-- | app/scripts/lib/controllers/preferences.js | 33 | ||||
-rw-r--r-- | app/scripts/lib/controllers/shapeshift.js | 104 | ||||
-rw-r--r-- | app/scripts/lib/eth-store.js | 220 | ||||
-rw-r--r-- | app/scripts/lib/idStore.js | 4 | ||||
-rw-r--r-- | app/scripts/lib/inpage-provider.js | 79 | ||||
-rw-r--r-- | app/scripts/lib/message-manager.js | 151 | ||||
-rw-r--r-- | app/scripts/lib/migrations.js | 5 | ||||
-rw-r--r-- | app/scripts/lib/migrator/index.js | 51 | ||||
-rw-r--r-- | app/scripts/lib/remote-store.js | 97 | ||||
-rw-r--r-- | app/scripts/lib/stream-utils.js | 9 |
12 files changed, 524 insertions, 455 deletions
diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index e927c78ec..7ae2d4400 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -1,6 +1,4 @@ -const Migrator = require('pojo-migrator') const MetamaskConfig = require('../config.js') -const migrations = require('./migrations') const ethUtil = require('ethereumjs-util') const normalize = require('./sig-util').normalize @@ -19,50 +17,19 @@ module.exports = ConfigManager function ConfigManager (opts) { // ConfigManager is observable and will emit updates this._subs = [] - - /* The migrator exported on the config-manager - * has two methods the user should be concerned with: - * - * getData(), which returns the app-consumable data object - * saveData(), which persists the app-consumable data object. - */ - this.migrator = new Migrator({ - - // Migrations must start at version 1 or later. - // They are objects with a `version` number - // and a `migrate` function. - // - // The `migrate` function receives the previous - // config data format, and returns the new one. - migrations: migrations, - - // How to load initial config. - // Includes step on migrating pre-pojo-migrator data. - loadData: opts.loadData, - - // How to persist migrated config. - setData: opts.setData, - }) + this.store = opts.store } ConfigManager.prototype.setConfig = function (config) { - var data = this.migrator.getData() + var data = this.getData() data.config = config this.setData(data) this._emitUpdates(config) } ConfigManager.prototype.getConfig = function () { - var data = this.migrator.getData() - if ('config' in data) { - return data.config - } else { - return { - provider: { - type: 'testnet', - }, - } - } + var data = this.getData() + return data.config } ConfigManager.prototype.setRpcTarget = function (rpcUrl) { @@ -96,15 +63,15 @@ ConfigManager.prototype.getProvider = function () { } ConfigManager.prototype.setData = function (data) { - this.migrator.saveData(data) + this.store.putState(data) } ConfigManager.prototype.getData = function () { - return this.migrator.getData() + return this.store.getState() } ConfigManager.prototype.setWallet = function (wallet) { - var data = this.migrator.getData() + var data = this.getData() data.wallet = wallet this.setData(data) } @@ -121,11 +88,11 @@ ConfigManager.prototype.getVault = function () { } ConfigManager.prototype.getKeychains = function () { - return this.migrator.getData().keychains || [] + return this.getData().keychains || [] } ConfigManager.prototype.setKeychains = function (keychains) { - var data = this.migrator.getData() + var data = this.getData() data.keychains = keychains this.setData(data) } @@ -142,19 +109,19 @@ ConfigManager.prototype.setSelectedAccount = function (address) { } ConfigManager.prototype.getWallet = function () { - return this.migrator.getData().wallet + return this.getData().wallet } // Takes a boolean ConfigManager.prototype.setShowSeedWords = function (should) { - var data = this.migrator.getData() + var data = this.getData() data.showSeedWords = should this.setData(data) } ConfigManager.prototype.getShouldShowSeedWords = function () { - var data = this.migrator.getData() + var data = this.getData() return data.showSeedWords } @@ -166,7 +133,7 @@ ConfigManager.prototype.setSeedWords = function (words) { ConfigManager.prototype.getSeedWords = function () { var data = this.getData() - return ('seedWords' in data) && data.seedWords + return data.seedWords } ConfigManager.prototype.getCurrentRpcAddress = function () { @@ -188,16 +155,12 @@ ConfigManager.prototype.getCurrentRpcAddress = function () { } } -ConfigManager.prototype.setData = function (data) { - this.migrator.saveData(data) -} - // // Tx // ConfigManager.prototype.getTxList = function () { - var data = this.migrator.getData() + var data = this.getData() if (data.transactions !== undefined) { return data.transactions } else { @@ -206,7 +169,7 @@ ConfigManager.prototype.getTxList = function () { } ConfigManager.prototype.setTxList = function (txList) { - var data = this.migrator.getData() + var data = this.getData() data.transactions = txList this.setData(data) } @@ -239,7 +202,7 @@ ConfigManager.prototype.setNicknameForWallet = function (account, nickname) { ConfigManager.prototype.getSalt = function () { var data = this.getData() - return ('salt' in data) && data.salt + return data.salt } ConfigManager.prototype.setSalt = function (salt) { @@ -273,7 +236,7 @@ ConfigManager.prototype.setConfirmedDisclaimer = function (confirmed) { ConfigManager.prototype.getConfirmedDisclaimer = function () { var data = this.getData() - return ('isDisclaimerConfirmed' in data) && data.isDisclaimerConfirmed + return data.isDisclaimerConfirmed } ConfigManager.prototype.setTOSHash = function (hash) { @@ -284,93 +247,12 @@ ConfigManager.prototype.setTOSHash = function (hash) { ConfigManager.prototype.getTOSHash = function () { var data = this.getData() - return ('TOSHash' in data) && data.TOSHash -} - -ConfigManager.prototype.setCurrentFiat = function (currency) { - var data = this.getData() - data.fiatCurrency = currency - this.setData(data) -} - -ConfigManager.prototype.getCurrentFiat = function () { - var data = this.getData() - return ('fiatCurrency' in data) && data.fiatCurrency -} - -ConfigManager.prototype.updateConversionRate = function () { - var data = this.getData() - return fetch(`https://www.cryptonator.com/api/ticker/eth-${data.fiatCurrency}`) - .then(response => response.json()) - .then((parsedResponse) => { - this.setConversionPrice(parsedResponse.ticker.price) - this.setConversionDate(parsedResponse.timestamp) - }).catch((err) => { - console.warn('MetaMask - Failed to query currency conversion.') - this.setConversionPrice(0) - this.setConversionDate('N/A') - }) -} - -ConfigManager.prototype.setConversionPrice = function (price) { - var data = this.getData() - data.conversionRate = Number(price) - this.setData(data) -} - -ConfigManager.prototype.setConversionDate = function (datestring) { - var data = this.getData() - data.conversionDate = datestring - this.setData(data) -} - -ConfigManager.prototype.getConversionRate = function () { - var data = this.getData() - return (('conversionRate' in data) && data.conversionRate) || 0 -} - -ConfigManager.prototype.getConversionDate = function () { - var data = this.getData() - return (('conversionDate' in data) && data.conversionDate) || 'N/A' -} - -ConfigManager.prototype.getShapeShiftTxList = function () { - var data = this.getData() - var shapeShiftTxList = data.shapeShiftTxList ? data.shapeShiftTxList : [] - shapeShiftTxList.forEach((tx) => { - if (tx.response.status !== 'complete') { - var requestListner = function (request) { - tx.response = JSON.parse(this.responseText) - if (tx.response.status === 'complete') { - tx.time = new Date().getTime() - } - } - - var shapShiftReq = new XMLHttpRequest() - shapShiftReq.addEventListener('load', requestListner) - shapShiftReq.open('GET', `https://shapeshift.io/txStat/${tx.depositAddress}`, true) - shapShiftReq.send() - } - }) - this.setData(data) - return shapeShiftTxList -} - -ConfigManager.prototype.createShapeShiftTx = function (depositAddress, depositType) { - var data = this.getData() - - var shapeShiftTx = {depositAddress, depositType, key: 'shapeshift', time: new Date().getTime(), response: {}} - if (!data.shapeShiftTxList) { - data.shapeShiftTxList = [shapeShiftTx] - } else { - data.shapeShiftTxList.push(shapeShiftTx) - } - this.setData(data) + return data.TOSHash } ConfigManager.prototype.getGasMultiplier = function () { var data = this.getData() - return ('gasMultiplier' in data) && data.gasMultiplier + return data.gasMultiplier } ConfigManager.prototype.setGasMultiplier = function (gasMultiplier) { diff --git a/app/scripts/lib/controllers/currency.js b/app/scripts/lib/controllers/currency.js new file mode 100644 index 000000000..c4904f8ac --- /dev/null +++ b/app/scripts/lib/controllers/currency.js @@ -0,0 +1,70 @@ +const ObservableStore = require('obs-store') +const extend = require('xtend') + +// every ten minutes +const POLLING_INTERVAL = 600000 + +class CurrencyController { + + constructor (opts = {}) { + const initState = extend({ + currentCurrency: 'USD', + conversionRate: 0, + conversionDate: 'N/A', + }, opts.initState) + this.store = new ObservableStore(initState) + } + + // + // PUBLIC METHODS + // + + getCurrentCurrency () { + return this.store.getState().currentCurrency + } + + setCurrentCurrency (currentCurrency) { + this.store.updateState({ currentCurrency }) + } + + getConversionRate () { + return this.store.getState().conversionRate + } + + setConversionRate (conversionRate) { + this.store.updateState({ conversionRate }) + } + + getConversionDate () { + return this.store.getState().conversionDate + } + + setConversionDate (conversionDate) { + this.store.updateState({ conversionDate }) + } + + updateConversionRate () { + const currentCurrency = this.getCurrentCurrency() + return fetch(`https://www.cryptonator.com/api/ticker/eth-${currentCurrency}`) + .then(response => response.json()) + .then((parsedResponse) => { + this.setConversionRate(Number(parsedResponse.ticker.price)) + this.setConversionDate(Number(parsedResponse.timestamp)) + }).catch((err) => { + console.warn('MetaMask - Failed to query currency conversion.') + this.setConversionRate(0) + this.setConversionDate('N/A') + }) + } + + scheduleConversionInterval () { + if (this.conversionInterval) { + clearInterval(this.conversionInterval) + } + this.conversionInterval = setInterval(() => { + this.updateConversionRate() + }, POLLING_INTERVAL) + } +} + +module.exports = CurrencyController diff --git a/app/scripts/lib/controllers/preferences.js b/app/scripts/lib/controllers/preferences.js new file mode 100644 index 000000000..dc9464c4e --- /dev/null +++ b/app/scripts/lib/controllers/preferences.js @@ -0,0 +1,33 @@ +const ObservableStore = require('obs-store') +const normalizeAddress = require('../sig-util').normalize + +class PreferencesController { + + constructor (opts = {}) { + const initState = opts.initState || {} + this.store = new ObservableStore(initState) + } + + // + // PUBLIC METHODS + // + + setSelectedAddress(_address) { + return new Promise((resolve, reject) => { + const address = normalizeAddress(_address) + this.store.updateState({ selectedAddress: address }) + resolve() + }) + } + + getSelectedAddress(_address) { + return this.store.getState().selectedAddress + } + + // + // PRIVATE METHODS + // + +} + +module.exports = PreferencesController diff --git a/app/scripts/lib/controllers/shapeshift.js b/app/scripts/lib/controllers/shapeshift.js new file mode 100644 index 000000000..3d955c01f --- /dev/null +++ b/app/scripts/lib/controllers/shapeshift.js @@ -0,0 +1,104 @@ +const ObservableStore = require('obs-store') +const extend = require('xtend') + +// every three seconds when an incomplete tx is waiting +const POLLING_INTERVAL = 3000 + +class ShapeshiftController { + + constructor (opts = {}) { + const initState = extend({ + shapeShiftTxList: [], + }, opts.initState) + this.store = new ObservableStore(initState) + this.pollForUpdates() + } + + // + // PUBLIC METHODS + // + + getShapeShiftTxList () { + const shapeShiftTxList = this.store.getState().shapeShiftTxList + return shapeShiftTxList + } + + getPendingTxs () { + const txs = this.getShapeShiftTxList() + const pending = txs.filter(tx => tx.response && tx.response.status !== 'complete') + return pending + } + + pollForUpdates () { + const pendingTxs = this.getPendingTxs() + + if (pendingTxs.length === 0) { + return + } + + Promise.all(pendingTxs.map((tx) => { + return this.updateTx(tx) + })) + .then((results) => { + results.forEach(tx => this.saveTx(tx)) + this.timeout = setTimeout(this.pollForUpdates.bind(this), POLLING_INTERVAL) + }) + } + + updateTx (tx) { + const url = `https://shapeshift.io/txStat/${tx.depositAddress}` + return fetch(url) + .then((response) => { + return response.json() + }).then((json) => { + tx.response = json + if (tx.response.status === 'complete') { + tx.time = new Date().getTime() + } + return tx + }) + } + + saveTx (tx) { + const { shapeShiftTxList } = this.store.getState() + const index = shapeShiftTxList.indexOf(tx) + if (index !== -1) { + shapeShiftTxList[index] = tx + this.store.updateState({ shapeShiftTxList }) + } + } + + removeShapeShiftTx (tx) { + const { shapeShiftTxList } = this.store.getState() + const index = shapeShiftTxList.indexOf(index) + if (index !== -1) { + shapeShiftTxList.splice(index, 1) + } + this.updateState({ shapeShiftTxList }) + } + + createShapeShiftTx (depositAddress, depositType) { + const state = this.store.getState() + let { shapeShiftTxList } = state + + var shapeShiftTx = { + depositAddress, + depositType, + key: 'shapeshift', + time: new Date().getTime(), + response: {}, + } + + if (!shapeShiftTxList) { + shapeShiftTxList = [shapeShiftTx] + } else { + shapeShiftTxList.push(shapeShiftTx) + } + + this.store.updateState({ shapeShiftTxList }) + this.pollForUpdates() + } + +} + +module.exports = ShapeshiftController diff --git a/app/scripts/lib/eth-store.js b/app/scripts/lib/eth-store.js index 7e2caf884..8812a507b 100644 --- a/app/scripts/lib/eth-store.js +++ b/app/scripts/lib/eth-store.js @@ -7,140 +7,126 @@ * on each new block. */ -const EventEmitter = require('events').EventEmitter -const inherits = require('util').inherits const async = require('async') -const clone = require('clone') const EthQuery = require('eth-query') - -module.exports = EthereumStore +const ObservableStore = require('obs-store') +function noop() {} -inherits(EthereumStore, EventEmitter) -function EthereumStore(engine) { - const self = this - EventEmitter.call(self) - self._currentState = { - accounts: {}, - transactions: {}, +class EthereumStore extends ObservableStore { + + constructor (opts = {}) { + super({ + accounts: {}, + transactions: {}, + }) + this._provider = opts.provider + this._query = new EthQuery(this._provider) + this._blockTracker = opts.blockTracker + // subscribe to latest block + this._blockTracker.on('block', this._updateForBlock.bind(this)) + // blockTracker.currentBlock may be null + this._currentBlockNumber = this._blockTracker.currentBlock } - self._query = new EthQuery(engine) - - engine.on('block', self._updateForBlock.bind(self)) -} - -// -// public -// - -EthereumStore.prototype.getState = function () { - const self = this - return clone(self._currentState) -} -EthereumStore.prototype.addAccount = function (address) { - const self = this - self._currentState.accounts[address] = {} - self._didUpdate() - if (!self.currentBlockNumber) return - self._updateAccount(address, () => { - self._didUpdate() - }) -} + // + // public + // -EthereumStore.prototype.removeAccount = function (address) { - const self = this - delete self._currentState.accounts[address] - self._didUpdate() -} + addAccount (address) { + const accounts = this.getState().accounts + accounts[address] = {} + this.updateState({ accounts }) + if (!this._currentBlockNumber) return + this._updateAccount(address) + } -EthereumStore.prototype.addTransaction = function (txHash) { - const self = this - self._currentState.transactions[txHash] = {} - self._didUpdate() - if (!self.currentBlockNumber) return - self._updateTransaction(self.currentBlockNumber, txHash, noop) -} + removeAccount (address) { + const accounts = this.getState().accounts + delete accounts[address] + this.updateState({ accounts }) + } -EthereumStore.prototype.removeTransaction = function (address) { - const self = this - delete self._currentState.transactions[address] - self._didUpdate() -} + addTransaction (txHash) { + const transactions = this.getState().transactions + transactions[txHash] = {} + this.updateState({ transactions }) + if (!this._currentBlockNumber) return + this._updateTransaction(this._currentBlockNumber, txHash, noop) + } + removeTransaction (txHash) { + const transactions = this.getState().transactions + delete transactions[txHash] + this.updateState({ transactions }) + } -// -// private -// -EthereumStore.prototype._didUpdate = function () { - const self = this - var state = self.getState() - self.emit('update', state) -} + // + // private + // + + _updateForBlock (block) { + const blockNumber = '0x' + block.number.toString('hex') + this._currentBlockNumber = blockNumber + async.parallel([ + this._updateAccounts.bind(this), + this._updateTransactions.bind(this, blockNumber), + ], (err) => { + if (err) return console.error(err) + this.emit('block', this.getState()) + }) + } -EthereumStore.prototype._updateForBlock = function (block) { - const self = this - var blockNumber = '0x' + block.number.toString('hex') - self.currentBlockNumber = blockNumber - async.parallel([ - self._updateAccounts.bind(self), - self._updateTransactions.bind(self, blockNumber), - ], function (err) { - if (err) return console.error(err) - self.emit('block', self.getState()) - self._didUpdate() - }) -} + _updateAccounts (cb = noop) { + const accounts = this.getState().accounts + const addresses = Object.keys(accounts) + async.each(addresses, this._updateAccount.bind(this), cb) + } -EthereumStore.prototype._updateAccounts = function (cb) { - var accountsState = this._currentState.accounts - var addresses = Object.keys(accountsState) - async.each(addresses, this._updateAccount.bind(this), cb) -} + _updateAccount (address, cb = noop) { + const accounts = this.getState().accounts + this._getAccount(address, (err, result) => { + if (err) return cb(err) + result.address = address + // only populate if the entry is still present + if (accounts[address]) { + accounts[address] = result + this.updateState({ accounts }) + } + cb(null, result) + }) + } -EthereumStore.prototype._updateAccount = function (address, cb) { - var accountsState = this._currentState.accounts - this.getAccount(address, function (err, result) { - if (err) return cb(err) - result.address = address - // only populate if the entry is still present - if (accountsState[address]) { - accountsState[address] = result - } - cb(null, result) - }) -} + _updateTransactions (block, cb = noop) { + const transactions = this.getState().transactions + const txHashes = Object.keys(transactions) + async.each(txHashes, this._updateTransaction.bind(this, block), cb) + } -EthereumStore.prototype.getAccount = function (address, cb) { - const query = this._query - async.parallel({ - balance: query.getBalance.bind(query, address), - nonce: query.getTransactionCount.bind(query, address), - code: query.getCode.bind(query, address), - }, cb) -} + _updateTransaction (block, txHash, cb = noop) { + // would use the block here to determine how many confirmations the tx has + const transactions = this.getState().transactions + this._query.getTransaction(txHash, (err, result) => { + if (err) return cb(err) + // only populate if the entry is still present + if (transactions[txHash]) { + transactions[txHash] = result + this.updateState({ transactions }) + } + cb(null, result) + }) + } -EthereumStore.prototype._updateTransactions = function (block, cb) { - const self = this - var transactionsState = self._currentState.transactions - var txHashes = Object.keys(transactionsState) - async.each(txHashes, self._updateTransaction.bind(self, block), cb) -} + _getAccount (address, cb = noop) { + const query = this._query + async.parallel({ + balance: query.getBalance.bind(query, address), + nonce: query.getTransactionCount.bind(query, address), + code: query.getCode.bind(query, address), + }, cb) + } -EthereumStore.prototype._updateTransaction = function (block, txHash, cb) { - const self = this - // would use the block here to determine how many confirmations the tx has - var transactionsState = self._currentState.transactions - self._query.getTransaction(txHash, function (err, result) { - if (err) return cb(err) - // only populate if the entry is still present - if (transactionsState[txHash]) { - transactionsState[txHash] = result - self._didUpdate() - } - cb(null, result) - }) } -function noop() {} +module.exports = EthereumStore
\ No newline at end of file diff --git a/app/scripts/lib/idStore.js b/app/scripts/lib/idStore.js index e4cbca456..1afe5f651 100644 --- a/app/scripts/lib/idStore.js +++ b/app/scripts/lib/idStore.js @@ -96,10 +96,6 @@ IdentityStore.prototype.getState = function () { seedWords: seedWords, isDisclaimerConfirmed: configManager.getConfirmedDisclaimer(), selectedAddress: configManager.getSelectedAccount(), - shapeShiftTxList: configManager.getShapeShiftTxList(), - currentFiat: configManager.getCurrentFiat(), - conversionRate: configManager.getConversionRate(), - conversionDate: configManager.getConversionDate(), gasMultiplier: configManager.getGasMultiplier(), })) } diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js index 11bd5cc3a..faecac137 100644 --- a/app/scripts/lib/inpage-provider.js +++ b/app/scripts/lib/inpage-provider.js @@ -1,7 +1,7 @@ -const Streams = require('mississippi') +const pipe = require('pump') const StreamProvider = require('web3-stream-provider') +const LocalStorageStore = require('obs-store') const ObjectMultiplex = require('./obj-multiplex') -const RemoteStore = require('./remote-store.js').RemoteStore const createRandomId = require('./random-id') module.exports = MetamaskInpageProvider @@ -10,33 +10,30 @@ function MetamaskInpageProvider (connectionStream) { const self = this // setup connectionStream multiplexing - var multiStream = ObjectMultiplex() - Streams.pipe(connectionStream, multiStream, connectionStream, function (err) { - let warningMsg = 'MetamaskInpageProvider - lost connection to MetaMask' - if (err) warningMsg += '\n' + err.stack - console.warn(warningMsg) - }) - self.multiStream = multiStream - - // subscribe to metamask public config - var publicConfigStore = remoteStoreWithLocalStorageCache('MetaMask-Config') - var storeStream = publicConfigStore.createStream() - Streams.pipe(storeStream, multiStream.createStream('publicConfig'), storeStream, function (err) { - let warningMsg = 'MetamaskInpageProvider - lost connection to MetaMask publicConfig' - if (err) warningMsg += '\n' + err.stack - console.warn(warningMsg) - }) - self.publicConfigStore = publicConfigStore + var multiStream = self.multiStream = ObjectMultiplex() + pipe( + connectionStream, + multiStream, + connectionStream, + (err) => logStreamDisconnectWarning('MetaMask', err) + ) + + // subscribe to metamask public config (one-way) + self.publicConfigStore = new LocalStorageStore({ storageKey: 'MetaMask-Config' }) + pipe( + multiStream.createStream('publicConfig'), + self.publicConfigStore, + (err) => logStreamDisconnectWarning('MetaMask PublicConfigStore', err) + ) // connect to async provider - var asyncProvider = new StreamProvider() - Streams.pipe(asyncProvider, multiStream.createStream('provider'), asyncProvider, function (err) { - let warningMsg = 'MetamaskInpageProvider - lost connection to MetaMask provider' - if (err) warningMsg += '\n' + err.stack - console.warn(warningMsg) - }) - asyncProvider.on('error', console.error.bind(console)) - self.asyncProvider = asyncProvider + const asyncProvider = self.asyncProvider = new StreamProvider() + pipe( + asyncProvider, + multiStream.createStream('provider'), + asyncProvider, + (err) => logStreamDisconnectWarning('MetaMask RpcProvider', err) + ) self.idMap = {} // handle sendAsync requests via asyncProvider @@ -66,20 +63,20 @@ function MetamaskInpageProvider (connectionStream) { MetamaskInpageProvider.prototype.send = function (payload) { const self = this - let selectedAccount + let selectedAddress let result = null switch (payload.method) { case 'eth_accounts': // read from localStorage - selectedAccount = self.publicConfigStore.get('selectedAccount') - result = selectedAccount ? [selectedAccount] : [] + selectedAddress = self.publicConfigStore.getState().selectedAddress + result = selectedAddress ? [selectedAddress] : [] break case 'eth_coinbase': // read from localStorage - selectedAccount = self.publicConfigStore.get('selectedAccount') - result = selectedAccount || '0x0000000000000000000000000000000000000000' + selectedAddress = self.publicConfigStore.getState().selectedAddress + result = selectedAddress break case 'eth_uninstallFilter': @@ -115,18 +112,6 @@ MetamaskInpageProvider.prototype.isMetaMask = true // util -function remoteStoreWithLocalStorageCache (storageKey) { - // read local cache - var initState = JSON.parse(localStorage[storageKey] || '{}') - var store = new RemoteStore(initState) - // cache the latest state locally - store.subscribe(function (state) { - localStorage[storageKey] = JSON.stringify(state) - }) - - return store -} - function eachJsonMessage (payload, transformFn) { if (Array.isArray(payload)) { return payload.map(transformFn) @@ -135,4 +120,10 @@ function eachJsonMessage (payload, transformFn) { } } +function logStreamDisconnectWarning(remoteLabel, err){ + let warningMsg = `MetamaskInpageProvider - lost connection to ${remoteLabel}` + if (err) warningMsg += '\n' + err.stack + console.warn(warningMsg) +} + function noop () {} diff --git a/app/scripts/lib/message-manager.js b/app/scripts/lib/message-manager.js index b609b820e..ceaf8ee2f 100644 --- a/app/scripts/lib/message-manager.js +++ b/app/scripts/lib/message-manager.js @@ -1,61 +1,118 @@ -module.exports = new MessageManager() +const EventEmitter = require('events') +const ObservableStore = require('obs-store') +const ethUtil = require('ethereumjs-util') +const createId = require('./random-id') -function MessageManager (opts) { - this.messages = [] -} -MessageManager.prototype.getMsgList = function () { - return this.messages -} +module.exports = class MessageManager extends EventEmitter{ + constructor (opts) { + super() + this.memStore = new ObservableStore({ + unapprovedMsgs: {}, + unapprovedMsgCount: 0, + }) + this.messages = [] + } -MessageManager.prototype.unconfirmedMsgs = function () { - var messages = this.getMsgList() - return messages.filter(msg => msg.status === 'unconfirmed') - .reduce((result, msg) => { result[msg.id] = msg; return result }, {}) -} + get unapprovedMsgCount () { + return Object.keys(this.getUnapprovedMsgs()).length + } -MessageManager.prototype._saveMsgList = function (msgList) { - this.messages = msgList -} + getUnapprovedMsgs () { + return this.messages.filter(msg => msg.status === 'unapproved') + .reduce((result, msg) => { result[msg.id] = msg; return result }, {}) + } -MessageManager.prototype.addMsg = function (msg) { - var messages = this.getMsgList() - messages.push(msg) - this._saveMsgList(messages) -} + addUnapprovedMessage (msgParams) { + msgParams.data = normalizeMsgData(msgParams.data) + // create txData obj with parameters and meta data + var time = (new Date()).getTime() + var msgId = createId() + var msgData = { + id: msgId, + msgParams: msgParams, + time: time, + status: 'unapproved', + } + this.addMsg(msgData) -MessageManager.prototype.getMsg = function (msgId) { - var messages = this.getMsgList() - var matching = messages.filter(msg => msg.id === msgId) - return matching.length > 0 ? matching[0] : null -} + // signal update + this.emit('update') + return msgId + } -MessageManager.prototype.confirmMsg = function (msgId) { - this._setMsgStatus(msgId, 'confirmed') -} + addMsg (msg) { + this.messages.push(msg) + this._saveMsgList() + } -MessageManager.prototype.rejectMsg = function (msgId) { - this._setMsgStatus(msgId, 'rejected') -} + getMsg (msgId) { + return this.messages.find(msg => msg.id === msgId) + } -MessageManager.prototype._setMsgStatus = function (msgId, status) { - var msg = this.getMsg(msgId) - if (msg) msg.status = status - this.updateMsg(msg) -} + approveMessage (msgParams) { + this.setMsgStatusApproved(msgParams.metamaskId) + return this.prepMsgForSigning(msgParams) + } -MessageManager.prototype.updateMsg = function (msg) { - var messages = this.getMsgList() - var found, index - messages.forEach((otherMsg, i) => { - if (otherMsg.id === msg.id) { - found = true - index = i + setMsgStatusApproved (msgId) { + this._setMsgStatus(msgId, 'approved') + } + + setMsgStatusSigned (msgId, rawSig) { + const msg = this.getMsg(msgId) + msg.rawSig = rawSig + this._updateMsg(msg) + this._setMsgStatus(msgId, 'signed') + } + + prepMsgForSigning (msgParams) { + delete msgParams.metamaskId + return Promise.resolve(msgParams) + } + + rejectMsg (msgId) { + this._setMsgStatus(msgId, 'rejected') + } + + // + // PRIVATE METHODS + // + + _setMsgStatus (msgId, status) { + const msg = this.getMsg(msgId) + if (!msg) throw new Error('MessageManager - Message not found for id: "${msgId}".') + msg.status = status + this._updateMsg(msg) + this.emit(`${msgId}:${status}`, msg) + if (status === 'rejected' || status === 'signed') { + this.emit(`${msgId}:finished`, msg) + } + } + + _updateMsg (msg) { + const index = this.messages.findIndex((message) => message.id === msg.id) + if (index !== -1) { + this.messages[index] = msg } - }) - if (found) { - messages[index] = msg + this._saveMsgList() } - this._saveMsgList(messages) + + _saveMsgList () { + const unapprovedMsgs = this.getUnapprovedMsgs() + const unapprovedMsgCount = Object.keys(unapprovedMsgs).length + this.memStore.updateState({ unapprovedMsgs, unapprovedMsgCount }) + this.emit('updateBadge') + } + } +function normalizeMsgData(data) { + if (data.slice(0, 2) === '0x') { + // data is already hex + return data + } else { + // data is unicode, convert to hex + return ethUtil.bufferToHex(new Buffer(data, 'utf8')) + } +}
\ No newline at end of file diff --git a/app/scripts/lib/migrations.js b/app/scripts/lib/migrations.js deleted file mode 100644 index f026cbe53..000000000 --- a/app/scripts/lib/migrations.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = [ - require('../migrations/002'), - require('../migrations/003'), - require('../migrations/004'), -] diff --git a/app/scripts/lib/migrator/index.js b/app/scripts/lib/migrator/index.js new file mode 100644 index 000000000..312345263 --- /dev/null +++ b/app/scripts/lib/migrator/index.js @@ -0,0 +1,51 @@ +const asyncQ = require('async-q') + +class Migrator { + + constructor (opts = {}) { + let migrations = opts.migrations || [] + this.migrations = migrations.sort((a, b) => a.version - b.version) + let lastMigration = this.migrations.slice(-1)[0] + // use specified defaultVersion or highest migration version + this.defaultVersion = opts.defaultVersion || (lastMigration && lastMigration.version) || 0 + } + + // run all pending migrations on meta in place + migrateData (versionedData = this.generateInitialState()) { + let remaining = this.migrations.filter(migrationIsPending) + + return ( + asyncQ.eachSeries(remaining, (migration) => this.runMigration(versionedData, migration)) + .then(() => versionedData) + ) + + // migration is "pending" if hit has a higher + // version number than currentVersion + function migrationIsPending(migration) { + return migration.version > versionedData.meta.version + } + } + + runMigration(versionedData, migration) { + return ( + migration.migrate(versionedData) + .then((versionedData) => { + if (!versionedData.data) return Promise.reject(new Error('Migrator - Migration returned empty data')) + if (migration.version !== undefined && versionedData.meta.version !== migration.version) return Promise.reject(new Error('Migrator - Migration did not update version number correctly')) + return Promise.resolve(versionedData) + }) + ) + } + + generateInitialState (initState) { + return { + meta: { + version: this.defaultVersion, + }, + data: initState, + } + } + +} + +module.exports = Migrator diff --git a/app/scripts/lib/remote-store.js b/app/scripts/lib/remote-store.js deleted file mode 100644 index fbfab7bad..000000000 --- a/app/scripts/lib/remote-store.js +++ /dev/null @@ -1,97 +0,0 @@ -const Dnode = require('dnode') -const inherits = require('util').inherits - -module.exports = { - HostStore: HostStore, - RemoteStore: RemoteStore, -} - -function BaseStore (initState) { - this._state = initState || {} - this._subs = [] -} - -BaseStore.prototype.set = function (key, value) { - throw Error('Not implemented.') -} - -BaseStore.prototype.get = function (key) { - return this._state[key] -} - -BaseStore.prototype.subscribe = function (fn) { - this._subs.push(fn) - var unsubscribe = this.unsubscribe.bind(this, fn) - return unsubscribe -} - -BaseStore.prototype.unsubscribe = function (fn) { - var index = this._subs.indexOf(fn) - if (index !== -1) this._subs.splice(index, 1) -} - -BaseStore.prototype._emitUpdates = function (state) { - this._subs.forEach(function (handler) { - handler(state) - }) -} - -// -// host -// - -inherits(HostStore, BaseStore) -function HostStore (initState, opts) { - BaseStore.call(this, initState) -} - -HostStore.prototype.set = function (key, value) { - this._state[key] = value - process.nextTick(this._emitUpdates.bind(this, this._state)) -} - -HostStore.prototype.createStream = function () { - var dnode = Dnode({ - // update: this._didUpdate.bind(this), - }) - dnode.on('remote', this._didConnect.bind(this)) - return dnode -} - -HostStore.prototype._didConnect = function (remote) { - this.subscribe(function (state) { - remote.update(state) - }) - remote.update(this._state) -} - -// -// remote -// - -inherits(RemoteStore, BaseStore) -function RemoteStore (initState, opts) { - BaseStore.call(this, initState) - this._remote = null -} - -RemoteStore.prototype.set = function (key, value) { - this._remote.set(key, value) -} - -RemoteStore.prototype.createStream = function () { - var dnode = Dnode({ - update: this._didUpdate.bind(this), - }) - dnode.once('remote', this._didConnect.bind(this)) - return dnode -} - -RemoteStore.prototype._didConnect = function (remote) { - this._remote = remote -} - -RemoteStore.prototype._didUpdate = function (state) { - this._state = state - this._emitUpdates(state) -} diff --git a/app/scripts/lib/stream-utils.js b/app/scripts/lib/stream-utils.js index 1b7b89d14..ba79990cc 100644 --- a/app/scripts/lib/stream-utils.js +++ b/app/scripts/lib/stream-utils.js @@ -1,4 +1,5 @@ const Through = require('through2') +const endOfStream = require('end-of-stream') const ObjectMultiplex = require('./obj-multiplex') module.exports = { @@ -24,11 +25,11 @@ function jsonStringifyStream () { function setupMultiplex (connectionStream) { var mx = ObjectMultiplex() connectionStream.pipe(mx).pipe(connectionStream) - mx.on('error', function (err) { - console.error(err) + endOfStream(mx, function (err) { + if (err) console.error(err) }) - connectionStream.on('error', function (err) { - console.error(err) + endOfStream(connectionStream, function (err) { + if (err) console.error(err) mx.destroy() }) return mx |