diff --git a/src/components/modals/passphrase/Passphrase/index.js b/src/components/modals/passphrase/Passphrase/index.js index e0ef1989..7ce59e9e 100644 --- a/src/components/modals/passphrase/Passphrase/index.js +++ b/src/components/modals/passphrase/Passphrase/index.js @@ -2,6 +2,7 @@ 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'; @@ -24,6 +25,7 @@ type State = { passphraseCheckInputValue: string, doPassphraseInputsMatch: boolean, isPassphraseHidden: boolean, + byteLength: number, }; const Wrapper = styled.div` @@ -91,6 +93,7 @@ class Passphrase extends PureComponent { passphraseCheckInputValue: '', doPassphraseInputsMatch: true, isPassphraseHidden: true, + byteLength: 0, }; } @@ -117,6 +120,9 @@ class Passphrase extends PureComponent { 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) { @@ -181,13 +187,28 @@ class Passphrase extends PureComponent { handleKeyPress(event: KeyboardEvent) { if (event.key === 'Enter') { event.preventDefault(); - if (this.state.doPassphraseInputsMatch) { + 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 = ( + + + + ); + } + return (
@@ -236,11 +257,7 @@ class Passphrase extends PureComponent { /> )} - {!this.state.doPassphraseInputsMatch && ( - - - - )} + {error} { - diff --git a/src/components/modals/passphrase/Passphrase/index.messages.js b/src/components/modals/passphrase/Passphrase/index.messages.js index 1c059114..a6813822 100644 --- a/src/components/modals/passphrase/Passphrase/index.messages.js +++ b/src/components/modals/passphrase/Passphrase/index.messages.js @@ -24,6 +24,10 @@ const definedMessages: Messages = defineMessages({ id: 'TR_PASSPHRASES_DO_NOT_MATCH', defaultMessage: 'Passphrases do not match!', }, + TR_PASSPHRASES_IS_TOO_LONG: { + id: 'TR_PASSPHRASES_IS_TOO_LONG', + defaultMessage: 'Passphrase is too long!', + }, TR_SHOW_PASSPHRASE: { id: 'TR_SHOW_PASSPHRASE', defaultMessage: 'Show passphrase', diff --git a/src/utils/__tests__/formatUtils.test.js b/src/utils/__tests__/formatUtils.test.js index 4d57d0d1..67defb4a 100644 --- a/src/utils/__tests__/formatUtils.test.js +++ b/src/utils/__tests__/formatUtils.test.js @@ -21,4 +21,14 @@ describe('format utils', () => { expect(utils.fromDecimalAmount('a', 'a')).toBe('0'); expect(utils.fromDecimalAmount('a', '1')).toBe('0'); }); + + describe('byteLength', () => { + it('should return correct byte length for strings with special ASCII characters', () => { + expect(utils.byteLength('testString')).toEqual(10); + expect(utils.byteLength('~!@#$%^&*()_+{}|:?><')).toEqual(20); + expect(utils.byteLength('😀')).toEqual(4); + expect(utils.byteLength('ä')).toEqual(2); + expect(utils.byteLength('áľščť')).toEqual(10); + }); + }); }); diff --git a/src/utils/formatUtils.js b/src/utils/formatUtils.js index 4cea3e00..392ef109 100644 --- a/src/utils/formatUtils.js +++ b/src/utils/formatUtils.js @@ -27,3 +27,14 @@ export const fromDecimalAmount = (amount: string | number, decimals: number): st return '0'; } }; + +export const byteLength = (text: string): number => { + // returns length of the text in bytes, 0 in case of error. + try { + // regexp is handling cases when encodeURI returns '%uXXXX' or %XX%XX + return encodeURI(text).split(/%(?:u[0-9A-F]{2})?[0-9A-F]{2}|./).length - 1; + } catch (error) { + console.error(error); + return 0; + } +};