1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 18:00:18 +01:00

Merge pull request #1083 from MetaMask/kumavis-refactor6

Refactor round 6
This commit is contained in:
kumavis 2017-02-03 00:17:17 -08:00 committed by GitHub
commit 66be5ff275
20 changed files with 716 additions and 536 deletions

View File

@ -29,11 +29,11 @@ class KeyringController extends EventEmitter {
this.keyringTypes = keyringTypes
this.store = new ObservableStore(initState)
this.memStore = new ObservableStore({
isUnlocked: false,
keyringTypes: this.keyringTypes.map(krt => krt.type),
keyrings: [],
identities: {},
})
this.configManager = opts.configManager
this.ethStore = opts.ethStore
this.encryptor = encryptor
this.keyrings = []
@ -53,37 +53,7 @@ class KeyringController extends EventEmitter {
// Not all methods end with this, that might be a nice refactor.
fullUpdate () {
this.emit('update')
return Promise.resolve(this.getState())
}
// Get State
// returns @object state
//
// This method returns a hash representing the current state
// that the keyringController manages.
//
// It is extended in the MetamaskController along with the EthStore
// state, and its own state, to create the metamask state branch
// that is passed to the UI.
//
// This is currently a rare example of a synchronously resolving method
// in this class, but will need to be Promisified when we move our
// persistence to an async model.
getState () {
// old wallet
const memState = this.memStore.getState()
const result = {
// computed
isUnlocked: (!!this.password),
// memStore
keyringTypes: memState.keyringTypes,
identities: memState.identities,
keyrings: memState.keyrings,
// configManager
seedWords: this.configManager.getSeedWords(),
}
return result
return Promise.resolve(this.memStore.getState())
}
// Create New Vault And Keychain
@ -147,7 +117,10 @@ class KeyringController extends EventEmitter {
//
// This method deallocates all secrets, and effectively locks metamask.
setLocked () {
// set locked
this.password = null
this.memStore.updateState({ isUnlocked: false })
// remove keyrings
this.keyrings = []
this._updateMemStoreKeyrings()
return this.fullUpdate()
@ -385,6 +358,7 @@ class KeyringController extends EventEmitter {
persistAllKeyrings (password = this.password) {
if (typeof password === 'string') {
this.password = password
this.memStore.updateState({ isUnlocked: true })
}
return Promise.all(this.keyrings.map((keyring) => {
return Promise.all([keyring.type, keyring.serialize()])
@ -421,6 +395,7 @@ class KeyringController extends EventEmitter {
return this.encryptor.decrypt(password, encryptedVault)
.then((vault) => {
this.password = password
this.memStore.updateState({ isUnlocked: true })
vault.forEach(this.restoreKeyring.bind(this))
return this.keyrings
})

View File

@ -250,53 +250,6 @@ ConfigManager.prototype.getTOSHash = function () {
return 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 data.fiatCurrency || 'USD'
}
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 (data.conversionRate) || 0
}
ConfigManager.prototype.getConversionDate = function () {
var data = this.getData()
return (data.conversionDate) || 'N/A'
}
ConfigManager.prototype.getShapeShiftTxList = function () {
var data = this.getData()
var shapeShiftTxList = data.shapeShiftTxList ? data.shapeShiftTxList : []

View File

@ -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

View File

@ -7,140 +7,122 @@
* 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
inherits(EthereumStore, EventEmitter)
function EthereumStore(engine) {
const self = this
EventEmitter.call(self)
self._currentState = {
accounts: {},
transactions: {},
}
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()
})
}
EthereumStore.prototype.removeAccount = function (address) {
const self = this
delete self._currentState.accounts[address]
self._didUpdate()
}
EthereumStore.prototype.addTransaction = function (txHash) {
const self = this
self._currentState.transactions[txHash] = {}
self._didUpdate()
if (!self.currentBlockNumber) return
self._updateTransaction(self.currentBlockNumber, txHash, noop)
}
EthereumStore.prototype.removeTransaction = function (address) {
const self = this
delete self._currentState.transactions[address]
self._didUpdate()
}
//
// private
//
EthereumStore.prototype._didUpdate = function () {
const self = this
var state = self.getState()
self.emit('update', state)
}
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()
})
}
EthereumStore.prototype._updateAccounts = function (cb) {
var accountsState = this._currentState.accounts
var addresses = Object.keys(accountsState)
async.each(addresses, this._updateAccount.bind(this), cb)
}
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)
})
}
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)
}
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)
}
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)
})
}
const ObservableStore = require('obs-store')
function noop() {}
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))
}
//
// public
//
addAccount (address) {
const accounts = this.getState().accounts
accounts[address] = {}
this.updateState({ accounts })
if (!this._currentBlockNumber) return
this._updateAccount(address)
}
removeAccount (address) {
const accounts = this.getState().accounts
delete accounts[address]
this.updateState({ accounts })
}
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
//
_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())
})
}
_updateAccounts (cb = noop) {
const accounts = this.getState().accounts
const addresses = Object.keys(accounts)
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
}
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)
}
_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
}
cb(null, result)
})
}
_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)
}
}
module.exports = EthereumStore

View File

@ -97,9 +97,6 @@ IdentityStore.prototype.getState = function () {
isDisclaimerConfirmed: configManager.getConfirmedDisclaimer(),
selectedAddress: configManager.getSelectedAccount(),
shapeShiftTxList: configManager.getShapeShiftTxList(),
currentFiat: configManager.getCurrentFiat(),
conversionRate: configManager.getConversionRate(),
conversionDate: configManager.getConversionDate(),
gasMultiplier: configManager.getGasMultiplier(),
}))
}

View File

