commit
2c4426e4de
@ -0,0 +1,33 @@ |
||||
const clone = require('clone') |
||||
|
||||
module.exports = getObjStructure |
||||
|
||||
// This will create an object that represents the structure of the given object
|
||||
// it replaces all values with the result of their type
|
||||
|
||||
// {
|
||||
// "data": {
|
||||
// "CurrencyController": {
|
||||
// "conversionDate": "number",
|
||||
// "conversionRate": "number",
|
||||
// "currentCurrency": "string"
|
||||
// }
|
||||
// }
|
||||
|
||||
function getObjStructure(obj) { |
||||
const structure = clone(obj) |
||||
return deepMap(structure, (value) => { |
||||
return value === null ? 'null' : typeof value |
||||
}) |
||||
} |
||||
|
||||
function deepMap(target = {}, visit) { |
||||
Object.entries(target).forEach(([key, value]) => { |
||||
if (typeof value === 'object' && value !== null) { |
||||
target[key] = deepMap(value, visit) |
||||
} else { |
||||
target[key] = visit(value) |
||||
} |
||||
}) |
||||
return target |
||||
} |
@ -0,0 +1,41 @@ |
||||
|
||||
const version = 24 |
||||
|
||||
/* |
||||
|
||||
This migration ensures that the from address in txParams is to lower case for |
||||
all unapproved transactions |
||||
|
||||
*/ |
||||
|
||||
const clone = require('clone') |
||||
|
||||
module.exports = { |
||||
version, |
||||
|
||||
migrate: async function (originalVersionedData) { |
||||
const versionedData = clone(originalVersionedData) |
||||
versionedData.meta.version = version |
||||
const state = versionedData.data |
||||
const newState = transformState(state) |
||||
versionedData.data = newState |
||||
return versionedData |
||||
}, |
||||
} |
||||
|
||||
function transformState (state) { |
||||
const newState = state |
||||
if (!newState.TransactionController) return newState |
||||
const transactions = newState.TransactionController.transactions |
||||
newState.TransactionController.transactions = transactions.map((txMeta, _, txList) => { |
||||
if ( |
||||
txMeta.status === 'unapproved' && |
||||
txMeta.txParams && |
||||
txMeta.txParams.from |
||||
) { |
||||
txMeta.txParams.from = txMeta.txParams.from.toLowerCase() |
||||
} |
||||
return txMeta |
||||
}) |
||||
return newState |
||||
} |
@ -0,0 +1,61 @@ |
||||
// next version number
|
||||
const version = 25 |
||||
|
||||
/* |
||||
|
||||
normalizes txParams on unconfirmed txs |
||||
|
||||
*/ |
||||
const ethUtil = require('ethereumjs-util') |
||||
const clone = require('clone') |
||||
|
||||
module.exports = { |
||||
version, |
||||
|
||||
migrate: async function (originalVersionedData) { |
||||
const versionedData = clone(originalVersionedData) |
||||
versionedData.meta.version = version |
||||
const state = versionedData.data |
||||
const newState = transformState(state) |
||||
versionedData.data = newState |
||||
return versionedData |
||||
}, |
||||
} |
||||
|
||||
function transformState (state) { |
||||
const newState = state |
||||
|
||||
if (newState.TransactionController) { |
||||
if (newState.TransactionController.transactions) { |
||||
const transactions = newState.TransactionController.transactions |
||||
newState.TransactionController.transactions = transactions.map((txMeta) => { |
||||
if (txMeta.status !== 'unapproved') return txMeta |
||||
txMeta.txParams = normalizeTxParams(txMeta.txParams) |
||||
return txMeta |
||||
}) |
||||
} |
||||
} |
||||
|
||||
return newState |
||||
} |
||||
|
||||
function normalizeTxParams (txParams) { |
||||
// functions that handle normalizing of that key in txParams
|
||||
const whiteList = { |
||||
from: from => ethUtil.addHexPrefix(from).toLowerCase(), |
||||
to: to => ethUtil.addHexPrefix(txParams.to).toLowerCase(), |
||||
nonce: nonce => ethUtil.addHexPrefix(nonce), |
||||
value: value => ethUtil.addHexPrefix(value), |
||||
data: data => ethUtil.addHexPrefix(data), |
||||
gas: gas => ethUtil.addHexPrefix(gas), |
||||
gasPrice: gasPrice => ethUtil.addHexPrefix(gasPrice), |
||||
} |
||||
|
||||
// apply only keys in the whiteList
|
||||
const normalizedTxParams = {} |
||||
Object.keys(whiteList).forEach((key) => { |
||||
if (txParams[key]) normalizedTxParams[key] = whiteList[key](txParams[key]) |
||||
}) |
||||
|
||||
return normalizedTxParams |
||||
} |
@ -0,0 +1,29 @@ |
||||
// next version number
|
||||
const version = 0 |
||||
|
||||
/* |
||||
|
||||
description of migration and what it does |
||||
|
||||
*/ |
||||
|
||||
const clone = require('clone') |
||||
|
||||
module.exports = { |
||||
version, |
||||
|
||||
migrate: async function (originalVersionedData) { |
||||
const versionedData = clone(originalVersionedData) |
||||
versionedData.meta.version = version |
||||
const state = versionedData.data |
||||
const newState = transformState(state) |
||||
versionedData.data = newState |
||||
return versionedData |
||||
}, |
||||
} |
||||
|
||||
function transformState (state) { |
||||
const newState = state |
||||
// transform state here
|
||||
return newState |
||||
} |
@ -1,51 +1,62 @@ |
||||
#!/usr/bin/env node
|
||||
const request = require('request-promise') |
||||
const { version } = require('../dist/chrome/manifest.json') |
||||
|
||||
const GITHUB_COMMENT_TOKEN = process.env.GITHUB_COMMENT_TOKEN |
||||
const CIRCLE_PULL_REQUEST = process.env.CIRCLE_PULL_REQUEST |
||||
console.log('CIRCLE_PULL_REQUEST', CIRCLE_PULL_REQUEST) |
||||
const CIRCLE_SHA1 = process.env.CIRCLE_SHA1 |
||||
console.log('CIRCLE_SHA1', CIRCLE_SHA1) |
||||
const CIRCLE_BUILD_NUM = process.env.CIRCLE_BUILD_NUM |
||||
console.log('CIRCLE_BUILD_NUM', CIRCLE_BUILD_NUM) |
||||
|
||||
const CIRCLE_PR_NUMBER = CIRCLE_PULL_REQUEST.split('/').pop() |
||||
const SHORT_SHA1 = CIRCLE_SHA1.slice(0,7) |
||||
const BUILD_LINK_BASE = `https://${CIRCLE_BUILD_NUM}-42009758-gh.circle-artifacts.com/0` |
||||
|
||||
const MASCARA = `${BUILD_LINK_BASE}/builds/mascara/home.html` |
||||
const CHROME = `${BUILD_LINK_BASE}/builds/metamask-chrome-${version}.zip` |
||||
const FIREFOX = `${BUILD_LINK_BASE}/builds/metamask-firefox-${version}.zip` |
||||
const EDGE = `${BUILD_LINK_BASE}/builds/metamask-edge-${version}.zip` |
||||
const OPERA = `${BUILD_LINK_BASE}/builds/metamask-opera-${version}.zip` |
||||
const WALKTHROUGH = `${BUILD_LINK_BASE}/test-artifacts/screens/walkthrough%20%28en%29.gif` |
||||
|
||||
const commentBody = ` |
||||
<details> |
||||
<summary> |
||||
Builds ready [${SHORT_SHA1}]: |
||||
<a href="${MASCARA}">mascara</a>, |
||||
<a href="${CHROME}">chrome</a>, |
||||
<a href="${FIREFOX}">firefox</a>, |
||||
<a href="${EDGE}">edge</a>, |
||||
<a href="${OPERA}">opera</a> |
||||
</summary> |
||||
<image src="${WALKTHROUGH}"> |
||||
</details> |
||||
` |
||||
|
||||
const JSON_PAYLOAD = JSON.stringify({ body: commentBody }) |
||||
const POST_COMMENT_URI = `https://api.github.com/repos/metamask/metamask-extension/issues/${CIRCLE_PR_NUMBER}/comments` |
||||
console.log(`Announcement:\n${commentBody}`) |
||||
console.log(`Posting to: ${POST_COMMENT_URI}`) |
||||
|
||||
request({ |
||||
method: 'POST', |
||||
uri: POST_COMMENT_URI, |
||||
body: JSON_PAYLOAD, |
||||
headers: { |
||||
'User-Agent': 'metamaskbot', |
||||
'Authorization': `token ${GITHUB_COMMENT_TOKEN}`, |
||||
}, |
||||
}) |
||||
const VERSION = require('../dist/chrome/manifest.json').version |
||||
|
||||
start().catch(console.error) |
||||
|
||||
async function start() { |
||||
|
||||
const GITHUB_COMMENT_TOKEN = process.env.GITHUB_COMMENT_TOKEN |
||||
const CIRCLE_PULL_REQUEST = process.env.CIRCLE_PULL_REQUEST |
||||
console.log('CIRCLE_PULL_REQUEST', CIRCLE_PULL_REQUEST) |
||||
const CIRCLE_SHA1 = process.env.CIRCLE_SHA1 |
||||
console.log('CIRCLE_SHA1', CIRCLE_SHA1) |
||||
const CIRCLE_BUILD_NUM = process.env.CIRCLE_BUILD_NUM |
||||
console.log('CIRCLE_BUILD_NUM', CIRCLE_BUILD_NUM) |
||||
|
||||
if (!CIRCLE_PULL_REQUEST) { |
||||
console.warn(`No pull request detected for commit "${CIRCLE_SHA1}"`) |
||||
return |
||||
} |
||||
|
||||
const CIRCLE_PR_NUMBER = CIRCLE_PULL_REQUEST.split('/').pop() |
||||
const SHORT_SHA1 = CIRCLE_SHA1.slice(0,7) |
||||
const BUILD_LINK_BASE = `https://${CIRCLE_BUILD_NUM}-42009758-gh.circle-artifacts.com/0` |
||||
|
||||
const MASCARA = `${BUILD_LINK_BASE}/builds/mascara/home.html` |
||||
const CHROME = `${BUILD_LINK_BASE}/builds/metamask-chrome-${VERSION}.zip` |
||||
const FIREFOX = `${BUILD_LINK_BASE}/builds/metamask-firefox-${VERSION}.zip` |
||||
const EDGE = `${BUILD_LINK_BASE}/builds/metamask-edge-${VERSION}.zip` |
||||
const OPERA = `${BUILD_LINK_BASE}/builds/metamask-opera-${VERSION}.zip` |
||||
const WALKTHROUGH = `${BUILD_LINK_BASE}/test-artifacts/screens/walkthrough%20%28en%29.gif` |
||||
|
||||
const commentBody = ` |
||||
<details> |
||||
<summary> |
||||
Builds ready [${SHORT_SHA1}]: |
||||
<a href="${MASCARA}">mascara</a>, |
||||
<a href="${CHROME}">chrome</a>, |
||||
<a href="${FIREFOX}">firefox</a>, |
||||
<a href="${EDGE}">edge</a>, |
||||
<a href="${OPERA}">opera</a> |
||||
</summary> |
||||
<image src="${WALKTHROUGH}"> |
||||
</details> |
||||
` |
||||
|
||||
const JSON_PAYLOAD = JSON.stringify({ body: commentBody }) |
||||
const POST_COMMENT_URI = `https://api.github.com/repos/metamask/metamask-extension/issues/${CIRCLE_PR_NUMBER}/comments` |
||||
console.log(`Announcement:\n${commentBody}`) |
||||
console.log(`Posting to: ${POST_COMMENT_URI}`) |
||||
|
||||
await request({ |
||||
method: 'POST', |
||||
uri: POST_COMMENT_URI, |
||||
body: JSON_PAYLOAD, |
||||
headers: { |
||||
'User-Agent': 'metamaskbot', |
||||
'Authorization': `token ${GITHUB_COMMENT_TOKEN}`, |
||||
}, |
||||
}) |
||||
|
||||
} |
||||
|
@ -0,0 +1,55 @@ |
||||
#!/usr/bin/env node
|
||||
const pify = require('pify') |
||||
const exec = pify(require('child_process').exec, { multiArgs: true }) |
||||
const VERSION = require('../dist/chrome/manifest.json').version |
||||
|
||||
start().catch(console.error) |
||||
|
||||
async function start(){ |
||||
const authWorked = await checkIfAuthWorks() |
||||
if (!authWorked) { |
||||
console.log(`Sentry auth failed...`) |
||||
} |
||||
// check if version exists or not
|
||||
const versionAlreadyExists = await checkIfVersionExists() |
||||
// abort if versions exists
|
||||
if (versionAlreadyExists) { |
||||
console.log(`Version "${VERSION}" already exists on Sentry, aborting sourcemap upload.`) |
||||
return |
||||
} |
||||
|
||||
// create sentry release
|
||||
console.log(`creating Sentry release for "${VERSION}"...`) |
||||
await exec(`sentry-cli releases --org 'metamask' --project 'metamask' new ${VERSION}`) |
||||
console.log(`removing any existing files from Sentry release "${VERSION}"...`) |
||||
await exec(`sentry-cli releases --org 'metamask' --project 'metamask' files ${VERSION} delete --all`) |
||||
// upload sentry source and sourcemaps
|
||||
console.log(`uploading source files Sentry release "${VERSION}"...`) |
||||
await exec(`for FILEPATH in ./dist/chrome/*.js; do [ -e $FILEPATH ] || continue; export FILE=\`basename $FILEPATH\` && echo uploading $FILE && sentry-cli releases --org 'metamask' --project 'metamask' files ${VERSION} upload $FILEPATH metamask/$FILE; done;`) |
||||
console.log(`uploading sourcemaps Sentry release "${VERSION}"...`) |
||||
await exec(`sentry-cli releases --org 'metamask' --project 'metamask' files ${VERSION} upload-sourcemaps ./dist/sourcemaps/ --url-prefix 'sourcemaps'`) |
||||
console.log('all done!') |
||||
} |
||||
|
||||
async function checkIfAuthWorks() { |
||||
const itWorked = await doesNotFail(async () => { |
||||
await exec(`sentry-cli releases --org 'metamask' --project 'metamask' list`) |
||||
}) |
||||
return itWorked |
||||
} |
||||
|
||||
async function checkIfVersionExists() { |
||||
const versionAlreadyExists = await doesNotFail(async () => { |
||||
await exec(`sentry-cli releases --org 'metamask' --project 'metamask' info ${VERSION}`) |
||||
}) |
||||
return versionAlreadyExists |
||||
} |
||||
|
||||
async function doesNotFail(asyncFn) { |
||||
try { |
||||
await asyncFn() |
||||
return true |
||||
} catch (err) { |
||||
return false |
||||
} |
||||
} |
@ -0,0 +1,48 @@ |
||||
# QA Guide |
||||
|
||||
Steps to mark a full pass of QA complete. |
||||
* Browsers: Opera, Chrome, Firefox, Edge. |
||||
* OS: Ubuntu, Mac OSX, Windows |
||||
* Load older version of MetaMask and attempt to simulate updating the extension. |
||||
* Open Developer Console in background and popup, inspect errors. |
||||
* Watch the state logs |
||||
* Transactions (unapproved txs -> rejected/submitted -> confirmed) |
||||
* Nonces/LocalNonces |
||||
* Vault integrity |
||||
* create vault |
||||
* Log out |
||||
* Log in again |
||||
* Log out |
||||
* Restore from seed |
||||
* Create a second account |
||||
* Import a loose account (not related to HD Wallet) |
||||
* Import old existing vault seed phrase (pref with test Ether) |
||||
* Download State Logs, Priv key file, seed phrase file. |
||||
* Send Ether |
||||
* by address |
||||
* by ens name |
||||
* Web3 API Stability |
||||
* Create a contract from a Ðapp (remix) |
||||
* Load a Ðapp that reads using events/logs (ENS) |
||||
* Connect to MEW/MyCypto |
||||
* Send a transaction from any Ðapp |
||||
- MEW |
||||
- EtherDelta |
||||
- Leeroy |
||||
- Aragon |
||||
- (https://tmashuang.github.io/demo-dapp) |
||||
* Check account balances |
||||
* Token Management |
||||
* create a token with tokenfactory (http://tokenfactory.surge.sh/#/factory) |
||||
* Add that token to the token view |
||||
* Send that token to another metamask address. |
||||
* confirm the token arrived. |
||||
* Send a transaction and sign a message (https://danfinlay.github.io/js-eth-personal-sign-examples/) for each keyring type |
||||
* hd keyring |
||||
* imported keyring |
||||
* Change network from mainnet → ropsten → rinkeby → localhost (ganache) |
||||
* Ganache set blocktime to simulate retryTx in MetaMask |
||||
* Copy public key to clipboard |
||||
* Export private key |
||||
|
||||
* Explore changes in master, target features that have been changed and break. |
@ -0,0 +1,151 @@ |
||||
import React, { Component } from 'react' |
||||
import PropTypes from 'prop-types' |
||||
import { connect } from 'react-redux' |
||||
import { withRouter } from 'react-router-dom' |
||||
import classnames from 'classnames' |
||||
import shuffle from 'lodash.shuffle' |
||||
import { compose } from 'recompose' |
||||
import Identicon from '../../../../ui/app/components/identicon' |
||||
import { confirmSeedWords, showModal } from '../../../../ui/app/actions' |
||||
import Breadcrumbs from './breadcrumbs' |
||||
import LoadingScreen from './loading-screen' |
||||
import { DEFAULT_ROUTE } from '../../../../ui/app/routes' |
||||
|
||||
class ConfirmSeedScreen extends Component { |
||||
static propTypes = { |
||||
isLoading: PropTypes.bool, |
||||
address: PropTypes.string, |
||||
seedWords: PropTypes.string, |
||||
confirmSeedWords: PropTypes.func, |
||||
history: PropTypes.object, |
||||
openBuyEtherModal: PropTypes.func, |
||||
}; |
||||
|
||||
static defaultProps = { |
||||
seedWords: '', |
||||
} |
||||
|
||||
constructor (props) { |
||||
super(props) |
||||
const { seedWords } = props |
||||
this.state = { |
||||
selectedSeeds: [], |
||||
shuffledSeeds: seedWords && shuffle(seedWords.split(' ')) || [], |
||||
} |
||||
} |
||||
|
||||
componentWillMount () { |
||||
const { seedWords, history } = this.props |
||||
|
||||
if (!seedWords) { |
||||
history.push(DEFAULT_ROUTE) |
||||
} |
||||
} |
||||
|
||||
handleClick () { |
||||
const { confirmSeedWords, history, openBuyEtherModal } = this.props |
||||
|
||||
confirmSeedWords() |
||||
.then(() => { |
||||
history.push(DEFAULT_ROUTE) |
||||
openBuyEtherModal() |
||||
}) |
||||
} |
||||
|
||||
render () { |
||||
const { seedWords } = this.props |
||||
const { selectedSeeds, shuffledSeeds } = this.state |
||||
const isValid = seedWords === selectedSeeds.map(([_, seed]) => seed).join(' ') |
||||
|
||||
return ( |
||||
<div className="first-time-flow"> |
||||
{ |
||||
this.props.isLoading |
||||
? <LoadingScreen loadingMessage="Creating your new account" /> |
||||
: ( |
||||
<div className="first-view-main-wrapper"> |
||||
<div className="first-view-main"> |
||||
<div className="backup-phrase"> |
||||
<Identicon address={this.props.address} diameter={70} /> |
||||
<div className="backup-phrase__content-wrapper"> |
||||
<div> |
||||
<div className="backup-phrase__title"> |
||||
Confirm your Secret Backup Phrase |
||||
</div> |
||||
<div className="backup-phrase__body-text"> |
||||
Please select each phrase in order to make sure it is correct. |
||||
</div> |
||||
<div className="backup-phrase__confirm-secret"> |
||||
{selectedSeeds.map(([_, word], i) => ( |
||||
<button |
||||
key={i} |
||||
className="backup-phrase__confirm-seed-option" |
||||
> |
||||
{word} |
||||
</button> |
||||
))} |
||||
</div> |
||||
<div className="backup-phrase__confirm-seed-options"> |
||||
{shuffledSeeds.map((word, i) => { |
||||
const isSelected = selectedSeeds |
||||
.filter(([index, seed]) => seed === word && index === i) |
||||
.length |
||||
|
||||
return ( |
||||
<button |
||||
key={i} |
||||
className={classnames('backup-phrase__confirm-seed-option', { |
||||
'backup-phrase__confirm-seed-option--selected': isSelected, |
||||
})} |
||||
onClick={() => { |
||||
if (!isSelected) { |
||||
this.setState({ |
||||
selectedSeeds: [...selectedSeeds, [i, word]], |
||||
}) |
||||
} else { |
||||
this.setState({ |
||||
selectedSeeds: selectedSeeds |
||||
.filter(([index, seed]) => !(seed === word && index === i)), |
||||
}) |
||||
} |
||||
}} |
||||
> |
||||
{word} |
||||
</button> |
||||
) |
||||
})} |
||||
</div> |
||||
<button |
||||
className="first-time-flow__button" |
||||
onClick={() => isValid && this.handleClick()} |
||||
disabled={!isValid} |
||||
> |
||||
Confirm |
||||
</button> |
||||
</div> |
||||
</div> |
||||
<Breadcrumbs total={3} currentIndex={1} /> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
</div> |
||||
) |
||||
} |
||||
} |
||||
|
||||
export default compose( |
||||
withRouter, |
||||
connect( |
||||
({ metamask: { selectedAddress, seedWords }, appState: { isLoading } }) => ({ |
||||
seedWords, |
||||
isLoading, |
||||
address: selectedAddress, |
||||
}), |
||||
dispatch => ({ |
||||
confirmSeedWords: () => dispatch(confirmSeedWords()), |
||||
openBuyEtherModal: () => dispatch(showModal({ name: 'DEPOSIT_ETHER'})), |
||||
}) |
||||
) |
||||
)(ConfirmSeedScreen) |
@ -0,0 +1,49 @@ |
||||
const assert = require('assert') |
||||
const migration24 = require('../../../app/scripts/migrations/024') |
||||
const firstTimeState = { |
||||
meta: {}, |
||||
data: require('../../../app/scripts/first-time-state'), |
||||
} |
||||
const properTime = (new Date()).getTime() |
||||
const storage = { |
||||
"meta": {}, |
||||
"data": { |
||||
"TransactionController": { |
||||
"transactions": [ |
||||
] |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
const transactions = [] |
||||
|
||||
|
||||
while (transactions.length <= 10) { |
||||
transactions.push({ txParams: { from: '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675' }, status: 'unapproved' }) |
||||
transactions.push({ txParams: { from: '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675' }, status: 'confirmed' }) |
||||
} |
||||
|
||||
|
||||
storage.data.TransactionController.transactions = transactions |
||||
|
||||
describe('storage is migrated successfully and the txParams.from are lowercase', () => { |
||||
it('should lowercase the from for unapproved txs', (done) => { |
||||
migration24.migrate(storage) |
||||
.then((migratedData) => { |
||||
const migratedTransactions = migratedData.data.TransactionController.transactions |
||||
migratedTransactions.forEach((tx) => { |
||||
if (tx.status === 'unapproved') assert.equal(tx.txParams.from, '0x8acce2391c0d510a6c5e5d8f819a678f79b7e675') |
||||
else assert.equal(tx.txParams.from, '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675') |
||||
}) |
||||
done() |
||||
}).catch(done) |
||||
}) |
||||
|
||||
it('should migrate first time state', (done) => { |
||||
migration24.migrate(firstTimeState) |
||||
.then((migratedData) => { |
||||
assert.equal(migratedData.meta.version, 24) |
||||
done() |
||||
}).catch(done) |
||||
}) |
||||
}) |
@ -0,0 +1,49 @@ |
||||
const assert = require('assert') |
||||
const migration25 = require('../../../app/scripts/migrations/025') |
||||
const firstTimeState = { |
||||
meta: {}, |
||||
data: require('../../../app/scripts/first-time-state'), |
||||
} |
||||
|
||||
const storage = { |
||||
"meta": {}, |
||||
"data": { |
||||
"TransactionController": { |
||||
"transactions": [ |
||||
] |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
const transactions = [] |
||||
|
||||
|
||||
while (transactions.length <= 10) { |
||||
transactions.push({ txParams: { from: '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675', random: 'stuff', chainId: 2 }, status: 'unapproved' }) |
||||
transactions.push({ txParams: { from: '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675' }, status: 'confirmed' }) |
||||
} |
||||
|
||||
|
||||
storage.data.TransactionController.transactions = transactions |
||||
|
||||
describe('storage is migrated successfully and the txParams.from are lowercase', () => { |
||||
it('should lowercase the from for unapproved txs', (done) => { |
||||
migration25.migrate(storage) |
||||
.then((migratedData) => { |
||||
const migratedTransactions = migratedData.data.TransactionController.transactions |
||||
migratedTransactions.forEach((tx) => { |
||||
if (tx.status === 'unapproved') assert(!tx.txParams.random) |
||||
if (tx.status === 'unapproved') assert(!tx.txParams.chainId) |
||||
}) |
||||
done() |
||||
}).catch(done) |
||||
}) |
||||
|
||||
it('should migrate first time state', (done) => { |
||||
migration25.migrate(firstTimeState) |
||||
.then((migratedData) => { |
||||
assert.equal(migratedData.meta.version, 25) |
||||
done() |
||||
}).catch(done) |
||||
}) |
||||
}) |
@ -0,0 +1,17 @@ |
||||
const assert = require('assert') |
||||
const migrationTemplate = require('../../../app/scripts/migrations/template') |
||||
const properTime = (new Date()).getTime() |
||||
const storage = { |
||||
meta: {}, |
||||
data: {}, |
||||
} |
||||
|
||||
describe('storage is migrated successfully', () => { |
||||
it('should work', (done) => { |
||||
migrationTemplate.migrate(storage) |
||||
.then((migratedData) => { |
||||
assert.equal(migratedData.meta.version, 0) |
||||
done() |
||||
}).catch(done) |
||||
}) |
||||
}) |
@ -0,0 +1,34 @@ |
||||
const { connect } = require('react-redux') |
||||
const PropTypes = require('prop-types') |
||||
const { Redirect } = require('react-router-dom') |
||||
const h = require('react-hyperscript') |
||||
const MetamaskRoute = require('./metamask-route') |
||||
const { UNLOCK_ROUTE, INITIALIZE_ROUTE } = require('../../routes') |
||||
|
||||
const Authenticated = props => { |
||||
const { isUnlocked, isInitialized } = props |
||||
|
||||
switch (true) { |
||||
case isUnlocked && isInitialized: |
||||
return h(MetamaskRoute, { ...props }) |
||||
case !isInitialized: |
||||
return h(Redirect, { to: { pathname: INITIALIZE_ROUTE } }) |
||||
default: |
||||
return h(Redirect, { to: { pathname: UNLOCK_ROUTE } }) |
||||
} |
||||
} |
||||
|
||||
Authenticated.propTypes = { |
||||
isUnlocked: PropTypes.bool, |
||||
isInitialized: PropTypes.bool, |
||||
} |
||||
|
||||
const mapStateToProps = state => { |
||||
const { metamask: { isUnlocked, isInitialized } } = state |
||||
return { |
||||
isUnlocked, |
||||
isInitialized, |
||||
} |
||||
} |
||||
|
||||
module.exports = connect(mapStateToProps)(Authenticated) |
@ -0,0 +1,81 @@ |
||||
const Component = require('react').Component |
||||
const { Switch, Route, matchPath } = require('react-router-dom') |
||||
const PropTypes = require('prop-types') |
||||
const h = require('react-hyperscript') |
||||
const connect = require('react-redux').connect |
||||
const actions = require('../../../actions') |
||||
const { getCurrentViewContext } = require('../../../selectors') |
||||
const classnames = require('classnames') |
||||
const NewAccountCreateForm = require('./new-account') |
||||
const NewAccountImportForm = require('./import-account') |
||||
const { NEW_ACCOUNT_ROUTE, IMPORT_ACCOUNT_ROUTE } = require('../../../routes') |
||||
|
||||
class CreateAccountPage extends Component { |
||||
renderTabs () { |
||||
const { history, location } = this.props |
||||
|
||||
return h('div.new-account__tabs', [ |
||||
h('div.new-account__tabs__tab', { |
||||
className: classnames('new-account__tabs__tab', { |
||||
'new-account__tabs__selected': matchPath(location.pathname, { |
||||
path: NEW_ACCOUNT_ROUTE, exact: true, |
||||
}), |
||||
}), |
||||
onClick: () => history.push(NEW_ACCOUNT_ROUTE), |
||||
}, 'Create'), |
||||
|
||||
h('div.new-account__tabs__tab', { |
||||
className: classnames('new-account__tabs__tab', { |
||||
'new-account__tabs__selected': matchPath(location.pathname, { |
||||
path: IMPORT_ACCOUNT_ROUTE, exact: true, |
||||
}), |
||||
}), |
||||
onClick: () => history.push(IMPORT_ACCOUNT_ROUTE), |
||||
}, 'Import'), |
||||
]) |
||||
} |
||||
|
||||
render () { |
||||
return h('div.new-account', {}, [ |
||||
h('div.new-account__header', [ |
||||
h('div.new-account__title', 'New Account'), |
||||
this.renderTabs(), |
||||
]), |
||||
h('div.new-account__form', [ |
||||
h(Switch, [ |
||||
h(Route, { |
||||
exact: true, |
||||
path: NEW_ACCOUNT_ROUTE, |
||||
component: NewAccountCreateForm, |
||||
}), |
||||
h(Route, { |
||||
exact: true, |
||||
path: IMPORT_ACCOUNT_ROUTE, |
||||
component: NewAccountImportForm, |
||||
}), |
||||
]), |
||||
]), |
||||
]) |
||||
} |
||||
} |
||||
|
||||
CreateAccountPage.propTypes = { |
||||
location: PropTypes.object, |
||||
history: PropTypes.object, |
||||
} |
||||
|
||||
const mapStateToProps = state => ({ |
||||
displayedForm: getCurrentViewContext(state), |
||||
}) |
||||
|
||||
const mapDispatchToProps = dispatch => ({ |
||||
displayForm: form => dispatch(actions.setNewAccountForm(form)), |
||||
showQrView: (selected, identity) => dispatch(actions.showQrView(selected, identity)), |
||||
showExportPrivateKeyModal: () => { |
||||
dispatch(actions.showModal({ name: 'EXPORT_PRIVATE_KEY' })) |
||||
}, |
||||
hideModal: () => dispatch(actions.hideModal()), |
||||
saveAccountLabel: (address, label) => dispatch(actions.saveAccountLabel(address, label)), |
||||
}) |
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(CreateAccountPage) |
@ -0,0 +1,332 @@ |
||||
const { Component } = require('react') |
||||
const PropTypes = require('prop-types') |
||||
const connect = require('../../metamask-connect') |
||||
const { Redirect, withRouter } = require('react-router-dom') |
||||
const { compose } = require('recompose') |
||||
const h = require('react-hyperscript') |
||||
const actions = require('../../actions') |
||||
|
||||
// init
|
||||
const NewKeyChainScreen = require('../../new-keychain') |
||||
// mascara
|
||||
const MascaraBuyEtherScreen = require('../../../../mascara/src/app/first-time/buy-ether-screen').default |
||||
|
||||
// accounts
|
||||
const MainContainer = require('../../main-container') |
||||
|
||||
// other views
|
||||
const BuyView = require('../../components/buy-button-subview') |
||||
const QrView = require('../../components/qr-code') |
||||
|
||||
// Routes
|
||||
const { |
||||
REVEAL_SEED_ROUTE, |
||||
RESTORE_VAULT_ROUTE, |
||||
CONFIRM_TRANSACTION_ROUTE, |
||||
NOTICE_ROUTE, |
||||
} = require('../../routes') |
||||
|
||||
class Home extends Component { |
||||
componentDidMount () { |
||||
const { |
||||
history, |
||||
unapprovedTxs = {}, |
||||
unapprovedMsgCount = 0, |
||||
unapprovedPersonalMsgCount = 0, |
||||
unapprovedTypedMessagesCount = 0, |
||||
} = this.props |
||||
|
||||
// unapprovedTxs and unapproved messages
|
||||
if (Object.keys(unapprovedTxs).length || |
||||
unapprovedTypedMessagesCount + unapprovedMsgCount + unapprovedPersonalMsgCount > 0) { |
||||
history.push(CONFIRM_TRANSACTION_ROUTE) |
||||
} |
||||
} |
||||
|
||||
render () { |
||||
log.debug('rendering primary') |
||||
const { |
||||
noActiveNotices, |
||||
lostAccounts, |
||||
forgottenPassword, |
||||
currentView, |
||||
activeAddress, |
||||
seedWords, |
||||
} = this.props |
||||
|
||||
// notices
|
||||
if (!noActiveNotices || (lostAccounts && lostAccounts.length > 0)) { |
||||
return h(Redirect, { |
||||
to: { |
||||
pathname: NOTICE_ROUTE, |
||||
}, |
||||
}) |
||||
} |
||||
|
||||
// seed words
|
||||
if (seedWords) { |
||||
log.debug('rendering seed words') |
||||
return h(Redirect, { |
||||
to: { |
||||
pathname: REVEAL_SEED_ROUTE, |
||||
}, |
||||
}) |
||||
} |
||||
|
||||
if (forgottenPassword) { |
||||
log.debug('rendering restore vault screen') |
||||
return h(Redirect, { |
||||
to: { |
||||
pathname: RESTORE_VAULT_ROUTE, |
||||
}, |
||||
}) |
||||
} |
||||
|
||||
// if (!props.noActiveNotices) {
|
||||
// log.debug('rendering notice screen for unread notices.')
|
||||
// return h(NoticeScreen, {
|
||||
// notice: props.lastUnreadNotice,
|
||||
// key: 'NoticeScreen',
|
||||
// onConfirm: () => props.dispatch(actions.markNoticeRead(props.lastUnreadNotice)),
|
||||
// })
|
||||
// } else if (props.lostAccounts && props.lostAccounts.length > 0) {
|
||||
// log.debug('rendering notice screen for lost accounts view.')
|
||||
// return h(NoticeScreen, {
|
||||
// notice: generateLostAccountsNotice(props.lostAccounts),
|
||||
// key: 'LostAccountsNotice',
|
||||
// onConfirm: () => props.dispatch(actions.markAccountsFound()),
|
||||
// })
|
||||
// }
|
||||
|
||||
// if (props.seedWords) {
|
||||
// log.debug('rendering seed words')
|
||||
// return h(HDCreateVaultComplete, {key: 'HDCreateVaultComplete'})
|
||||
// }
|
||||
|
||||
// show initialize screen
|
||||
// if (!isInitialized || forgottenPassword) {
|
||||
// // show current view
|
||||
// log.debug('rendering an initialize screen')
|
||||
// // switch (props.currentView.name) {
|
||||
|
||||
// // case 'restoreVault':
|
||||
// // log.debug('rendering restore vault screen')
|
||||
// // return h(HDRestoreVaultScreen, {key: 'HDRestoreVaultScreen'})
|
||||
|
||||
// // default:
|
||||
// // log.debug('rendering menu screen')
|
||||
// // return h(InitializeScreen, {key: 'menuScreenInit'})
|
||||
// // }
|
||||
// }
|
||||
|
||||
// // show unlock screen
|
||||
// if (!props.isUnlocked) {
|
||||
// return h(MainContainer, {
|
||||
// currentViewName: props.currentView.name,
|
||||
// isUnlocked: props.isUnlocked,
|
||||
// })
|
||||
// }
|
||||
|
||||
// show current view
|
||||
switch (currentView.name) { |
||||
|
||||
case 'accountDetail': |
||||
log.debug('rendering main container') |
||||
return h(MainContainer, {key: 'account-detail'}) |
||||
|
||||
// case 'sendTransaction':
|
||||
// log.debug('rendering send tx screen')
|
||||
|
||||
// // Going to leave this here until we are ready to delete SendTransactionScreen v1
|
||||
// // const SendComponentToRender = checkFeatureToggle('send-v2')
|
||||
// // ? SendTransactionScreen2
|
||||
// // : SendTransactionScreen
|
||||
|
||||
// return h(SendTransactionScreen2, {key: 'send-transaction'})
|
||||
|
||||
// case 'sendToken':
|
||||
// log.debug('rendering send token screen')
|
||||
|
||||
// // Going to leave this here until we are ready to delete SendTransactionScreen v1
|
||||
// // const SendTokenComponentToRender = checkFeatureToggle('send-v2')
|
||||
// // ? SendTransactionScreen2
|
||||
// // : SendTokenScreen
|
||||
|
||||
// return h(SendTransactionScreen2, {key: 'sendToken'})
|
||||
|
||||
case 'newKeychain': |
||||
log.debug('rendering new keychain screen') |
||||
return h(NewKeyChainScreen, {key: 'new-keychain'}) |
||||
|
||||
// case 'confTx':
|
||||
// log.debug('rendering confirm tx screen')
|
||||
// return h(Redirect, {
|
||||
// to: {
|
||||
// pathname: CONFIRM_TRANSACTION_ROUTE,
|
||||
// },
|
||||
// })
|
||||
// return h(ConfirmTxScreen, {key: 'confirm-tx'})
|
||||
|
||||
// case 'add-token':
|
||||
// log.debug('rendering add-token screen from unlock screen.')
|
||||
// return h(AddTokenScreen, {key: 'add-token'})
|
||||
|
||||
// case 'config':
|
||||
// log.debug('rendering config screen')
|
||||
// return h(Settings, {key: 'config'})
|
||||
|
||||
// case 'import-menu':
|
||||
// log.debug('rendering import screen')
|
||||
// return h(Import, {key: 'import-menu'})
|
||||
|
||||
// case 'reveal-seed-conf':
|
||||
// log.debug('rendering reveal seed confirmation screen')
|
||||
// return h(RevealSeedConfirmation, {key: 'reveal-seed-conf'})
|
||||
|
||||
// case 'info':
|
||||
// log.debug('rendering info screen')
|
||||
// return h(Settings, {key: 'info', tab: 'info'})
|
||||
|
||||
case 'buyEth': |
||||
log.debug('rendering buy ether screen') |
||||
return h(BuyView, {key: 'buyEthView'}) |
||||
|
||||
case 'onboardingBuyEth': |
||||
log.debug('rendering onboarding buy ether screen') |
||||
return h(MascaraBuyEtherScreen, {key: 'buyEthView'}) |
||||
|
||||
case 'qr': |
||||
log.debug('rendering show qr screen') |
||||
return h('div', { |
||||
style: { |
||||
position: 'absolute', |
||||
height: '100%', |
||||
top: '0px', |
||||
left: '0px', |
||||
}, |
||||
}, [ |
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', { |
||||
onClick: () => this.props.dispatch(actions.backToAccountDetail(activeAddress)), |
||||
style: { |
||||
marginLeft: '10px', |
||||
marginTop: '50px', |
||||
}, |
||||
}), |
||||
h('div', { |
||||
style: { |
||||
position: 'absolute', |
||||
left: '44px', |
||||
width: '285px', |
||||
}, |
||||
}, [ |
||||
h(QrView, {key: 'qr'}), |
||||
]), |
||||
]) |
||||
|
||||
default: |
||||
log.debug('rendering default, account detail screen') |
||||
return h(MainContainer, {key: 'account-detail'}) |
||||
} |
||||
} |
||||
} |
||||
|
||||
Home.propTypes = { |
||||
currentCurrency: PropTypes.string, |
||||
isLoading: PropTypes.bool, |
||||
loadingMessage: PropTypes.string, |
||||
network: PropTypes.string, |
||||
provider: PropTypes.object, |
||||
frequentRpcList: PropTypes.array, |
||||
currentView: PropTypes.object, |
||||
sidebarOpen: PropTypes.bool, |
||||
isMascara: PropTypes.bool, |
||||
isOnboarding: PropTypes.bool, |
||||
isUnlocked: PropTypes.bool, |
||||
networkDropdownOpen: PropTypes.bool, |
||||
history: PropTypes.object, |
||||
dispatch: PropTypes.func, |
||||
selectedAddress: PropTypes.string, |
||||
noActiveNotices: PropTypes.bool, |
||||
lostAccounts: PropTypes.array, |
||||
isInitialized: PropTypes.bool, |
||||
forgottenPassword: PropTypes.bool, |
||||
activeAddress: PropTypes.string, |
||||
unapprovedTxs: PropTypes.object, |
||||
seedWords: PropTypes.string, |
||||
unapprovedMsgCount: PropTypes.number, |
||||
unapprovedPersonalMsgCount: PropTypes.number, |
||||
unapprovedTypedMessagesCount: PropTypes.number, |
||||
welcomeScreenSeen: PropTypes.bool, |
||||
isPopup: PropTypes.bool, |
||||
isMouseUser: PropTypes.bool, |
||||
t: PropTypes.func, |
||||
} |
||||
|
||||
function mapStateToProps (state) { |
||||
const { appState, metamask } = state |
||||
const { |
||||
networkDropdownOpen, |
||||
sidebarOpen, |
||||
isLoading, |
||||
loadingMessage, |
||||
} = appState |
||||
|
||||
const { |
||||
accounts, |
||||
address, |
||||
isInitialized, |
||||
noActiveNotices, |
||||
seedWords, |
||||
unapprovedTxs, |
||||
lastUnreadNotice, |
||||
lostAccounts, |
||||
unapprovedMsgCount, |
||||
unapprovedPersonalMsgCount, |
||||
unapprovedTypedMessagesCount, |
||||
} = metamask |
||||
const selected = address || Object.keys(accounts)[0] |
||||
|
||||
return { |
||||
// state from plugin
|
||||
networkDropdownOpen, |
||||
sidebarOpen, |
||||
isLoading, |
||||
loadingMessage, |
||||
noActiveNotices, |
||||
isInitialized, |
||||
isUnlocked: state.metamask.isUnlocked, |
||||
selectedAddress: state.metamask.selectedAddress, |
||||
currentView: state.appState.currentView, |
||||
activeAddress: state.appState.activeAddress, |
||||
transForward: state.appState.transForward, |
||||
isMascara: state.metamask.isMascara, |
||||
isOnboarding: Boolean(!noActiveNotices || seedWords || !isInitialized), |
||||
isPopup: state.metamask.isPopup, |
||||
seedWords: state.metamask.seedWords, |
||||
unapprovedTxs, |
||||
unapprovedMsgs: state.metamask.unapprovedMsgs, |
||||
unapprovedMsgCount, |
||||
unapprovedPersonalMsgCount, |
||||
unapprovedTypedMessagesCount, |
||||
menuOpen: state.appState.menuOpen, |
||||
network: state.metamask.network, |
||||
provider: state.metamask.provider, |
||||
forgottenPassword: state.appState.forgottenPassword, |
||||
lastUnreadNotice, |
||||
lostAccounts, |
||||
frequentRpcList: state.metamask.frequentRpcList || [], |
||||
currentCurrency: state.metamask.currentCurrency, |
||||
isMouseUser: state.appState.isMouseUser, |
||||
isRevealingSeedWords: state.metamask.isRevealingSeedWords, |
||||
Qr: state.appState.Qr, |
||||
welcomeScreenSeen: state.metamask.welcomeScreenSeen, |
||||
|
||||
// state needed to get account dropdown temporarily rendering from app bar
|
||||
selected, |
||||
} |
||||
} |
||||
|
||||
module.exports = compose( |
||||
withRouter, |
||||
connect(mapStateToProps) |
||||
)(Home) |
@ -0,0 +1,25 @@ |
||||
const { connect } = require('react-redux') |
||||
const PropTypes = require('prop-types') |
||||
const { Redirect } = require('react-router-dom') |
||||
const h = require('react-hyperscript') |
||||
const { INITIALIZE_ROUTE } = require('../../routes') |
||||
const MetamaskRoute = require('./metamask-route') |
||||
|
||||
const Initialized = props => { |
||||
return props.isInitialized |
||||
? h(MetamaskRoute, { ...props }) |
||||
: h(Redirect, { to: { pathname: INITIALIZE_ROUTE } }) |
||||
} |
||||
|
||||
Initialized.propTypes = { |
||||
isInitialized: PropTypes.bool, |
||||
} |
||||
|
||||
const mapStateToProps = state => { |
||||
const { metamask: { isInitialized } } = state |
||||
return { |
||||
isInitialized, |
||||
} |
||||
} |
||||
|
||||
module.exports = connect(mapStateToProps)(Initialized) |
@ -0,0 +1,177 @@ |
||||
const { withRouter } = require('react-router-dom') |
||||
const PropTypes = require('prop-types') |
||||
const { compose } = require('recompose') |
||||
const PersistentForm = require('../../../../lib/persistent-form') |
||||
const connect = require('../../../metamask-connect') |
||||
const h = require('react-hyperscript') |
||||
const { createNewVaultAndRestore, unMarkPasswordForgotten } = require('../../../actions') |
||||
const { DEFAULT_ROUTE } = require('../../../routes') |
||||
|
||||
class RestoreVaultPage extends PersistentForm { |
||||
constructor (props) { |
||||
super(props) |
||||
|
||||
this.state = { |
||||
error: null, |
||||
} |
||||
} |
||||
|
||||
createOnEnter (event) { |
||||
if (event.key === 'Enter') { |
||||
this.createNewVaultAndRestore() |
||||
} |
||||
} |
||||
|
||||
cancel () { |
||||
this.props.unMarkPasswordForgotten() |
||||
.then(this.props.history.push(DEFAULT_ROUTE)) |
||||
} |
||||
|
||||
createNewVaultAndRestore () { |
||||
this.setState({ error: null }) |
||||
|
||||
// check password
|
||||
var passwordBox = document.getElementById('password-box') |
||||
var password = passwordBox.value |
||||
var passwordConfirmBox = document.getElementById('password-box-confirm') |
||||
var passwordConfirm = passwordConfirmBox.value |
||||
|
||||
if (password.length < 8) { |
||||
this.setState({ error: 'Password not long enough' }) |
||||
return |
||||
} |
||||
|
||||
if (password !== passwordConfirm) { |
||||
this.setState({ error: 'Passwords don\'t match' }) |
||||
return |
||||
} |
||||
|
||||
// check seed
|
||||
var seedBox = document.querySelector('textarea.twelve-word-phrase') |
||||
var seed = seedBox.value.trim() |
||||
if (seed.split(' ').length !== 12) { |
||||
this.setState({ error: 'Seed phrases are 12 words long' }) |
||||
return |
||||
} |
||||
|
||||
// submit
|
||||
this.props.createNewVaultAndRestore(password, seed) |
||||
.then(() => this.props.history.push(DEFAULT_ROUTE)) |
||||
.catch(({ message }) => { |
||||
this.setState({ error: message }) |
||||
log.error(message) |
||||
}) |
||||
} |
||||
|
||||
render () { |
||||
const { error } = this.state |
||||
this.persistentFormParentId = 'restore-vault-form' |
||||
|
||||
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, |
||||
}, |
||||
}, [ |
||||
this.props.t('restoreVault'), |
||||
]), |
||||
|
||||
// wallet seed entry
|
||||
h('h3', 'Wallet Seed'), |
||||
h('textarea.twelve-word-phrase.letter-spacey', { |
||||
dataset: { |
||||
persistentFormId: 'wallet-seed', |
||||
}, |
||||
placeholder: this.props.t('secretPhrase'), |
||||
}), |
||||
|
||||
// password
|
||||
h('input.large-input.letter-spacey', { |
||||
type: 'password', |
||||
id: 'password-box', |
||||
placeholder: this.props.t('newPassword8Chars'), |
||||
dataset: { |
||||
persistentFormId: 'password', |
||||
}, |
||||
style: { |
||||
width: 260, |
||||
marginTop: 12, |
||||
}, |
||||
}), |
||||
|
||||
// confirm password
|
||||
h('input.large-input.letter-spacey', { |
||||
type: 'password', |
||||
id: 'password-box-confirm', |
||||
placeholder: this.props.t('confirmPassword'), |
||||
onKeyPress: this.createOnEnter.bind(this), |
||||
dataset: { |
||||
persistentFormId: 'password-confirmation', |
||||
}, |
||||
style: { |
||||
width: 260, |
||||
marginTop: 16, |
||||
}, |
||||
}), |
||||
|
||||
error && ( |
||||
h('span.error.in-progress-notification', error) |
||||
), |
||||
|
||||
// submit
|
||||
h('.flex-row.flex-space-between', { |
||||
style: { |
||||
marginTop: 30, |
||||
width: '50%', |
||||
}, |
||||
}, [ |
||||
|
||||
// cancel
|
||||
h('button.primary', { |
||||
onClick: () => this.cancel(), |
||||
}, this.props.t('cancel')), |
||||
|
||||
// submit
|
||||
h('button.primary', { |
||||
onClick: this.createNewVaultAndRestore.bind(this), |
||||
}, this.props.t('ok')), |
||||
|
||||
]), |
||||
]) |
||||
) |
||||
} |
||||
} |
||||
|
||||
RestoreVaultPage.propTypes = { |
||||
history: PropTypes.object, |
||||
} |
||||
|
||||
const mapStateToProps = state => { |
||||
const { appState: { warning, forgottenPassword } } = state |
||||
|
||||
return { |
||||
warning, |
||||
forgottenPassword, |
||||
} |
||||
} |
||||
|
||||
const mapDispatchToProps = dispatch => { |
||||
return { |
||||
createNewVaultAndRestore: (password, seed) => { |
||||
return dispatch(createNewVaultAndRestore(password, seed)) |
||||
}, |
||||
unMarkPasswordForgotten: () => dispatch(unMarkPasswordForgotten()), |
||||
} |
||||
} |
||||
|
||||
module.exports = compose( |
||||
withRouter, |
||||
connect(mapStateToProps, mapDispatchToProps) |
||||
)(RestoreVaultPage) |
@ -0,0 +1,195 @@ |
||||
const { Component } = require('react') |
||||
const { connect } = require('react-redux') |
||||
const PropTypes = require('prop-types') |
||||
const h = require('react-hyperscript') |
||||
const { exportAsFile } = require('../../../util') |
||||
const { requestRevealSeed, confirmSeedWords } = require('../../../actions') |
||||
const { DEFAULT_ROUTE } = require('../../../routes') |
||||
|
||||
class RevealSeedPage extends Component { |
||||
componentDidMount () { |
||||
const passwordBox = document.getElementById('password-box') |
||||
if (passwordBox) { |
||||
passwordBox.focus() |
||||
} |
||||
} |
||||
|
||||
checkConfirmation (event) { |
||||
if (event.key === 'Enter') { |
||||
event.preventDefault() |
||||
this.revealSeedWords() |
||||
} |
||||
} |
||||
|
||||
revealSeedWords () { |
||||
const password = document.getElementById('password-box').value |
||||
this.props.requestRevealSeed(password) |
||||
} |
||||
|
||||
renderSeed () { |
||||
const { seedWords, confirmSeedWords, history } = this.props |
||||
|
||||
return ( |
||||
h('.initialize-screen.flex-column.flex-center.flex-grow', [ |
||||
|
||||
h('h3.flex-center.text-transform-uppercase', { |
||||
style: { |
||||
background: '#EBEBEB', |
||||
color: '#AEAEAE', |
||||
marginTop: 36, |
||||
marginBottom: 8, |
||||
width: '100%', |
||||
fontSize: '20px', |
||||
padding: 6, |
||||
}, |
||||
}, [ |
||||
'Vault Created', |
||||
]), |
||||
|
||||
h('div', { |
||||
style: { |
||||
fontSize: '1em', |
||||
marginTop: '10px', |
||||
textAlign: 'center', |
||||
}, |
||||
}, [ |
||||
h('span.error', 'These 12 words are the only way to restore your MetaMask accounts.\nSave them somewhere safe and secret.'), |
||||
]), |
||||
|
||||
h('textarea.twelve-word-phrase', { |
||||
readOnly: true, |
||||
value: seedWords, |
||||
}), |
||||
|
||||
h('button.primary', { |
||||
onClick: () => confirmSeedWords().then(() => history.push(DEFAULT_ROUTE)), |
||||
style: { |
||||
margin: '24px', |
||||
fontSize: '0.9em', |
||||
marginBottom: '10px', |
||||
}, |
||||
}, 'I\'ve copied it somewhere safe'), |
||||
|
||||
h('button.primary', { |
||||
onClick: () => exportAsFile(`MetaMask Seed Words`, seedWords), |
||||
style: { |
||||
margin: '10px', |
||||
fontSize: '0.9em', |
||||
}, |
||||
}, 'Save Seed Words As File'), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
renderConfirmation () { |
||||
const { history, warning, inProgress } = this.props |
||||
|
||||
return ( |
||||
h('.initialize-screen.flex-column.flex-center.flex-grow', { |
||||
style: { maxWidth: '420px' }, |
||||
}, [ |
||||
|
||||
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('.flex-row.flex-start', { |
||||
style: { |
||||
marginTop: 30, |
||||
width: '50%', |
||||
}, |
||||
}, [ |
||||
// cancel
|
||||
h('button.primary', { |
||||
onClick: () => history.push(DEFAULT_ROUTE), |
||||
}, 'CANCEL'), |
||||
|
||||
// submit
|
||||
h('button.primary', { |
||||
style: { marginLeft: '10px' }, |
||||
onClick: this.revealSeedWords.bind(this), |
||||
}, 'OK'), |
||||
|
||||
]), |
||||
|
||||
warning && ( |
||||
h('span.error', { |
||||
style: { |
||||
margin: '20px', |
||||
}, |
||||
}, warning.split('-')) |
||||
), |
||||
|
||||
inProgress && ( |
||||
h('span.in-progress-notification', 'Generating Seed...') |
||||
), |
||||
]), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
render () { |
||||
return this.props.seedWords |
||||
? this.renderSeed() |
||||
: this.renderConfirmation() |
||||
} |
||||
} |
||||
|
||||
RevealSeedPage.propTypes = { |
||||
requestRevealSeed: PropTypes.func, |
||||
confirmSeedWords: PropTypes.func, |
||||
seedWords: PropTypes.string, |
||||
inProgress: PropTypes.bool, |
||||
history: PropTypes.object, |
||||
warning: PropTypes.string, |
||||
} |
||||
|
||||
const mapStateToProps = state => { |
||||
const { appState: { warning }, metamask: { seedWords } } = state |
||||
|
||||
return { |
||||
warning, |
||||
seedWords, |
||||
} |
||||
} |
||||
|
||||
const mapDispatchToProps = dispatch => { |
||||
return { |
||||
requestRevealSeed: password => dispatch(requestRevealSeed(password)), |
||||
confirmSeedWords: () => dispatch(confirmSeedWords()), |
||||
} |
||||
} |
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(RevealSeedPage) |
@ -0,0 +1,28 @@ |
||||
const { connect } = require('react-redux') |
||||
const PropTypes = require('prop-types') |
||||
const { Route } = require('react-router-dom') |
||||
const h = require('react-hyperscript') |
||||
|
||||
const MetamaskRoute = ({ component, mascaraComponent, isMascara, ...props }) => { |
||||
return ( |
||||
h(Route, { |
||||
...props, |
||||
component: isMascara && mascaraComponent ? mascaraComponent : component, |
||||
}) |
||||
) |
||||
} |
||||
|
||||
MetamaskRoute.propTypes = { |
||||
component: PropTypes.func, |
||||
mascaraComponent: PropTypes.func, |
||||
isMascara: PropTypes.bool, |
||||
} |
||||
|
||||
const mapStateToProps = state => { |
||||
const { metamask: { isMascara } } = state |
||||
return { |
||||
isMascara, |
||||
} |
||||
} |
||||
|
||||
module.exports = connect(mapStateToProps)(MetamaskRoute) |
@ -0,0 +1,203 @@ |
||||
const { Component } = require('react') |
||||
const h = require('react-hyperscript') |
||||
const { connect } = require('react-redux') |
||||
const PropTypes = require('prop-types') |
||||
const ReactMarkdown = require('react-markdown') |
||||
const linker = require('extension-link-enabler') |
||||
const generateLostAccountsNotice = require('../../../lib/lost-accounts-notice') |
||||
const findDOMNode = require('react-dom').findDOMNode |
||||
const actions = require('../../actions') |
||||
const { DEFAULT_ROUTE } = require('../../routes') |
||||
|
||||
class Notice extends Component { |
||||
constructor (props) { |
||||
super(props) |
||||
|
||||
this.state = { |
||||
disclaimerDisabled: true, |
||||
} |
||||
} |
||||
|
||||
componentWillMount () { |
||||
if (!this.props.notice) { |
||||
this.props.history.push(DEFAULT_ROUTE) |
||||
} |
||||
} |
||||
|
||||
componentDidMount () { |
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
var node = findDOMNode(this) |
||||
linker.setupListener(node) |
||||
if (document.getElementsByClassName('notice-box')[0].clientHeight < 310) { |
||||
this.setState({ disclaimerDisabled: false }) |
||||
} |
||||
} |
||||
|
||||
componentWillReceiveProps (nextProps) { |
||||
if (!nextProps.notice) { |
||||
this.props.history.push(DEFAULT_ROUTE) |
||||
} |
||||
} |
||||
|
||||
componentWillUnmount () { |
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
var node = findDOMNode(this) |
||||
linker.teardownListener(node) |
||||
} |
||||
|
||||
handleAccept () { |
||||
this.setState({ disclaimerDisabled: true }) |
||||
this.props.onConfirm() |
||||
} |
||||
|
||||
render () { |
||||
const { notice = {} } = this.props |
||||
const { title, date, body } = notice |
||||
const { disclaimerDisabled } = this.state |
||||
|
||||
return ( |
||||
h('.flex-column.flex-center.flex-grow', { |
||||
style: { |
||||
width: '100%', |
||||
}, |
||||
}, [ |
||||
h('h3.flex-center.text-transform-uppercase.terms-header', { |
||||
style: { |
||||
background: '#EBEBEB', |
||||
color: '#AEAEAE', |
||||
width: '100%', |
||||
fontSize: '20px', |
||||
textAlign: 'center', |
||||
padding: 6, |
||||
}, |
||||
}, [ |
||||
title, |
||||
]), |
||||
|
||||
h('h5.flex-center.text-transform-uppercase.terms-header', { |
||||
style: { |
||||
background: '#EBEBEB', |
||||
color: '#AEAEAE', |
||||
marginBottom: 24, |
||||
width: '100%', |
||||
fontSize: '20px', |
||||
textAlign: 'center', |
||||
padding: 6, |
||||
}, |
||||
}, [ |
||||
date, |
||||
]), |
||||
|
||||
h('style', ` |
||||
|
||||
.markdown { |
||||
overflow-x: hidden; |
||||
} |
||||
|
||||
.markdown h1, .markdown h2, .markdown h3 { |
||||
margin: 10px 0; |
||||
font-weight: bold; |
||||
} |
||||
|
||||
.markdown strong { |
||||
font-weight: bold; |
||||
} |
||||
.markdown em { |
||||
font-style: italic; |
||||
} |
||||
|
||||
.markdown p { |
||||
margin: 10px 0; |
||||
} |
||||
|
||||
.markdown a { |
||||
color: #df6b0e; |
||||
} |
||||
|
||||
`),
|
||||
|
||||
h('div.markdown', { |
||||
onScroll: (e) => { |
||||
var object = e.currentTarget |
||||
if (object.offsetHeight + object.scrollTop + 100 >= object.scrollHeight) { |
||||
this.setState({ disclaimerDisabled: false }) |
||||
} |
||||
}, |
||||
style: { |
||||
background: 'rgb(235, 235, 235)', |
||||
height: '310px', |
||||
padding: '6px', |
||||
width: '90%', |
||||
overflowY: 'scroll', |
||||
scroll: 'auto', |
||||
}, |
||||
}, [ |
||||
h(ReactMarkdown, { |
||||
className: 'notice-box', |
||||
source: body, |
||||
skipHtml: true, |
||||
}), |
||||
]), |
||||
|
||||
h('button.primary', { |
||||
disabled: disclaimerDisabled, |
||||
onClick: () => this.handleAccept(), |
||||
style: { |
||||
marginTop: '18px', |
||||
}, |
||||
}, 'Accept'), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
} |
||||
|
||||
const mapStateToProps = state => { |
||||
const { metamask } = state |
||||
const { noActiveNotices, lastUnreadNotice, lostAccounts } = metamask |
||||
|
||||
return { |
||||
noActiveNotices, |
||||
lastUnreadNotice, |
||||
lostAccounts, |
||||
} |
||||
} |
||||
|
||||
Notice.propTypes = { |
||||
notice: PropTypes.object, |
||||
onConfirm: PropTypes.func, |
||||
history: PropTypes.object, |
||||
} |
||||
|
||||
const mapDispatchToProps = dispatch => { |
||||
return { |
||||
markNoticeRead: lastUnreadNotice => dispatch(actions.markNoticeRead(lastUnreadNotice)), |
||||
markAccountsFound: () => dispatch(actions.markAccountsFound()), |
||||
} |
||||
} |
||||
|
||||
const mergeProps = (stateProps, dispatchProps, ownProps) => { |
||||
const { noActiveNotices, lastUnreadNotice, lostAccounts } = stateProps |
||||
const { markNoticeRead, markAccountsFound } = dispatchProps |
||||
|
||||
let notice |
||||
let onConfirm |
||||
|
||||
if (!noActiveNotices) { |
||||
notice = lastUnreadNotice |
||||
onConfirm = () => markNoticeRead(lastUnreadNotice) |
||||
} else if (lostAccounts && lostAccounts.length > 0) { |
||||
notice = generateLostAccountsNotice(lostAccounts) |
||||
onConfirm = () => markAccountsFound() |
||||
} |
||||
|
||||
return { |
||||
...stateProps, |
||||
...dispatchProps, |
||||
...ownProps, |
||||
notice, |
||||
onConfirm, |
||||
} |
||||
} |
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps, mergeProps)(Notice) |
@ -0,0 +1,59 @@ |
||||
const { Component } = require('react') |
||||
const { Switch, Route, matchPath } = require('react-router-dom') |
||||
const PropTypes = require('prop-types') |
||||
const h = require('react-hyperscript') |
||||
const TabBar = require('../../tab-bar') |
||||
const Settings = require('./settings') |
||||
const Info = require('./info') |
||||
const { DEFAULT_ROUTE, SETTINGS_ROUTE, INFO_ROUTE } = require('../../../routes') |
||||
|
||||
class Config extends Component { |
||||
renderTabs () { |
||||
const { history, location } = this.props |
||||
|
||||
return h('div.settings__tabs', [ |
||||
h(TabBar, { |
||||
tabs: [ |
||||
{ content: 'Settings', key: SETTINGS_ROUTE }, |
||||
{ content: 'Info', key: INFO_ROUTE }, |
||||
], |
||||
isActive: key => matchPath(location.pathname, { path: key, exact: true }), |
||||
onSelect: key => history.push(key), |
||||
}), |
||||
]) |
||||
} |
||||
|
||||
render () { |
||||
const { history } = this.props |
||||
|
||||
return ( |
||||
h('.main-container.settings', {}, [ |
||||
h('.settings__header', [ |
||||
h('div.settings__close-button', { |
||||
onClick: () => history.push(DEFAULT_ROUTE), |
||||
}), |
||||
this.renderTabs(), |
||||
]), |
||||
h(Switch, [ |
||||
h(Route, { |
||||
exact: true, |
||||
path: INFO_ROUTE, |
||||
component: Info, |
||||
}), |
||||
h(Route, { |
||||
exact: true, |
||||
path: SETTINGS_ROUTE, |
||||
component: Settings, |
||||
}), |
||||
]), |
||||
]) |
||||
) |
||||
} |
||||
} |
||||
|
||||
Config.propTypes = { |
||||
location: PropTypes.object, |
||||
history: PropTypes.object, |
||||
} |
||||
|
||||
module.exports = Config |
@ -0,0 +1,112 @@ |
||||
const { Component } = require('react') |
||||
const PropTypes = require('prop-types') |
||||
const h = require('react-hyperscript') |
||||
|
||||
class Info extends Component { |
||||
renderLogo () { |
||||
return ( |
||||
h('div.settings__info-logo-wrapper', [ |
||||
h('img.settings__info-logo', { src: 'images/info-logo.png' }), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
renderInfoLinks () { |
||||
return ( |
||||
h('div.settings__content-item.settings__content-item--without-height', [ |
||||
h('div.settings__info-link-header', this.context.t('links')), |
||||
h('div.settings__info-link-item', [ |
||||
h('a', { |
||||
href: 'https://metamask.io/privacy.html', |
||||
target: '_blank', |
||||
}, [ |
||||
h('span.settings__info-link', this.context.t('privacyMsg')), |
||||
]), |
||||
]), |
||||
h('div.settings__info-link-item', [ |
||||
h('a', { |
||||
href: 'https://metamask.io/terms.html', |
||||
target: '_blank', |
||||
}, [ |
||||
h('span.settings__info-link', this.context.t('terms')), |
||||
]), |
||||
]), |
||||
h('div.settings__info-link-item', [ |
||||
h('a', { |
||||
href: 'https://metamask.io/attributions.html', |
||||
target: '_blank', |
||||
}, [ |
||||
h('span.settings__info-link', this.context.t('attributions')), |
||||
]), |
||||
]), |
||||
h('hr.settings__info-separator'), |
||||
h('div.settings__info-link-item', [ |
||||
h('a', { |
||||
href: 'https://support.metamask.io', |
||||
target: '_blank', |
||||
}, [ |
||||
h('span.settings__info-link', this.context.t('supportCenter')), |
||||
]), |
||||
]), |
||||
h('div.settings__info-link-item', [ |
||||
h('a', { |
||||
href: 'https://metamask.io/', |
||||
target: '_blank', |
||||
}, [ |
||||
h('span.settings__info-link', this.context.t('visitWebSite')), |
||||
]), |
||||
]), |
||||
h('div.settings__info-link-item', [ |
||||
h('a', { |
||||
target: '_blank', |
||||
href: 'mailto:help@metamask.io?subject=Feedback', |
||||
}, [ |
||||
h('span.settings__info-link', this.context.t('emailUs')), |
||||
]), |
||||
]), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
render () { |
||||
return ( |
||||
h('div.settings__content', [ |
||||
h('div.settings__content-row', [ |
||||
h('div.settings__content-item.settings__content-item--without-height', [ |
||||
this.renderLogo(), |
||||
h('div.settings__info-item', [ |
||||
h('div.settings__info-version-header', 'MetaMask Version'), |
||||
h('div.settings__info-version-number', '4.0.0'), |
||||
]), |
||||
h('div.settings__info-item', [ |
||||
h( |
||||
'div.settings__info-about', |
||||
this.context.t('builtInCalifornia') |
||||
), |
||||
]), |
||||
]), |
||||
this.renderInfoLinks(), |
||||
]), |
||||
]) |
||||
) |
||||
} |
||||
} |
||||
|
||||
Info.propTypes = { |
||||
tab: PropTypes.string, |
||||
metamask: PropTypes.object, |
||||
setCurrentCurrency: PropTypes.func, |
||||
setRpcTarget: PropTypes.func, |
||||
displayWarning: PropTypes.func, |
||||
revealSeedConfirmation: PropTypes.func, |
||||
warning: PropTypes.string, |
||||
location: PropTypes.object, |
||||
history: PropTypes.object, |
||||
t: PropTypes.func, |
||||
} |
||||
|
||||
Info.contextTypes = { |
||||
t: PropTypes.func, |
||||
} |
||||
|
||||
module.exports = Info |
@ -0,0 +1,193 @@ |
||||
const { Component } = require('react') |
||||
const PropTypes = require('prop-types') |
||||
const connect = require('../../metamask-connect') |
||||
const h = require('react-hyperscript') |
||||
const { withRouter } = require('react-router-dom') |
||||
const { compose } = require('recompose') |
||||
const { |
||||
tryUnlockMetamask, |
||||
forgotPassword, |
||||
markPasswordForgotten, |
||||
setNetworkEndpoints, |
||||
setFeatureFlag, |
||||
} = require('../../actions') |
||||
const environmentType = require('../../../../app/scripts/lib/environment-type') |
||||
const getCaretCoordinates = require('textarea-caret') |
||||
const EventEmitter = require('events').EventEmitter |
||||
const Mascot = require('../mascot') |
||||
const { OLD_UI_NETWORK_TYPE } = require('../../../../app/scripts/config').enums |
||||
const { DEFAULT_ROUTE, RESTORE_VAULT_ROUTE } = require('../../routes') |
||||
|
||||
class UnlockScreen extends Component { |
||||
constructor (props) { |
||||
super(props) |
||||
|
||||
this.state = { |
||||
error: null, |
||||
} |
||||
|
||||
this.animationEventEmitter = new EventEmitter() |
||||
} |
||||
|
||||
componentWillMount () { |
||||
const { isUnlocked, history } = this.props |
||||
|
||||
if (isUnlocked) { |
||||
history.push(DEFAULT_ROUTE) |
||||
} |
||||
} |
||||
|
||||
componentDidMount () { |
||||
const passwordBox = document.getElementById('password-box') |
||||
|
||||
if (passwordBox) { |
||||
passwordBox.focus() |
||||
} |
||||
} |
||||
|
||||
tryUnlockMetamask (password) { |
||||
const { tryUnlockMetamask, history } = this.props |
||||
tryUnlockMetamask(password) |
||||
.then(() => history.push(DEFAULT_ROUTE)) |
||||
.catch(({ message }) => this.setState({ error: message })) |
||||
} |
||||
|
||||
onSubmit (event) { |
||||
const input = document.getElementById('password-box') |
||||
const password = input.value |
||||
this.tryUnlockMetamask(password) |
||||
} |
||||
|
||||
onKeyPress (event) { |
||||
if (event.key === 'Enter') { |
||||
this.submitPassword(event) |
||||
} |
||||
} |
||||
|
||||
submitPassword (event) { |
||||
var element = event.target |
||||
var password = element.value |
||||
// reset input
|
||||
element.value = '' |
||||
this.tryUnlockMetamask(password) |
||||
} |
||||
|
||||
inputChanged (event) { |
||||
// tell mascot to look at page action
|
||||
var element = event.target |
||||
var boundingRect = element.getBoundingClientRect() |
||||
var coordinates = getCaretCoordinates(element, element.selectionEnd) |
||||
this.animationEventEmitter.emit('point', { |
||||
x: boundingRect.left + coordinates.left - element.scrollLeft, |
||||
y: boundingRect.top + coordinates.top - element.scrollTop, |
||||
}) |
||||
} |
||||
|
||||
render () { |
||||
const { error } = this.state |
||||
return ( |
||||
h('.unlock-screen', [ |
||||
|
||||
h(Mascot, { |
||||
animationEventEmitter: this.animationEventEmitter, |
||||
}), |
||||
|
||||
h('h1', { |
||||
style: { |
||||
fontSize: '1.4em', |
||||
textTransform: 'uppercase', |
||||
color: '#7F8082', |
||||
}, |
||||
}, this.props.t('appName')), |
||||
|
||||
h('input.large-input', { |
||||
type: 'password', |
||||
id: 'password-box', |
||||
placeholder: 'enter password', |
||||
style: { |
||||
background: 'white', |
||||
}, |
||||
onKeyPress: this.onKeyPress.bind(this), |
||||
onInput: this.inputChanged.bind(this), |
||||
}), |
||||
|
||||
h('.error', { |
||||
style: { |
||||
display: error ? 'block' : 'none', |
||||
padding: '0 20px', |
||||
textAlign: 'center', |
||||
}, |
||||
}, error), |
||||
|
||||
h('button.primary.cursor-pointer', { |
||||
onClick: this.onSubmit.bind(this), |
||||
style: { |
||||
margin: 10, |
||||
}, |
||||
}, this.props.t('login')), |
||||
|
||||
h('p.pointer', { |
||||
onClick: () => { |
||||
this.props.markPasswordForgotten() |
||||
this.props.history.push(RESTORE_VAULT_ROUTE) |
||||
|
||||
if (environmentType() === 'popup') { |
||||
global.platform.openExtensionInBrowser() |
||||
} |
||||
}, |
||||
style: { |
||||
fontSize: '0.8em', |
||||
color: 'rgb(247, 134, 28)', |
||||
textDecoration: 'underline', |
||||
}, |
||||
}, this.props.t('restoreFromSeed')), |
||||
|
||||
h('p.pointer', { |
||||
onClick: () => { |
||||
this.props.useOldInterface() |
||||
.then(() => this.props.setNetworkEndpoints(OLD_UI_NETWORK_TYPE)) |
||||
}, |
||||
style: { |
||||
fontSize: '0.8em', |
||||
color: '#aeaeae', |
||||
textDecoration: 'underline', |
||||
marginTop: '32px', |
||||
}, |
||||
}, this.props.t('classicInterface')), |
||||
]) |
||||
) |
||||
} |
||||
} |
||||
|
||||
UnlockScreen.propTypes = { |
||||
forgotPassword: PropTypes.func, |
||||
tryUnlockMetamask: PropTypes.func, |
||||
markPasswordForgotten: PropTypes.func, |
||||
history: PropTypes.object, |
||||
isUnlocked: PropTypes.bool, |
||||
t: PropTypes.func, |
||||
useOldInterface: PropTypes.func, |
||||
setNetworkEndpoints: PropTypes.func, |
||||
} |
||||
|
||||
const mapStateToProps = state => { |
||||
const { metamask: { isUnlocked } } = state |
||||
return { |
||||
isUnlocked, |
||||
} |
||||
} |
||||
|
||||
const mapDispatchToProps = dispatch => { |
||||
return { |
||||
forgotPassword: () => dispatch(forgotPassword()), |
||||
tryUnlockMetamask: password => dispatch(tryUnlockMetamask(password)), |
||||
markPasswordForgotten: () => dispatch(markPasswordForgotten()), |
||||
useOldInterface: () => dispatch(setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL')), |
||||
setNetworkEndpoints: type => dispatch(setNetworkEndpoints(type)), |
||||
} |
||||
} |
||||
|
||||
module.exports = compose( |
||||
withRouter, |
||||
connect(mapStateToProps, mapDispatchToProps) |
||||
)(UnlockScreen) |
@ -0,0 +1 @@ |
||||
@import './unlock.scss'; |
@ -0,0 +1,9 @@ |
||||
.unlock-page { |
||||
box-shadow: none; |
||||
display: flex; |
||||
flex-direction: column; |
||||
align-items: center; |
||||
justify-content: center; |
||||
background: rgb(247, 247, 247); |
||||
width: 100%; |
||||
} |
@ -1,22 +1,23 @@ |
||||
const inherits = require('util').inherits |
||||
const Component = require('react').Component |
||||
const Provider = require('react-redux').Provider |
||||
const { Component } = require('react') |
||||
const PropTypes = require('prop-types') |
||||
const { Provider } = require('react-redux') |
||||
const h = require('react-hyperscript') |
||||
const SelectedApp = require('./select-app') |
||||
|
||||
module.exports = Root |
||||
|
||||
inherits(Root, Component) |
||||
function Root () { Component.call(this) } |
||||
|
||||
Root.prototype.render = function () { |
||||
return ( |
||||
class Root extends Component { |
||||
render () { |
||||
const { store } = this.props |
||||
|
||||
h(Provider, { |
||||
store: this.props.store, |
||||
}, [ |
||||
h(SelectedApp), |
||||
]) |
||||
return ( |
||||
h(Provider, { store }, [ |
||||
h(SelectedApp), |
||||
]) |
||||
) |
||||
} |
||||
} |
||||
|
||||
) |
||||
Root.propTypes = { |
||||
store: PropTypes.object, |
||||
} |
||||
|
||||
module.exports = Root |
||||
|
@ -0,0 +1,49 @@ |
||||
const DEFAULT_ROUTE = '/' |
||||
const UNLOCK_ROUTE = '/unlock' |
||||
const SETTINGS_ROUTE = '/settings' |
||||
const INFO_ROUTE = '/settings/info' |
||||
const REVEAL_SEED_ROUTE = '/seed' |
||||
const CONFIRM_SEED_ROUTE = '/confirm-seed' |
||||
const RESTORE_VAULT_ROUTE = '/restore-vault' |
||||
const ADD_TOKEN_ROUTE = '/add-token' |
||||
const NEW_ACCOUNT_ROUTE = '/new-account' |
||||
const IMPORT_ACCOUNT_ROUTE = '/new-account/import' |
||||
const SEND_ROUTE = '/send' |
||||
const CONFIRM_TRANSACTION_ROUTE = '/confirm-transaction' |
||||
const SIGNATURE_REQUEST_ROUTE = '/confirm-transaction/signature-request' |
||||
const NOTICE_ROUTE = '/notice' |
||||
const WELCOME_ROUTE = '/welcome' |
||||
const INITIALIZE_ROUTE = '/initialize' |
||||
const INITIALIZE_CREATE_PASSWORD_ROUTE = '/initialize/create-password' |
||||
const INITIALIZE_IMPORT_ACCOUNT_ROUTE = '/initialize/import-account' |
||||
const INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE = '/initialize/import-with-seed-phrase' |
||||
const INITIALIZE_UNIQUE_IMAGE_ROUTE = '/initialize/unique-image' |
||||
const INITIALIZE_NOTICE_ROUTE = '/initialize/notice' |
||||
const INITIALIZE_BACKUP_PHRASE_ROUTE = '/initialize/backup-phrase' |
||||
const INITIALIZE_CONFIRM_SEED_ROUTE = '/initialize/confirm-phrase' |
||||
|
||||
module.exports = { |
||||
DEFAULT_ROUTE, |
||||
UNLOCK_ROUTE, |
||||
SETTINGS_ROUTE, |
||||
INFO_ROUTE, |
||||
REVEAL_SEED_ROUTE, |
||||
CONFIRM_SEED_ROUTE, |
||||
RESTORE_VAULT_ROUTE, |
||||
ADD_TOKEN_ROUTE, |
||||
NEW_ACCOUNT_ROUTE, |
||||
IMPORT_ACCOUNT_ROUTE, |
||||
SEND_ROUTE, |
||||
CONFIRM_TRANSACTION_ROUTE, |
||||
NOTICE_ROUTE, |
||||
SIGNATURE_REQUEST_ROUTE, |
||||
WELCOME_ROUTE, |
||||
INITIALIZE_ROUTE, |
||||
INITIALIZE_CREATE_PASSWORD_ROUTE, |
||||
INITIALIZE_IMPORT_ACCOUNT_ROUTE, |
||||
INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE, |
||||
INITIALIZE_UNIQUE_IMAGE_ROUTE, |
||||
INITIALIZE_NOTICE_ROUTE, |
||||
INITIALIZE_BACKUP_PHRASE_ROUTE, |
||||
INITIALIZE_CONFIRM_SEED_ROUTE, |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue