Backporting to beta (#4175)

* verification: check if server is running (#4140)

* verification: check if server is running

See also ethcore/email-verification#67c6466 and ethcore/sms-verification#a585e42.

* verification: show in the UI if server is running

* verification: code style , more i18n

* fix i18n key

* Optimized hash lookups (#4144)

* Optimize hash comparison

* Use libc

* Ropsten fork detection (#4163)

* Stop flickering + added loader in AddressSelector (#4149)

* Stop UI flickering + added loader to AddressSelector #4103

* PR Grumbles

* Add a password strength component (#4153)

* Added new PasswordStrength Component

* Added tests

* PR Grumbles

* icarus -> update, increase web timeout. (#4165)

* icarus -> update, increase web timeout.

* Fix estimate gas

* Fix token images // Error in Contract Queries (#4169)

* Fix dapps not loading (#4170)

* Add secure to dappsreg

* Remove trailing slash // fix dapps
This commit is contained in:
Arkadiy Paronyan 2017-01-16 13:41:37 +01:00 committed by Gav Wood
parent 5b30a61158
commit 65594b8865
36 changed files with 589 additions and 112 deletions

1
Cargo.lock generated
View File

@ -390,6 +390,7 @@ version = "0.1.2"
dependencies = [
"bigint 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
]

View File

@ -34,7 +34,7 @@ use endpoint::EndpointPath;
use handlers::{ContentHandler, StreamingHandler};
use page::{LocalPageEndpoint, PageHandlerWaiting};
const FETCH_TIMEOUT: u64 = 30;
const FETCH_TIMEOUT: u64 = 300;
pub enum ValidatorResponse {
Local(LocalPageEndpoint),

View File

@ -24,7 +24,9 @@
"accountStartNonce": "0x0",
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"networkID" : "0x3"
"networkID" : "0x3",
"forkBlock": 333922,
"forkCanonHash": "0x8737eb141d4f05db57af63fc8d3b4d4d8f9cddb0c4e1ab855de8c288fdc1924f"
},
"genesis": {
"seal": {

View File

@ -908,7 +908,7 @@ impl BlockChainClient for Client {
Executive::new(&mut state, &env_info, &*self.engine, &self.factories.vm)
.transact(&tx, options.clone())
.map(|r| r.exception.is_some())
.map(|r| r.exception.is_none())
.unwrap_or(false)
};

View File

@ -189,6 +189,7 @@
"valid-url": "1.0.9",
"validator": "6.2.0",
"web3": "0.17.0-beta",
"whatwg-fetch": "2.0.1"
"whatwg-fetch": "2.0.1",
"zxcvbn": "4.4.1"
}
}

View File

@ -16,6 +16,19 @@
import { stringify } from 'querystring';
export const isServerRunning = (isTestnet = false) => {
const port = isTestnet ? 28443 : 18443;
return fetch(`https://email-verification.parity.io:${port}/health`, {
mode: 'cors', cache: 'no-store'
})
.then((res) => {
return res.ok;
})
.catch(() => {
return false;
});
};
export const postToServer = (query, isTestnet = false) => {
const port = isTestnet ? 28443 : 18443;
query = stringify(query);

View File

@ -16,6 +16,19 @@
import { stringify } from 'querystring';
export const isServerRunning = (isTestnet = false) => {
const port = isTestnet ? 8443 : 443;
return fetch(`https://sms-verification.parity.io:${port}/health`, {
mode: 'cors', cache: 'no-store'
})
.then((res) => {
return res.ok;
})
.catch(() => {
return false;
});
};
export const postToServer = (query, isTestnet = false) => {
const port = isTestnet ? 8443 : 443;
query = stringify(query);

View File

@ -16,7 +16,7 @@
module.exports = [
{ name: 'basiccoin', entry: 'basiccoin.js', title: 'Basic Token Deployment' },
{ name: 'dappreg', entry: 'dappreg.js', title: 'Dapp Registry' },
{ name: 'dappreg', entry: 'dappreg.js', title: 'Dapp Registry', secure: true },
{ name: 'githubhint', entry: 'githubhint.js', title: 'GitHub Hint', secure: true },
{ name: 'localtx', entry: 'localtx.js', title: 'Local transactions Viewer', secure: true },
{ name: 'registry', entry: 'registry.js', title: 'Registry' },

View File

@ -15,7 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import IconButton from 'material-ui/IconButton';
import { IconButton } from 'material-ui';
import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton';
import ActionAutorenew from 'material-ui/svg-icons/action/autorenew';

View File

@ -88,12 +88,13 @@ export default class RecoveryPhrase extends Component {
value={ password2 }
onChange={ this.onEditPassword2 } />
</div>
<Checkbox
className={ styles.checkbox }
label='Key was created with Parity <1.4.5 on Windows'
checked={ windowsPhrase }
onCheck={ this.onToggleWindowsPhrase } />
</div>
<Checkbox
className={ styles.checkbox }
label='Key was created with Parity <1.4.5 on Windows'
checked={ windowsPhrase }
onCheck={ this.onToggleWindowsPhrase }
/>
</Form>
);
}

View File

@ -16,6 +16,7 @@
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import ActionDone from 'material-ui/svg-icons/action/done';
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
import ContentClear from 'material-ui/svg-icons/content/clear';
@ -100,44 +101,45 @@ export default class CreateAccount extends Component {
switch (stage) {
case 0:
return (
<CreationType
onChange={ this.onChangeType } />
<CreationType onChange={ this.onChangeType } />
);
case 1:
if (createType === 'fromNew') {
return (
<NewAccount
onChange={ this.onChangeDetails } />
<NewAccount onChange={ this.onChangeDetails } />
);
} else if (createType === 'fromGeth') {
}
if (createType === 'fromGeth') {
return (
<NewGeth
accounts={ accounts }
onChange={ this.onChangeGeth } />
onChange={ this.onChangeGeth }
/>
);
} else if (createType === 'fromPhrase') {
}
if (createType === 'fromPhrase') {
return (
<RecoveryPhrase
onChange={ this.onChangeDetails } />
<RecoveryPhrase onChange={ this.onChangeDetails } />
);
} else if (createType === 'fromRaw') {
}
if (createType === 'fromRaw') {
return (
<RawKey
onChange={ this.onChangeDetails } />
<RawKey onChange={ this.onChangeDetails } />
);
}
return (
<NewImport
onChange={ this.onChangeWallet } />
<NewImport onChange={ this.onChangeWallet } />
);
case 2:
if (createType === 'fromGeth') {
return (
<AccountDetailsGeth
addresses={ this.state.gethAddresses } />
<AccountDetailsGeth addresses={ this.state.gethAddresses } />
);
}
@ -145,7 +147,8 @@ export default class CreateAccount extends Component {
<AccountDetails
address={ this.state.address }
name={ this.state.name }
phrase={ this.state.phrase } />
phrase={ this.state.phrase }
/>
);
}
}
@ -210,11 +213,14 @@ export default class CreateAccount extends Component {
}
return (
<Warning warning={
<FormattedMessage
id='createAccount.warning.insecurePassword'
defaultMessage='It is recommended that a strong password be used to secure your accounts. Empty and trivial passwords are a security risk.' />
} />
<Warning
warning={
<FormattedMessage
id='createAccount.warning.insecurePassword'
defaultMessage='It is recommended that a strong password be used to secure your accounts. Empty and trivial passwords are a security risk.'
/>
}
/>
);
}
@ -371,7 +377,7 @@ export default class CreateAccount extends Component {
}
onChangeDetails = (canCreate, { name, passwordHint, address, password, phrase, rawKey, windowsPhrase }) => {
this.setState({
const nextState = {
canCreate,
name,
passwordHint,
@ -380,7 +386,9 @@ export default class CreateAccount extends Component {
phrase,
windowsPhrase: windowsPhrase || false,
rawKey
});
};
this.setState(nextState);
}
onChangeRaw = (canCreate, rawKey) => {

View File

@ -23,7 +23,7 @@ import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { newError, openSnackbar } from '~/redux/actions';
import { Button, Modal, IdentityName, IdentityIcon } from '~/ui';
import { Button, Modal, IdentityName, IdentityIcon, PasswordStrength } from '~/ui';
import Form, { Input } from '~/ui/Form';
import { CancelIcon, CheckIcon, SendIcon } from '~/ui/Icons';
@ -120,7 +120,7 @@ class PasswordManager extends Component {
}
renderPage () {
const { busy, isRepeatValid, passwordHint } = this.store;
const { busy, isRepeatValid, newPassword, passwordHint } = this.store;
return (
<Tabs
@ -236,6 +236,8 @@ class PasswordManager extends Component {
type='password' />
</div>
</div>
<PasswordStrength input={ newPassword } />
</div>
</Form>
</Tab>

View File

@ -220,13 +220,27 @@ export default class TransferStore {
}
@action _attachWalletOperation = (txhash) => {
if (!txhash || /^(0x)?0*$/.test(txhash)) {
return;
}
let ethSubscriptionId = null;
// Number of blocks left to look-up (unsub after 15 blocks if nothing)
let nBlocksLeft = 15;
return this.api.subscribe('eth_blockNumber', () => {
this.api.eth
.getTransactionReceipt(txhash)
.then((tx) => {
if (nBlocksLeft <= 0) {
this.api.unsubscribe(ethSubscriptionId);
ethSubscriptionId = null;
return;
}
if (!tx) {
nBlocksLeft--;
return;
}
@ -239,6 +253,10 @@ export default class TransferStore {
this.operation = operations[0];
}
this.api.unsubscribe(ethSubscriptionId);
ethSubscriptionId = null;
})
.catch(() => {
this.api.unsubscribe(ethSubscriptionId);
ethSubscriptionId = null;
});

View File

@ -15,6 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import BigNumber from 'bignumber.js';
import { Checkbox } from 'material-ui';
import InfoIcon from 'material-ui/svg-icons/action/info-outline';
@ -33,10 +34,11 @@ import styles from './gatherData.css';
export default class GatherData extends Component {
static propTypes = {
fee: React.PropTypes.instanceOf(BigNumber),
method: PropTypes.string.isRequired,
fields: PropTypes.array.isRequired,
isVerified: nullableProptype(PropTypes.bool.isRequired),
hasRequested: nullableProptype(PropTypes.bool.isRequired),
isServerRunning: nullableProptype(PropTypes.bool.isRequired),
isVerified: nullableProptype(PropTypes.bool.isRequired),
method: PropTypes.string.isRequired,
setConsentGiven: PropTypes.func.isRequired
}
@ -48,13 +50,19 @@ export default class GatherData extends Component {
return (
<Form>
{ howItWorks }
{ this.renderServerRunning() }
{ this.renderFee() }
{ this.renderCertified() }
{ this.renderRequested() }
{ this.renderFields() }
<Checkbox
className={ styles.spacing }
label={ 'I agree to the terms and conditions below.' }
label={
<FormattedMessage
id='ui.verification.gatherData.termsOfService'
defaultMessage='I agree to the terms and conditions below.'
/>
}
disabled={ isVerified }
onCheck={ this.consentOnChange }
/>
@ -63,6 +71,44 @@ export default class GatherData extends Component {
);
}
renderServerRunning () {
const { isServerRunning } = this.props;
if (isServerRunning) {
return (
<div className={ styles.container }>
<SuccessIcon />
<p className={ styles.message }>
<FormattedMessage
id='ui.verification.gatherData.isServerRunning.true'
defaultMessage='The verification server is running.'
/>
</p>
</div>
);
} else if (isServerRunning === false) {
return (
<div className={ styles.container }>
<ErrorIcon />
<p className={ styles.message }>
<FormattedMessage
id='ui.verification.gatherData.isServerRunning.false'
defaultMessage='The verification server is not running.'
/>
</p>
</div>
);
}
return (
<p className={ styles.message }>
<FormattedMessage
id='ui.verification.gatherData.isServerRunning.pending'
defaultMessage='Checking if the verification server is running…'
/>
</p>
);
}
renderFee () {
const { fee } = this.props;
@ -72,7 +118,15 @@ export default class GatherData extends Component {
return (
<div className={ styles.container }>
<InfoIcon />
<p className={ styles.message }>The fee is { fromWei(fee).toFixed(3) } ETH.</p>
<p className={ styles.message }>
<FormattedMessage
id='ui.verification.gatherData.fee'
defaultMessage='The fee is {amount} ETH.'
values={ {
amount: fromWei(fee).toFixed(3)
} }
/>
</p>
</div>
);
}
@ -84,19 +138,34 @@ export default class GatherData extends Component {
return (
<div className={ styles.container }>
<ErrorIcon />
<p className={ styles.message }>Your account is already verified.</p>
<p className={ styles.message }>
<FormattedMessage
id='ui.verification.gatherData.isVerified.true'
defaultMessage='Your account is already verified.'
/>
</p>
</div>
);
} else if (isVerified === false) {
return (
<div className={ styles.container }>
<SuccessIcon />
<p className={ styles.message }>Your account is not verified yet.</p>
<p className={ styles.message }>
<FormattedMessage
id='ui.verification.gatherData.isVerified.false'
defaultMessage='Your account is not verified yet.'
/>
</p>
</div>
);
}
return (
<p className={ styles.message }>Checking if your account is verified</p>
<p className={ styles.message }>
<FormattedMessage
id='ui.verification.gatherData.isVerified.pending'
defaultMessage='Checking if your account is verified…'
/>
</p>
);
}
@ -112,19 +181,34 @@ export default class GatherData extends Component {
return (
<div className={ styles.container }>
<InfoIcon />
<p className={ styles.message }>You already requested verification.</p>
<p className={ styles.message }>
<FormattedMessage
id='ui.verification.gatherData.hasRequested.true'
defaultMessage='You already requested verification.'
/>
</p>
</div>
);
} else if (hasRequested === false) {
return (
<div className={ styles.container }>
<SuccessIcon />
<p className={ styles.message }>You did not request verification yet.</p>
<p className={ styles.message }>
<FormattedMessage
id='ui.verification.gatherData.hasRequested.false'
defaultMessage='You did not request verification yet.'
/>
</p>
</div>
);
}
return (
<p className={ styles.message }>Checking if you requested verification</p>
<p className={ styles.message }>
<FormattedMessage
id='ui.verification.gatherData.hasRequested.pending'
defaultMessage='Checking if you requested verification…'
/>
</p>
);
}

View File

@ -21,7 +21,7 @@ import EmailVerificationABI from '~/contracts/abi/email-verification.json';
import VerificationStore, {
LOADING, QUERY_DATA, QUERY_CODE, POSTED_CONFIRMATION, DONE
} from './store';
import { postToServer } from '../../3rdparty/email-verification';
import { isServerRunning, postToServer } from '../../3rdparty/email-verification';
const EMAIL_VERIFICATION = 7; // id in the `BadgeReg.sol` contract
@ -59,6 +59,10 @@ export default class EmailVerificationStore extends VerificationStore {
super(api, EmailVerificationABI, EMAIL_VERIFICATION, account, isTestnet);
}
isServerRunning = () => {
return isServerRunning(this.isTestnet);
}
requestValues = () => [ sha3.text(this.email) ]
@action setEmail = (email) => {

View File

@ -21,7 +21,7 @@ import SMSVerificationABI from '~/contracts/abi/sms-verification.json';
import VerificationStore, {
LOADING, QUERY_DATA, QUERY_CODE, POSTED_CONFIRMATION, DONE
} from './store';
import { postToServer } from '../../3rdparty/sms-verification';
import { isServerRunning, postToServer } from '../../3rdparty/sms-verification';
const SMS_VERIFICATION = 0; // id in the `BadgeReg.sol` contract
@ -58,6 +58,10 @@ export default class SMSVerificationStore extends VerificationStore {
super(api, SMSVerificationABI, SMS_VERIFICATION, account, isTestnet);
}
isServerRunning = () => {
return isServerRunning(this.isTestnet);
}
@action setNumber = (number) => {
this.number = number;
}

View File

@ -40,6 +40,7 @@ export default class VerificationStore {
@observable fee = null;
@observable isVerified = null;
@observable hasRequested = null;
@observable isServerRunning = null;
@observable consentGiven = false;
@observable requestTx = null;
@observable code = '';
@ -73,6 +74,14 @@ export default class VerificationStore {
const { contract, account } = this;
this.step = LOADING;
const isServerRunning = this.isServerRunning()
.then((isRunning) => {
this.isServerRunning = isRunning;
})
.catch((err) => {
this.error = 'Failed to check if server is running: ' + err.message;
});
const fee = contract.instance.fee.call()
.then((fee) => {
this.fee = fee;
@ -101,7 +110,7 @@ export default class VerificationStore {
});
Promise
.all([ fee, isVerified, hasRequested ])
.all([ isServerRunning, fee, isVerified, hasRequested ])
.then(() => {
this.step = QUERY_DATA;
});

View File

@ -94,10 +94,10 @@ class Verification extends Component {
return (
<Modal
actions={ this.renderDialogActions(phase, error, isStepValid) }
title='verify your account'
visible
current={ phase }
steps={ ['Method', 'Enter Data', 'Request', 'Enter Code', 'Confirm', 'Done!'] }
title='verify your account'
visible
waiting={ error ? [] : [ 2, 4 ] }
>
{ this.renderStep(phase, error) }
@ -111,8 +111,9 @@ class Verification extends Component {
const cancel = (
<Button
key='cancel' label='Cancel'
icon={ <CancelIcon /> }
key='cancel'
label='Cancel'
onClick={ onClose }
/>
);
@ -125,9 +126,10 @@ class Verification extends Component {
<div>
{ cancel }
<Button
key='done' label='Done'
disabled={ !isStepValid }
icon={ <DoneIcon /> }
key='done'
label='Done'
onClick={ onClose }
/>
</div>
@ -160,9 +162,15 @@ class Verification extends Component {
<div>
{ cancel }
<Button
key='next' label='Next'
disabled={ !isStepValid }
icon={ <IdentityIcon address={ account } button /> }
icon={
<IdentityIcon
address={ account }
button
/>
}
key='next'
label='Next'
onClick={ action }
/>
</div>
@ -180,16 +188,16 @@ class Verification extends Component {
const value = values.findIndex((v) => v.value === method);
return (
<RadioButtons
onChange={ this.selectMethod }
value={ value < 0 ? 0 : value }
values={ values }
onChange={ this.selectMethod }
/>
);
}
const {
step,
fee, isVerified, hasRequested,
isServerRunning, fee, isVerified, hasRequested,
requestTx, isCodeValid, confirmationTx,
setCode
} = this.store;
@ -223,15 +231,21 @@ class Verification extends Component {
return (
<GatherData
fee={ fee }
hasRequested={ hasRequested }
isServerRunning={ isServerRunning }
isVerified={ isVerified }
method={ method } fields={ fields }
fee={ fee } isVerified={ isVerified } hasRequested={ hasRequested }
setConsentGiven={ setConsentGiven }
/>
);
case 2:
return (
<SendRequest step={ step } tx={ requestTx } />
<SendRequest
step={ step }
tx={ requestTx }
/>
);
case 3:
@ -245,16 +259,19 @@ class Verification extends Component {
}
return (
<QueryCode
receiver={ receiver }
hint={ hint }
isCodeValid={ isCodeValid }
receiver={ receiver }
setCode={ setCode }
/>
);
case 4:
return (
<SendConfirmation step={ step } tx={ confirmationTx } />
<SendConfirmation
step={ step }
tx={ confirmationTx }
/>
);
case 5:

View File

@ -27,6 +27,7 @@
transition: transform ease-out 0.1s;
transform: scale(1);
overflow: hidden;
&.copied {
animation-duration: 0.25s;

View File

@ -62,11 +62,15 @@ class Balance extends Component {
value = api.util.fromWei(balance.value).toFormat(3);
}
let imagesrc = token.image;
if (!imagesrc) {
imagesrc = images[token.address]
? `${api.dappsUrl}${images[token.address]}`
: unknownImage;
const imageurl = token.image || images[token.address];
let imagesrc = unknownImage;
if (imageurl) {
const host = /^(\/)?api/.test(imageurl)
? api.dappsUrl
: '';
imagesrc = `${host}${imageurl}`;
}
return (

View File

@ -15,6 +15,22 @@
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.outerInput {
display: flex;
flex-direction: row;
position: relative;
.input {
flex: 1;
}
.loader {
position: absolute;
bottom: 1rem;
right: 9rem;
}
}
.input {
box-sizing: border-box;
appearance: textfield;
@ -58,13 +74,13 @@
}
.label {
margin: 1rem 2.5rem 0.25em;
margin: 1rem 0.5rem 0.25em;
color: rgba(255, 255, 255, 0.498039);
}
.underline {
position: relative;
margin: 0 9rem 0 2.5rem;
margin: 0 0.5rem 0 0.5rem;
}
.empty {
@ -78,7 +94,7 @@
.input {
font-size: 1.5em;
padding: 0 9rem 0.5em 2.5rem;
padding: 0 9rem 0.5em 0.5rem;
display: block;
padding-right: 6rem;
@ -92,7 +108,7 @@
flex-direction: row;
justify-content: flex-start;
margin: 2rem 2rem 0;
margin: 2rem 0 0;
> * {
flex: 1;
@ -107,8 +123,11 @@
.title {
text-transform: uppercase;
font-size: 1.5em;
font-color: white;
h3 {
margin: 0;
}
}
.cards {

View File

@ -25,6 +25,7 @@ import TextFieldUnderline from 'material-ui/TextField/TextFieldUnderline';
import AccountCard from '~/ui/AccountCard';
import InputAddress from '~/ui/Form/InputAddress';
import Loading from '~/ui/Loading';
import Portal from '~/ui/Portal';
import { nodeOrStringProptype } from '~/util/proptypes';
import { validateAddress } from '~/util/validation';
@ -130,7 +131,7 @@ class AddressSelect extends Component {
const input = (
<InputAddress
accountsInfo={ accountsInfo }
allowCopy={ allowCopy }
allowCopy={ (disabled || readOnly) && allowCopy ? allowCopy : false }
className={ className }
disabled={ disabled || readOnly }
error={ error }
@ -182,17 +183,18 @@ class AddressSelect extends Component {
<label className={ styles.label } htmlFor={ id }>
{ label }
</label>
<input
id={ id }
className={ styles.input }
placeholder={ ilHint }
onBlur={ this.handleInputBlur }
onFocus={ this.handleInputFocus }
onChange={ this.handleChange }
ref={ this.setInputRef }
/>
<div className={ styles.outerInput }>
<input
id={ id }
className={ styles.input }
placeholder={ ilHint }
onBlur={ this.handleInputBlur }
onFocus={ this.handleInputFocus }
onChange={ this.handleChange }
ref={ this.setInputRef }
/>
{ this.renderLoader() }
</div>
<div className={ styles.underline }>
<TextFieldUnderline
@ -210,6 +212,19 @@ class AddressSelect extends Component {
);
}
renderLoader () {
if (!this.store.loading) {
return null;
}
return (
<Loading
className={ styles.loader }
size={ 0.5 }
/>
);
}
renderCurrentInput () {
const { inputValue } = this.state;
@ -304,7 +319,9 @@ class AddressSelect extends Component {
return (
<div className={ styles.category } key={ `${key}_${index}` }>
<div className={ styles.title }>{ label }</div>
<div className={ styles.title }>
<h3>{ label }</h3>
</div>
{ content }
</div>
);

View File

@ -15,7 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React from 'react';
import { observable, action } from 'mobx';
import { observable, action, transaction } from 'mobx';
import { flatMap, uniqBy } from 'lodash';
import { FormattedMessage } from 'react-intl';
@ -26,6 +26,7 @@ const ZERO = /^(0x)?0*$/;
export default class AddressSelectStore {
@observable loading = false;
@observable values = [];
@observable registryValues = [];
@ -224,21 +225,28 @@ export default class AddressSelectStore {
};
});
// Registries Lookup
this.registryValues = [];
// Clear the previous results after 50ms
// if still fetching
const timeoutId = setTimeout(() => {
transaction(() => {
this.registryValues = [];
this.loading = true;
});
}, 50);
const lookups = this.regLookups.map((regLookup) => regLookup(value));
Promise
// Registries Lookup
return Promise
.all(lookups)
.then((results) => {
return results
.filter((result) => result && !ZERO.test(result.address));
})
.then((results) => {
results = uniqBy(results, (result) => result.address);
clearTimeout(timeoutId);
this.registryValues = results
const registryValues = uniqBy(results, (result) => result.address)
.map((result) => {
const lowercaseAddress = result.address.toLowerCase();
@ -253,6 +261,11 @@ export default class AddressSelectStore {
return result;
});
transaction(() => {
this.loading = false;
this.registryValues = registryValues;
});
});
}

View File

@ -76,7 +76,7 @@ class InputAddress extends Component {
const props = {};
if (!readOnly && !disabled) {
if (!disabled) {
props.focused = focused;
}

View File

@ -0,0 +1,17 @@
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './passwordStrength';

View File

@ -0,0 +1,31 @@
/* Copyright 2015, 2016 Parity Technologies (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.strength {
margin-top: 1.25em;
}
.feedback {
font-size: 0.75em;
}
.label {
user-select: none;
line-height: 18px;
font-size: 12px;
color: rgba(255, 255, 255, 0.498039);
}

View File

@ -0,0 +1,125 @@
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { debounce } from 'lodash';
import { LinearProgress } from 'material-ui';
import { FormattedMessage } from 'react-intl';
import zxcvbn from 'zxcvbn';
import styles from './passwordStrength.css';
const BAR_STYLE = {
borderRadius: 1,
height: 7,
marginTop: '0.5em'
};
export default class PasswordStrength extends Component {
static propTypes = {
input: PropTypes.string.isRequired
};
state = {
strength: null
};
constructor (props) {
super(props);
this.updateStrength = debounce(this._updateStrength, 50, { leading: true });
}
componentWillMount () {
this.updateStrength(this.props.input);
}
componentWillReceiveProps (nextProps) {
if (nextProps.input !== this.props.input) {
this.updateStrength(nextProps.input);
}
}
_updateStrength (input = '') {
const strength = zxcvbn(input);
this.setState({ strength });
}
render () {
const { strength } = this.state;
if (!strength) {
return null;
}
const { score, feedback } = strength;
// Score is between 0 and 4
const value = score * 100 / 5 + 20;
const color = this.getStrengthBarColor(score);
return (
<div className={ styles.strength }>
<label className={ styles.label }>
<FormattedMessage
id='ui.passwordStrength.label'
defaultMessage='password strength'
/>
</label>
<LinearProgress
color={ color }
mode='determinate'
style={ BAR_STYLE }
value={ value }
/>
<div className={ styles.feedback }>
{ this.renderFeedback(feedback) }
</div>
</div>
);
}
// Note that the suggestions are in english, thus it wouldn't
// make sense to add translations to surrounding words
renderFeedback (feedback = {}) {
const { suggestions = [] } = feedback;
return (
<div>
<p>
{ suggestions.join(' ') }
</p>
</div>
);
}
getStrengthBarColor (score) {
switch (score) {
case 4:
case 3:
return 'lightgreen';
case 2:
return 'yellow';
case 1:
return 'orange';
default:
return 'red';
}
}
}

View File

@ -0,0 +1,62 @@
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { shallow } from 'enzyme';
import React from 'react';
import PasswordStrength from './passwordStrength';
const INPUT_A = 'l33t_test';
const INPUT_B = 'Fu£dk;s$%kdlaOe9)_';
const INPUT_NULL = '';
function render (props) {
return shallow(
<PasswordStrength { ...props } />
).shallow();
}
describe('ui/Form/PasswordStrength', () => {
describe('rendering', () => {
it('renders', () => {
expect(render({ input: INPUT_A })).to.be.ok;
});
it('renders a linear progress', () => {
expect(render({ input: INPUT_A }).find('LinearProgress')).to.be.ok;
});
describe('compute strength', () => {
it('has low score with empty input', () => {
expect(
render({ input: INPUT_NULL }).find('LinearProgress').props().value
).to.equal(20);
});
it('has medium score', () => {
expect(
render({ input: INPUT_A }).find('LinearProgress').props().value
).to.equal(60);
});
it('has high score', () => {
expect(
render({ input: INPUT_B }).find('LinearProgress').props().value
).to.equal(100);
});
});
});
});

View File

@ -71,7 +71,9 @@ export default class TypedInput extends Component {
const { isEth, value } = this.props;
if (typeof isEth === 'boolean' && value) {
const ethValue = isEth ? fromWei(value) : value;
// Remove formatting commas
const sanitizedValue = typeof value === 'string' ? value.replace(/,/g, '') : value;
const ethValue = isEth ? fromWei(sanitizedValue) : value;
this.setState({ isEth, ethValue });
}
}
@ -393,7 +395,9 @@ export default class TypedInput extends Component {
return this.setState({ isEth: !isEth });
}
const value = isEth ? toWei(ethValue) : fromWei(ethValue);
// Remove formatting commas
const sanitizedValue = typeof ethValue === 'string' ? ethValue.replace(/,/g, '') : ethValue;
const value = isEth ? toWei(sanitizedValue) : fromWei(sanitizedValue);
this.setState({ isEth: !isEth, ethValue: value }, () => {
this.onEthValueChange(null, value);
});

View File

@ -21,15 +21,22 @@ import styles from './loading.css';
export default class Loading extends Component {
static propTypes = {
className: PropTypes.string,
size: PropTypes.number
};
static defaultProps = {
className: '',
size: 2
};
render () {
const size = (this.props.size || 2) * 60;
const { className, size } = this.props;
const computedSize = size * 60;
return (
<div className={ styles.loading }>
<CircularProgress size={ size } />
<div className={ [ styles.loading, className ].join(' ') }>
<CircularProgress size={ computedSize } />
</div>
);
}

View File

@ -68,6 +68,9 @@ $top: 20vh;
opacity: 0;
z-index: -10;
padding: 1em;
box-sizing: border-box;
* {
min-width: 0;
}
@ -83,6 +86,7 @@ $top: 20vh;
top: 0.5rem;
right: 1rem;
font-size: 4em;
z-index: 100;
transition-property: opacity;
transition-duration: 0.25s;

View File

@ -43,6 +43,7 @@ import Modal, { Busy as BusyStep, Completed as CompletedStep } from './Modal';
import muiTheme from './Theme';
import Page from './Page';
import ParityBackground from './ParityBackground';
import PasswordStrength from './Form/PasswordStrength';
import ShortenedHash from './ShortenedHash';
import SignerIcon from './SignerIcon';
import Tags from './Tags';
@ -91,6 +92,7 @@ export {
muiTheme,
Page,
ParityBackground,
PasswordStrength,
RadioButtons,
ShortenedHash,
Select,

View File

@ -43,7 +43,7 @@ module.exports = {
index: './index.js'
}),
output: {
publicPath: '/',
// publicPath: '/',
path: path.join(__dirname, '../', DEST),
filename: '[name].[hash:10].js'
},

View File

@ -12,6 +12,7 @@ bigint = "1.0"
rustc-serialize = "0.3"
heapsize = "0.3"
rand = "0.3.12"
libc = "0.2"
[features]
x64asm_arithmetic=[]

View File

@ -26,6 +26,7 @@ use rand::Rng;
use rand::os::OsRng;
use rustc_serialize::hex::{FromHex, FromHexError};
use bigint::{Uint, U256};
use libc::{c_void, memcmp};
/// Trait for a fixed-size byte array to be used as the output of hash functions.
pub trait FixedHash: Sized {
@ -214,25 +215,16 @@ macro_rules! impl_hash {
impl PartialEq for $from {
fn eq(&self, other: &Self) -> bool {
for i in 0..$size {
if self.0[i] != other.0[i] {
return false;
}
}
true
unsafe { memcmp(self.0.as_ptr() as *const c_void, other.0.as_ptr() as *const c_void, $size) == 0 }
}
}
impl Ord for $from {
fn cmp(&self, other: &Self) -> Ordering {
for i in 0..$size {
if self.0[i] > other.0[i] {
return Ordering::Greater;
} else if self.0[i] < other.0[i] {
return Ordering::Less;
}
}
Ordering::Equal
let r = unsafe { memcmp(self.0.as_ptr() as *const c_void, other.0.as_ptr() as *const c_void, $size) };
if r < 0 { return Ordering::Less }
if r > 0 { return Ordering::Greater }
return Ordering::Equal;
}
}

View File

@ -21,6 +21,7 @@
extern crate rand;
extern crate rustc_serialize;
extern crate bigint;
extern crate libc;
#[macro_use] extern crate heapsize;
pub mod hash;