Merge pull request #1076 from MetaMask/messageManagerCleanUp

Message manager clean up
This commit is contained in:
kumavis 2017-02-01 15:08:04 -08:00 committed by GitHub
commit 3afa3592fe
22 changed files with 315 additions and 225 deletions

View File

@ -2,6 +2,7 @@
## Current Master ## Current Master
- Fix unapproved messages not being included in extension badge.
- Fix rendering bug where the Confirm transaction view would lets you approve transactions when the account has insufficient balance. - Fix rendering bug where the Confirm transaction view would lets you approve transactions when the account has insufficient balance.
- Add ability to import accounts in JSON file format (used by Mist, Geth, MyEtherWallet, and more!) - Add ability to import accounts in JSON file format (used by Mist, Geth, MyEtherWallet, and more!)

View File

@ -8,7 +8,6 @@ const Migrator = require('./lib/migrator/')
const migrations = require('./migrations/') const migrations = require('./migrations/')
const PortStream = require('./lib/port-stream.js') const PortStream = require('./lib/port-stream.js')
const notification = require('./lib/notifications.js') const notification = require('./lib/notifications.js')
const messageManager = require('./lib/message-manager')
const MetamaskController = require('./metamask-controller') const MetamaskController = require('./metamask-controller')
const extension = require('./lib/extension') const extension = require('./lib/extension')
const firstTimeState = require('./first-time-state') const firstTimeState = require('./first-time-state')
@ -112,14 +111,14 @@ function setupController (initState) {
updateBadge() updateBadge()
controller.txManager.on('updateBadge', updateBadge) controller.txManager.on('updateBadge', updateBadge)
controller.messageManager.on('updateBadge', updateBadge)
// plugin badge text // plugin badge text
function updateBadge () { function updateBadge () {
var label = '' var label = ''
var unapprovedTxCount = controller.txManager.unapprovedTxCount var unapprovedTxCount = controller.txManager.unapprovedTxCount
var unconfMsgs = messageManager.unconfirmedMsgs() var unapprovedMsgCount = controller.messageManager.unapprovedMsgCount
var unconfMsgLen = Object.keys(unconfMsgs).length var count = unapprovedTxCount + unapprovedMsgCount
var count = unapprovedTxCount + unconfMsgLen
if (count) { if (count) {
label = String(count) label = String(count)
} }
@ -145,4 +144,4 @@ extension.runtime.onInstalled.addListener(function (details) {
if ((details.reason === 'install') && (!METAMASK_DEBUG)) { if ((details.reason === 'install') && (!METAMASK_DEBUG)) {
extension.tabs.create({url: 'https://metamask.io/#how-it-works'}) extension.tabs.create({url: 'https://metamask.io/#how-it-works'})
} }
}) })

View File

@ -5,11 +5,7 @@ const EventEmitter = require('events').EventEmitter
const ObservableStore = require('obs-store') const ObservableStore = require('obs-store')
const filter = require('promise-filter') const filter = require('promise-filter')
const encryptor = require('browser-passworder') const encryptor = require('browser-passworder')
const createId = require('./lib/random-id')
const normalizeAddress = require('./lib/sig-util').normalize const normalizeAddress = require('./lib/sig-util').normalize
const messageManager = require('./lib/message-manager')
function noop () {}
// Keyrings: // Keyrings:
const SimpleKeyring = require('./keyrings/simple') const SimpleKeyring = require('./keyrings/simple')
const HdKeyring = require('./keyrings/hd') const HdKeyring = require('./keyrings/hd')
@ -18,7 +14,6 @@ const keyringTypes = [
HdKeyring, HdKeyring,
] ]
class KeyringController extends EventEmitter { class KeyringController extends EventEmitter {
// PUBLIC METHODS // PUBLIC METHODS
@ -42,9 +37,6 @@ class KeyringController extends EventEmitter {
this.ethStore = opts.ethStore this.ethStore = opts.ethStore
this.encryptor = encryptor this.encryptor = encryptor
this.keyrings = [] this.keyrings = []
this._unconfMsgCbs = {}
this.getNetwork = opts.getNetwork this.getNetwork = opts.getNetwork
} }
@ -78,6 +70,7 @@ class KeyringController extends EventEmitter {
// in this class, but will need to be Promisified when we move our // in this class, but will need to be Promisified when we move our
// persistence to an async model. // persistence to an async model.
getState () { getState () {
// old wallet // old wallet
const memState = this.memStore.getState() const memState = this.memStore.getState()
const result = { const result = {
@ -87,9 +80,6 @@ class KeyringController extends EventEmitter {
keyringTypes: memState.keyringTypes, keyringTypes: memState.keyringTypes,
identities: memState.identities, identities: memState.identities,
keyrings: memState.keyrings, keyrings: memState.keyrings,
// messageManager
unconfMsgs: messageManager.unconfirmedMsgs(),
messages: messageManager.getMsgList(),
// configManager // configManager
seedWords: this.configManager.getSeedWords(), seedWords: this.configManager.getSeedWords(),
isDisclaimerConfirmed: this.configManager.getConfirmedDisclaimer(), isDisclaimerConfirmed: this.configManager.getConfirmedDisclaimer(),
@ -284,86 +274,19 @@ class KeyringController extends EventEmitter {
return keyring.signTransaction(fromAddress, ethTx) return keyring.signTransaction(fromAddress, ethTx)
}) })
} }
// Add Unconfirmed Message
// @object msgParams
// @function cb
//
// Does not call back, only emits an `update` event.
//
// Adds the given `msgParams` and `cb` to a local cache,
// for displaying to a user for approval before signing or canceling.
addUnconfirmedMessage (msgParams, cb) {
// 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: 'unconfirmed',
}
messageManager.addMsg(msgData)
console.log('addUnconfirmedMessage:', msgData)
// keep the cb around for after approval (requires user interaction)
// This cb fires completion to the Dapp's write operation.
this._unconfMsgCbs[msgId] = cb
// signal update
this.emit('update')
return msgId
}
// Cancel Message
// @string msgId
// @function cb (optional)
//
// Calls back to cached `unconfMsgCb`.
// Calls back to `cb` if provided.
//
// Forgets any messages matching `msgId`.
cancelMessage (msgId, cb) {
var approvalCb = this._unconfMsgCbs[msgId] || noop
// reject tx
approvalCb(null, false)
// clean up
messageManager.rejectMsg(msgId)
delete this._unconfTxCbs[msgId]
if (cb && typeof cb === 'function') {
cb()
}
}
// Sign Message // Sign Message
// @object msgParams // @object msgParams
// @function cb
// //
// returns Promise(@buffer rawSig) // returns Promise(@buffer rawSig)
// calls back @function cb with @buffer rawSig
// calls back cached Dapp's @function unconfMsgCb.
// //
// Attempts to sign the provided @object msgParams. // Attempts to sign the provided @object msgParams.
signMessage (msgParams, cb) { signMessage (msgParams) {
try { const address = normalizeAddress(msgParams.from)
const msgId = msgParams.metamaskId return this.getKeyringForAccount(address)
delete msgParams.metamaskId .then((keyring) => {
const approvalCb = this._unconfMsgCbs[msgId] || noop return keyring.signMessage(address, msgParams.data)
})
const address = normalizeAddress(msgParams.from)
return this.getKeyringForAccount(address)
.then((keyring) => {
return keyring.signMessage(address, msgParams.data)
}).then((rawSig) => {
cb(null, rawSig)
approvalCb(null, true)
messageManager.confirmMsg(msgId)
return rawSig
})
} catch (e) {
cb(e)
}
} }
// PRIVATE METHODS // PRIVATE METHODS

View File

@ -1,61 +1,110 @@
module.exports = new MessageManager() const EventEmitter = require('events')
const ObservableStore = require('obs-store')
const createId = require('./random-id')
function MessageManager (opts) {
this.messages = []
}
MessageManager.prototype.getMsgList = function () { module.exports = class MessageManager extends EventEmitter{
return this.messages constructor (opts) {
} super()
this.memStore = new ObservableStore({ 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 }, {})
}
MessageManager.prototype._saveMsgList = function (msgList) {
this.messages = msgList
}
MessageManager.prototype.addMsg = function (msg) {
var messages = this.getMsgList()
messages.push(msg)
this._saveMsgList(messages)
}
MessageManager.prototype.getMsg = function (msgId) {
var messages = this.getMsgList()
var matching = messages.filter(msg => msg.id === msgId)
return matching.length > 0 ? matching[0] : null
}
MessageManager.prototype.confirmMsg = function (msgId) {
this._setMsgStatus(msgId, 'confirmed')
}
MessageManager.prototype.rejectMsg = function (msgId) {
this._setMsgStatus(msgId, 'rejected')
}
MessageManager.prototype._setMsgStatus = function (msgId, status) {
var msg = this.getMsg(msgId)
if (msg) msg.status = status
this.updateMsg(msg)
}
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
}
})
if (found) {
messages[index] = msg
} }
this._saveMsgList(messages)
}
getState() {
return {
unapprovedMsgs: this.getUnapprovedMsgs(),
messages: this.getMsgList(),
}
}
getMsgList () {
return this.memStore.getState().messages
}
get unapprovedMsgCount () {
return Object.keys(this.getUnapprovedMsgs()).length
}
getUnapprovedMsgs () {
let messages = this.getMsgList()
return messages.filter(msg => msg.status === 'unapproved')
.reduce((result, msg) => { result[msg.id] = msg; return result }, {})
}
addUnapprovedMessage (msgParams) {
// 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)
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')
return msgId
}
addMsg (msg) {
let messages = this.getMsgList()
messages.push(msg)
this._saveMsgList(messages)
}
getMsg (msgId) {
let messages = this.getMsgList()
let matching = messages.filter(msg => msg.id === msgId)
return matching.length > 0 ? matching[0] : null
}
approveMessage (msgParams) {
this.setMsgStatusApproved(msgParams.metamaskId)
return this.prepMsgForSigning(msgParams)
}
setMsgStatusApproved (msgId) {
this._setMsgStatus(msgId, 'approved')
}
prepMsgForSigning (msgParams) {
delete msgParams.metamaskId
return Promise.resolve(msgParams)
}
rejectMsg (msgId) {
this.brodcastMessage(null, msgId, 'rejected')
this._setMsgStatus(msgId, 'rejected')
}
brodcastMessage (rawSig, msgId, status) {
this.emit(`${msgId}:finished`, {status, rawSig})
}
// PRIVATE METHODS
_setMsgStatus (msgId, status) {
let msg = this.getMsg(msgId)
if (msg) msg.status = status
this._updateMsg(msg)
}
_updateMsg (msg) {
let messages = this.getMsgList()
let index = messages.findIndex((message) => message.id === msg.id)
if (index !== -1) {
messages[index] = msg
}
this._saveMsgList(messages)
}
_saveMsgList (msgList) {
this.memStore.updateState({ messages: msgList })
}
}

