Merge branch 'master' into fox-sub

This commit is contained in:
Kevin Serrano 2016-08-23 14:51:09 -07:00
commit 4bceda37a8
17 changed files with 186 additions and 18 deletions

1
.gitignore vendored
View File

@ -13,3 +13,4 @@ builds/
notes.txt notes.txt
app/.DS_Store app/.DS_Store
development/bundle.js development/bundle.js
builds.zip

View File

@ -3,6 +3,8 @@
## Current Master ## Current Master
- Added static image as fallback for when WebGL isn't supported. - Added static image as fallback for when WebGL isn't supported.
- Transaction history now has a hard limit.
- Added info link on account screen that visits Etherscan.
## 2.9.0 2016-08-22 ## 2.9.0 2016-08-22

View File

@ -0,0 +1,10 @@
{
"appName": {
"message": "MetaMask",
"description": "The name of the application"
},
"appDescription": {
"message": "Administración de identidad en Ethereum",
"description": "The description of the application"
}
}

View File

@ -0,0 +1,10 @@
{
"appName": {
"message": "MetaMask",
"description": "The name of the application"
},
"appDescription": {
"message": "Administración de identidad en Ethereum",
"description": "The description of the application"
}
}

View File

@ -0,0 +1,10 @@
{
"appName": {
"message": "MetaMask",
"description": "The name of the application"
},
"appDescription": {
"message": "以太坊身份管理",
"description": "The description of the application"
}
}

View File

