commit
1816eca914
@ -1,10 +1,18 @@ |
||||
const promiseToCallback = require('promise-to-callback') |
||||
const noop = function(){} |
||||
|
||||
module.exports = function nodeify (fn, context) { |
||||
return function(){ |
||||
const args = [].slice.call(arguments) |
||||
const callback = args.pop() |
||||
if (typeof callback !== 'function') throw new Error('callback is not a function') |
||||
const lastArg = args[args.length - 1] |
||||
const lastArgIsCallback = typeof lastArg === 'function' |
||||
let callback |
||||
if (lastArgIsCallback) { |
||||
callback = lastArg |
||||
args.pop() |
||||
} else { |
||||
callback = noop |
||||
} |
||||
promiseToCallback(fn.apply(context, args))(callback) |
||||
} |
||||
} |
||||
|
@ -0,0 +1,123 @@ |
||||
const EventEmitter = require('events') |
||||
const ObservableStore = require('obs-store') |
||||
const createId = require('./random-id') |
||||
const assert = require('assert') |
||||
const sigUtil = require('eth-sig-util') |
||||
|
||||
|
||||
module.exports = class TypedMessageManager extends EventEmitter { |
||||
constructor (opts) { |
||||
super() |
||||
this.memStore = new ObservableStore({ |
||||
unapprovedTypedMessages: {}, |
||||
unapprovedTypedMessagesCount: 0, |
||||
}) |
||||
this.messages = [] |
||||
} |
||||
|
||||
get unapprovedTypedMessagesCount () { |
||||
return Object.keys(this.getUnapprovedMsgs()).length |
||||
} |
||||
|
||||
getUnapprovedMsgs () { |
||||
return this.messages.filter(msg => msg.status === 'unapproved') |
||||
.reduce((result, msg) => { result[msg.id] = msg; return result }, {}) |
||||
} |
||||
|
||||
addUnapprovedMessage (msgParams) { |
||||
this.validateParams(msgParams) |
||||
|
||||
log.debug(`TypedMessageManager addUnapprovedMessage: ${JSON.stringify(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', |
||||
type: 'eth_signTypedData', |
||||
} |
||||
this.addMsg(msgData) |
||||
|
||||
// signal update
|
||||
this.emit('update') |
||||
return msgId |
||||
} |
||||
|
||||
validateParams (params) { |
||||
assert.equal(typeof params, 'object', 'Params should ben an object.') |
||||
assert.ok('data' in params, 'Params must include a data field.') |
||||
assert.ok('from' in params, 'Params must include a from field.') |
||||
assert.ok(Array.isArray(params.data), 'Data should be an array.') |
||||
assert.equal(typeof params.from, 'string', 'From field must be a string.') |
||||
assert.doesNotThrow(() => { |
||||
sigUtil.typedSignatureHash(params.data) |
||||
}, 'Expected EIP712 typed data') |
||||
} |
||||
|
||||
addMsg (msg) { |
||||
this.messages.push(msg) |
||||
this._saveMsgList() |
||||
} |
||||
|
||||
getMsg (msgId) { |
||||
return this.messages.find(msg => msg.id === msgId) |
||||
} |
||||
|
||||
approveMessage (msgParams) { |
||||
this.setMsgStatusApproved(msgParams.metamaskId) |
||||
return this.prepMsgForSigning(msgParams) |
||||
} |
||||
|
||||
setMsgStatusApproved (msgId) { |
||||
this._setMsgStatus(msgId, 'approved') |
||||
} |
||||
|
||||
setMsgStatusSigned (msgId, rawSig) { |
||||
const msg = this.getMsg(msgId) |
||||
msg.rawSig = rawSig |
||||
this._updateMsg(msg) |
||||
this._setMsgStatus(msgId, 'signed') |
||||
} |
||||
|
||||
prepMsgForSigning (msgParams) { |
||||
delete msgParams.metamaskId |
||||
return Promise.resolve(msgParams) |
||||
} |
||||
|
||||
rejectMsg (msgId) { |
||||
this._setMsgStatus(msgId, 'rejected') |
||||
} |
||||
|
||||
//
|
||||
// PRIVATE METHODS
|
||||
//
|
||||
|
||||
_setMsgStatus (msgId, status) { |
||||
const msg = this.getMsg(msgId) |
||||
if (!msg) throw new Error('TypedMessageManager - Message not found for id: "${msgId}".') |
||||
msg.status = status |
||||
this._updateMsg(msg) |
||||
this.emit(`${msgId}:${status}`, msg) |
||||
if (status === 'rejected' || status === 'signed') { |
||||
this.emit(`${msgId}:finished`, msg) |
||||
} |
||||
} |
||||
|
||||
_updateMsg (msg) { |
||||
const index = this.messages.findIndex((message) => message.id === msg.id) |
||||
if (index !== -1) { |
||||
this.messages[index] = msg |
||||
} |
||||
this._saveMsgList() |
||||
} |
||||
|
||||
_saveMsgList () { |
||||
const unapprovedTypedMessages = this.getUnapprovedMsgs() |
||||
const unapprovedTypedMessagesCount = Object.keys(unapprovedTypedMessages).length |
||||
this.memStore.updateState({ unapprovedTypedMessages, unapprovedTypedMessagesCount }) |
||||
this.emit('updateBadge') |
||||
} |
||||
|
||||
} |
@ -0,0 +1,59 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
|
||||
const AccountPanel = require('./account-panel') |
||||
const TypedMessageRenderer = require('./typed-message-renderer') |
||||
|
||||
module.exports = PendingMsgDetails |
||||
|
||||
inherits(PendingMsgDetails, Component) |
||||
function PendingMsgDetails () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
PendingMsgDetails.prototype.render = function () { |
||||
var state = this.props |
||||
var msgData = state.txData |
||||
|
||||
var msgParams = msgData.msgParams || {} |
||||
var address = msgParams.from || state.selectedAddress |
||||
var identity = state.identities[address] || { address: address } |
||||
var account = state.accounts[address] || { address: address } |
||||
|
||||
var { data } = msgParams |
||||
|
||||
return ( |
||||
h('div', { |
||||
key: msgData.id, |
||||
style: { |
||||
margin: '10px 20px', |
||||
}, |
||||
}, [ |
||||
|
||||
// account that will sign
|
||||
h(AccountPanel, { |
||||
showFullAddress: true, |
||||
identity: identity, |
||||
account: account, |
||||
imageifyIdenticons: state.imageifyIdenticons, |
||||
}), |
||||
|
||||
// message data
|
||||
h('div', { |
||||
style: { |
||||
height: '260px', |
||||
}, |
||||
}, [ |
||||
h('label.font-small', { style: { display: 'block' } }, 'YOU ARE SIGNING'), |
||||
h(TypedMessageRenderer, { |
||||
value: data, |
||||
style: { |
||||
height: '215px', |
||||
}, |
||||
}), |
||||
]), |
||||
|
||||
]) |
||||
) |
||||
} |
@ -0,0 +1,46 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
const PendingTxDetails = require('./pending-typed-msg-details') |
||||
|
||||
module.exports = PendingMsg |
||||
|
||||
inherits(PendingMsg, Component) |
||||
function PendingMsg () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
PendingMsg.prototype.render = function () { |
||||
var state = this.props |
||||
var msgData = state.txData |
||||
|
||||
return ( |
||||
|
||||
h('div', { |
||||
key: msgData.id, |
||||
}, [ |
||||
|
||||
// header
|
||||
h('h3', { |
||||
style: { |
||||
fontWeight: 'bold', |
||||
textAlign: 'center', |
||||
}, |
||||
}, 'Sign Message'), |
||||
|
||||
// message details
|
||||
h(PendingTxDetails, state), |
||||
|
||||
// sign + cancel
|
||||
h('.flex-row.flex-space-around', [ |
||||
h('button', { |
||||
onClick: state.cancelTypedMessage, |
||||
}, 'Cancel'), |
||||
h('button', { |
||||
onClick: state.signTypedMessage, |
||||
}, 'Sign'), |
||||
]), |
||||
]) |
||||
|
||||
) |
||||
} |
@ -0,0 +1,42 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
const extend = require('xtend') |
||||
|
||||
module.exports = TypedMessageRenderer |
||||
|
||||
inherits(TypedMessageRenderer, Component) |
||||
function TypedMessageRenderer () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
TypedMessageRenderer.prototype.render = function () { |
||||
const props = this.props |
||||
const { value, style } = props |
||||
const text = renderTypedData(value) |
||||
|
||||
const defaultStyle = extend({ |
||||
width: '315px', |
||||
maxHeight: '210px', |
||||
resize: 'none', |
||||
border: 'none', |
||||
background: 'white', |
||||
padding: '3px', |
||||
overflow: 'scroll', |
||||
}, style) |
||||
|
||||
return ( |
||||
h('div.font-small', { |
||||
style: defaultStyle, |
||||
}, text) |
||||
) |
||||
} |
||||
|
||||
function renderTypedData(values) { |
||||
return values.map(function (value) { |
||||
return h('div', {}, [ |
||||
h('strong', {style: {display: 'block', fontWeight: 'bold'}}, String(value.name) + ':'), |
||||
h('div', {}, value.value), |
||||
]) |
||||
}) |
||||
} |
@ -1,20 +1,27 @@ |
||||
const valuesFor = require('../app/util').valuesFor |
||||
|
||||
module.exports = function (unapprovedTxs, unapprovedMsgs, personalMsgs, network) { |
||||
module.exports = function (unapprovedTxs, unapprovedMsgs, personalMsgs, typedMessages, network) { |
||||
log.debug('tx-helper called with params:') |
||||
log.debug({ unapprovedTxs, unapprovedMsgs, personalMsgs, network }) |
||||
log.debug({ unapprovedTxs, unapprovedMsgs, personalMsgs, typedMessages, network }) |
||||
|
||||
const txValues = network ? valuesFor(unapprovedTxs).filter(txMeta => txMeta.metamaskNetworkId === network) : valuesFor(unapprovedTxs) |
||||
log.debug(`tx helper found ${txValues.length} unapproved txs`) |
||||
|
||||
const msgValues = valuesFor(unapprovedMsgs) |
||||
log.debug(`tx helper found ${msgValues.length} unsigned messages`) |
||||
let allValues = txValues.concat(msgValues) |
||||
|
||||
const personalValues = valuesFor(personalMsgs) |
||||
log.debug(`tx helper found ${personalValues.length} unsigned personal messages`) |
||||
allValues = allValues.concat(personalValues) |
||||
|
||||
const typedValues = valuesFor(typedMessages) |
||||
log.debug(`tx helper found ${typedValues.length} unsigned typed messages`) |
||||
allValues = allValues.concat(typedValues) |
||||
|
||||
allValues = allValues.sort((a, b) => { |
||||
return a.time > b.time |
||||
}) |
||||
|
||||
return allValues |
||||
} |
||||
} |
Loading…
Reference in new issue