diff --git a/assets/_locales/en/messages.json b/assets/_locales/en/messages.json index 257f87ea..a9614de1 100644 --- a/assets/_locales/en/messages.json +++ b/assets/_locales/en/messages.json @@ -53,6 +53,10 @@ "message": "Close", "description": "Close button text" }, + "retry": { + "message": "Retry", + "description": "Retry button text" + }, "add_wallet": { "message": "Add wallet", "description": "Add a wallet text" @@ -677,6 +681,10 @@ "message": "The key length of the wallet you are importing is less than the required length to transact on the AO network. It is recommended you create a new ArConnect wallet and transfer your assets to your new wallet.", "description": "Key Length Too Short error" }, + "generate_wallet_in_progress": { + "message": "The wallet creation process can take 30-60 seconds.", + "description": "Generate wallet in progresss text" + }, "development_version": { "message": "Development", "description": "Development version badge text" @@ -791,6 +799,10 @@ "message": "Generating wallet...", "description": "Generating wallet in progress text" }, + "longer_than_usual": { + "message": "Please wait, this is taking longer than usual...", + "description": "Longer than usual text" + }, "keyfile": { "message": "Keyfile", "description": "Keyfile text" diff --git a/assets/_locales/zh_CN/messages.json b/assets/_locales/zh_CN/messages.json index 6bcb11a2..2caba310 100644 --- a/assets/_locales/zh_CN/messages.json +++ b/assets/_locales/zh_CN/messages.json @@ -53,6 +53,10 @@ "message": "关闭", "description": "Close button text" }, + "retry": { + "message": "重试", + "description": "Retry button text" + }, "add_wallet": { "message": "添加钱包", "description": "Add a wallet text" @@ -677,6 +681,10 @@ "message": "您导入的钱包的密钥长度小于在 AO 网络上进行交易所需的长度。建议您创建一个新的ArConnect钱包,并将您的资产转移到新钱包中。", "description": "Key Length Too Short error" }, + "generate_wallet_in_progress": { + "message": "钱包创建过程可能需要 30-60 秒。", + "description": "Generate wallet in progresss text" + }, "development_version": { "message": "开发版本", "description": "Development version badge text" @@ -791,6 +799,10 @@ "message": "生成钱包中...", "description": "Generating wallet in progress text" }, + "longer_than_usual": { + "message": "请稍等,这比平常需要更长的时间...", + "description": "Longer than usual text" + }, "keyfile": { "message": "密钥文件", "description": "Keyfile text" diff --git a/src/components/dashboard/subsettings/AddWallet.tsx b/src/components/dashboard/subsettings/AddWallet.tsx index d48e8292..a1767f30 100644 --- a/src/components/dashboard/subsettings/AddWallet.tsx +++ b/src/components/dashboard/subsettings/AddWallet.tsx @@ -31,6 +31,9 @@ export default function AddWallet() { // wallet size error modal const walletModal = useModal(); + // wallet generation taking longer + const [showLongWaitMessage, setShowLongWaitMessage] = useState(false); + // toasts const { setToast } = useToasts(); @@ -112,6 +115,7 @@ export default function AddWallet() { const finishUp = () => { // reset before unload window.onbeforeunload = null; + setShowLongWaitMessage(false); setLoading(false); }; @@ -131,17 +135,26 @@ export default function AddWallet() { } try { + const startTime = Date.now(); // load jwk from seedphrase input state - const jwk = + let jwk = typeof providedWallet === "string" ? await jwkFromMnemonic(providedWallet) : providedWallet; - const { actualLength, expectedLength } = await getWalletKeyLength(jwk); + let { actualLength, expectedLength } = await getWalletKeyLength(jwk); if (expectedLength !== actualLength) { - walletModal.setOpen(true); - finishUp(); - return; + if (typeof providedWallet !== "string") { + walletModal.setOpen(true); + finishUp(); + return; + } else { + while (expectedLength !== actualLength) { + setShowLongWaitMessage(Date.now() - startTime > 30000); + jwk = await jwkFromMnemonic(providedWallet); + ({ actualLength, expectedLength } = await getWalletKeyLength(jwk)); + } + } } await addWallet(jwk, passwordInput.state); @@ -187,15 +200,25 @@ export default function AddWallet() { async function generateWallet() { setGenerating(true); + const startTime = Date.now(); + // generate a seedphrase const seedphrase = await bip39.generateMnemonic(); setGeneratedWallet({ seedphrase }); // generate from seedphrase - const jwk = await jwkFromMnemonic(seedphrase); + let jwk = await jwkFromMnemonic(seedphrase); + + let { actualLength, expectedLength } = await getWalletKeyLength(jwk); + while (expectedLength !== actualLength) { + setShowLongWaitMessage(Date.now() - startTime > 30000); + jwk = await jwkFromMnemonic(seedphrase); + ({ actualLength, expectedLength } = await getWalletKeyLength(jwk)); + } setGeneratedWallet((val) => ({ ...val, jwk })); + setShowLongWaitMessage(false); setGenerating(false); return { jwk, seedphrase }; @@ -337,6 +360,11 @@ export default function AddWallet() { {browser.i18n.getMessage("generate_wallet")} + {(generating || loading) && showLongWaitMessage && ( + + {browser.i18n.getMessage("longer_than_usual")} + + )} diff --git a/src/routes/popup/passwordPopup.tsx b/src/routes/popup/passwordPopup.tsx index e9648c10..0b0f0d76 100644 --- a/src/routes/popup/passwordPopup.tsx +++ b/src/routes/popup/passwordPopup.tsx @@ -12,7 +12,7 @@ import aoLogo from "url:/assets/ecosystem/ao-token-logo.png"; import styled from "styled-components"; import { CheckIcon, CloseIcon } from "@iconicicons/react"; import { ResetButton } from "~components/dashboard/Reset"; -import { Content, ContentWrapper } from "./announcement"; +import { Content, ContentWrapper } from "~components/modals/Components"; export const PasswordWarningModal = ({ open, diff --git a/src/routes/welcome/generate/backup.tsx b/src/routes/welcome/generate/backup.tsx index 846ea723..38976d24 100644 --- a/src/routes/welcome/generate/backup.tsx +++ b/src/routes/welcome/generate/backup.tsx @@ -1,6 +1,6 @@ import { ButtonV2, Spacer, Text } from "@arconnect/components"; import { useLocation, useRoute } from "wouter"; -import { useContext, useEffect, useState } from "react"; +import { useContext, useEffect, useRef, useState } from "react"; import { WalletContext } from "../setup"; import Paragraph from "~components/Paragraph"; import browser from "webextension-polyfill"; @@ -20,7 +20,10 @@ export default function Backup() { const [shown, setShown] = useState(false); // wallet context - const generatedWallet = useContext(WalletContext); + const { wallet: generatedWallet } = useContext(WalletContext); + + // ref to track the latest generated wallet + const walletRef = useRef(generatedWallet); // route const [, params] = useRoute<{ setup: string; page: string }>("/:setup/:page"); @@ -36,6 +39,10 @@ export default function Backup() { setTimeout(() => setCopyDisplay(true), 1050); } + useEffect(() => { + walletRef.current = generatedWallet; + }, [generatedWallet]); + // Segment useEffect(() => { trackPage(PageType.ONBOARD_BACKUP); diff --git a/src/routes/welcome/generate/confirm.tsx b/src/routes/welcome/generate/confirm.tsx index 96a4d46a..6e243010 100644 --- a/src/routes/welcome/generate/confirm.tsx +++ b/src/routes/welcome/generate/confirm.tsx @@ -10,7 +10,7 @@ import { PageType, trackPage } from "~utils/analytics"; export default function Confirm() { // wallet context - const generatedWallet = useContext(WalletContext); + const { wallet: generatedWallet } = useContext(WalletContext); // toasts const { setToast } = useToasts(); diff --git a/src/routes/welcome/generate/done.tsx b/src/routes/welcome/generate/done.tsx index c9bebde0..89f2a93e 100644 --- a/src/routes/welcome/generate/done.tsx +++ b/src/routes/welcome/generate/done.tsx @@ -5,7 +5,7 @@ import { formatAddress } from "~utils/format"; import Paragraph from "~components/Paragraph"; import browser from "webextension-polyfill"; import { addWallet } from "~wallets"; -import { useContext, useEffect } from "react"; +import { useContext, useEffect, useRef, useState } from "react"; import { EventType, PageType, @@ -22,7 +22,14 @@ import { addExpiration } from "~wallets/auth"; export default function Done() { // wallet context - const wallet = useContext(WalletContext); + const { wallet } = useContext(WalletContext); + const walletRef = useRef(wallet); + + // loading + const [loading, setLoading] = useState(false); + + // wallet generation taking longer + const [showLongWaitMessage, setShowLongWaitMessage] = useState(false); const [, setLocation] = useLocation(); @@ -36,13 +43,32 @@ export default function Done() { // add generated wallet async function done() { + if (loading) return; + + const startTime = Date.now(); + + setLoading(true); // add wallet let nickname: string; - if (!wallet.address || !wallet.jwk) return; + if (!walletRef.current.address || !walletRef.current.jwk) { + await new Promise((resolve) => { + const checkState = setInterval(() => { + if (walletRef.current.jwk) { + clearInterval(checkState); + resolve(null); + } + if (!showLongWaitMessage) { + setShowLongWaitMessage(Date.now() - startTime > 10000); + } + }, 1000); + }); + } try { - const ansProfile = (await getAnsProfile(wallet.address)) as AnsUser; + const ansProfile = (await getAnsProfile( + walletRef.current.address + )) as AnsUser; if (ansProfile) { nickname = ansProfile.currentLabel; @@ -51,7 +77,9 @@ export default function Done() { // add the wallet await addWallet( - nickname ? { nickname, wallet: wallet.jwk } : wallet.jwk, + nickname + ? { nickname, wallet: walletRef.current.jwk } + : walletRef.current.jwk, password ); @@ -68,6 +96,12 @@ export default function Done() { // redirect to getting started pages setLocation("/getting-started/1"); + + setShowLongWaitMessage(false); + setLoading(false); + + // reset before unload + window.onbeforeunload = null; } useEffect(() => { @@ -90,6 +124,10 @@ export default function Done() { getLocation(); }, []); + useEffect(() => { + walletRef.current = wallet; + }, [wallet]); + // Segment useEffect(() => { trackPage(PageType.ONBOARD_COMPLETE); @@ -113,9 +151,14 @@ export default function Done() { {browser.i18n.getMessage("analytics_title")} - + {browser.i18n.getMessage("done")} + {loading && showLongWaitMessage && ( + + {browser.i18n.getMessage("longer_than_usual")} + + )} ); } diff --git a/src/routes/welcome/gettingStarted.tsx b/src/routes/welcome/gettingStarted.tsx index cd74a154..a79d7a20 100644 --- a/src/routes/welcome/gettingStarted.tsx +++ b/src/routes/welcome/gettingStarted.tsx @@ -38,6 +38,8 @@ export default function GettingStarted({ page }) { if (pageNum < 5) { setLocation(`/getting-started/${pageNum}`); } else { + // reset before unload + window.onbeforeunload = null; window.top.close(); } }; diff --git a/src/routes/welcome/load/done.tsx b/src/routes/welcome/load/done.tsx index 329c7d86..fdbc4536 100644 --- a/src/routes/welcome/load/done.tsx +++ b/src/routes/welcome/load/done.tsx @@ -25,6 +25,9 @@ export default function Done() { await setAnalytics(false); } + // reset before unload + window.onbeforeunload = null; + // redirect to getting started pages setLocation("/getting-started/1"); } diff --git a/src/routes/welcome/load/password.tsx b/src/routes/welcome/load/password.tsx index e961db78..16e6b9bb 100644 --- a/src/routes/welcome/load/password.tsx +++ b/src/routes/welcome/load/password.tsx @@ -70,16 +70,12 @@ export default function Password() { setLocation(`/${params.setup}/${Number(params.page) + 1}`); } - // password valid - const validPassword = useMemo( - () => checkPasswordValid(passwordInput.state), - [passwordInput] - ); - // passwords match const matches = useMemo( - () => passwordInput.state === validPasswordInput.state && validPassword, - [passwordInput, validPasswordInput, validPassword] + () => + passwordInput.state === validPasswordInput.state && + passwordInput.state?.length >= 5, + [passwordInput, validPasswordInput] ); // Segment diff --git a/src/routes/welcome/load/wallets.tsx b/src/routes/welcome/load/wallets.tsx index 25e37d62..2b579101 100644 --- a/src/routes/welcome/load/wallets.tsx +++ b/src/routes/welcome/load/wallets.tsx @@ -34,6 +34,9 @@ export default function Wallets() { // password context const { password } = useContext(PasswordContext); + // wallet generation taking longer + const [showLongWaitMessage, setShowLongWaitMessage] = useState(false); + // migration available const [oldState] = useStorage({ key: OLD_STORAGE_NAME, @@ -110,6 +113,7 @@ export default function Wallets() { const finishUp = () => { // reset before unload window.onbeforeunload = null; + setShowLongWaitMessage(false); setLoading(false); }; @@ -135,16 +139,28 @@ export default function Wallets() { if (loadedWallet) { // load jwk from seedphrase input state - const jwk = + const startTime = Date.now(); + + let jwk = typeof loadedWallet === "string" ? await jwkFromMnemonic(loadedWallet) : loadedWallet; - const { expectedLength, actualLength } = await getWalletKeyLength(jwk); + let { actualLength, expectedLength } = await getWalletKeyLength(jwk); if (expectedLength !== actualLength) { - walletModal.setOpen(true); - finishUp(); - return; + if (typeof loadedWallet !== "string") { + walletModal.setOpen(true); + finishUp(); + return; + } else { + while (expectedLength !== actualLength) { + setShowLongWaitMessage(Date.now() - startTime > 30000); + jwk = await jwkFromMnemonic(loadedWallet); + ({ actualLength, expectedLength } = await getWalletKeyLength( + jwk + )); + } + } } // add wallet @@ -214,6 +230,11 @@ export default function Wallets() { {browser.i18n.getMessage("next")} + {loading && showLongWaitMessage && ( + + {browser.i18n.getMessage("longer_than_usual")} + + )} ( - {} - ); + const [generatedWallet, setGeneratedWallet] = useState({}); const navigate = () => { setLocation(`/${params.setup}/${page - 1}`); }; - useEffect(() => { - (async () => { - // only generate wallet if the - // setup mode is wallet generation - if (!isGenerateWallet || generatedWallet.address) return; + async function generateWallet() { + // only generate wallet if the + // setup mode is wallet generation + if (!isGenerateWallet || generatedWallet.address) return; - // prevent user from closing the window - // while ArConnect is generating a wallet - window.onbeforeunload = () => - browser.i18n.getMessage("close_tab_generate_wallet_message"); + // prevent user from closing the window + // while ArConnect is generating a wallet + window.onbeforeunload = () => + browser.i18n.getMessage("close_tab_generate_wallet_message"); - try { - const arweave = new Arweave(defaultGateway); + try { + const arweave = new Arweave(defaultGateway); - // generate seed - const seed = await bip39.generateMnemonic(); + // generate seed + const seed = await bip39.generateMnemonic(); - setGeneratedWallet({ mnemonic: seed }); + setGeneratedWallet({ mnemonic: seed }); - // generate wallet from seedphrase - const generatedKeyfile = await jwkFromMnemonic(seed); + // generate wallet from seedphrase + let generatedKeyfile = await jwkFromMnemonic(seed); - setGeneratedWallet((val) => ({ ...val, jwk: generatedKeyfile })); + let { actualLength, expectedLength } = await getWalletKeyLength( + generatedKeyfile + ); + while (expectedLength !== actualLength) { + generatedKeyfile = await jwkFromMnemonic(seed); + ({ actualLength, expectedLength } = await getWalletKeyLength( + generatedKeyfile + )); + } - // get address - const address = await arweave.wallets.jwkToAddress(generatedKeyfile); + setGeneratedWallet((val) => ({ ...val, jwk: generatedKeyfile })); - setGeneratedWallet((val) => ({ ...val, address })); - } catch (e) { - console.log("Error generating wallet", e); - setToast({ - type: "error", - content: browser.i18n.getMessage("error_generating_wallet"), - duration: 2300 - }); - } + // get address + const address = await arweave.wallets.jwkToAddress(generatedKeyfile); + + setGeneratedWallet((val) => ({ ...val, address })); + + return generatedWallet; + } catch (e) { + console.log("Error generating wallet", e); + setToast({ + type: "error", + content: browser.i18n.getMessage("error_generating_wallet"), + duration: 2300 + }); + } - // reset before unload - window.onbeforeunload = null; - })(); + return {}; + } + + useEffect(() => { + generateWallet(); }, [isGenerateWallet]); // animate content sice @@ -189,7 +201,9 @@ export default function Setup({ setupMode, page }: Props) { - + @@ -290,9 +304,17 @@ export const PasswordContext = createContext({ password: "" }); -export const WalletContext = createContext({}); +export const WalletContext = createContext({ + wallet: {}, + generateWallet: (retry?: boolean) => Promise.resolve({}) +}); interface WalletContextValue { + wallet: GeneratedWallet; + generateWallet: (retry?: boolean) => Promise; +} + +interface GeneratedWallet { address?: string; mnemonic?: string; jwk?: JWKInterface;