/* @flow */ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import styled from 'styled-components'; import { byteLength } from 'utils/formatUtils'; import { FormattedMessage } from 'react-intl'; import { Button, Input, Checkbox, P, H5, colors } from 'trezor-ui-components'; import { FONT_SIZE } from 'config/variables'; import type { TrezorDevice } from 'flowtype'; import l10nCommonMessages from 'views/common.messages'; import l10nMessages from './index.messages'; import type { Props as BaseProps } from '../../Container'; type Props = { device: TrezorDevice, selectedDevice: ?TrezorDevice, onPassphraseSubmit: $ElementType<$ElementType, 'onPassphraseSubmit'>, }; type State = { deviceLabel: string, shouldShowSingleInput: boolean, passphraseInputValue: string, passphraseCheckInputValue: string, doPassphraseInputsMatch: boolean, isPassphraseHidden: boolean, byteLength: number, }; const Wrapper = styled.div` padding: 30px 48px; max-width: 390px; `; const Label = styled.div` ${colors.TEXT_SECONDARY}; font-size: ${FONT_SIZE.BASE}; padding-bottom: 5px; `; const PassphraseError = styled.div` margin-top: 8px; color: ${colors.ERROR_PRIMARY}; `; const Row = styled.div` position: relative; text-align: left; padding-top: 24px; display: flex; flex-direction: column; `; const Footer = styled.div` display: flex; padding-top: 10px; align-items: center; flex-direction: column; justify-content: center; `; const LinkButton = styled(Button)` padding: 0; margin: 0; font-size: ${FONT_SIZE.SMALL}; text-decoration: underline; color: ${colors.GREEN_PRIMARY}; &:hover { color: ${colors.GREEN_PRIMARY}; text-decoration: none; } `; const OnDeviceButton = styled(Button)` margin: 10px 0 10px 0; `; class Passphrase extends PureComponent { constructor(props: Props) { super(props); const { device, selectedDevice } = props; // if device is already remembered then only one input is presented let deviceLabel = device.label; let shouldShowSingleInput = false; if (selectedDevice && selectedDevice.path === device.path) { deviceLabel = selectedDevice.instanceLabel; shouldShowSingleInput = selectedDevice.remember || selectedDevice.state !== null; } this.state = { deviceLabel, shouldShowSingleInput, passphraseInputValue: '', passphraseCheckInputValue: '', doPassphraseInputsMatch: true, isPassphraseHidden: true, byteLength: 0, }; } componentDidMount() { this.passphraseInput.focus(); this.handleKeyPress = this.handleKeyPress.bind(this); window.addEventListener('keypress', this.handleKeyPress, false); } componentWillUnmount() { window.removeEventListener('keypress', this.handleKeyPress, false); } handleKeyPress: (event: KeyboardEvent) => void; passphraseInput: HTMLInputElement; handleInputChange(event: Event) { const { target } = event; if (target instanceof HTMLInputElement) { const inputValue = target.value; const inputName = target.name; let doPassphraseInputsMatch = false; if (inputName === 'passphraseInputValue') { this.setState({ byteLength: byteLength(inputValue), }); // If passphrase is not hidden the second input should get filled automatically // and should be disabled if (this.state.isPassphraseHidden) { doPassphraseInputsMatch = inputValue === this.state.passphraseCheckInputValue; } else { // Since both inputs are same they really do match // see: comment above this.setState({ passphraseCheckInputValue: inputValue, }); doPassphraseInputsMatch = true; } } else if (inputName === 'passphraseCheckInputValue') { doPassphraseInputsMatch = inputValue === this.state.passphraseInputValue; } if (this.state.shouldShowSingleInput) { doPassphraseInputsMatch = true; } this.setState({ [inputName]: inputValue, doPassphraseInputsMatch, }); } } handleCheckboxClick() { let match = false; if ( this.state.shouldShowSingleInput || this.state.passphraseInputValue === this.state.passphraseCheckInputValue ) { match = true; } else { match = !!this.state.isPassphraseHidden; } this.setState(previousState => ({ isPassphraseHidden: !previousState.isPassphraseHidden, passphraseInputValue: previousState.passphraseInputValue, passphraseCheckInputValue: previousState.passphraseInputValue, doPassphraseInputsMatch: match, })); } submitPassphrase( shouldLeavePassphraseBlank: boolean = false, passphraseOnDevice: boolean = false ) { const { onPassphraseSubmit } = this.props; const passphrase = this.state.passphraseInputValue; // Reset state so same passphrase isn't filled when the modal will be visible again this.setState({ passphraseInputValue: '', passphraseCheckInputValue: '', doPassphraseInputsMatch: true, isPassphraseHidden: true, }); onPassphraseSubmit(shouldLeavePassphraseBlank ? '' : passphrase, passphraseOnDevice); } handleKeyPress(event: KeyboardEvent) { if (event.key === 'Enter') { event.preventDefault(); if (this.state.doPassphraseInputsMatch && this.state.byteLength <= 50) { this.submitPassphrase(); } } } render() { let error = null; if (this.state.byteLength > 50) { error = ( ); } else if (!this.state.doPassphraseInputsMatch) { error = ( ); } const { device } = this.props; const onDeviceOffer = !!( device.features && device.features.capabilities && device.features.capabilities.includes('Capability_PassphraseEntry') ); return (

{' '}

{ this.passphraseInput = input; }} name="passphraseInputValue" type={this.state.isPassphraseHidden ? 'password' : 'text'} autocorrect="off" autocapitalize="off" autocomplete="off" value={this.state.passphraseInputValue} onChange={event => this.handleInputChange(event)} /> {!this.state.shouldShowSingleInput && ( this.handleInputChange(event)} isDisabled={!this.state.isPassphraseHidden} /> )} {error} this.handleCheckboxClick()} > {onDeviceOffer && ( this.submitPassphrase(false, true)}> Enter passphrase on device )}
); } } Passphrase.propTypes = { device: PropTypes.object.isRequired, selectedDevice: PropTypes.object.isRequired, onPassphraseSubmit: PropTypes.func.isRequired, }; export default Passphrase;