From ba183e813bb7156ea8624a9d20fd28d0167d481a Mon Sep 17 00:00:00 2001 From: Brent Date: Fri, 1 Jul 2022 14:45:22 -0400 Subject: [PATCH 1/7] mnemonic import split --- packages/test-project/tests/common/tests.js | 7 +- packages/ui/src/pages/ImportAccount.ts | 249 ++++++++++++++++++-- 2 files changed, 239 insertions(+), 17 deletions(-) diff --git a/packages/test-project/tests/common/tests.js b/packages/test-project/tests/common/tests.js index ca6abfbf..0481f472 100644 --- a/packages/test-project/tests/common/tests.js +++ b/packages/test-project/tests/common/tests.js @@ -50,7 +50,12 @@ function ImportAccount(account) { await extensionPage.click('#importAccount'); await extensionPage.waitForSelector('#accountName'); await extensionPage.type('#accountName', account.name); - await extensionPage.type('#enterMnemonic', account.mnemonic); + const accountMnemonic = account.mnemonic.split(' '); + for (let i = 0; i < accountMnemonic.length; i++) { + const mnemonicWordId = '#mnemonicWord' + i; + await extensionPage.type(mnemonicWordId, accountMnemonic[i]); + } + await extensionPage.waitForTimeout(200); await extensionPage.click('#nextStep'); await inputPassword(); await extensionPage.waitForTimeout(2000); diff --git a/packages/ui/src/pages/ImportAccount.ts b/packages/ui/src/pages/ImportAccount.ts index 393ae1c2..a118198b 100644 --- a/packages/ui/src/pages/ImportAccount.ts +++ b/packages/ui/src/pages/ImportAccount.ts @@ -1,6 +1,6 @@ import { FunctionalComponent } from 'preact'; import { html } from 'htm/preact'; -import { useState, useContext } from 'preact/hooks'; +import { useState, useContext, useEffect } from 'preact/hooks'; import { route } from 'preact-router'; import { JsonRpcMethod } from '@algosigner/common/messaging/types'; @@ -14,8 +14,9 @@ import algosdk from 'algosdk'; const ImportAccount: FunctionalComponent = (props: any) => { const store: any = useContext(StoreContext); + const wordsNumber = 25; const { ledger } = props; - const [mnemonic, setMnemonic] = useState(''); + const [mnemonicArray, setMnemonicArray] = useState>(new Array(wordsNumber)); const [address, setAddress] = useState(''); const [isRef, setIsRef] = useState(false); const [name, setName] = useState(''); @@ -24,16 +25,15 @@ const ImportAccount: FunctionalComponent = (props: any) => { const [authError, setAuthError] = useState(''); const [error, setError] = useState(''); - const matches = mnemonic.trim().split(/[\s\t\r\n]+/) || []; const disabled = name.trim().length === 0 || - (!isRef && matches.length !== 25) || + (!isRef && (mnemonicArray.length !== 25 || mnemonicArray.some((e) => e === ''))) || (isRef && !algosdk.isValidAddress(address)); const importAccount = (pwd: string) => { const params = { passphrase: pwd, - mnemonic: matches.join(' '), + mnemonic: mnemonicArray.join(' '), address: address, isRef: isRef, name: name.trim(), @@ -64,13 +64,61 @@ const ImportAccount: FunctionalComponent = (props: any) => { }; const handleMnemonicInput = (e) => { - setMnemonic(e.target.value); + const localMnemonicArray = mnemonicArray; + const inputText = e.target.value.toString().trim(); + const inputArray = inputText.split(/[\s\t\r\n,]+/); + + // If it is a single word then update that word placement in the array + if (inputArray.length === 1) { + localMnemonicArray[e.target.id.toString().replace('mnemonicWord', '')] = inputText; + } + // If it is multiple words then verify there are 25 and split + else if (inputArray.length === wordsNumber) { + for(let i = 0; i < wordsNumber; i++) { + localMnemonicArray[i.toString()] = inputArray[i].trim(); + } + + // For the case the user is typing the full mnemonic + // after the split the last element will be empty + // so we must focust that element now to continue typing + const element = document.getElementById('mnemonicWord24'); + if (element) { + element.focus(); + } + + } + // If it is multiple words but they are not 25 then warn, but allow for typing out 25. + else { + console.log('[WARNING] - Mnemonic words must be a single word or the entire 25 mnemonic.') + localMnemonicArray[e.target.id.toString().replace('mnemonicWord', '')] = inputText; + } + + setMnemonicArray([]); + setMnemonicArray(localMnemonicArray); }; const handleAddressInput = (e) => { setAddress(e.target.value); }; + const handleMnemonicIconClick = (iconNumber) => { + // Mnemonic boxes are prefixed with "mnemonicWord" + const wordId = 'mnemonicWord' + iconNumber; + const element = document.getElementById(wordId); + + // As long as we can locate the element just swap the show/hide icon and text/password + if (element !== null) { + if (element['type'] === 'password') { + element['type'] = 'text'; + element.parentElement?.querySelectorAll('svg')[0]?.setAttribute('data-icon','eye'); + } + else { + element['type'] = 'password'; + element.parentElement?.querySelectorAll('svg')[0]?.setAttribute('data-icon','eye-slash'); + } + } + } + return html`
<${HeaderView} action="${() => route('/wallet')}" title="Import ${ledger} account" /> @@ -122,17 +170,186 @@ const ImportAccount: FunctionalComponent = (props: any) => { `} ${!isRef && html` -

Insert the 25 word mnemonic of the account:

-