@ -54,7 +54,7 @@ var __define
function cleanContextForImports () { function cleanContextForImports () {
__define = global.define __define = global.define
try { try {
delete global.define global.define = undefined
} catch (_) { } catch (_) {
console.warn('MetaMask - global.define could not be deleted.') console.warn('MetaMask - global.define could not be deleted.')
} }

View File

@ -5,6 +5,7 @@ const rp = require('request-promise')
const TESTNET_RPC = MetamaskConfig.network.testnet const TESTNET_RPC = MetamaskConfig.network.testnet
const MAINNET_RPC = MetamaskConfig.network.mainnet const MAINNET_RPC = MetamaskConfig.network.mainnet
const txLimit = 40
/* The config-manager is a convenience object /* The config-manager is a convenience object
* wrapping a pojo-migrator. * wrapping a pojo-migrator.
@ -15,6 +16,8 @@ const MAINNET_RPC = MetamaskConfig.network.mainnet
*/ */
module.exports = ConfigManager module.exports = ConfigManager
function ConfigManager (opts) { function ConfigManager (opts) {
this.txLimit = txLimit
// ConfigManager is observable and will emit updates // ConfigManager is observable and will emit updates
this._subs = [] this._subs = []
@ -181,6 +184,9 @@ ConfigManager.prototype._saveTxList = function (txList) {
ConfigManager.prototype.addTx = function (tx) { ConfigManager.prototype.addTx = function (tx) {
var transactions = this.getTxList() var transactions = this.getTxList()
while (transactions.length > this.txLimit - 1) {
transactions.shift()
}
transactions.push(tx) transactions.push(tx)
this._saveTxList(transactions) this._saveTxList(transactions)
} }

View File

@ -33,8 +33,16 @@ function MetamaskInpageProvider (connectionStream) {
}) })
asyncProvider.on('error', console.error.bind(console)) asyncProvider.on('error', console.error.bind(console))
self.asyncProvider = asyncProvider self.asyncProvider = asyncProvider
// overwrite own sendAsync method // handle sendAsync requests via asyncProvider
self.sendAsync = asyncProvider.sendAsync.bind(asyncProvider) self.sendAsync = function(payload, cb){
// rewrite request ids
var request = jsonrpcMessageTransform(payload, (message) => {
message.id = createRandomId()
return message
})
// forward to asyncProvider
asyncProvider.sendAsync(request, cb)
}
} }
MetamaskInpageProvider.prototype.send = function (payload) { MetamaskInpageProvider.prototype.send = function (payload) {
@ -92,3 +100,21 @@ function remoteStoreWithLocalStorageCache (storageKey) {
return store return store
} }
function createRandomId(){
const extraDigits = 3
// 13 time digits
const datePart = new Date().getTime() * Math.pow(10, extraDigits)
// 3 random digits
const extraPart = Math.floor(Math.random() * Math.pow(10, extraDigits))
// 16 digits
return datePart + extraPart
}
function jsonrpcMessageTransform(payload, transformFn){
if (Array.isArray(payload)) {
return payload.map(transformFn)
} else {
return transformFn(payload)
}
}

View File

@ -5,6 +5,9 @@
"private": true, "private": true,
"scripts": { "scripts": {
"start": "gulp dev", "start": "gulp dev",
"lint": "gulp lint",
"dev": "gulp dev",
"dist": "gulp dist",
"test": "npm run fastTest && npm run ci", "test": "npm run fastTest && npm run ci",
"fastTest": "mocha --require test/helper.js --compilers js:babel-register --recursive \"test/unit/**/*.js\"", "fastTest": "mocha --require test/helper.js --compilers js:babel-register --recursive \"test/unit/**/*.js\"",
"watch": "mocha watch --compilers js:babel-register --recursive \"test/unit/**/*.js\"", "watch": "mocha watch --compilers js:babel-register --recursive \"test/unit/**/*.js\"",

View File

@ -0,0 +1,12 @@
var assert = require('assert')
var linkGen = require('../../ui/lib/account-link')
describe('account-link', function() {
it('adds testnet prefix to morden test network', function() {
var result = linkGen('account', '2')
assert.notEqual(result.indexOf('testnet'), -1, 'testnet injected')
assert.notEqual(result.indexOf('account'), -1, 'account included')
})
})

View File

@ -233,6 +233,17 @@ describe('config-manager', function() {
assert.equal(result.length, 1) assert.equal(result.length, 1)
assert.equal(result[0].id, 1) assert.equal(result[0].id, 1)
}) })
it('cuts off early txs beyond a limit', function() {
const limit = configManager.txLimit
for (let i = 0; i < limit + 1; i++) {
let tx = { id: i }
configManager.addTx(tx)
}
var result = configManager.getTxList()
assert.equal(result.length, limit, `limit of ${limit} txs enforced`)
assert.equal(result[0].id, 1, 'early txs truncted')
})
}) })
describe('#confirmTx', function() { describe('#confirmTx', function() {

View File

@ -4,6 +4,7 @@ const Component = require('react').Component
const h = require('react-hyperscript') const h = require('react-hyperscript')
const connect = require('react-redux').connect const connect = require('react-redux').connect
const CopyButton = require('./components/copyButton') const CopyButton = require('./components/copyButton')
const AccountInfoLink = require('./components/account-info-link')
const actions = require('./actions') const actions = require('./actions')
const ReactCSSTransitionGroup = require('react-addons-css-transition-group') const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
const valuesFor = require('./util').valuesFor const valuesFor = require('./util').valuesFor
@ -44,6 +45,7 @@ AccountDetailScreen.prototype.render = function () {
var selected = props.address || Object.keys(props.accounts)[0] var selected = props.address || Object.keys(props.accounts)[0]
var identity = props.identities[selected] var identity = props.identities[selected]
var account = props.accounts[selected] var account = props.accounts[selected]
const { network } = props
return ( return (
@ -127,6 +129,9 @@ AccountDetailScreen.prototype.render = function () {
bottom: '15px', bottom: '15px',
}, },
}, [ }, [
h(AccountInfoLink, { selected, network }),
h(CopyButton, { h(CopyButton, {
value: ethUtil.toChecksumAddress(selected), value: ethUtil.toChecksumAddress(selected),
}), }),
@ -136,16 +141,15 @@ AccountDetailScreen.prototype.render = function () {
}, [ }, [
h('div', { h('div', {
style: { style: {
margin: '5px', display: 'flex',
alignItems: 'center',
}, },
}, [ }, [
h('img.cursor-pointer.color-orange', { h('img.cursor-pointer.color-orange', {
src: 'images/key-32.png', src: 'images/key-32.png',
onClick: () => this.requestAccountExport(selected), onClick: () => this.requestAccountExport(selected),
style: { style: {
margin: '0px 5px', height: '19px',
width: '20px',
height: '20px',
}, },
}), }),
]), ]),
@ -244,6 +248,7 @@ AccountDetailScreen.prototype.transactionList = function () {
network, network,
unconfTxs, unconfTxs,
unconfMsgs, unconfMsgs,
address,
shapeShiftTxList, shapeShiftTxList,
viewPendingTx: (txId) => { viewPendingTx: (txId) => {
this.props.dispatch(actions.viewPendingTx(txId)) this.props.dispatch(actions.viewPendingTx(txId))

View File

@ -0,0 +1,42 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const Tooltip = require('./tooltip')
const genAccountLink = require('../../lib/account-link')
const extension = require('../../../app/scripts/lib/extension')
module.exports = AccountInfoLink
inherits(AccountInfoLink, Component)
function AccountInfoLink () {
Component.call(this)
}
AccountInfoLink.prototype.render = function () {
const { selected, network } = this.props
const title = 'View account on etherscan'
const url = genAccountLink(selected, network)
if (!url) {
return null
}
return h('.account-info-link', {
style: {
display: 'flex',
alignItems: 'center',
},
}, [
h(Tooltip, {
title,
}, [
h('i.fa.fa-info-circle.cursor-pointer.color-orange', {
style: {
margin: '5px',
},
onClick () { extension.tabs.create({ url }) },
}),
]),
])
}

View File

@ -26,6 +26,7 @@ function ShiftListItem () {
} }
ShiftListItem.prototype.render = function () { ShiftListItem.prototype.render = function () {
return ( return (
h('.transaction-list-item.flex-row', { h('.transaction-list-item.flex-row', {
style: { style: {
@ -113,7 +114,6 @@ ShiftListItem.prototype.renderUtilComponents = function () {
default: default:
return '' return ''
} }
} }
ShiftListItem.prototype.renderInfo = function () { ShiftListItem.prototype.renderInfo = function () {

View File

@ -19,7 +19,7 @@ function TransactionListItem () {
} }
TransactionListItem.prototype.render = function () { TransactionListItem.prototype.render = function () {
const { transaction, i, network } = this.props const { transaction, network } = this.props
if (transaction.key === 'shapeshift') { if (transaction.key === 'shapeshift') {
if (network === '1') return h(ShiftListItem, transaction) if (network === '1') return h(ShiftListItem, transaction)
} }
@ -44,7 +44,6 @@ TransactionListItem.prototype.render = function () {
return ( return (
h(`.transaction-list-item.flex-row.flex-space-between${isClickable ? '.pointer' : ''}`, { h(`.transaction-list-item.flex-row.flex-space-between${isClickable ? '.pointer' : ''}`, {
key: `tx-${transaction.id + i}`,
onClick: (event) => { onClick: (event) => {
if (isPending) { if (isPending) {
this.props.showTx(transaction.id) this.props.showTx(transaction.id)

View File

@ -15,7 +15,7 @@ function TransactionList () {
TransactionList.prototype.render = function () { TransactionList.prototype.render = function () {
const { txsToRender, network, unconfMsgs } = this.props const { txsToRender, network, unconfMsgs } = this.props
var shapeShiftTxList var shapeShiftTxList
if (network === '1'){ if (network === '1') {
shapeShiftTxList = this.props.shapeShiftTxList shapeShiftTxList = this.props.shapeShiftTxList
} }
const transactions = !shapeShiftTxList ? txsToRender.concat(unconfMsgs) : txsToRender.concat(unconfMsgs, shapeShiftTxList) const transactions = !shapeShiftTxList ? txsToRender.concat(unconfMsgs) : txsToRender.concat(unconfMsgs, shapeShiftTxList)
@ -43,33 +43,46 @@ TransactionList.prototype.render = function () {
paddingBottom: '4px', paddingBottom: '4px',
}, },
}, [ }, [
'Transactions', 'History',
]), ]),
h('.tx-list', { h('.tx-list', {
style: { style: {
overflowY: 'auto', overflowY: 'auto',
height: '305px', height: '300px',
padding: '0 20px', padding: '0 20px',
textAlign: 'center', textAlign: 'center',
}, },
}, ( }, [
transactions.length transactions.length
? transactions.map((transaction, i) => { ? transactions.map((transaction, i) => {
let key
switch (transaction.key) {
case 'shapeshift':
const { depositAddress, time } = transaction
key = `shift-tx-${depositAddress}-${time}-${i}`
break
default:
key = `tx-${transaction.id}-${i}`
}
return h(TransactionListItem, { return h(TransactionListItem, {
transaction, i, network, transaction, i, network, key,
showTx: (txId) => { showTx: (txId) => {
this.props.viewPendingTx(txId) this.props.viewPendingTx(txId)
}, },
}) })
}) })
: [h('.flex-center', { : h('.flex-center', {
style: { style: {
flexDirection: 'column',
height: '100%', height: '100%',
}, },
}, 'No transaction history...')] }, [
)), 'No transaction history.',
]),
]),
]) ])
) )
} }

18
ui/lib/account-link.js Normal file
View File

@ -0,0 +1,18 @@
module.exports = function(address, network) {
const net = parseInt(network)
let link
switch (net) {
case 1: // main net
link = `http://etherscan.io/address/${address}`
break
case 2: // morden test net
link = `http://testnet.etherscan.io/address/${address}`
break
default:
link = ''
break
}
return link
}