View File

@ -13,7 +13,7 @@ const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
const KeyringController = require('./keyring-controller') const KeyringController = require('./keyring-controller')
const PreferencesController = require('./lib/controllers/preferences') const PreferencesController = require('./lib/controllers/preferences')
const NoticeController = require('./notice-controller') const NoticeController = require('./notice-controller')
const messageManager = require('./lib/message-manager') const MessageManager = require('./lib/message-manager')
const TxManager = require('./transaction-manager') const TxManager = require('./transaction-manager')
const ConfigManager = require('./lib/config-manager') const ConfigManager = require('./lib/config-manager')
const extension = require('./lib/extension') const extension = require('./lib/extension')
@ -34,7 +34,7 @@ module.exports = class MetamaskController extends EventEmitter {
// observable state store // observable state store
this.store = new ObservableStore(initState) this.store = new ObservableStore(initState)
// config manager // config manager
this.configManager = new ConfigManager({ this.configManager = new ConfigManager({
store: this.store, store: this.store,
@ -54,7 +54,7 @@ module.exports = class MetamaskController extends EventEmitter {
// eth data query tools // eth data query tools
this.ethQuery = new EthQuery(this.provider) this.ethQuery = new EthQuery(this.provider)
this.ethStore = new EthStore(this.provider) this.ethStore = new EthStore(this.provider)
// key mgmt // key mgmt
this.keyringController = new KeyringController({ this.keyringController = new KeyringController({
initState: initState.KeyringController, initState: initState.KeyringController,
@ -79,7 +79,7 @@ module.exports = class MetamaskController extends EventEmitter {
provider: this.provider, provider: this.provider,
blockTracker: this.provider, blockTracker: this.provider,
}) })
// notices // notices
this.noticeController = new NoticeController({ this.noticeController = new NoticeController({
configManager: this.configManager, configManager: this.configManager,
@ -89,7 +89,7 @@ module.exports = class MetamaskController extends EventEmitter {
// this.noticeController.startPolling() // this.noticeController.startPolling()
this.getNetwork() this.getNetwork()
this.messageManager = messageManager this.messageManager = new MessageManager()
this.publicConfigStore = this.initPublicConfigStore() this.publicConfigStore = this.initPublicConfigStore()
this.checkTOSChange() this.checkTOSChange()
@ -105,6 +105,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.ethStore.on('update', this.sendUpdate.bind(this)) this.ethStore.on('update', this.sendUpdate.bind(this))
this.keyringController.on('update', this.sendUpdate.bind(this)) this.keyringController.on('update', this.sendUpdate.bind(this))
this.txManager.on('update', this.sendUpdate.bind(this)) this.txManager.on('update', this.sendUpdate.bind(this))
this.messageManager.on('update', this.sendUpdate.bind(this))
this.keyringController.store.subscribe((state) => { this.keyringController.store.subscribe((state) => {
this.store.updateState({ KeyringController: state }) this.store.updateState({ KeyringController: state })
}) })
@ -133,11 +134,7 @@ module.exports = class MetamaskController extends EventEmitter {
// tx signing // tx signing
processTransaction: (txParams, cb) => this.newUnapprovedTransaction(txParams, cb), processTransaction: (txParams, cb) => this.newUnapprovedTransaction(txParams, cb),
// msg signing // msg signing
approveMessage: this.newUnsignedMessage.bind(this), processMessage: this.newUnsignedMessage.bind(this),
signMessage: (...args) => {
this.keyringController.signMessage(...args)
this.sendUpdate()
},
}) })
return provider return provider
} }
@ -169,6 +166,7 @@ module.exports = class MetamaskController extends EventEmitter {
// //
getState () { getState () {
const wallet = this.configManager.getWallet() const wallet = this.configManager.getWallet()
const vault = this.keyringController.store.getState().vault const vault = this.keyringController.store.getState().vault
const isInitialized = (!!wallet || !!vault) const isInitialized = (!!wallet || !!vault)
@ -179,6 +177,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.state, this.state,
this.ethStore.getState(), this.ethStore.getState(),
this.txManager.getState(), this.txManager.getState(),
this.messageManager.getState(),
this.keyringController.getState(), this.keyringController.getState(),
this.preferencesController.store.getState(), this.preferencesController.store.getState(),
this.noticeController.getState(), this.noticeController.getState(),
@ -202,6 +201,7 @@ module.exports = class MetamaskController extends EventEmitter {
const keyringController = this.keyringController const keyringController = this.keyringController
const preferencesController = this.preferencesController const preferencesController = this.preferencesController
const txManager = this.txManager const txManager = this.txManager
const messageManager = this.messageManager
const noticeController = this.noticeController const noticeController = this.noticeController
return { return {
@ -221,7 +221,7 @@ module.exports = class MetamaskController extends EventEmitter {
buyEth: this.buyEth.bind(this), buyEth: this.buyEth.bind(this),
// shapeshift // shapeshift
createShapeShiftTx: this.createShapeShiftTx.bind(this), createShapeShiftTx: this.createShapeShiftTx.bind(this),
// primary HD keyring management // primary HD keyring management
addNewAccount: this.addNewAccount.bind(this), addNewAccount: this.addNewAccount.bind(this),
placeSeedWords: this.placeSeedWords.bind(this), placeSeedWords: this.placeSeedWords.bind(this),
@ -245,8 +245,8 @@ module.exports = class MetamaskController extends EventEmitter {
// signing methods // signing methods
approveTransaction: txManager.approveTransaction.bind(txManager), approveTransaction: txManager.approveTransaction.bind(txManager),
cancelTransaction: txManager.cancelTransaction.bind(txManager), cancelTransaction: txManager.cancelTransaction.bind(txManager),
signMessage: keyringController.signMessage.bind(keyringController), signMessage: this.signMessage.bind(this),
cancelMessage: keyringController.cancelMessage.bind(keyringController), cancelMessage: messageManager.rejectMsg.bind(messageManager),
// notices // notices
checkNotices: noticeController.updateNoticesList.bind(noticeController), checkNotices: noticeController.updateNoticesList.bind(noticeController),
@ -388,20 +388,37 @@ module.exports = class MetamaskController extends EventEmitter {
} }
newUnsignedMessage (msgParams, cb) { newUnsignedMessage (msgParams, cb) {
var state = this.keyringController.getState() let msgId = this.messageManager.addUnapprovedMessage(msgParams)
if (!state.isUnlocked) { this.sendUpdate()
this.keyringController.addUnconfirmedMessage(msgParams, cb) this.opts.showUnconfirmedMessage()
this.opts.unlockAccountMessage() this.messageManager.once(`${msgId}:finished`, (data) => {
} else { switch (data.status) {
this.addUnconfirmedMessage(msgParams, cb) case 'approved':
this.sendUpdate() return cb(null, data.rawSig)
} case 'rejected':
return cb(new Error('MetaMask Message Signature: User denied transaction signature.'))
default:
return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
}
})
} }
addUnconfirmedMessage (msgParams, cb) { signMessage (msgParams, cb) {
const keyringController = this.keyringController const msgId = msgParams.metamaskId
const msgId = keyringController.addUnconfirmedMessage(msgParams, cb) // sets the status op the message to 'approved'
this.opts.showUnconfirmedMessage(msgParams, msgId) // and removes the metamaskId for signing
return this.messageManager.approveMessage(msgParams)
.then((cleanMsgParams) => {
// signs the message
return this.keyringController.signMessage(cleanMsgParams)
})
.then((rawSig) => {
// tells the listener that the message has been signed
// and can be returned to the dapp
this.messageManager.brodcastMessage(rawSig, msgId, 'approved')
}).then(() => {
cb()
}).catch((err) => cb(err))
} }

View File

@ -28,7 +28,7 @@ module.exports = class TransactionManager extends EventEmitter {
var selectedAddress = this.getSelectedAddress() var selectedAddress = this.getSelectedAddress()
return { return {
transactions: this.getTxList(), transactions: this.getTxList(),
unconfTxs: this.getUnapprovedTxList(), unapprovedTxs: this.getUnapprovedTxList(),
selectedAddressTxList: this.getFilteredTxList({metamaskNetworkId: this.getNetwork(), from: selectedAddress}), selectedAddressTxList: this.getFilteredTxList({metamaskNetworkId: this.getNetwork(), from: selectedAddress}),
} }
} }

View File

@ -102,7 +102,7 @@
"valid-url": "^1.0.9", "valid-url": "^1.0.9",
"vreme": "^3.0.2", "vreme": "^3.0.2",
"web3": "0.17.0-beta", "web3": "0.17.0-beta",
"web3-provider-engine": "^8.4.0", "web3-provider-engine": "^8.5.0",
"web3-stream-provider": "^2.0.6", "web3-stream-provider": "^2.0.6",
"xtend": "^4.0.1" "xtend": "^4.0.1"
}, },

View File

@ -31,7 +31,7 @@ describe('tx confirmation screen', function() {
}, },
}, },
metamask: { metamask: {
unconfTxs: { unapprovedTxs: {
'1457634084250832': { '1457634084250832': {
id: 1457634084250832, id: 1457634084250832,
status: "unconfirmed", status: "unconfirmed",
@ -119,7 +119,7 @@ describe('tx confirmation screen', function() {
}, },
}, },
metamask: { metamask: {
unconfTxs: { unapprovedTxs: {
'1457634084250832': { '1457634084250832': {
id: 1457634084250832, id: 1457634084250832,
status: "unconfirmed", status: "unconfirmed",
@ -162,7 +162,7 @@ describe('tx confirmation screen', function() {
}); });
function getUnconfirmedTxCount(state) { function getUnconfirmedTxCount(state) {
var txs = state.metamask.unconfTxs var txs = state.metamask.unapprovedTxs
var count = Object.keys(txs).length var count = Object.keys(txs).length
return count return count
} }

View File

@ -0,0 +1,98 @@
const assert = require('assert')
const extend = require('xtend')
const EventEmitter = require('events')
const MessageManger = require('../../app/scripts/lib/message-manager')
describe('Transaction Manager', function() {
let messageManager
beforeEach(function() {
messageManager = new MessageManger ()
})
describe('#getMsgList', function() {
it('when new should return empty array', function() {
var result = messageManager.getMsgList()
assert.ok(Array.isArray(result))
assert.equal(result.length, 0)
})
it('should also return transactions from local storage if any', 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()
assert.ok(Array.isArray(result))
assert.equal(result.length, 1)
assert.equal(result[0].id, 1)
})
})
describe('#setMsgStatusApproved', function() {
it('sets the Msg status to approved', function() {
var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
messageManager.addMsg(Msg)
messageManager.setMsgStatusApproved(1)
var result = messageManager.getMsgList()
assert.ok(Array.isArray(result))
assert.equal(result.length, 1)
assert.equal(result[0].status, 'approved')
})
})
describe('#rejectMsg', function() {
it('sets the Msg status to rejected', function() {
var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' }
messageManager.addMsg(Msg)
messageManager.rejectMsg(1)
var result = messageManager.getMsgList()
assert.ok(Array.isArray(result))
assert.equal(result.length, 1)
assert.equal(result[0].status, 'rejected')
})
})
describe('#_updateMsg', function() {
it('replaces the Msg with the same id', function() {
messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' })
messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' })
messageManager._updateMsg({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: 'unit test' })
var result = messageManager.getMsg('1')
assert.equal(result.hash, 'foo')
})
})
describe('#getUnapprovedMsgs', function() {
it('returns unapproved Msgs in a hash', function() {
messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' })
messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' })
let result = messageManager.getUnapprovedMsgs()
assert.equal(typeof result, 'object')
assert.equal(result['1'].status, 'unapproved')
assert.equal(result['2'], undefined)
})
})
describe('#getMsg', function() {
it('returns a Msg with the requested id', function() {
messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' })
messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' })
assert.equal(messageManager.getMsg('1').status, 'unapproved')
assert.equal(messageManager.getMsg('2').status, 'approved')
})
})
})

View File

@ -27,7 +27,7 @@ function mapStateToProps (state) {
address: state.metamask.selectedAddress, address: state.metamask.selectedAddress,
accountDetail: state.appState.accountDetail, accountDetail: state.appState.accountDetail,
network: state.metamask.network, network: state.metamask.network,
unconfMsgs: valuesFor(state.metamask.unconfMsgs), unapprovedMsgs: valuesFor(state.metamask.unapprovedMsgs),
shapeShiftTxList: state.metamask.shapeShiftTxList, shapeShiftTxList: state.metamask.shapeShiftTxList,
transactions: state.metamask.selectedAddressTxList || [], transactions: state.metamask.selectedAddressTxList || [],
} }
@ -246,11 +246,11 @@ AccountDetailScreen.prototype.subview = function () {
} }
AccountDetailScreen.prototype.transactionList = function () { AccountDetailScreen.prototype.transactionList = function () {
const {transactions, unconfMsgs, address, network, shapeShiftTxList } = this.props const {transactions, unapprovedMsgs, address, network, shapeShiftTxList } = this.props
return h(TransactionList, { return h(TransactionList, {
transactions: transactions.sort((a, b) => b.time - a.time), transactions: transactions.sort((a, b) => b.time - a.time),
network, network,
unconfMsgs, unapprovedMsgs,
address, address,
shapeShiftTxList, shapeShiftTxList,
viewPendingTx: (txId) => { viewPendingTx: (txId) => {

View File

@ -10,15 +10,15 @@ const AccountListItem = require('./account-list-item')
module.exports = connect(mapStateToProps)(AccountsScreen) module.exports = connect(mapStateToProps)(AccountsScreen)
function mapStateToProps (state) { function mapStateToProps (state) {
const pendingTxs = valuesFor(state.metamask.unconfTxs) const pendingTxs = valuesFor(state.metamask.unapprovedTxs)
.filter(tx => tx.txParams.metamaskNetworkId === state.metamask.network) .filter(tx => tx.txParams.metamaskNetworkId === state.metamask.network)
const pendingMsgs = valuesFor(state.metamask.unconfMsgs) const pendingMsgs = valuesFor(state.metamask.unapprovedMsgs)
const pending = pendingTxs.concat(pendingMsgs) const pending = pendingTxs.concat(pendingMsgs)
return { return {
accounts: state.metamask.accounts, accounts: state.metamask.accounts,
identities: state.metamask.identities, identities: state.metamask.identities,
unconfTxs: state.metamask.unconfTxs, unapprovedTxs: state.metamask.unapprovedTxs,
selectedAddress: state.metamask.selectedAddress, selectedAddress: state.metamask.selectedAddress,
scrollToBottom: state.appState.scrollToBottom, scrollToBottom: state.appState.scrollToBottom,
pending, pending,
@ -35,7 +35,7 @@ AccountsScreen.prototype.render = function () {
const props = this.props const props = this.props
const { keyrings } = props const { keyrings } = props
const identityList = valuesFor(props.identities) const identityList = valuesFor(props.identities)
const unconfTxList = valuesFor(props.unconfTxs) const unapprovedTxList = valuesFor(props.unapprovedTxs)
return ( return (
@ -107,7 +107,7 @@ AccountsScreen.prototype.render = function () {
h('hr.horizontal-line'), h('hr.horizontal-line'),
]), ]),
unconfTxList.length ? ( unapprovedTxList.length ? (
h('.unconftx-link.flex-row.flex-center', { h('.unconftx-link.flex-row.flex-center', {
onClick: this.navigateToConfTx.bind(this), onClick: this.navigateToConfTx.bind(this),

View File

@ -52,8 +52,8 @@ function mapStateToProps (state) {
activeAddress: state.appState.activeAddress, activeAddress: state.appState.activeAddress,
transForward: state.appState.transForward, transForward: state.appState.transForward,
seedWords: state.metamask.seedWords, seedWords: state.metamask.seedWords,
unconfTxs: state.metamask.unconfTxs, unapprovedTxs: state.metamask.unapprovedTxs,
unconfMsgs: state.metamask.unconfMsgs, unapprovedMsgs: state.metamask.unapprovedMsgs,
menuOpen: state.appState.menuOpen, menuOpen: state.appState.menuOpen,
network: state.metamask.network, network: state.metamask.network,
provider: state.metamask.provider, provider: state.metamask.provider,

View File

@ -15,15 +15,9 @@ TransactionIcon.prototype.render = function () {
const { transaction, txParams, isMsg } = this.props const { transaction, txParams, isMsg } = this.props
switch (transaction.status) { switch (transaction.status) {
case 'unapproved': case 'unapproved':
return h('.unapproved-tx', { return h( !isMsg ? '.unapproved-tx-icon' : 'i.fa.fa-certificate.fa-lg', {
style: { style: {
width: '24px', width: '24px',
height: '24px',
background: '#4dffff',
border: 'solid',
borderColor: '#AEAEAE',
borderWidth: '0.5px',
borderRadius: '13px',
}, },
}) })

View File

@ -33,7 +33,6 @@ TransactionListItem.prototype.render = function () {
var isMsg = ('msgParams' in transaction) var isMsg = ('msgParams' in transaction)
var isTx = ('txParams' in transaction) var isTx = ('txParams' in transaction)
var isPending = transaction.status === 'unapproved' var isPending = transaction.status === 'unapproved'
let txParams let txParams
if (isTx) { if (isTx) {
txParams = transaction.txParams txParams = transaction.txParams

View File

@ -13,13 +13,13 @@ function TransactionList () {
} }
TransactionList.prototype.render = function () { TransactionList.prototype.render = function () {
const { transactions, network, unconfMsgs } = this.props const { transactions, network, unapprovedMsgs } = this.props
var shapeShiftTxList var shapeShiftTxList
if (network === '1') { if (network === '1') {
shapeShiftTxList = this.props.shapeShiftTxList shapeShiftTxList = this.props.shapeShiftTxList
} }
const txsToRender = !shapeShiftTxList ? transactions.concat(unconfMsgs) : transactions.concat(unconfMsgs, shapeShiftTxList) const txsToRender = !shapeShiftTxList ? transactions.concat(unapprovedMsgs) : transactions.concat(unapprovedMsgs, shapeShiftTxList)
.sort((a, b) => b.time - a.time) .sort((a, b) => b.time - a.time)
return ( return (

View File

@ -20,8 +20,8 @@ function mapStateToProps (state) {
identities: state.metamask.identities, identities: state.metamask.identities,
accounts: state.metamask.accounts, accounts: state.metamask.accounts,
selectedAddress: state.metamask.selectedAddress, selectedAddress: state.metamask.selectedAddress,
unconfTxs: state.metamask.unconfTxs, unapprovedTxs: state.metamask.unapprovedTxs,
unconfMsgs: state.metamask.unconfMsgs, unapprovedMsgs: state.metamask.unapprovedMsgs,
index: state.appState.currentView.context, index: state.appState.currentView.context,
warning: state.appState.warning, warning: state.appState.warning,
network: state.metamask.network, network: state.metamask.network,
@ -39,10 +39,10 @@ ConfirmTxScreen.prototype.render = function () {
var network = state.network var network = state.network
var provider = state.provider var provider = state.provider
var unconfTxs = state.unconfTxs var unapprovedTxs = state.unapprovedTxs
var unconfMsgs = state.unconfMsgs var unapprovedMsgs = state.unapprovedMsgs
var unconfTxList = txHelper(unconfTxs, unconfMsgs, network) var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, network)
var index = state.index !== undefined && unconfTxList[index] ? state.index : 0 var index = state.index !== undefined && unconfTxList[index] ? state.index : 0
var txData = unconfTxList[index] || {} var txData = unconfTxList[index] || {}
var txParams = txData.params || {} var txParams = txData.params || {}

View File

@ -408,6 +408,16 @@ input.large-input {
.name-label{ .name-label{
} }
.unapproved-tx-icon {
height: 24px;
background: #4dffff;
border: solid;
border-color: #AEAEAE;
border-width: 0.5px;
border-radius: 13px;
}
.edit-text { .edit-text {
height: 100%; height: 100%;
visibility: hidden; visibility: hidden;

View File

@ -307,11 +307,11 @@ function reduceApp (state, action) {
}) })
case actions.COMPLETED_TX: case actions.COMPLETED_TX:
var unconfTxs = state.metamask.unconfTxs var unapprovedTxs = state.metamask.unapprovedTxs
var unconfMsgs = state.metamask.unconfMsgs var unapprovedMsgs = state.metamask.unapprovedMsgs
var network = state.metamask.network var network = state.metamask.network
var unconfTxList = txHelper(unconfTxs, unconfMsgs, network) var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, network)
.filter(tx => tx !== tx.id) .filter(tx => tx !== tx.id)
if (unconfTxList && unconfTxList.length > 0) { if (unconfTxList && unconfTxList.length > 0) {
@ -572,18 +572,18 @@ function reduceApp (state, action) {
} }
function hasPendingTxs (state) { function hasPendingTxs (state) {
var unconfTxs = state.metamask.unconfTxs var unapprovedTxs = state.metamask.unapprovedTxs
var unconfMsgs = state.metamask.unconfMsgs var unapprovedMsgs = state.metamask.unapprovedMsgs
var network = state.metamask.network var network = state.metamask.network
var unconfTxList = txHelper(unconfTxs, unconfMsgs, network) var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, network)
return unconfTxList.length > 0 return unconfTxList.length > 0
} }
function indexForPending (state, txId) { function indexForPending (state, txId) {
var unconfTxs = state.metamask.unconfTxs var unapprovedTxs = state.metamask.unapprovedTxs
var unconfMsgs = state.metamask.unconfMsgs var unapprovedMsgs = state.metamask.unapprovedMsgs
var network = state.metamask.network var network = state.metamask.network
var unconfTxList = txHelper(unconfTxs, unconfMsgs, network) var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, network)
let idx let idx
unconfTxList.forEach((tx, i) => { unconfTxList.forEach((tx, i) => {
if (tx.id === txId) { if (tx.id === txId) {

View File

@ -12,7 +12,7 @@ function reduceMetamask (state, action) {
isUnlocked: false, isUnlocked: false,
rpcTarget: 'https://rawtestrpc.metamask.io/', rpcTarget: 'https://rawtestrpc.metamask.io/',
identities: {}, identities: {},
unconfTxs: {}, unapprovedTxs: {},
currentFiat: 'USD', currentFiat: 'USD',
conversionRate: 0, conversionRate: 0,
conversionDate: 'N/A', conversionDate: 'N/A',
@ -76,17 +76,17 @@ function reduceMetamask (state, action) {
case actions.COMPLETED_TX: case actions.COMPLETED_TX:
var stringId = String(action.id) var stringId = String(action.id)
newState = extend(metamaskState, { newState = extend(metamaskState, {
unconfTxs: {}, unapprovedTxs: {},
unconfMsgs: {}, unapprovedMsgs: {},
}) })
for (const id in metamaskState.unconfTxs) { for (const id in metamaskState.unapprovedTxs) {
if (id !== stringId) { if (id !== stringId) {
newState.unconfTxs[id] = metamaskState.unconfTxs[id] newState.unapprovedTxs[id] = metamaskState.unapprovedTxs[id]
} }
} }
for (const id in metamaskState.unconfMsgs) { for (const id in metamaskState.unapprovedMsgs) {
if (id !== stringId) { if (id !== stringId) {
newState.unconfMsgs[id] = metamaskState.unconfMsgs[id] newState.unapprovedMsgs[id] = metamaskState.unapprovedMsgs[id]
} }
} }
return newState return newState

View File

@ -29,7 +29,7 @@ var identities = {
}, },
} }
var unconfTxs = {} var unapprovedTxs = {}
addUnconfTx({ addUnconfTx({
from: '0x222462427bcc9133bb46e88bcbe39cd7ef0e7222', from: '0x222462427bcc9133bb46e88bcbe39cd7ef0e7222',
to: '0x1113462427bcc9133bb46e88bcbe39cd7ef0e111', to: '0x1113462427bcc9133bb46e88bcbe39cd7ef0e111',
@ -45,7 +45,7 @@ addUnconfTx({
function addUnconfTx (txParams) { function addUnconfTx (txParams) {
var time = (new Date()).getTime() var time = (new Date()).getTime()
var id = createRandomId() var id = createRandomId()
unconfTxs[id] = { unapprovedTxs[id] = {
id: id, id: id,
txParams: txParams, txParams: txParams,
time: time, time: time,
@ -59,7 +59,7 @@ function getState () {
return { return {
isUnlocked: isUnlocked, isUnlocked: isUnlocked,
identities: isUnlocked ? identities : {}, identities: isUnlocked ? identities : {},
unconfTxs: isUnlocked ? unconfTxs : {}, unapprovedTxs: isUnlocked ? unapprovedTxs : {},
selectedAccount: selectedAccount, selectedAccount: selectedAccount,
} }
} }

View File

@ -32,8 +32,8 @@ function startApp (metamaskState, accountManager, opts) {
}) })
// if unconfirmed txs, start on txConf page // if unconfirmed txs, start on txConf page
var unconfirmedTxsAll = txHelper(metamaskState.unconfTxs, metamaskState.unconfMsgs, metamaskState.network) var unapprovedTxsAll = txHelper(metamaskState.unapprovedTxs, metamaskState.unapprovedMsgs, metamaskState.network)
if (unconfirmedTxsAll.length > 0) { if (unapprovedTxsAll.length > 0) {
store.dispatch(actions.showConfTxPage()) store.dispatch(actions.showConfTxPage())
} }

View File

@ -1,8 +1,8 @@
const valuesFor = require('../app/util').valuesFor const valuesFor = require('../app/util').valuesFor
module.exports = function (unconfTxs, unconfMsgs, network) { module.exports = function (unapprovedTxs, unapprovedMsgs, network) {
var txValues = network ? valuesFor(unconfTxs).filter(tx => tx.txParams.metamaskNetworkId === network) : valuesFor(unconfTxs) var txValues = network ? valuesFor(unapprovedTxs).filter(tx => tx.txParams.metamaskNetworkId === network) : valuesFor(unapprovedTxs)
var msgValues = valuesFor(unconfMsgs) var msgValues = valuesFor(unapprovedMsgs)
var allValues = txValues.concat(msgValues) var allValues = txValues.concat(msgValues)
return allValues.sort(tx => tx.time) return allValues.sort(tx => tx.time)
} }