Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add 24 word mnemonic phrase to recover-account #1070

Merged
merged 17 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ module.exports = {
chrome: "readonly",
DEV_SERVER: "readonly",
},
ignorePatterns: ["dist/", "node_modules/", "build/", "__mocks__/"],
ignorePatterns: [
"dist/",
"node_modules/",
"build/",
"__mocks__/",
"e2e-tests/",
],
overrides: [
{
files: ["webpack.*.js"],
Expand Down
18 changes: 18 additions & 0 deletions config/jest/resolver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module.exports = (path, options) =>
options.defaultResolver(path, {
...options,
// Use packageFilter to process parsed `package.json` before the resolution (see https://www.npmjs.com/package/resolve#resolveid-opts-cb)
packageFilter: (pkg) => {
if (pkg.name === "@stellar/design-system") {
Copy link
Contributor Author

@piyalbasu piyalbasu Jan 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting use-case here:

Webpack/Rollup default to using the file listed under module in package.json, but Jest uses main. I set this up because I'd like Jest to use the same file path that Webpack will be using

return {
...pkg,
// Alter the value of `main` before resolving the package
main: pkg.module || pkg.main,
};
}

return {
...pkg,
};
},
});
53 changes: 52 additions & 1 deletion extension/e2e-tests/onboarding.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ test("Create new wallet", async ({ page }) => {
await expect(page).toHaveScreenshot("wallet-create-complete-page.png");
});

test("Import wallet", async ({ page }) => {
test("Import 12 word wallet", async ({ page }) => {
await expect(page).toHaveScreenshot("welcome-page.png");
await page.getByText("Import Wallet").click();
await expect(
Expand Down Expand Up @@ -89,6 +89,57 @@ test("Import wallet", async ({ page }) => {
await expect(page).toHaveScreenshot("wallet-import-complete-page.png");
});

test("Import 24 word wallet", async ({ page }) => {
await expect(page).toHaveScreenshot("welcome-page.png");
await page.getByText("Import Wallet").click();
await expect(
page.getByText("Import wallet from recovery phrase"),
).toBeVisible();
await page.locator(".RecoverAccount__phrase-toggle > label").click();

// time out to accomodate animation
await page.waitForTimeout(150);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this probably works well enough but wanted to point out that there is also page.waitForSelector('yourselector') and page.waitForLoadState('domcontentloaded') which could give you less of a racy condition to wait for in the case of waiting for css animations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, nice! That's much better. I was not psyched about doing this, but couldn't get it to work reliably otherwise


const TEST_WORDS = [
"shrug",
"absent",
"sausage",
"later",
"salute",
"mesh",
"increase",
"flavor",
"pilot",
"patch",
"pole",
"twenty",
"chef",
"coffee",
"faint",
"apology",
"crucial",
"scene",
"attend",
"replace",
"wolf",
"error",
"swift",
"device",
];

for (let i = 1; i <= TEST_WORDS.length; i++) {
await page.locator(`#MnemonicPhrase-${i}`).fill(TEST_WORDS[i - 1]);
}

await page.locator("#password-input").fill("My-password123");
await page.locator("#confirm-password-input").fill("My-password123");
await page.locator("#termsOfUse-input").check({ force: true });
await page.getByRole("button", { name: "Import" }).click();

await expect(page.getByText("Wallet created successfully!")).toBeVisible();
await expect(page).toHaveScreenshot("wallet-import-complete-page.png");
});

test("Import wallet with wrong password", async ({ page }) => {
await expect(page).toHaveScreenshot("welcome-page.png");
await page.getByText("Import Wallet").click();
Expand Down
2 changes: 1 addition & 1 deletion extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"@shared/api": "1.0.0",
"@shared/constants": "1.0.0",
"@shared/helpers": "1.0.0",
"@stellar/design-system": "^1.0.1",
"@stellar/design-system": "^1.1.2",
"@stellar/wallet-sdk": "v0.11.0-beta.1",
"@testing-library/react": "^10.4.8",
"@testing-library/user-event": "^7.1.2",
Expand Down
2 changes: 1 addition & 1 deletion extension/src/popup/components/AutoSave/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
position: fixed;
top: 0;
width: 100%;
transition: opacity var(--anim-transition-default);
transition: opacity var(--dropdown-animation);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apparently this var doesn't exist in the repo anymore, so replacing it with this other animation timing var


&--failed {
opacity: 1;
Expand Down
105 changes: 79 additions & 26 deletions extension/src/popup/views/RecoverAccount/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ import React, { useEffect, useRef, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Field, Form, Formik, FieldProps } from "formik";
import { object as YupObject } from "yup";
import { Input, Checkbox, Icon, Link, Button } from "@stellar/design-system";
import {
Input,
Checkbox,
Icon,
Link,
Button,
Toggle,
} from "@stellar/design-system";
import { useTranslation } from "react-i18next";

import {
Expand Down Expand Up @@ -35,16 +42,23 @@ interface PhraseInputProps {
phraseInput: string;
index: number;
handleMnemonicInputChange: (value: string, index: number) => void;
isTextShowing: boolean;
isLongPhrase: boolean;
}

const PhraseInput = ({
phraseInput,
index,
handleMnemonicInputChange,
isTextShowing,
isLongPhrase,
}: PhraseInputProps) => {
const [isTextShowing, setIsTextShowing] = useState(false);
const [inputValue, setInputValue] = useState("");

useEffect(() => {
setInputValue("");
}, [isLongPhrase]);

return (
<div key={phraseInput} className="RecoverAccount__phrase-input">
<Input
Expand All @@ -61,16 +75,13 @@ const PhraseInput = ({
type={isTextShowing ? "text" : "password"}
value={inputValue}
/>
<div
className="RecoverAccount__password-toggle"
onClick={() => setIsTextShowing(!isTextShowing)}
>
{isTextShowing ? <Icon.Show /> : <Icon.Hide />}
</div>
</div>
);
};

const SHORT_PHRASE = 12;
const LONG_PHRASE = 24;

const buildMnemonicPhrase = (mnemonicPhraseArr: string[]) =>
mnemonicPhraseArr.join(" ").trim();

Expand Down Expand Up @@ -98,7 +109,8 @@ export const RecoverAccount = () => {
});

const dispatch = useDispatch();
const PHRASE_LENGTH = 12;
const [isLongPhrase, setIsLongPhrase] = useState(false);
const [isTextShowing, setIsTextShowing] = useState(false);
const [phraseInputs, setPhraseInputs] = useState([] as string[]);
const [mnemonicPhraseArr, setMnemonicPhraseArr] = useState([] as string[]);

Expand All @@ -120,14 +132,21 @@ export const RecoverAccount = () => {
}, [publicKey]);

useEffect(() => {
const phraseInputsArr = [];
const phraseInputsArr: string[] = [];
let PHRASE_LENGTH = SHORT_PHRASE;

// eslint-disable-next-line no-plusplus
for (let i = 1; i <= PHRASE_LENGTH; i++) {
phraseInputsArr.push(`MnemonicPhrase-${i}`);
}
setPhraseInputs(phraseInputsArr);
}, [PHRASE_LENGTH]);
// delay to account for css transition
setTimeout(() => {
PHRASE_LENGTH = isLongPhrase ? LONG_PHRASE : SHORT_PHRASE;

// eslint-disable-next-line no-plusplus
for (let i = 1; i <= PHRASE_LENGTH; i++) {
phraseInputsArr.push(`MnemonicPhrase-${i}`);
}
setPhraseInputs(phraseInputsArr);
setMnemonicPhraseArr([]);
}, 150);
}, [isLongPhrase]);

const handleMnemonicInputChange = (value: string, i: number) => {
const arr = [...mnemonicPhraseArr];
Expand Down Expand Up @@ -159,17 +178,51 @@ export const RecoverAccount = () => {

<OnboardingTwoCol>
<OnboardingOneCol>
<div className="RecoverAccount__mnemonic-input">
{phraseInputs.map((phraseInput, i) => (
<PhraseInput
key={phraseInput}
phraseInput={phraseInput}
handleMnemonicInputChange={handleMnemonicInputChange}
index={i}
/>
))}
<div>
<div
className={`RecoverAccount__mnemonic-input ${
isLongPhrase
? "RecoverAccount__mnemonic-input--long-phrase"
: ""
}`}
>
{phraseInputs.map((phraseInput, i) => (
<PhraseInput
key={phraseInput}
phraseInput={phraseInput}
handleMnemonicInputChange={
handleMnemonicInputChange
}
isTextShowing={isTextShowing}
isLongPhrase={isLongPhrase}
index={i}
/>
))}
</div>
{authError ? <FormError>{authError}</FormError> : <></>}
<div className="RecoverAccount__mnemonic-footer">
<div className="RecoverAccount__phrase-toggle">
<div>{SHORT_PHRASE} word</div>
<Toggle
checked={isLongPhrase}
id="RecoverAccount__toggle"
onChange={() => setIsLongPhrase(!isLongPhrase)}
/>
<div>{LONG_PHRASE} word</div>
</div>
<div className="RecoverAccount__mnemonic__text-toggle">
<Button
variant="secondary"
onClick={() => setIsTextShowing(!isTextShowing)}
size="xs"
type="button"
>
<span> {isTextShowing ? "Hide" : "Show"}</span>
{isTextShowing ? <Icon.Hide /> : <Icon.Show />}
</Button>
</div>
</div>
</div>
{authError ? <FormError>{authError}</FormError> : <></>}
</OnboardingOneCol>

<OnboardingOneCol>
Expand Down
33 changes: 29 additions & 4 deletions extension/src/popup/views/RecoverAccount/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,46 @@
&__mnemonic-input {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
gap: 0.5rem;
margin-bottom: 0.25rem;
max-height: 23.25rem;
overflow: hidden;
transition: max-height var(--dropdown-animation);
width: 24rem;

&--long-phrase {
max-height: 48rem;
}
}

&__phrase-input {
align-items: center;
display: flex;
gap: 0.5rem;
width: 11rem;
width: 11.6875rem;
}

&__phrase-toggle {
align-items: center;
display: flex;
font-size: 0.875rem;
gap: 0.75rem;
}

&__password-toggle {
&__mnemonic-footer {
display: flex;
justify-content: space-between;
margin-top: 0.5rem;
}

&__text-toggle {
align-items: center;
cursor: pointer;
display: flex;
width: 1rem;

span {
color: var(--color-white);
font-size: 0.875rem;
}
}
}
5 changes: 3 additions & 2 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const esModules = ["@stellar/wallet-sdk"];
const esModules = ["@stellar/wallet-sdk", "@stellar/design-system"];

const jsdomTests = {
rootDir: __dirname,
Expand All @@ -18,7 +18,7 @@ const jsdomTests = {
transform: {
"^.+\\.(js|jsx|ts|tsx|mjs)$": ["babel-jest"],
},
transformIgnorePatterns: [`/node_modules/(?!${esModules})`],
transformIgnorePatterns: [`/node_modules/(?!${esModules.join("|")})`],
moduleNameMapper: {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
"<rootDir>/config/jest/__mocks__/fileMock.ts",
Expand All @@ -27,6 +27,7 @@ const jsdomTests = {
moduleFileExtensions: ["js", "jsx", "json", "node", "mjs", "ts", "tsx"],
moduleDirectories: ["node_modules", "<rootDir>/extension/src", "<rootDir>/."],
testEnvironment: "jsdom",
resolver: "<rootDir>/config/jest/resolver.js",
modulePathIgnorePatterns: ["extension/e2e-tests"],
};

Expand Down
3 changes: 0 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@
"extension",
"@stellar/freighter-api"
],
"resolutions": {
"**/terser-webpack-plugin": "^1.4.3"
Copy link
Contributor Author

@piyalbasu piyalbasu Jan 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Webpack 5 comes with a newer version of this included. This was causing an issue as this old version of Terser contained an outdated JS spec

},
"scripts": {
"setup": "yarn install && yarn allow-scripts",
"build": "run-p --print-label build:freighter-api build:docs build:extension",
Expand Down
Loading
Loading