diff options
Merge branch 'master' into uiFixes
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | CHANGELOG.md | 7 | ||||
-rw-r--r-- | app/images/ethereum-network.jpg | bin | 0 -> 10807 bytes | |||
-rw-r--r-- | app/images/morden-test-network.jpg | bin | 0 -> 10517 bytes | |||
-rw-r--r-- | app/images/no-connection.jpg | bin | 0 -> 6946 bytes | |||
-rw-r--r-- | app/images/unknown-private-network.jpg | bin | 0 -> 3962 bytes | |||
-rw-r--r-- | app/manifest.json | 2 | ||||
-rw-r--r-- | app/scripts/background.js | 21 | ||||
-rw-r--r-- | app/scripts/lib/idStore.js | 27 | ||||
-rw-r--r-- | app/scripts/lib/notifications.js | 43 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | svg-notifications.md | 20 | ||||
-rw-r--r-- | test/unit/util_test.js | 7 | ||||
-rw-r--r-- | ui/app/actions.js | 35 | ||||
-rw-r--r-- | ui/app/app.js | 89 | ||||
-rw-r--r-- | ui/app/components/network.js | 65 | ||||
-rw-r--r-- | ui/app/config.js | 30 | ||||
-rw-r--r-- | ui/app/css/index.css | 4 | ||||
-rw-r--r-- | ui/app/css/lib.css | 19 | ||||
-rw-r--r-- | ui/app/first-time/create-vault-complete.js | 2 | ||||
-rw-r--r-- | ui/app/recover-seed/confirmation.js | 149 | ||||
-rw-r--r-- | ui/app/reducers/app.js | 20 | ||||
-rw-r--r-- | ui/app/util.js | 2 |
23 files changed, 477 insertions, 68 deletions
diff --git a/.gitignore b/.gitignore index 476b197db..2ad6b035f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ package .DS_Store builds/ +notes.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bf056b31..64200fe6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,16 @@ ## Current Master +- Show network status in title bar +- Added seed word recovery to config screen. +- Clicking network status indicator now reveals a provider menu. + +## 2.2.0 2016-06-02 + - Redesigned init, vault create, vault restore and seed confirmation screens. - Added pending transactions to transaction list on account screen. - Clicking a pending transaction takes you back to the transaction approval screen. +- Update provider-engine to fix intermittent out of gas errors. ## 2.1.0 2016-05-26 diff --git a/app/images/ethereum-network.jpg b/app/images/ethereum-network.jpg Binary files differnew file mode 100644 index 000000000..61cb000ed --- /dev/null +++ b/app/images/ethereum-network.jpg diff --git a/app/images/morden-test-network.jpg b/app/images/morden-test-network.jpg Binary files differnew file mode 100644 index 000000000..458708c78 --- /dev/null +++ b/app/images/morden-test-network.jpg diff --git a/app/images/no-connection.jpg b/app/images/no-connection.jpg Binary files differnew file mode 100644 index 000000000..a5d21242b --- /dev/null +++ b/app/images/no-connection.jpg diff --git a/app/images/unknown-private-network.jpg b/app/images/unknown-private-network.jpg Binary files differnew file mode 100644 index 000000000..b8a5a9bbf --- /dev/null +++ b/app/images/unknown-private-network.jpg diff --git a/app/manifest.json b/app/manifest.json index 5b1be504d..ce157bdf3 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "Metamask", - "version": "2.1.0", + "version": "2.2.0", "manifest_version": 2, "description": "__MSG_appDescription__", "icons": { diff --git a/app/scripts/background.js b/app/scripts/background.js index bfd1fc92b..f64209ecc 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -76,13 +76,20 @@ var providerOpts = { var provider = MetaMaskProvider(providerOpts) var web3 = new Web3(provider) idStore.web3 = web3 -idStore.getNetwork(3) +idStore.getNetwork() // log new blocks provider.on('block', function(block){ console.log('BLOCK CHANGED:', '#'+block.number.toString('hex'), '0x'+block.hash.toString('hex')) + + // Check network when restoring connectivity: + if (idStore._currentState.network === 'loading') { + idStore.getNetwork() + } }) +provider.on('error', idStore.getNetwork.bind(idStore)) + var ethStore = new EthStore(provider) idStore.setStore(ethStore) @@ -145,7 +152,7 @@ function setupPublicConfig(stream){ } function setupProviderConnection(stream, originDomain){ - + stream.on('data', function onRpcRequest(payload){ // Append origin to rpc payload payload.origin = originDomain @@ -195,6 +202,8 @@ function setupControllerConnection(stream){ exportAccount: idStore.exportAccount.bind(idStore), revealAccount: idStore.revealAccount.bind(idStore), saveAccountLabel: idStore.saveAccountLabel.bind(idStore), + tryPassword: idStore.tryPassword.bind(idStore), + recoverSeed: idStore.recoverSeed.bind(idStore), }) stream.pipe(dnode).pipe(stream) dnode.on('remote', function(remote){ @@ -246,7 +255,7 @@ function newUnsignedTransaction(txParams, cb){ }) var txId = idStore.addUnconfirmedTransaction(txParams, cb) } else { - addUnconfirmedTx(txParams, cb) + addUnconfirmedTx(txParams, cb) } } @@ -258,7 +267,7 @@ function newUnsignedMessage(msgParams, cb){ }) var msgId = idStore.addUnconfirmedMessage(msgParams, cb) } else { - addUnconfirmedMsg(msgParams, cb) + addUnconfirmedMsg(msgParams, cb) } } @@ -290,13 +299,13 @@ function addUnconfirmedMsg(msgParams, cb){ function setRpcTarget(rpcTarget){ configManager.setRpcTarget(rpcTarget) chrome.runtime.reload() - idStore.getNetwork(3) // 3 retry attempts + idStore.getNetwork() } function setProviderType(type) { configManager.setProviderType(type) chrome.runtime.reload() - idStore.getNetwork(3) + idStore.getNetwork() } function useEtherscanProvider() { diff --git a/app/scripts/lib/idStore.js b/app/scripts/lib/idStore.js index 4ce4fd6f2..33d842d54 100644 --- a/app/scripts/lib/idStore.js +++ b/app/scripts/lib/idStore.js @@ -59,6 +59,13 @@ IdentityStore.prototype.createNewVault = function(password, entropy, cb){ }) } +IdentityStore.prototype.recoverSeed = function(cb){ + configManager.setShowSeedWords(true) + if (!this._idmgmt) return cb(new Error('Unauthenticated. Please sign in.')) + var seedWords = this._idmgmt.getSeed() + cb(null, seedWords) +} + IdentityStore.prototype.recoverFromSeed = function(password, seed, cb){ this._createIdmgmt(password, seed, null, (err) => { if (err) return cb(err) @@ -130,16 +137,22 @@ IdentityStore.prototype.revealAccount = function(cb) { cb(null) } -IdentityStore.prototype.getNetwork = function(tries) { - if (tries === 0) { - this._currentState.network = 'error' - return +IdentityStore.prototype.getNetwork = function(err) { + + if (err) { + this._currentState.network = 'loading' + this._didUpdate() } + this.web3.version.getNetwork((err, network) => { if (err) { - return this.getNetwork(tries - 1, cb) + this._currentState.network = 'loading' + return this._didUpdate() } + + console.log('web3.getNetwork returned ' + network) this._currentState.network = network + this._didUpdate() }) } @@ -150,7 +163,7 @@ IdentityStore.prototype.setLocked = function(cb){ } IdentityStore.prototype.submitPassword = function(password, cb){ - this._tryPassword(password, (err) => { + this.tryPassword(password, (err) => { if (err) return cb(err) // load identities before returning... this._loadIdentities() @@ -366,7 +379,7 @@ IdentityStore.prototype._mayBeFauceting = function(i) { // keyStore managment - unlocking + deserialization // -IdentityStore.prototype._tryPassword = function(password, cb){ +IdentityStore.prototype.tryPassword = function(password, cb){ this._createIdmgmt(password, null, null, cb) } diff --git a/app/scripts/lib/notifications.js b/app/scripts/lib/notifications.js index d011d778b..90edaea12 100644 --- a/app/scripts/lib/notifications.js +++ b/app/scripts/lib/notifications.js @@ -8,24 +8,35 @@ module.exports = { createMsgNotification: createMsgNotification, } -// notification button press -chrome.notifications.onButtonClicked.addListener(function(notificationId, buttonIndex){ - var handlers = notificationHandlers[notificationId] - if (buttonIndex === 0) { - handlers.confirm() - } else { - handlers.cancel() - } - chrome.notifications.clear(notificationId) -}) +setupListeners() + +function setupListeners(){ + + // guard for chrome bug https://github.com/MetaMask/metamask-plugin/issues/236 + if (!chrome.notifications) return console.error('Chrome notifications API missing...') + + // notification button press + chrome.notifications.onButtonClicked.addListener(function(notificationId, buttonIndex){ + var handlers = notificationHandlers[notificationId] + if (buttonIndex === 0) { + handlers.confirm() + } else { + handlers.cancel() + } + chrome.notifications.clear(notificationId) + }) + + // notification teardown + chrome.notifications.onClosed.addListener(function(notificationId){ + delete notificationHandlers[notificationId] + }) -// notification teardown -chrome.notifications.onClosed.addListener(function(notificationId){ - delete notificationHandlers[notificationId] -}) +} // creation helper function createUnlockRequestNotification(opts){ + // guard for chrome bug https://github.com/MetaMask/metamask-plugin/issues/236 + if (!chrome.notifications) return console.error('Chrome notifications API missing...') var message = 'An Ethereum app has requested a signature. Please unlock your account.' var id = createId() @@ -39,6 +50,8 @@ function createUnlockRequestNotification(opts){ } function createTxNotification(opts){ + // guard for chrome bug https://github.com/MetaMask/metamask-plugin/issues/236 + if (!chrome.notifications) return console.error('Chrome notifications API missing...') var message = [ 'Submitted by '+opts.txParams.origin, 'to: '+uiUtils.addressSummary(opts.txParams.to), @@ -67,6 +80,8 @@ function createTxNotification(opts){ } function createMsgNotification(opts){ + // guard for chrome bug https://github.com/MetaMask/metamask-plugin/issues/236 + if (!chrome.notifications) return console.error('Chrome notifications API missing...') var message = [ 'Submitted by '+opts.msgParams.origin, 'to be signed by: '+uiUtils.addressSummary(opts.msgParams.from), diff --git a/package.json b/package.json index e9faea36c..af032a79a 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "through2": "^2.0.1", "vreme": "^3.0.2", "web3": "ethereum/web3.js#0.16.0", - "web3-provider-engine": "^7.7.0", + "web3-provider-engine": "^7.8.1", "web3-stream-provider": "^2.0.1", "xtend": "^4.0.1" }, diff --git a/svg-notifications.md b/svg-notifications.md index fd3b63f7a..577e7d07c 100644 --- a/svg-notifications.md +++ b/svg-notifications.md @@ -2,19 +2,9 @@ Chrome notifications allow you to show an SVG image via a data-uri Taking advantage of this might allow us to show nicely formatted notifications -Heres some utilities for preparing the data uri: - http://dopiaza.org/tools/datauri/index.php - provide text - no base64 - specify mime type: image/svg+xml - result should look like: - data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%0D%0A%20%20width%3D%271000px%27%20height%3D%27500px%27%20viewBox%3D%270%200%20200%20100%27%3E%0D%0A%20%20%3Crect%20x%3D%270%27%20y%3D%270%27%20width%3D%27100%25%27%20height%3D%27100%25%27%20fill%3D%27white%27%20%2F%3E%0D%0A%20%20%3Ctext%20x%3D%270%27%20y%3D%2720%27%20font-family%3D%27monospace%27%20font-size%3D%276%27%20fill%3D%27black%27%3E%0D%0A%20%20%20%20%3Ctspan%20x%3D%270%27%20dy%3D%271.2em%27%3EDomain%3A%20https%3A%2F%2Fboardroom.to%3C%2Ftspan%3E%0D%0A%20%20%20%20%3Ctspan%20x%3D%270%27%20dy%3D%271.2em%27%3EFrom%3A%20%200xabcdef%3C%2Ftspan%3E%0D%0A%20%20%20%20%3Ctspan%20x%3D%270%27%20dy%3D%271.2em%27%3ETo%3A%20%20%20%200xfedcba%3C%2Ftspan%3E%0D%0A%20%20%20%20%3Ctspan%20x%3D%270%27%20dy%3D%271.2em%27%3EValue%3A%201.025%20Ether%3C%2Ftspan%3E%0D%0A%20%20%20%20%3Ctspan%20x%3D%270%27%20dy%3D%271.2em%27%3EGas%3A%200.025%20Ether%3C%2Ftspan%3E%0D%0A%20%20%3C%2Ftext%3E%0D%0A%3C%2Fsvg%3E - build a template using pure svg: -generate uri -'data:image/svg+xml;charset=utf-8,'+encodeURIComponent(svgSrc) - +```svg <svg xmlns='http://www.w3.org/2000/svg' width='1000px' height='500px' viewBox='0 0 200 100'> <rect x='0' y='0' width='100%' height='100%' fill='white' /> @@ -26,9 +16,14 @@ generate uri <tspan x='0' dy='1.2em'>Gas: 0.025 Ether</tspan> </text> </svg> +``` + +generate uri +`'data:image/svg+xml;charset=utf-8,'+encodeURIComponent(svgSrc)` or svg-embedded html: +```svg <svg xmlns="http://www.w3.org/2000/svg" width="800" height="500"> <rect x='0' y='0' width='100%' height='100%' fill='white' /> <foreignObject class="node" x="46" y="22" width="200" height="300"> @@ -39,4 +34,5 @@ or svg-embedded html: </div> </body> </foreignObject> -</svg>
\ No newline at end of file +</svg> +``` diff --git a/test/unit/util_test.js b/test/unit/util_test.js index f003395b3..12a16999e 100644 --- a/test/unit/util_test.js +++ b/test/unit/util_test.js @@ -77,6 +77,13 @@ describe('util', function() { assert.ok(!result) }) + it('should recognize this sample hashed address', function() { + const address = '0x5Fda30Bb72B8Dfe20e48A00dFc108d0915BE9Bb0' + const result = util.isValidAddress(address) + const hashed = ethUtil.toChecksumAddress(address.toLowerCase()) + assert.equal(hashed, address, 'example is hashed correctly') + assert.ok(result, 'is valid by our check') + }) }) describe('numericBalance', function() { diff --git a/ui/app/actions.js b/ui/app/actions.js index ae6125b20..dd38c5f0a 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -6,6 +6,8 @@ var actions = { toggleMenu: toggleMenu, SET_MENU_STATE: 'SET_MENU_STATE', closeMenu: closeMenu, + getNetworkStatus: 'getNetworkStatus', + // remote state UPDATE_METAMASK_STATE: 'UPDATE_METAMASK_STATE', updateMetamaskState: updateMetamaskState, @@ -29,6 +31,10 @@ var actions = { createNewVaultInProgress: createNewVaultInProgress, showNewVaultSeed: showNewVaultSeed, showInfoPage: showInfoPage, + // seed recovery actions + REVEAL_SEED_CONFIRMATION: 'REVEAL_SEED_CONFIRMATION', + revealSeedConfirmation: revealSeedConfirmation, + requestRevealSeed: requestRevealSeed, // unlock screen UNLOCK_IN_PROGRESS: 'UNLOCK_IN_PROGRESS', UNLOCK_FAILED: 'UNLOCK_FAILED', @@ -131,6 +137,12 @@ function closeMenu() { } } +function getNetworkStatus(){ + return { + type: actions.getNetworkStatus, + } +} + // async actions function tryUnlockMetamask(password) { @@ -155,6 +167,26 @@ function createNewVault(password, entropy) { } } +function revealSeedConfirmation() { + return { + type: this.REVEAL_SEED_CONFIRMATION, + } +} + +function requestRevealSeed(password) { + return (dispatch) => { + dispatch(actions.showLoadingIndication()) + _accountManager.tryPassword(password, (err, seed) => { + dispatch(actions.hideLoadingIndication()) + if (err) return dispatch(actions.displayWarning(err.message)) + _accountManager.recoverSeed((err, seed) => { + if (err) return dispatch(actions.displayWarning(err.message)) + dispatch(actions.showNewVaultSeed(seed)) + }) + }) + } +} + function recoverFromSeed(password, seed) { return (dispatch) => { // dispatch(actions.createNewVaultInProgress()) @@ -402,9 +434,10 @@ function previousTx() { } } -function showConfigPage() { +function showConfigPage(transitionForward = true) { return { type: actions.SHOW_CONFIG_PAGE, + value: transitionForward, } } diff --git a/ui/app/app.js b/ui/app/app.js index 7e7ca24ad..446c743a9 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -21,12 +21,14 @@ const SendTransactionScreen = require('./send') const ConfirmTxScreen = require('./conf-tx') // other views const ConfigScreen = require('./config') +const RevealSeedConfirmation = require('./recover-seed/confirmation') const InfoScreen = require('./info') const LoadingIndicator = require('./loading') const txHelper = require('../lib/tx-helper') const SandwichExpando = require('sandwich-expando') const MenuDroppo = require('menu-droppo') const DropMenuItem = require('./components/drop-menu-item') +const NetworkIndicator = require('./components/network') module.exports = connect(mapStateToProps)(App) @@ -46,14 +48,14 @@ function mapStateToProps(state) { unconfTxs: state.metamask.unconfTxs, unconfMsgs: state.metamask.unconfMsgs, menuOpen: state.appState.menuOpen, + network: state.metamask.network, } } App.prototype.render = function() { - // const { selectedReddit, posts, isFetching, lastUpdated } = this.props - var state = this.props - var view = state.currentView.name - var transForward = state.transForward + var props = this.props + var view = props.currentView.name + var transForward = props.transForward return ( @@ -68,6 +70,7 @@ App.prototype.render = function() { // app bar this.renderAppBar(), + this.renderNetworkDropdown(), this.renderDropdown(), // panel content @@ -91,7 +94,9 @@ App.prototype.render = function() { } App.prototype.renderAppBar = function(){ - var state = this.props + const props = this.props + const state = this.state || {} + const isNetworkMenuOpen = state.isNetworkMenuOpen || false return ( @@ -100,30 +105,31 @@ App.prototype.renderAppBar = function(){ h('.app-header.flex-row.flex-space-between', { style: { alignItems: 'center', - visibility: state.isUnlocked ? 'visible' : 'none', - background: state.isUnlocked ? 'white' : 'none', + visibility: props.isUnlocked ? 'visible' : 'none', + background: props.isUnlocked ? 'white' : 'none', height: '36px', position: 'relative', zIndex: 1, }, - }, state.isUnlocked && [ + }, props.isUnlocked && [ - // mini logo - h('img', { - height: 24, - width: 24, - src: '/images/icon-128.png', + h(NetworkIndicator, { + network: this.props.network, + onClick:(event) => { + event.preventDefault() + event.stopPropagation() + this.setState({ isNetworkMenuOpen: !isNetworkMenuOpen }) + } }), // metamask name h('h1', 'MetaMask'), - // hamburger h(SandwichExpando, { width: 16, barHeight: 2, padding: 0, - isOpen: state.menuOpen, + isOpen: props.menuOpen, color: 'rgb(247,146,30)', onClick: (event) => { event.preventDefault() @@ -136,6 +142,56 @@ App.prototype.renderAppBar = function(){ ) } +App.prototype.renderNetworkDropdown = function() { + const props = this.props + const state = this.state || {} + const isOpen = state.isNetworkMenuOpen + + const checked = h('i.fa.fa-check.fa-lg', { ariaHidden: true }) + + return h(MenuDroppo, { + isOpen, + onClickOutside:(event) => { + this.setState({ isNetworkMenuOpen: !isOpen }) + }, + style: { + position: 'fixed', + left: 0, + zIndex: 0, + }, + innerStyle: { + background: 'white', + boxShadow: '1px 1px 2px rgba(0,0,0,0.1)', + }, + }, [ // DROP MENU ITEMS + h('style', ` + .drop-menu-item:hover { background:rgb(235, 235, 235); } + .drop-menu-item i { margin: 11px; } + `), + + h(DropMenuItem, { + label: 'Main Ethereum Network', + closeMenu:() => this.setState({ isNetworkMenuOpen: false }), + action:() => props.dispatch(actions.setProviderType('mainnet')), + icon: h('.menu-icon.ether-icon'), + }), + + h(DropMenuItem, { + label: 'Morden Test Network', + closeMenu:() => this.setState({ isNetworkMenuOpen: false }), + action:() => props.dispatch(actions.setProviderType('testnet')), + icon: h('.menu-icon.morden-icon'), + }), + + h(DropMenuItem, { + label: 'Localhost 8545', + closeMenu:() => this.setState({ isNetworkMenuOpen: false }), + action:() => props.dispatch(actions.setRpcTarget('http://localhost:8545')), + icon: h('i.fa.fa-question-circle.fa-lg', { ariaHidden: true }), + }), + ]) +} + App.prototype.renderDropdown = function() { const props = this.props return h(MenuDroppo, { @@ -232,6 +288,9 @@ App.prototype.renderPrimary = function(){ case 'config': return h(ConfigScreen, {key: 'config'}) + case 'reveal-seed-conf': + return h(RevealSeedConfirmation, {key: 'reveal-seed-conf'}) + case 'info': return h(InfoScreen, {key: 'info'}) diff --git a/ui/app/components/network.js b/ui/app/components/network.js new file mode 100644 index 000000000..5ad5f9351 --- /dev/null +++ b/ui/app/components/network.js @@ -0,0 +1,65 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits + +module.exports = Network + +inherits(Network, Component) + +function Network() { + Component.call(this) +} + +Network.prototype.render = function() { + const state = this.props + const networkNumber = state.network + let iconName, hoverText + const imagePath = "/images/" + + if (networkNumber == 'loading') { + return h('img', { + title: 'Attempting to connect to blockchain.', + style: { + width: '27px', + marginRight: '-27px' + }, + src: 'images/loading.svg', + }) + } else if (parseInt(networkNumber) == 1) { + hoverText = 'Main Ethereum Network' + iconName = 'ethereum-network' + }else if (parseInt(networkNumber) == 2) { + hoverText = "Morden Test Network" + iconName = 'morden-test-network' + }else { + hoverText = "Unknown Private Network" + iconName = 'unknown-private-network' + } + return ( + h('#network_component.flex-center.pointer', { + style: { + marginRight: '-27px', + marginLeft: '-3px', + }, + title: hoverText, + onClick:(event) => this.props.onClick(event), + },[ + function() { + switch (iconName) { + case 'ethereum-network': + return h('.menu-icon.ether-icon') + case 'morden-test-network': + return h('.menu-icon.morden-icon') + default: + return h('i.fa.fa-question-circle.fa-lg', { + ariaHidden: true, + style: { + margin: '10px', + color: 'rgb(125, 128, 130)', + }, + }) + } + }() + ]) + ) +} diff --git a/ui/app/config.js b/ui/app/config.js index ddf158325..c4d473b10 100644 --- a/ui/app/config.js +++ b/ui/app/config.js @@ -78,7 +78,7 @@ ConfigScreen.prototype.render = function() { ]), h('div', [ - h('button', { + h('button.spaced', { style: { alignSelf: 'center', }, @@ -86,11 +86,11 @@ ConfigScreen.prototype.render = function() { event.preventDefault() state.dispatch(actions.setProviderType('mainnet')) } - }, 'Use Main Network') + }, 'Use Main Network'), ]), h('div', [ - h('button', { + h('button.spaced', { style: { alignSelf: 'center', }, @@ -98,11 +98,11 @@ ConfigScreen.prototype.render = function() { event.preventDefault() state.dispatch(actions.setProviderType('testnet')) } - }, 'Use Morden Test Network') + }, 'Use Morden Test Network'), ]), h('div', [ - h('button', { + h('button.spaced', { style: { alignSelf: 'center', }, @@ -110,7 +110,25 @@ ConfigScreen.prototype.render = function() { event.preventDefault() state.dispatch(actions.setRpcTarget('http://localhost:8545/')) } - }, 'Use http://localhost:8545') + }, 'Use http://localhost:8545'), + ]), + + h('hr.horizontal-line'), + + h('div', { + style: { + marginTop: '20px', + } + }, [ + h('button', { + style: { + alignSelf: 'center', + }, + onClick(event) { + event.preventDefault() + state.dispatch(actions.revealSeedConfirmation()) + } + }, 'Reveal Seed Words') ]), ]), diff --git a/ui/app/css/index.css b/ui/app/css/index.css index fb2a1f552..2ee7c4094 100644 --- a/ui/app/css/index.css +++ b/ui/app/css/index.css @@ -45,6 +45,10 @@ button { transition: transform 50ms ease-in; } +button.spaced { + margin: 2px; +} + button:hover { transform: scale(1.1); } diff --git a/ui/app/css/lib.css b/ui/app/css/lib.css index 865d8060c..f17882b9b 100644 --- a/ui/app/css/lib.css +++ b/ui/app/css/lib.css @@ -199,3 +199,22 @@ hr.horizontal-line { display: flex; align-items: center; } + +.menu-icon { + display: inline-block; + width: 14px; + height: 14px; + margin: 13px; +} +.ether-icon { + background: rgb(0, 163, 68); + border-radius: 20px; +} +.morden-icon { + background: #2465E1; +} + +.drop-menu-item { + display: flex; + align-items: center; +} diff --git a/ui/app/first-time/create-vault-complete.js b/ui/app/first-time/create-vault-complete.js index 7d2db8003..9eceb4421 100644 --- a/ui/app/first-time/create-vault-complete.js +++ b/ui/app/first-time/create-vault-complete.js @@ -14,7 +14,7 @@ function CreateVaultCompleteScreen() { function mapStateToProps(state) { return { - seed: state.appState.currentView.context, + seed: state.appState.currentView.seedWords, cachedSeed: state.metamask.seedWords, } } diff --git a/ui/app/recover-seed/confirmation.js b/ui/app/recover-seed/confirmation.js new file mode 100644 index 000000000..0276d547d --- /dev/null +++ b/ui/app/recover-seed/confirmation.js @@ -0,0 +1,149 @@ +const inherits = require('util').inherits + +const Component = require('react').Component +const connect = require('react-redux').connect +const h = require('react-hyperscript') +const actions = require('../actions') + +module.exports = connect(mapStateToProps)(RevealSeedConfirmatoin) + + +inherits(RevealSeedConfirmatoin, Component) +function RevealSeedConfirmatoin() { + Component.call(this) +} + +function mapStateToProps(state) { + return { + warning: state.appState.warning, + } +} + +RevealSeedConfirmatoin.prototype.confirmationPhrase = 'I understand' + +RevealSeedConfirmatoin.prototype.render = function() { + const props = this.props + const state = this.state + + return ( + + h('.initialize-screen.flex-column.flex-center.flex-grow', [ + + h('h3.flex-center.text-transform-uppercase', { + style: { + background: '#EBEBEB', + color: '#AEAEAE', + marginBottom: 24, + width: '100%', + fontSize: '20px', + padding: 6, + }, + }, [ + 'Reveal Seed Words', + ]), + + h('.div', { + style: { + display: 'flex', + flexDirection: 'column', + padding: '20px', + justifyContent: 'center', + } + }, [ + + h('h4', 'Do not recover your seed words in a public place! These words can be used to steal all your accounts.'), + + // confirmation + h('input.large-input.letter-spacey', { + type: 'password', + id: 'password-box', + placeholder: 'Enter your password to confirm', + onKeyPress: this.checkConfirmation.bind(this), + style: { + width: 260, + marginTop: '12px', + }, + }), + + h(`h4${state && state.confirmationWrong ? '.error' : ''}`, { + style: { + marginTop: '12px', + } + }, `Enter the phrase "I understand" to proceed.`), + + // confirm confirmation + h('input.large-input.letter-spacey', { + type: 'text', + id: 'confirm-box', + placeholder: this.confirmationPhrase, + onKeyPress: this.checkConfirmation.bind(this), + style: { + width: 260, + marginTop: 16, + }, + }), + + h('.flex-row.flex-space-between', { + style: { + marginTop: 30, + width: '50%', + }, + }, [ +// cancel + h('button.primary', { + onClick: this.goHome.bind(this), + }, 'CANCEL'), + + // submit + h('button.primary', { + onClick: this.revealSeedWords.bind(this), + }, 'OK'), + + ]), + + (props.warning) && ( + h('span.error', { + style: { + margin: '20px', + } + }, props.warning.split('-')) + ), + + props.inProgress && ( + h('span.in-progress-notification', 'Generating Seed...') + ), + ]), + ]) + ) +} + +RevealSeedConfirmatoin.prototype.componentDidMount = function(){ + document.getElementById('password-box').focus() +} + +RevealSeedConfirmatoin.prototype.goHome = function() { + this.props.dispatch(actions.showConfigPage(false)) +} + +// create vault + +RevealSeedConfirmatoin.prototype.checkConfirmation = function(event) { + if (event.key === 'Enter') { + event.preventDefault() + this.revealSeedWords() + } +} + +RevealSeedConfirmatoin.prototype.revealSeedWords = function(){ + this.setState({ confirmationWrong: false }) + + const confirmBox = document.getElementById('confirm-box') + const confirmation = confirmBox.value + if (confirmation !== this.confirmationPhrase) { + confirmBox.value = '' + return this.setState({ confirmationWrong: true }) + } + + var password = document.getElementById('password-box').value + this.props.dispatch(actions.requestRevealSeed(password)) +} diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 08c2268c1..3ee9a61fe 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -25,10 +25,11 @@ function reduceApp(state, action) { } // confirm seed words + var seedWords = state.metamask.seedWords var seedConfView = { name: 'createVaultComplete', + seedWords, } - var seedWords = state.metamask.seedWords var appState = extend({ menuOpen: false, @@ -85,7 +86,7 @@ function reduceApp(state, action) { name: 'config', context: appState.currentView.context, }, - transForward: true, + transForward: action.value, }) case actions.SHOW_INFO_PAGE: @@ -111,7 +112,7 @@ function reduceApp(state, action) { return extend(appState, { currentView: { name: 'createVaultComplete', - context: action.value, + seedWords: action.value, }, transForward: true, isLoading: false, @@ -144,6 +145,18 @@ function reduceApp(state, action) { warning: null, }) + // reveal seed words + + case actions.REVEAL_SEED_CONFIRMATION: + return extend(appState, { + currentView: { + name: 'reveal-seed-conf', + }, + transForward: true, + warning: null, + }) + + // accounts case actions.SET_SELECTED_ACCOUNT: @@ -198,6 +211,7 @@ function reduceApp(state, action) { return extend(appState, { currentView: { name: seedWords ? 'createVaultComplete' : 'accounts', + seedWords, }, transForward: true, isLoading: false, diff --git a/ui/app/util.js b/ui/app/util.js index 91f85e43f..6ece28a9e 100644 --- a/ui/app/util.js +++ b/ui/app/util.js @@ -52,7 +52,7 @@ function addressSummary(address) { function isValidAddress(address) { var prefixed = ethUtil.addHexPrefix(address) - return isAllOneCase(prefixed) && ethUtil.isValidAddress(prefixed) || ethUtil.isValidChecksumAddress(prefixed) + return (isAllOneCase(prefixed) && ethUtil.isValidAddress(prefixed)) || ethUtil.isValidChecksumAddress(prefixed) } function isAllOneCase(address) { |