@ -6,18 +6,11 @@ const createId = require('./random-id')
module.exports = class MessageManager extends EventEmitter{
constructor (opts) {
super()
this.memStore = new ObservableStore({ messages: [] })
}
getState() {
return {
unapprovedMsgs: this.getUnapprovedMsgs(),
messages: this.getMsgList(),
}
}
getMsgList () {
return this.memStore.getState().messages
this.memStore = new ObservableStore({
unapprovedMsgs: {},
unapprovedMsgCount: 0,
})
this.messages = []
}
get unapprovedMsgCount () {
@ -25,8 +18,7 @@ module.exports = class MessageManager extends EventEmitter{
}
getUnapprovedMsgs () {
let messages = this.getMsgList()
return messages.filter(msg => msg.status === 'unapproved')
return this.messages.filter(msg => msg.status === 'unapproved')
.reduce((result, msg) => { result[msg.id] = msg; return result }, {})
}
@ -41,10 +33,6 @@ module.exports = class MessageManager extends EventEmitter{
status: 'unapproved',
}
this.addMsg(msgData)
console.log('addUnapprovedMessage:', msgData)
// keep the cb around for after approval (requires user interaction)
// This cb fires completion to the Dapp's write operation.
// signal update
this.emit('update')
@ -52,15 +40,12 @@ module.exports = class MessageManager extends EventEmitter{
}
addMsg (msg) {
let messages = this.getMsgList()
messages.push(msg)
this._saveMsgList(messages)
this.messages.push(msg)
this._saveMsgList()
}
getMsg (msgId) {
let messages = this.getMsgList()
let matching = messages.filter(msg => msg.id === msgId)
return matching.length > 0 ? matching[0] : null
return this.messages.find(msg => msg.id === msgId)
}
approveMessage (msgParams) {
@ -85,7 +70,10 @@ module.exports = class MessageManager extends EventEmitter{
brodcastMessage (rawSig, msgId, status) {
this.emit(`${msgId}:finished`, {status, rawSig})
}
// PRIVATE METHODS
//
// PRIVATE METHODS
//
_setMsgStatus (msgId, status) {
let msg = this.getMsg(msgId)
@ -94,18 +82,18 @@ module.exports = class MessageManager extends EventEmitter{
}
_updateMsg (msg) {
let messages = this.getMsgList()
let index = messages.findIndex((message) => message.id === msg.id)
let index = this.messages.findIndex((message) => message.id === msg.id)
if (index !== -1) {
messages[index] = msg
this.messages[index] = msg
}
this._saveMsgList(messages)
this._saveMsgList()
}
_saveMsgList (msgList) {
_saveMsgList () {
const unapprovedMsgs = this.getUnapprovedMsgs()
const unapprovedMsgCount = Object.keys(unapprovedMsgs).length
this.memStore.updateState({ unapprovedMsgs, unapprovedMsgCount })
this.emit('updateBadge')
this.memStore.updateState({ messages: msgList })
}
}

View File

@ -12,6 +12,7 @@ const MetaMaskProvider = require('web3-provider-engine/zero.js')
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
const KeyringController = require('./keyring-controller')
const PreferencesController = require('./lib/controllers/preferences')
const CurrencyController = require('./lib/controllers/currency')
const NoticeController = require('./notice-controller')
const MessageManager = require('./lib/message-manager')
const TxManager = require('./transaction-manager')
@ -29,38 +30,48 @@ module.exports = class MetamaskController extends EventEmitter {
constructor (opts) {
super()
this.opts = opts
this.state = { network: 'loading' }
let initState = opts.initState || {}
// observable state store
this.store = new ObservableStore(initState)
// network store
this.networkStore = new ObservableStore({ network: 'loading' })
// config manager
this.configManager = new ConfigManager({
store: this.store,
})
this.configManager.updateConversionRate()
// preferences controller
this.preferencesController = new PreferencesController({
initState: initState.PreferencesController,
})
// currency controller
this.currencyController = new CurrencyController({
initState: initState.CurrencyController,
})
this.currencyController.updateConversionRate()
this.currencyController.scheduleConversionInterval()
// rpc provider
this.provider = this.initializeProvider(opts)
this.provider.on('block', this.logBlock.bind(this))
this.provider.on('error', this.getNetwork.bind(this))
this.provider.on('error', this.verifyNetwork.bind(this))
// eth data query tools
this.ethQuery = new EthQuery(this.provider)
this.ethStore = new EthStore(this.provider)
this.ethStore = new EthStore({
provider: this.provider,
blockTracker: this.provider,
})
// key mgmt
this.keyringController = new KeyringController({
initState: initState.KeyringController,
ethStore: this.ethStore,
configManager: this.configManager,
getNetwork: this.getStateNetwork.bind(this),
getNetwork: this.getNetworkState.bind(this),
})
this.keyringController.on('newAccount', (address) => {
this.preferencesController.setSelectedAddress(address)
@ -69,12 +80,11 @@ module.exports = class MetamaskController extends EventEmitter {
// tx mgmt
this.txManager = new TxManager({
txList: this.configManager.getTxList(),
initState: initState.TransactionManager,
networkStore: this.networkStore,
preferencesStore: this.preferencesController.store,
txHistoryLimit: 40,
setTxList: this.configManager.setTxList.bind(this.configManager),
getSelectedAddress: this.preferencesController.getSelectedAddress.bind(this.preferencesController),
getGasMultiplier: this.configManager.getGasMultiplier.bind(this.configManager),
getNetwork: this.getStateNetwork.bind(this),
getNetwork: this.getNetworkState.bind(this),
signTransaction: this.keyringController.signTransaction.bind(this.keyringController),
provider: this.provider,
blockTracker: this.provider,
@ -82,36 +92,49 @@ module.exports = class MetamaskController extends EventEmitter {
// notices
this.noticeController = new NoticeController({
configManager: this.configManager,
initState: initState.NoticeController,
})
this.noticeController.updateNoticesList()
// to be uncommented when retrieving notices from a remote server.
// this.noticeController.startPolling()
this.getNetwork()
this.lookupNetwork()
this.messageManager = new MessageManager()
this.publicConfigStore = this.initPublicConfigStore()
this.checkTOSChange()
this.scheduleConversionInterval()
// TEMPORARY UNTIL FULL DEPRECATION:
this.idStoreMigrator = new IdStoreMigrator({
configManager: this.configManager,
})
// manual state subscriptions
this.ethStore.on('update', this.sendUpdate.bind(this))
this.keyringController.on('update', this.sendUpdate.bind(this))
this.txManager.on('update', this.sendUpdate.bind(this))
this.messageManager.on('update', this.sendUpdate.bind(this))
// manual disk state subscriptions
this.txManager.store.subscribe((state) => {
this.store.updateState({ TransactionManager: state })
})
this.keyringController.store.subscribe((state) => {
this.store.updateState({ KeyringController: state })
})
this.preferencesController.store.subscribe((state) => {
this.store.updateState({ PreferencesController: state })
})
this.currencyController.store.subscribe((state) => {
this.store.updateState({ CurrencyController: state })
})
this.noticeController.store.subscribe((state) => {
this.store.updateState({ NoticeController: state })
})
// manual mem state subscriptions
this.networkStore.subscribe(this.sendUpdate.bind(this))
this.ethStore.subscribe(this.sendUpdate.bind(this))
this.txManager.memStore.subscribe(this.sendUpdate.bind(this))
this.messageManager.memStore.subscribe(this.sendUpdate.bind(this))
this.keyringController.memStore.subscribe(this.sendUpdate.bind(this))
this.preferencesController.store.subscribe(this.sendUpdate.bind(this))
this.currencyController.store.subscribe(this.sendUpdate.bind(this))
this.noticeController.memStore.subscribe(this.sendUpdate.bind(this))
}
//
@ -174,22 +197,21 @@ module.exports = class MetamaskController extends EventEmitter {
{
isInitialized,
},
this.state,
this.networkStore.getState(),
this.ethStore.getState(),
this.txManager.getState(),
this.messageManager.getState(),
this.keyringController.getState(),
this.txManager.memStore.getState(),
this.messageManager.memStore.getState(),
this.keyringController.memStore.getState(),
this.preferencesController.store.getState(),
this.noticeController.getState(),
this.currencyController.store.getState(),
this.noticeController.memStore.getState(),
// config manager
this.configManager.getConfig(),
{
shapeShiftTxList: this.configManager.getShapeShiftTxList(),
lostAccounts: this.configManager.getLostAccounts(),
currentFiat: this.configManager.getCurrentFiat(),
conversionRate: this.configManager.getConversionRate(),
conversionDate: this.configManager.getConversionDate(),
isDisclaimerConfirmed: this.configManager.getConfirmedDisclaimer(),
seedWords: this.configManager.getSeedWords(),
}
)
}
@ -213,7 +235,7 @@ module.exports = class MetamaskController extends EventEmitter {
useEtherscanProvider: this.useEtherscanProvider.bind(this),
agreeToDisclaimer: this.agreeToDisclaimer.bind(this),
resetDisclaimer: this.resetDisclaimer.bind(this),
setCurrentFiat: this.setCurrentFiat.bind(this),
setCurrentCurrency: this.setCurrentCurrency.bind(this),
setTOSHash: this.setTOSHash.bind(this),
checkTOSChange: this.checkTOSChange.bind(this),
setGasMultiplier: this.setGasMultiplier.bind(this),
@ -243,11 +265,13 @@ module.exports = class MetamaskController extends EventEmitter {
saveAccountLabel: nodeify(keyringController.saveAccountLabel).bind(keyringController),
exportAccount: nodeify(keyringController.exportAccount).bind(keyringController),
// signing methods
// txManager
approveTransaction: txManager.approveTransaction.bind(txManager),
cancelTransaction: txManager.cancelTransaction.bind(txManager),
signMessage: this.signMessage.bind(this),
cancelMessage: messageManager.rejectMsg.bind(messageManager),
// messageManager
signMessage: this.signMessage.bind(this),
cancelMessage: messageManager.rejectMsg.bind(messageManager),
// notices
checkNotices: noticeController.updateNoticesList.bind(noticeController),
@ -339,7 +363,7 @@ module.exports = class MetamaskController extends EventEmitter {
.then((serialized) => {
const seedWords = serialized.mnemonic
this.configManager.setSeedWords(seedWords)
promiseToCallback(this.keyringController.fullUpdate())(cb)
cb()
})
}
@ -538,44 +562,38 @@ module.exports = class MetamaskController extends EventEmitter {
this.verifyNetwork()
}
setCurrentFiat (fiat, cb) {
setCurrentCurrency (currencyCode, cb) {
try {
this.configManager.setCurrentFiat(fiat)
this.configManager.updateConversionRate()
this.scheduleConversionInterval()
this.currencyController.setCurrentCurrency(currencyCode)
this.currencyController.updateConversionRate()
const data = {
conversionRate: this.configManager.getConversionRate(),
currentFiat: this.configManager.getCurrentFiat(),
conversionDate: this.configManager.getConversionDate(),
conversionRate: this.currencyController.getConversionRate(),
currentFiat: this.currencyController.getCurrentCurrency(),
conversionDate: this.currencyController.getConversionDate(),
}
cb(data)
cb(null, data)
} catch (err) {
cb(null, err)
cb(err)
}
}
scheduleConversionInterval () {
if (this.conversionInterval) {
clearInterval(this.conversionInterval)
}
this.conversionInterval = setInterval(() => {
this.configManager.updateConversionRate()
}, 300000)
}
buyEth (address, amount) {
if (!amount) amount = '5'
var network = this.state.network
var url = `https://buy.coinbase.com/?code=9ec56d01-7e81-5017-930c-513daa27bb6a&amount=${amount}&address=${address}&crypto_currency=ETH`
const network = this.getNetworkState()
let url
if (network === '3') {
url = 'https://faucet.metamask.io/'
switch (network) {
case '1':
url = `https://buy.coinbase.com/?code=9ec56d01-7e81-5017-930c-513daa27bb6a&amount=${amount}&address=${address}&crypto_currency=ETH`
break
case '3':
url = 'https://faucet.metamask.io/'
break
}
extension.tabs.create({
url,
})
if (url) extension.tabs.create({ url })
}
createShapeShiftTx (depositAddress, depositType) {
@ -584,7 +602,7 @@ module.exports = class MetamaskController extends EventEmitter {
setGasMultiplier (gasMultiplier, cb) {
try {
this.configManager.setGasMultiplier(gasMultiplier)
this.txManager.setGasMultiplier(gasMultiplier)
cb()
} catch (err) {
cb(err)
@ -597,21 +615,19 @@ module.exports = class MetamaskController extends EventEmitter {
verifyNetwork () {
// Check network when restoring connectivity:
if (this.state.network === 'loading') {
this.getNetwork()
}
if (this.isNetworkLoading()) this.lookupNetwork()
}
setRpcTarget (rpcTarget) {
this.configManager.setRpcTarget(rpcTarget)
extension.runtime.reload()
this.getNetwork()
this.lookupNetwork()
}
setProviderType (type) {
this.configManager.setProviderType(type)
extension.runtime.reload()
this.getNetwork()
this.lookupNetwork()
}
useEtherscanProvider () {
@ -619,26 +635,32 @@ module.exports = class MetamaskController extends EventEmitter {
extension.runtime.reload()
}
getStateNetwork () {
return this.state.network
getNetworkState () {
return this.networkStore.getState().network
}
getNetwork (err) {
setNetworkState (network) {
return this.networkStore.updateState({ network })
}
isNetworkLoading () {
return this.getNetworkState() === 'loading'
}
lookupNetwork (err) {
if (err) {
this.state.network = 'loading'
this.sendUpdate()
this.setNetworkState('loading')
}
this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => {
if (err) {
this.state.network = 'loading'
return this.sendUpdate()
this.setNetworkState('loading')
return
}
if (global.METAMASK_DEBUG) {
console.log('web3.getNetwork returned ' + network)
}
this.state.network = network
this.sendUpdate()
this.setNetworkState(network)
})
}

View File

@ -0,0 +1,38 @@
const version = 7
/*
This migration breaks out the TransactionManager substate
*/
const extend = require('xtend')
module.exports = {
version,
migrate: function (versionedData) {
versionedData.meta.version = version
try {
const state = versionedData.data
const newState = transformState(state)
versionedData.data = newState
} catch (err) {
console.warn(`MetaMask Migration #${version}` + err.stack)
}
return Promise.resolve(versionedData)
},
}
function transformState (state) {
const newState = extend(state, {
TransactionManager: {
transactions: state.transactions || [],
gasMultiplier: state.gasMultiplier || 1,
},
})
delete newState.transactions
delete newState.gasMultiplier
return newState
}

View File

@ -0,0 +1,36 @@
const version = 8
/*
This migration breaks out the NoticeController substate
*/
const extend = require('xtend')
module.exports = {
version,
migrate: function (versionedData) {
versionedData.meta.version = version
try {
const state = versionedData.data
const newState = transformState(state)
versionedData.data = newState
} catch (err) {
console.warn(`MetaMask Migration #${version}` + err.stack)
}
return Promise.resolve(versionedData)
},
}
function transformState (state) {
const newState = extend(state, {
NoticeController: {
noticesList: state.noticesList || [],
},
})
delete newState.noticesList
return newState
}

View File

@ -0,0 +1,40 @@
const version = 9
/*
This migration breaks out the CurrencyController substate
*/
const merge = require('deep-merge')
module.exports = {
version,
migrate: function (versionedData) {
versionedData.meta.version = version
try {
const state = versionedData.data
const newState = transformState(state)
versionedData.data = newState
} catch (err) {
console.warn(`MetaMask Migration #${version}` + err.stack)
}
return Promise.resolve(versionedData)
},
}
function transformState (state) {
const newState = merge(state, {
CurrencyController: {
currentCurrency: state.currentFiat || 'USD',
conversionRate: state.conversionRate,
conversionDate: state.conversionDate,
},
})
delete newState.currentFiat
delete newState.conversionRate
delete newState.conversionDate
return newState
}

View File

@ -17,4 +17,7 @@ module.exports = [
require('./004'),
require('./005'),
require('./006'),
require('./007'),
require('./008'),
require('./009'),
]

View File

@ -1,36 +1,37 @@
const EventEmitter = require('events').EventEmitter
const extend = require('xtend')
const ObservableStore = require('obs-store')
const hardCodedNotices = require('../../notices/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,
}
const initState = extend({
noticesList: [],
}, opts.initState)
this.store = new ObservableStore(initState)
this.memStore = new ObservableStore({})
this.store.subscribe(() => this._updateMemstore())
}
getNoticesList () {
var data = this.configManager.getData()
if ('noticesList' in data) {
return data.noticesList
} else {
return []
}
return this.store.getState().noticesList
}
setNoticesList (list) {
var data = this.configManager.getData()
data.noticesList = list
this.configManager.setData(data)
getUnreadNotices () {
const notices = this.getNoticesList()
return notices.filter((notice) => notice.read === false)
}
getLatestUnreadNotice () {
const unreadNotices = this.getUnreadNotices()
return unreadNotices[unreadNotices.length - 1]
}
setNoticesList (noticesList) {
this.store.updateState({ noticesList })
return Promise.resolve(true)
}
@ -56,14 +57,6 @@ module.exports = class NoticeController extends EventEmitter {
})
}
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)
@ -92,5 +85,10 @@ module.exports = class NoticeController extends EventEmitter {
return Promise.resolve(hardCodedNotices)
}
_updateMemstore () {
const lastUnreadNotice = this.getLatestUnreadNotice()
const noActiveNotices = !lastUnreadNotice
this.memStore.updateState({ lastUnreadNotice, noActiveNotices })
}
}

View File

@ -2,6 +2,7 @@ const EventEmitter = require('events')
const async = require('async')
const extend = require('xtend')
const Semaphore = require('semaphore')
const ObservableStore = require('obs-store')
const ethUtil = require('ethereumjs-util')
const BN = require('ethereumjs-util').BN
const TxProviderUtil = require('./lib/tx-utils')
@ -10,33 +11,53 @@ const createId = require('./lib/random-id')
module.exports = class TransactionManager extends EventEmitter {
constructor (opts) {
super()
this.txList = opts.txList || []
this._setTxList = opts.setTxList
this.store = new ObservableStore(extend({
transactions: [],
gasMultiplier: 1,
}, opts.initState))
this.memStore = new ObservableStore({})
this.networkStore = opts.networkStore || new ObservableStore({})
this.preferencesStore = opts.preferencesStore || new ObservableStore({})
this.txHistoryLimit = opts.txHistoryLimit
this.getSelectedAddress = opts.getSelectedAddress
this.provider = opts.provider
this.blockTracker = opts.blockTracker
this.txProviderUtils = new TxProviderUtil(this.provider)
this.blockTracker.on('block', this.checkForTxInBlock.bind(this))
this.getGasMultiplier = opts.getGasMultiplier
this.getNetwork = opts.getNetwork
this.signEthTx = opts.signTransaction
this.nonceLock = Semaphore(1)
// memstore is computed from a few different stores
this._updateMemstore()
this.store.subscribe(() => this._updateMemstore() )
this.networkStore.subscribe(() => this._updateMemstore() )
this.preferencesStore.subscribe(() => this._updateMemstore() )
}
getState () {
var selectedAddress = this.getSelectedAddress()
return {
transactions: this.getTxList(),
unapprovedTxs: this.getUnapprovedTxList(),
selectedAddressTxList: this.getFilteredTxList({metamaskNetworkId: this.getNetwork(), from: selectedAddress}),
}
return this.memStore.getState()
}
// Returns the tx list
getNetwork () {
return this.networkStore.getState().network
}
getSelectedAddress () {
return this.preferencesStore.getState().selectedAddress
}
// Returns the tx list
getTxList () {
let network = this.getNetwork()
return this.txList.filter(txMeta => txMeta.metamaskNetworkId === network)
let fullTxList = this.store.getState().transactions
return fullTxList.filter(txMeta => txMeta.metamaskNetworkId === network)
}
getGasMultiplier () {
return this.store.getState().gasMultiplier
}
setGasMultiplier (gasMultiplier) {
return this.store.updateState({ gasMultiplier })
}
// Adds a tx to the txlist
@ -108,7 +129,7 @@ module.exports = class TransactionManager extends EventEmitter {
id: txId,
time: time,
status: 'unapproved',
gasMultiplier: this.getGasMultiplier() || 1,
gasMultiplier: this.getGasMultiplier(),
metamaskNetworkId: this.getNetwork(),
txParams: txParams,
}
@ -239,7 +260,7 @@ module.exports = class TransactionManager extends EventEmitter {
getTxsByMetaData (key, value, txList = this.getTxList()) {
return txList.filter((txMeta) => {
if (key in txMeta.txParams) {
if (txMeta.txParams[key]) {
return txMeta.txParams[key] === value
} else {
return txMeta[key] === value
@ -351,9 +372,17 @@ module.exports = class TransactionManager extends EventEmitter {
// Saves the new/updated txList.
// Function is intended only for internal use
_saveTxList (txList) {
this.txList = txList
this._setTxList(txList)
_saveTxList (transactions) {
this.store.updateState({ transactions })
}
_updateMemstore () {
const unapprovedTxs = this.getUnapprovedTxList()
const selectedAddressTxList = this.getFilteredTxList({
from: this.getSelectedAddress(),
metamaskNetworkId: this.getNetwork(),
})
this.memStore.updateState({ unapprovedTxs, selectedAddressTxList })
}
}

View File

@ -11,8 +11,8 @@
"disc": "gulp disc --debug",
"dist": "gulp dist --disableLiveReload",
"test": "npm run lint && npm run fastTest && npm run ci",
"fastTest": "METAMASK_ENV=test mocha --require test/helper.js --compilers js:babel-register --recursive \"test/unit/**/*.js\"",
"watch": "mocha watch --compilers js:babel-register --recursive \"test/unit/**/*.js\"",
"fastTest": "METAMASK_ENV=test mocha --require test/helper.js --recursive \"test/unit/**/*.js\"",
"watch": "mocha watch --recursive \"test/unit/**/*.js\"",
"genStates": "node development/genStates.js",
"ui": "npm run genStates && beefy ui-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./",
"mock": "beefy mock-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./",
@ -45,6 +45,7 @@
"clone": "^1.0.2",
"copy-to-clipboard": "^2.0.0",
"debounce": "^1.0.0",
"deep-merge": "^1.0.0",
"denodeify": "^1.2.1",
"disc": "^1.3.2",
"dnode": "^1.2.2",
@ -71,7 +72,7 @@
"mississippi": "^1.2.0",
"mkdirp": "^0.5.1",
"multiplex": "^6.7.0",
"obs-store": "^2.3.0",
"obs-store": "^2.3.1",
"once": "^1.3.3",
"ping-pong-stream": "^1.0.0",
"pojo-migrator": "^2.1.0",

View File

@ -15,7 +15,7 @@
<script src="bundle.js"></script>
<script src="/testem.js"></script>
<iframe src="/development/test.html" height="500px" width="360px">
<iframe src="/development/test.html" height="800px" width="500px">
<p>Your browser does not support iframes</p>
</iframe>
</body>

View File

@ -14,81 +14,6 @@ describe('config-manager', function() {
configManager = configManagerGen()
})
describe('currency conversions', function() {
describe('#setCurrentFiat', function() {
it('should return USD as default', function() {
assert.equal(configManager.getCurrentFiat(), 'USD')
})
it('should be able to set to other currency', function() {
assert.equal(configManager.getCurrentFiat(), 'USD')
configManager.setCurrentFiat('JPY')
var result = configManager.getCurrentFiat()
assert.equal(result, 'JPY')
})
})
describe('#getConversionRate', function() {
it('should return undefined if non-existent', function() {
var result = configManager.getConversionRate()
assert.ok(!result)
})
})
describe('#updateConversionRate', function() {
it('should retrieve an update for ETH to USD and set it in memory', function(done) {
this.timeout(15000)
var usdMock = nock('https://www.cryptonator.com')
.get('/api/ticker/eth-USD')
.reply(200, '{"ticker":{"base":"ETH","target":"USD","price":"11.02456145","volume":"44948.91745289","change":"-0.01472534"},"timestamp":1472072136,"success":true,"error":""}')
assert.equal(configManager.getConversionRate(), 0)
var promise = new Promise(
function (resolve, reject) {
configManager.setCurrentFiat('USD')
configManager.updateConversionRate().then(function() {
resolve()
})
})
promise.then(function() {
var result = configManager.getConversionRate()
assert.equal(typeof result, 'number')
done()
}).catch(function(err) {
console.log(err)
})
})
it('should work for JPY as well.', function() {
this.timeout(15000)
assert.equal(configManager.getConversionRate(), 0)
var jpyMock = nock('https://www.cryptonator.com')
.get('/api/ticker/eth-JPY')
.reply(200, '{"ticker":{"base":"ETH","target":"JPY","price":"11.02456145","volume":"44948.91745289","change":"-0.01472534"},"timestamp":1472072136,"success":true,"error":""}')
var promise = new Promise(
function (resolve, reject) {
configManager.setCurrentFiat('JPY')
configManager.updateConversionRate().then(function() {
resolve()
})
})
promise.then(function() {
var result = configManager.getConversionRate()
assert.equal(typeof result, 'number')
}).catch(function(err) {
console.log(err)
})
})
})
})
describe('confirmation', function() {
describe('#getConfirmedDisclaimer', function() {

View File

@ -0,0 +1,87 @@
// polyfill fetch
global.fetch = global.fetch || require('isomorphic-fetch')
const assert = require('assert')
const extend = require('xtend')
const rp = require('request-promise')
const nock = require('nock')
const CurrencyController = require('../../app/scripts/lib/controllers/currency')
describe('config-manager', function() {
var currencyController
beforeEach(function() {
currencyController = new CurrencyController()
})
describe('currency conversions', function() {
describe('#setCurrentCurrency', function() {
it('should return USD as default', function() {
assert.equal(currencyController.getCurrentCurrency(), 'USD')
})
it('should be able to set to other currency', function() {
assert.equal(currencyController.getCurrentCurrency(), 'USD')
currencyController.setCurrentCurrency('JPY')
var result = currencyController.getCurrentCurrency()
assert.equal(result, 'JPY')
})
})
describe('#getConversionRate', function() {
it('should return undefined if non-existent', function() {
var result = currencyController.getConversionRate()
assert.ok(!result)
})
})
describe('#updateConversionRate', function() {
it('should retrieve an update for ETH to USD and set it in memory', function(done) {
this.timeout(15000)
var usdMock = nock('https://www.cryptonator.com')
.get('/api/ticker/eth-USD')
.reply(200, '{"ticker":{"base":"ETH","target":"USD","price":"11.02456145","volume":"44948.91745289","change":"-0.01472534"},"timestamp":1472072136,"success":true,"error":""}')
assert.equal(currencyController.getConversionRate(), 0)
currencyController.setCurrentCurrency('USD')
currencyController.updateConversionRate()
.then(function() {
var result = currencyController.getConversionRate()
console.log('currencyController.getConversionRate:', result)
assert.equal(typeof result, 'number')
done()
}).catch(function(err) {
done(err)
})
})
it('should work for JPY as well.', function() {
this.timeout(15000)
assert.equal(currencyController.getConversionRate(), 0)
var jpyMock = nock('https://www.cryptonator.com')
.get('/api/ticker/eth-JPY')
.reply(200, '{"ticker":{"base":"ETH","target":"JPY","price":"11.02456145","volume":"44948.91745289","change":"-0.01472534"},"timestamp":1472072136,"success":true,"error":""}')
var promise = new Promise(
function (resolve, reject) {
currencyController.setCurrentCurrency('JPY')
currencyController.updateConversionRate().then(function() {
resolve()
})
})
promise.then(function() {
var result = currencyController.getConversionRate()
assert.equal(typeof result, 'number')
}).catch(function(err) {
done(err)
})
})
})
})
})

View File

@ -13,7 +13,7 @@ describe('Transaction Manager', function() {
describe('#getMsgList', function() {
it('when new should return empty array', function() {
var result = messageManager.getMsgList()
var result = messageManager.messages
assert.ok(Array.isArray(result))
assert.equal(result.length, 0)
})
@ -22,20 +22,11 @@ describe('Transaction Manager', function() {
})
})
describe('#_saveMsgList', function() {
it('saves the submitted data to the Msg list', function() {
var target = [{ foo: 'bar', metamaskNetworkId: 'unit test' }]
messageManager._saveMsgList(target)
var result = messageManager.getMsgList()
assert.equal(result[0].foo, 'bar')
})
})
describe('#addMsg', function() {
it('adds a Msg returned in getMsgList', function() {
var Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' }
messageManager.addMsg(Msg)
var result = messageManager.getMsgList()
var result = messageManager.messages
assert.ok(Array.isArray(result))
assert.equal(result.length, 1)
assert.equal(result[0].id, 1)
@ -47,7 +38,7 @@ describe('Transaction Manager', function() {
var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
messageManager.addMsg(Msg)
messageManager.setMsgStatusApproved(1)
var result = messageManager.getMsgList()
var result = messageManager.messages
assert.ok(Array.isArray(result))
assert.equal(result.length, 1)
assert.equal(result[0].status, 'approved')
@ -59,7 +50,7 @@ describe('Transaction Manager', function() {
var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
messageManager.addMsg(Msg)
messageManager.rejectMsg(1)
var result = messageManager.getMsgList()
var result = messageManager.messages
assert.ok(Array.isArray(result))
assert.equal(result.length, 1)
assert.equal(result[0].status, 'rejected')

View File

@ -1,21 +1,19 @@
const assert = require('assert')
const extend = require('xtend')
const EventEmitter = require('events')
const ObservableStore = require('obs-store')
const STORAGE_KEY = 'metamask-persistance-key'
const TransactionManager = require('../../app/scripts/transaction-manager')
const noop = () => true
describe('Transaction Manager', function() {
let txManager
const onTxDoneCb = () => true
beforeEach(function() {
txManager = new TransactionManager ({
txList: [],
setTxList: () => {},
provider: "testnet",
txManager = new TransactionManager({
networkStore: new ObservableStore({ network: 'unit test' }),
txHistoryLimit: 10,
blockTracker: new EventEmitter(),
getNetwork: function(){ return 'unit test' }
})
})
@ -51,19 +49,10 @@ describe('Transaction Manager', function() {
})
})
describe('#_saveTxList', function() {
it('saves the submitted data to the tx list', function() {
var target = [{ foo: 'bar', metamaskNetworkId: 'unit test' }]
txManager._saveTxList(target)
var result = txManager.getTxList()
assert.equal(result[0].foo, 'bar')
})
})
describe('#addTx', function() {
it('adds a tx returned in getTxList', function() {
var tx = { id: 1, status: 'confirmed', metamaskNetworkId: 'unit test' }
txManager.addTx(tx, onTxDoneCb)
var tx = { id: 1, status: 'confirmed', metamaskNetworkId: 'unit test', txParams: {} }
txManager.addTx(tx, noop)
var result = txManager.getTxList()
assert.ok(Array.isArray(result))
assert.equal(result.length, 1)
@ -73,8 +62,8 @@ describe('Transaction Manager', function() {
it('cuts off early txs beyond a limit', function() {
const limit = txManager.txHistoryLimit
for (let i = 0; i < limit + 1; i++) {
let tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: 'unit test' }
txManager.addTx(tx, onTxDoneCb)
let tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: 'unit test', txParams: {} }
txManager.addTx(tx, noop)
}
var result = txManager.getTxList()
assert.equal(result.length, limit, `limit of ${limit} txs enforced`)
@ -84,8 +73,8 @@ describe('Transaction Manager', function() {
it('cuts off early txs beyond a limit whether or not it is confirmed or rejected', function() {
const limit = txManager.txHistoryLimit
for (let i = 0; i < limit + 1; i++) {
let tx = { id: i, time: new Date(), status: 'rejected', metamaskNetworkId: 'unit test' }
txManager.addTx(tx, onTxDoneCb)
let tx = { id: i, time: new Date(), status: 'rejected', metamaskNetworkId: 'unit test', txParams: {} }
txManager.addTx(tx, noop)
}
var result = txManager.getTxList()
assert.equal(result.length, limit, `limit of ${limit} txs enforced`)
@ -93,12 +82,12 @@ describe('Transaction Manager', function() {
})
it('cuts off early txs beyond a limit but does not cut unapproved txs', function() {
var unconfirmedTx = { id: 0, time: new Date(), status: 'unapproved', metamaskNetworkId: 'unit test' }
txManager.addTx(unconfirmedTx, onTxDoneCb)
var unconfirmedTx = { id: 0, time: new Date(), status: 'unapproved', metamaskNetworkId: 'unit test', txParams: {} }
txManager.addTx(unconfirmedTx, noop)
const limit = txManager.txHistoryLimit
for (let i = 1; i < limit + 1; i++) {
let tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: 'unit test' }
txManager.addTx(tx, onTxDoneCb)
let tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: 'unit test', txParams: {} }
txManager.addTx(tx, noop)
}
var result = txManager.getTxList()
assert.equal(result.length, limit, `limit of ${limit} txs enforced`)
@ -110,8 +99,8 @@ describe('Transaction Manager', function() {
describe('#setTxStatusSigned', function() {
it('sets the tx status to signed', function() {
var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
txManager.addTx(tx, onTxDoneCb)
var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test', txParams: {} }
txManager.addTx(tx, noop)
txManager.setTxStatusSigned(1)
var result = txManager.getTxList()
assert.ok(Array.isArray(result))
@ -121,20 +110,20 @@ describe('Transaction Manager', function() {
it('should emit a signed event to signal the exciton of callback', (done) => {
this.timeout(10000)
var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
let onTxDoneCb = function () {
assert(true, 'event listener has been triggered and onTxDoneCb executed')
var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test', txParams: {} }
let noop = function () {
assert(true, 'event listener has been triggered and noop executed')
done()
}
txManager.addTx(tx)
txManager.on('1:signed', onTxDoneCb)
txManager.on('1:signed', noop)
txManager.setTxStatusSigned(1)
})
})
describe('#setTxStatusRejected', function() {
it('sets the tx status to rejected', function() {
var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test', txParams: {} }
txManager.addTx(tx)
txManager.setTxStatusRejected(1)
var result = txManager.getTxList()
@ -145,13 +134,13 @@ describe('Transaction Manager', function() {
it('should emit a rejected event to signal the exciton of callback', (done) => {
this.timeout(10000)
var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test', txParams: {} }
txManager.addTx(tx)
let onTxDoneCb = function (err, txId) {
assert(true, 'event listener has been triggered and onTxDoneCb executed')
let noop = function (err, txId) {
assert(true, 'event listener has been triggered and noop executed')
done()
}
txManager.on('1:rejected', onTxDoneCb)
txManager.on('1:rejected', noop)
txManager.setTxStatusRejected(1)
})
@ -159,9 +148,9 @@ describe('Transaction Manager', function() {
describe('#updateTx', function() {
it('replaces the tx with the same id', function() {
txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }, onTxDoneCb)
txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: 'unit test' }, onTxDoneCb)
txManager.updateTx({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: 'unit test' })
txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test', txParams: {} }, noop)
txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: 'unit test', txParams: {} }, noop)
txManager.updateTx({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: 'unit test', txParams: {} })
var result = txManager.getTx('1')
assert.equal(result.hash, 'foo')
})
@ -169,8 +158,8 @@ describe('Transaction Manager', function() {
describe('#getUnapprovedTxList', function() {
it('returns unapproved txs in a hash', function() {
txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }, onTxDoneCb)
txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: 'unit test' }, onTxDoneCb)
txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test', txParams: {} }, noop)
txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: 'unit test', txParams: {} }, noop)
let result = txManager.getUnapprovedTxList()
assert.equal(typeof result, 'object')
assert.equal(result['1'].status, 'unapproved')
@ -180,8 +169,8 @@ describe('Transaction Manager', function() {
describe('#getTx', function() {
it('returns a tx with the requested id', function() {
txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }, onTxDoneCb)
txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: 'unit test' }, onTxDoneCb)
txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test', txParams: {} }, noop)
txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: 'unit test', txParams: {} }, noop)
assert.equal(txManager.getTx('1').status, 'unapproved')
assert.equal(txManager.getTx('2').status, 'confirmed')
})
@ -189,26 +178,33 @@ describe('Transaction Manager', function() {
describe('#getFilteredTxList', function() {
it('returns a tx with the requested data', function() {
var foop = 0
var zoop = 0
for (let i = 0; i < 10; ++i ){
let everyOther = i % 2
txManager.addTx({ id: i,
status: everyOther ? 'unapproved' : 'confirmed',
metamaskNetworkId: 'unit test',
txParams: {
from: everyOther ? 'foop' : 'zoop',
to: everyOther ? 'zoop' : 'foop',
}
}, onTxDoneCb)
everyOther ? ++foop : ++zoop
}
assert.equal(txManager.getFilteredTxList({status: 'confirmed', from: 'zoop'}).length, zoop)
assert.equal(txManager.getFilteredTxList({status: 'confirmed', to: 'foop'}).length, zoop)
assert.equal(txManager.getFilteredTxList({status: 'confirmed', from: 'foop'}).length, 0)
assert.equal(txManager.getFilteredTxList({status: 'confirmed'}).length, zoop)
assert.equal(txManager.getFilteredTxList({from: 'foop'}).length, foop)
assert.equal(txManager.getFilteredTxList({from: 'zoop'}).length, zoop)
let txMetas = [
{ id: 0, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: 'unit test' },
{ id: 1, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: 'unit test' },
{ id: 2, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: 'unit test' },
{ id: 3, status: 'unapproved', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: 'unit test' },
{ id: 4, status: 'unapproved', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: 'unit test' },
{ id: 5, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: 'unit test' },
{ id: 6, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: 'unit test' },
{ id: 7, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: 'unit test' },
{ id: 8, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: 'unit test' },
{ id: 9, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: 'unit test' },
]
txMetas.forEach((txMeta) => txManager.addTx(txMeta, noop))
let filterParams
filterParams = { status: 'unapproved', from: '0xaa' }
assert.equal(txManager.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
filterParams = { status: 'unapproved', to: '0xaa' }
assert.equal(txManager.getFilteredTxList(filterParams).length, 2, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
filterParams = { status: 'confirmed', from: '0xbb' }
assert.equal(txManager.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
filterParams = { status: 'confirmed' }
assert.equal(txManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
filterParams = { from: '0xaa' }
assert.equal(txManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
filterParams = { to: '0xaa' }
assert.equal(txManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
})
})

View File

@ -158,6 +158,7 @@ var actions = {
showNewKeychain: showNewKeychain,
callBackgroundThenUpdate,
forceUpdateMetamaskState,
}
module.exports = actions
@ -179,13 +180,14 @@ function tryUnlockMetamask (password) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
dispatch(actions.unlockInProgress())
background.submitPassword(password, (err, newState) => {
if (global.METAMASK_DEBUG) console.log(`background.submitPassword`)
background.submitPassword(password, (err) => {
dispatch(actions.hideLoadingIndication())
if (err) {
dispatch(actions.unlockFailed(err.message))
} else {
dispatch(actions.transitionForward())
dispatch(actions.updateMetamaskState(newState))
forceUpdateMetamaskState(dispatch)
}
})
}
@ -206,6 +208,7 @@ function transitionBackward () {
function confirmSeedWords () {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
if (global.METAMASK_DEBUG) console.log(`background.clearSeedWordCache`)
background.clearSeedWordCache((err, account) => {
dispatch(actions.hideLoadingIndication())
if (err) {
@ -221,6 +224,7 @@ function confirmSeedWords () {
function createNewVaultAndRestore (password, seed) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
if (global.METAMASK_DEBUG) console.log(`background.createNewVaultAndRestore`)
background.createNewVaultAndRestore(password, seed, (err) => {
dispatch(actions.hideLoadingIndication())
if (err) return dispatch(actions.displayWarning(err.message))
@ -232,16 +236,18 @@ function createNewVaultAndRestore (password, seed) {
function createNewVaultAndKeychain (password) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
background.createNewVaultAndKeychain(password, (err, newState) => {
if (global.METAMASK_DEBUG) console.log(`background.createNewVaultAndKeychain`)
background.createNewVaultAndKeychain(password, (err) => {
if (err) {
return dispatch(actions.displayWarning(err.message))
}
background.placeSeedWords((err, newState) => {
if (global.METAMASK_DEBUG) console.log(`background.placeSeedWords`)
background.placeSeedWords((err) => {
if (err) {
return dispatch(actions.displayWarning(err.message))
}
dispatch(actions.hideLoadingIndication())
dispatch(actions.updateMetamaskState(newState))
forceUpdateMetamaskState(dispatch)
})
})
}
@ -256,8 +262,10 @@ function revealSeedConfirmation () {
function requestRevealSeed (password) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
if (global.METAMASK_DEBUG) console.log(`background.submitPassword`)
background.submitPassword(password, (err) => {
if (err) return dispatch(actions.displayWarning(err.message))
if (global.METAMASK_DEBUG) console.log(`background.placeSeedWords`)
background.placeSeedWords((err) => {
if (err) return dispatch(actions.displayWarning(err.message))
dispatch(actions.hideLoadingIndication())
@ -269,6 +277,7 @@ function requestRevealSeed (password) {
function addNewKeyring (type, opts) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
if (global.METAMASK_DEBUG) console.log(`background.addNewKeyring`)
background.addNewKeyring(type, opts, (err) => {
dispatch(actions.hideLoadingIndication())
if (err) return dispatch(actions.displayWarning(err.message))
@ -280,13 +289,20 @@ function addNewKeyring (type, opts) {
function importNewAccount (strategy, args) {
return (dispatch) => {
dispatch(actions.showLoadingIndication('This may take a while, be patient.'))
background.importAccountWithStrategy(strategy, args, (err, newState) => {
if (global.METAMASK_DEBUG) console.log(`background.importAccountWithStrategy`)
background.importAccountWithStrategy(strategy, args, (err) => {
dispatch(actions.hideLoadingIndication())
if (err) return dispatch(actions.displayWarning(err.message))
dispatch(actions.updateMetamaskState(newState))
dispatch({
type: actions.SHOW_ACCOUNT_DETAIL,
value: newState.selectedAddress,
if (global.METAMASK_DEBUG) console.log(`background.getState`)
background.getState((err, newState) => {
if (err) {
return dispatch(actions.displayWarning(err.message))
}
dispatch(actions.updateMetamaskState(newState))
dispatch({
type: actions.SHOW_ACCOUNT_DETAIL,
value: newState.selectedAddress,
})
})
})
}
@ -299,6 +315,7 @@ function navigateToNewAccountScreen() {
}
function addNewAccount () {
if (global.METAMASK_DEBUG) console.log(`background.addNewAccount`)
return callBackgroundThenUpdate(background.addNewAccount)
}
@ -308,11 +325,16 @@ function showInfoPage () {
}
}
function setCurrentFiat (fiat) {
function setCurrentFiat (currencyCode) {
return (dispatch) => {
dispatch(this.showLoadingIndication())
background.setCurrentFiat(fiat, (data, err) => {
if (global.METAMASK_DEBUG) console.log(`background.setCurrentFiat`)
background.setCurrentCurrency(currencyCode, (err, data) => {
dispatch(this.hideLoadingIndication())
if (err) {
console.error(err.stack)
return dispatch(actions.displayWarning(err.message))
}
dispatch({
type: this.SET_CURRENT_FIAT,
value: {
@ -329,6 +351,7 @@ function signMsg (msgData) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
if (global.METAMASK_DEBUG) console.log(`background.signMessage`)
background.signMessage(msgData, (err) => {
dispatch(actions.hideLoadingIndication())
@ -340,6 +363,7 @@ function signMsg (msgData) {
function signTx (txData) {
return (dispatch) => {
if (global.METAMASK_DEBUG) console.log(`background.setGasMultiplier`)
background.setGasMultiplier(txData.gasMultiplier, (err) => {
if (err) return dispatch(actions.displayWarning(err.message))
web3.eth.sendTransaction(txData, (err, data) => {
@ -355,6 +379,7 @@ function signTx (txData) {
function sendTx (txData) {
return (dispatch) => {
if (global.METAMASK_DEBUG) console.log(`background.approveTransaction`)
background.approveTransaction(txData.id, (err) => {
if (err) {
alert(err.message)
@ -381,11 +406,13 @@ function txError (err) {
}
function cancelMsg (msgData) {
if (global.METAMASK_DEBUG) console.log(`background.cancelMessage`)
background.cancelMessage(msgData.id)
return actions.completedTx(msgData.id)
}
function cancelTx (txData) {
if (global.METAMASK_DEBUG) console.log(`background.cancelTransaction`)
background.cancelTransaction(txData.id)
return actions.completedTx(txData.id)
}
@ -427,6 +454,7 @@ function showImportPage () {
function agreeToDisclaimer () {
return (dispatch) => {
dispatch(this.showLoadingIndication())
if (global.METAMASK_DEBUG) console.log(`background.agreeToDisclaimer`)
background.agreeToDisclaimer((err) => {
if (err) {
return dispatch(actions.displayWarning(err.message))
@ -497,12 +525,14 @@ function updateMetamaskState (newState) {
}
function lockMetamask () {
if (global.METAMASK_DEBUG) console.log(`background.setLocked`)
return callBackgroundThenUpdate(background.setLocked)
}
function showAccountDetail (address) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
if (global.METAMASK_DEBUG) console.log(`background.setSelectedAddress`)
background.setSelectedAddress(address, (err) => {
dispatch(actions.hideLoadingIndication())
if (err) {
@ -575,6 +605,7 @@ function goBackToInitView () {
function markNoticeRead (notice) {
return (dispatch) => {
dispatch(this.showLoadingIndication())
if (global.METAMASK_DEBUG) console.log(`background.markNoticeRead`)
background.markNoticeRead(notice, (err, notice) => {
dispatch(this.hideLoadingIndication())
if (err) {
@ -606,6 +637,7 @@ function clearNotices () {
}
function markAccountsFound() {
if (global.METAMASK_DEBUG) console.log(`background.markAccountsFound`)
return callBackgroundThenUpdate(background.markAccountsFound)
}
@ -614,6 +646,7 @@ function markAccountsFound() {
//
function setRpcTarget (newRpc) {
if (global.METAMASK_DEBUG) console.log(`background.setRpcTarget`)
background.setRpcTarget(newRpc)
return {
type: actions.SET_RPC_TARGET,
@ -622,6 +655,7 @@ function setRpcTarget (newRpc) {
}
function setProviderType (type) {
if (global.METAMASK_DEBUG) console.log(`background.setProviderType`)
background.setProviderType(type)
return {
type: actions.SET_PROVIDER_TYPE,
@ -630,6 +664,7 @@ function setProviderType (type) {
}
function useEtherscanProvider () {
if (global.METAMASK_DEBUG) console.log(`background.useEtherscanProvider`)
background.useEtherscanProvider()
return {
type: actions.USE_ETHERSCAN_PROVIDER,
@ -686,6 +721,7 @@ function exportAccount (address) {
return function (dispatch) {
dispatch(self.showLoadingIndication())
if (global.METAMASK_DEBUG) console.log(`background.exportAccount`)
background.exportAccount(address, function (err, result) {
dispatch(self.hideLoadingIndication())
@ -709,6 +745,7 @@ function showPrivateKey (key) {
function saveAccountLabel (account, label) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
if (global.METAMASK_DEBUG) console.log(`background.saveAccountLabel`)
background.saveAccountLabel(account, label, (err) => {
dispatch(actions.hideLoadingIndication())
if (err) {
@ -730,6 +767,7 @@ function showSendPage () {
function buyEth (address, amount) {
return (dispatch) => {
if (global.METAMASK_DEBUG) console.log(`background.buyEth`)
background.buyEth(address, amount)
dispatch({
type: actions.BUY_ETH,
@ -808,6 +846,7 @@ function coinShiftRquest (data, marketData) {
if (response.error) return dispatch(actions.displayWarning(response.error))
var message = `
Deposit your ${response.depositType} to the address bellow:`
if (global.METAMASK_DEBUG) console.log(`background.createShapeShiftTx`)
background.createShapeShiftTx(response.deposit, response.depositType)
dispatch(actions.showQrView(response.deposit, [message].concat(marketData)))
})
@ -876,12 +915,22 @@ function shapeShiftRequest (query, options, cb) {
function callBackgroundThenUpdate (method, ...args) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
method.call(background, ...args, (err, newState) => {
method.call(background, ...args, (err) => {
dispatch(actions.hideLoadingIndication())
if (err) {
return dispatch(actions.displayWarning(err.message))
}
dispatch(actions.updateMetamaskState(newState))
forceUpdateMetamaskState(dispatch)
})
}
}
function forceUpdateMetamaskState(dispatch){
if (global.METAMASK_DEBUG) console.log(`background.getState`)
background.getState((err, newState) => {
if (err) {
return dispatch(actions.displayWarning(err.message))
}
dispatch(actions.updateMetamaskState(newState))
})
}