Skip to content

Commit

Permalink
feat: Support reset lock window password (#3197)
Browse files Browse the repository at this point in the history
  • Loading branch information
yanguoyu authored Jul 1, 2024
1 parent 4493d79 commit 54994c3
Show file tree
Hide file tree
Showing 21 changed files with 364 additions and 94 deletions.
2 changes: 1 addition & 1 deletion packages/neuron-ui/src/components/CellManagement/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ export const usePassword = () => {
}

export const useHardWallet = ({ wallet, t }: { wallet: State.WalletIdentity; t: TFunction }) => {
const isWin32 = useMemo(() => {
const isWin32 = useMemo<boolean>(() => {
return getPlatform() === 'win32'
}, [])
const [error, setError] = useState<ErrorCode | string | undefined>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const useRepeatPassword = ({
password: string
t: TFunction
encryptedPassword?: string
onCancel: () => void
onCancel: (success: boolean) => void
}) => {
const dispatch = useDispatch()
const [errMsg, setErrMsg] = useState('')
Expand All @@ -89,7 +89,7 @@ export const useRepeatPassword = ({
updateLockWindowInfo(
encryptedPassword ? { password: updatedRepeatPassword } : { password: updatedRepeatPassword, locked: true }
)(dispatch)
onCancel()
onCancel(true)
}
} else {
setErrMsg('')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const LockWindowDialog = ({
encryptedPassword,
}: {
show: boolean
onCancel: () => void
onCancel: (success?: boolean) => void
encryptedPassword?: string
}) => {
const [t] = useTranslation()
Expand Down
70 changes: 61 additions & 9 deletions packages/neuron-ui/src/containers/LockWindow/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable jsx-a11y/media-has-caption */
import React, { useCallback, useEffect, useState } from 'react'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { AppActions, getLockWindowInfo, useDispatch, useState as useGlobalState } from 'states'
import Spinner from 'widgets/Spinner'
import Locked from 'widgets/Icons/Locked.png'
Expand All @@ -8,10 +8,13 @@ import UnLockMp4 from 'widgets/Icons/unlock.mp4'
import SplitPasswordInput from 'widgets/SplitPasswordInput'
import { useTranslation } from 'react-i18next'
import { clsx, isSuccessResponse } from 'utils'
import { isDark, unlockWindow } from 'services/remote'
import { isDark, signMessage, unlockWindow } from 'services/remote'
import { retryUnlockWindow } from 'services/localCache'
import { MILLISECS_PER_HOUR, MILLISECS_PER_MIN, MILLISECS_PER_SEC } from 'utils/getSyncLeftTime'
import { ControllerResponse } from 'services/remote/remoteApiWrapper'
import LockWindowDialog from 'components/GeneralSetting/LockWindowDialog'
import styles from './lockWindow.module.scss'
import VerifyWallet from './verifyWallet'

const passwordLen = 4
const wrongEnterTimes = 3
Expand All @@ -24,12 +27,6 @@ const formatterLockMillisecs = (lockMillisecs: number) => {

const getWaitMillisecs = (retryTimes: number) => {
if (retryTimes % wrongEnterTimes === 0) {
if (retryTimes >= 3 * wrongEnterTimes) {
return 24 * MILLISECS_PER_HOUR
}
if (retryTimes > wrongEnterTimes) {
return 30 * MILLISECS_PER_MIN
}
return 5 * MILLISECS_PER_MIN
}
return undefined
Expand All @@ -41,7 +38,7 @@ const LockWindow = ({ children }: { children: React.ReactNode }) => {
useEffect(() => {
getLockWindowInfo(dispatch)
}, [])
const { app } = useGlobalState()
const { app, wallet } = useGlobalState()
const [password, setPassword] = useState<string[]>(new Array(passwordLen).fill(''))
const [errMsg, setErrMsg] = useState('')
const [retryUnlockInfo, setRetryUnlockInfo] = useState(retryUnlockWindow.get())
Expand Down Expand Up @@ -130,6 +127,25 @@ const LockWindow = ({ children }: { children: React.ReactNode }) => {
}
return () => clearInterval(interval)
}, [retryUnlockInfo])
const splitPasswordInputRef = useRef<{ focus: () => void } | null>(null)
const [isVerifyWalletDialogShow, setIsVerifyWalletDialogShow] = useState(false)
const [isResetPasswordDialogShow, setIsResetPasswordDialogShow] = useState(false)
const onVerifyWallet = useCallback(
async (walletPassword?: string) => {
const res: ControllerResponse = await signMessage({
walletID: wallet?.id ?? '',
message: 'verify wallet for reset lock window password',
password: walletPassword ?? '',
})
if (isSuccessResponse(res)) {
setIsVerifyWalletDialogShow(false)
setIsResetPasswordDialogShow(true)
} else {
throw new Error(typeof res.message === 'string' ? res.message : res.message.content)
}
},
[setIsResetPasswordDialogShow, setIsVerifyWalletDialogShow, wallet]
)
if (!app.lockWindowInfo) {
return (
<div className={styles.loading}>
Expand All @@ -156,11 +172,47 @@ const LockWindow = ({ children }: { children: React.ReactNode }) => {
disabled={retryUnlockInfo.retryTimes % wrongEnterTimes === 0 && !!retryUnlockInfo.lastRetryTime}
values={password}
onChange={onUpdatePassword}
ref={splitPasswordInputRef}
/>
</div>
<div className={styles.notice} data-has-err={!!errMsg}>
{errMsg || t('lock-window.enter-lock-password')}
{wallet.isWatchOnly ? null : (
<button
type="button"
onClick={() => {
setIsVerifyWalletDialogShow(true)
}}
>
{t('lock-window.forget-password')}
</button>
)}
</div>
<VerifyWallet
show={isVerifyWalletDialogShow}
wallet={wallet}
onCancel={() => {
setIsVerifyWalletDialogShow(false)
setTimeout(() => {
// wait for dialog close
splitPasswordInputRef.current?.focus()
}, 10)
}}
onConfirm={onVerifyWallet}
/>
<LockWindowDialog
show={isResetPasswordDialogShow}
onCancel={success => {
setIsResetPasswordDialogShow(false)
if (success) {
setPassword(new Array(passwordLen).fill(''))
}
setTimeout(() => {
// wait for dialog close
splitPasswordInputRef.current?.focus()
}, 10)
}}
/>
</div>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
&[data-has-err='true'] {
color: var(--error-color);
}

& > button {
border: none;
color: var(--primary-color);
background-color: transparent;
cursor: pointer;
}
}

.passwordContainer {
Expand Down Expand Up @@ -53,3 +60,12 @@
height: 88px;
}
}

.verifyWallet {
min-width: 600px;

.hardwalletErr {
justify-content: center;
margin-top: 12px;
}
}
121 changes: 121 additions & 0 deletions packages/neuron-ui/src/containers/LockWindow/verifyWallet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import React, { useCallback, useEffect, useState } from 'react'
import Dialog from 'widgets/Dialog'
import Hardware from 'widgets/Icons/Hardware.png'
import Button from 'widgets/Button'
import { useHardWallet, usePassword } from 'components/CellManagement/hooks'
import Alert from 'widgets/Alert'
import TextField from 'widgets/TextField'
import { useTranslation } from 'react-i18next'
import styles from './lockWindow.module.scss'

const VerifyWallet = ({
wallet,
show,
onCancel,
onConfirm,
}: {
wallet: State.Wallet
show: boolean
onCancel: () => void
onConfirm: (password?: string) => Promise<void>
}) => {
const [t] = useTranslation()
const [loading, setLoading] = useState(false)
const {
isReconnecting,
isNotAvailable,
reconnect,
verifyDeviceStatus,
errorMessage: hardwalletError,
setError: setHardwalletError,
} = useHardWallet({
wallet,
t,
})
const { password, error, onPasswordChange, setError, resetPassword } = usePassword()
useEffect(() => {
if (show) {
resetPassword()
}
}, [show, resetPassword])
useEffect(() => {
if (show && wallet.device) {
verifyDeviceStatus()
}
}, [show, wallet.device, verifyDeviceStatus])
const onConfirmWrapper = useCallback(() => {
setLoading(true)
onConfirm(wallet.device ? undefined : password)
.catch(err => {
if (wallet.device) {
setHardwalletError(err.message)
} else {
setError(err.message)
}
})
.finally(() => {
setLoading(false)
})
}, [wallet.device, onConfirm, setLoading, setHardwalletError, setError, password])
if (wallet.device) {
return (
<Dialog
show={show}
title={`${t(`lock-window.verify-wallet`)}-${wallet.name}`}
onCancel={onCancel}
onConfirm={onConfirmWrapper}
showFooter={false}
className={styles.verifyWallet}
>
<div>
<img src={Hardware} alt="hard-wallet" className={styles.hardWalletImg} />
</div>
<div className={styles.lockActions}>
<Button
onClick={isNotAvailable ? reconnect : onConfirmWrapper}
loading={loading || isReconnecting}
type="primary"
>
{isNotAvailable || isReconnecting
? t('hardware-verify-address.actions.reconnect')
: t('cell-manage.verify')}
</Button>
</div>
{hardwalletError ? (
<Alert status="error" className={styles.hardwalletErr}>
{hardwalletError}
</Alert>
) : null}
</Dialog>
)
}
return (
<Dialog
show={show}
title={`${t(`lock-window.verify-wallet`)}-${wallet.name}`}
onCancel={onCancel}
onConfirm={onConfirmWrapper}
showCancel={false}
className={styles.verifyWallet}
isLoading={loading}
>
<TextField
className={styles.passwordInput}
placeholder={t('cell-manage.password-placeholder')}
width="100%"
label={t('cell-manage.enter-password')}
value={password}
field="password"
type="password"
title={t('cell-manage.enter-password')}
onChange={onPasswordChange}
autoFocus
error={error}
/>
</Dialog>
)
}

VerifyWallet.displayName = 'VerifyWallet'

export default VerifyWallet
4 changes: 3 additions & 1 deletion packages/neuron-ui/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1301,8 +1301,10 @@
"lock-window": {
"neuron-is-locked": "Neuron's window has been locked",
"enter-lock-password": "Enter lock password",
"forget-password": "Forget password?",
"lock-password-error": "Lock windows password error",
"failed-times": "Failed more than {{frequency}} times, please retry after {{time}}"
"failed-times": "Failed more than {{frequency}} times, please retry after {{time}}",
"verify-wallet": "Verify"
}
}
}
4 changes: 3 additions & 1 deletion packages/neuron-ui/src/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -1281,8 +1281,10 @@
"lock-window": {
"neuron-is-locked": "La ventana de Neuron está bloqueada",
"enter-lock-password": "Ingresar contraseña de bloqueo de pantalla",
"forget-password": "Olvidé mi contraseña?",
"lock-password-error": "Contraseña de bloqueo de pantalla incorrecta",
"failed-times": "Fallo más de {{frequency}} veces, por favor inténtalo de nuevo después de {{time}}"
"failed-times": "Fallo más de {{frequency}} veces, por favor inténtalo de nuevo después de {{time}}",
"verify-wallet": "Verificar"
}
}
}
4 changes: 3 additions & 1 deletion packages/neuron-ui/src/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1291,8 +1291,10 @@
"lock-window": {
"neuron-is-locked": "a fenêtre de Neuron est verrouillée",
"enter-lock-password": "Entrer le mot de passe de verrouillage d'écran",
"forget-password": "Mot de passe oublié?",
"lock-password-error": "Mot de passe de verrouillage d'écran incorrect",
"failed-times": "Échec plus de {{frequency}} fois, veuillez réessayer après {{time}}"
"failed-times": "Échec plus de {{frequency}} fois, veuillez réessayer après {{time}}",
"verify-wallet": "Vérifier"
}
}
}
4 changes: 3 additions & 1 deletion packages/neuron-ui/src/locales/zh-tw.json
Original file line number Diff line number Diff line change
Expand Up @@ -1290,8 +1290,10 @@
"lock-window": {
"neuron-is-locked": "Neuron 窗口已鎖定",
"enter-lock-password": "輸入鎖屏密碼",
"forget-password": "忘記密碼?",
"lock-password-error": "鎖屏密碼錯誤",
"failed-times": "失敗超過 {{frequency}} 次, 請在 {{time}} 後重試"
"failed-times": "失敗超過 {{frequency}} 次, 請在 {{time}} 後重試",
"verify-wallet": "驗證"
}
}
}
4 changes: 3 additions & 1 deletion packages/neuron-ui/src/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -1293,8 +1293,10 @@
"lock-window": {
"neuron-is-locked": "Neuron 窗口已锁定",
"enter-lock-password": "输入锁屏密码",
"forget-password": "忘记密码?",
"lock-password-error": "锁屏密码错误",
"failed-times": "失败超过 {{frequency}} 次, 请在 {{time}} 后重试"
"failed-times": "失败超过 {{frequency}} 次, 请在 {{time}} 后重试",
"verify-wallet": "验证"
}
}
}
2 changes: 1 addition & 1 deletion packages/neuron-ui/src/types/Controller/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ declare namespace Controller {

interface SignMessageParams {
walletID: string
address: string
address?: string
password: string
message: string
}
Expand Down
8 changes: 7 additions & 1 deletion packages/neuron-ui/src/widgets/Dialog/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,13 @@ const Dialog = ({
<dialog
ref={dialogRef}
className={`${styles.dialogWrap} ${className}`}
onKeyDown={e => (e.key === 'Escape' && enableCloseWithEsc ? onCancel : undefined)}
onKeyDown={e => {
if (e.key === 'Escape' && enableCloseWithEsc) {
onCancel?.()
} else if (e.key === 'Enter' && showFooter && showConfirm) {
handleConfirm(e)
}
}}
role="none"
>
{showHeader ? (
Expand Down
Loading

1 comment on commit 54994c3

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

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

Packaging for test is done in 9736862247

Please sign in to comment.