commit
1816eca914
@ -1,10 +1,18 @@ |
|||||||
const promiseToCallback = require('promise-to-callback') |
const promiseToCallback = require('promise-to-callback') |
||||||
|
const noop = function(){} |
||||||
|
|
||||||
module.exports = function nodeify (fn, context) { |
module.exports = function nodeify (fn, context) { |
||||||
return function(){ |
return function(){ |
||||||
const args = [].slice.call(arguments) |
const args = [].slice.call(arguments) |
||||||
const callback = args.pop() |
const lastArg = args[args.length - 1] |
||||||
if (typeof callback !== 'function') throw new Error('callback is not a function') |
const lastArgIsCallback = typeof lastArg === 'function' |
||||||
|
let callback |
||||||
|
if (lastArgIsCallback) { |
||||||
|
callback = lastArg |
||||||
|
args.pop() |
||||||
|
} else { |
||||||
|
callback = noop |
||||||
|
} |
||||||
promiseToCallback(fn.apply(context, args))(callback) |
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 |
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('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) |
const txValues = network ? valuesFor(unapprovedTxs).filter(txMeta => txMeta.metamaskNetworkId === network) : valuesFor(unapprovedTxs) |
||||||
log.debug(`tx helper found ${txValues.length} unapproved txs`) |
log.debug(`tx helper found ${txValues.length} unapproved txs`) |
||||||
|
|
||||||
const msgValues = valuesFor(unapprovedMsgs) |
const msgValues = valuesFor(unapprovedMsgs) |
||||||
log.debug(`tx helper found ${msgValues.length} unsigned messages`) |
log.debug(`tx helper found ${msgValues.length} unsigned messages`) |
||||||
let allValues = txValues.concat(msgValues) |
let allValues = txValues.concat(msgValues) |
||||||
|
|
||||||
const personalValues = valuesFor(personalMsgs) |
const personalValues = valuesFor(personalMsgs) |
||||||
log.debug(`tx helper found ${personalValues.length} unsigned personal messages`) |
log.debug(`tx helper found ${personalValues.length} unsigned personal messages`) |
||||||
allValues = allValues.concat(personalValues) |
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) => { |
allValues = allValues.sort((a, b) => { |
||||||
return a.time > b.time |
return a.time > b.time |
||||||
}) |
}) |
||||||
|
|
||||||
return allValues |
return allValues |
||||||
} |
} |
Loading…
Reference in new issue