From c2ccce57d27c37fbb1f3b493442a27a7f3b6be1e Mon Sep 17 00:00:00 2001 From: heisenberg <110591045+heisenberg-2077@users.noreply.github.com> Date: Thu, 28 Sep 2023 17:56:27 +0800 Subject: [PATCH] feat: added disable dapp and connection (#1775) * feat: added disable dapp * feat: added disable connection * fix: title style * fix: close popup * chore: rm api * fix: popup height * feat: + changelog --------- Co-authored-by: vvvvvv1vvvvvv --- _raw/locales/en/messages.json | 9 +- changeLogs/09225.md | 2 + changeLogs/index.ts | 2 + changeLogs/zh_CN/09225.md | 2 + .../controller/provider/controller.ts | 9 -- src/background/controller/wallet.ts | 11 +++ src/background/service/notification.ts | 92 ++++++++++++++++++- src/ui/assets/approval/arrow-down-blue.svg | 5 +- .../Approval/components/Connect/index.tsx | 37 +++++++- .../components/FooterBar/ActionsContainer.tsx | 41 ++++++++- .../CancelApproval/CancelApproval.tsx | 64 +++++++++++++ .../CommonPopup/CancelApproval/CancelItem.tsx | 34 +++++++ .../CancelConnect/CancelConnect.tsx | 39 ++++++++ src/ui/views/CommonPopup/index.tsx | 29 +++++- 14 files changed, 355 insertions(+), 21 deletions(-) create mode 100644 changeLogs/09225.md create mode 100644 changeLogs/zh_CN/09225.md create mode 100644 src/ui/views/CommonPopup/CancelApproval/CancelApproval.tsx create mode 100644 src/ui/views/CommonPopup/CancelApproval/CancelItem.tsx create mode 100644 src/ui/views/CommonPopup/CancelConnect/CancelConnect.tsx diff --git a/_raw/locales/en/messages.json b/_raw/locales/en/messages.json index 867350713bd..5769030e2b1 100644 --- a/_raw/locales/en/messages.json +++ b/_raw/locales/en/messages.json @@ -291,7 +291,14 @@ "resend": "Retry", "submitTx": "Submit Transaction", "testnet": "Testnet", - "mainnet": "Mainnet" + "mainnet": "Mainnet", + "cancelTransaction": "Cancel Transaction", + "detectedMultipleRequestsFromThisDapp": "Detected multiple requests from this Dapp", + "cancelCurrentTransaction": "Cancel current transaction", + "cancelAll": "Cancel all {{count}} requests from Dapp", + "blockDappFromSendingRequests": "Block Dapp from sending requests for 10 min", + "cancelConnection": "Cancel connection", + "cancelCurrentConnection": "Cancel current connection" }, "signTypedData": { "signTypeDataOnChain": "Sign {{chain}} Typed Data", diff --git a/changeLogs/09225.md b/changeLogs/09225.md new file mode 100644 index 00000000000..f821ba430a2 --- /dev/null +++ b/changeLogs/09225.md @@ -0,0 +1,2 @@ +- Supported 4 testnet, including Moonbase Alpha Testnet, KCC Testnet, IoTeX Testnet, Pulse V4 Testnet +- Support for block continuous signing from phishing Dapps \ No newline at end of file diff --git a/changeLogs/index.ts b/changeLogs/index.ts index d5fea940428..d48698b7fe4 100644 --- a/changeLogs/index.ts +++ b/changeLogs/index.ts @@ -43,6 +43,7 @@ import version09221 from './09221.md'; import version09222 from './09222.md'; import version09223 from './09223.md'; import version09224 from './09224.md'; +import version09225 from './09225.md'; const version = process.env.release || '0'; const versionMap = { @@ -92,6 +93,7 @@ const versionMap = { '0.92.22': version09222, '0.92.23': version09223, '0.92.24': version09224, + '0.92.25': version09225, }; export const getUpdateContent = () => { return versionMap[version]; diff --git a/changeLogs/zh_CN/09225.md b/changeLogs/zh_CN/09225.md new file mode 100644 index 00000000000..ac337cb3813 --- /dev/null +++ b/changeLogs/zh_CN/09225.md @@ -0,0 +1,2 @@ +- 支持4条测试网,包括 Moonbase Alpha Testnet,KCC Testnet,IoTeX Testnet,Pulse V4 Testnet +- 支持屏蔽钓鱼Dapp的连续签名 \ No newline at end of file diff --git a/src/background/controller/provider/controller.ts b/src/background/controller/provider/controller.ts index 4e958447cea..7c6c54d4aa9 100644 --- a/src/background/controller/provider/controller.ts +++ b/src/background/controller/provider/controller.ts @@ -595,15 +595,6 @@ class ProviderController extends BaseController { 'eth_sendRawTransaction', [rawTx] ); - try { - openapiService.traceTx( - hash, - traceId || '', - chainItem?.serverId || '' - ); - } catch (e) { - // DO nothing - } } else { hash = await openapiService.pushTx( { diff --git a/src/background/controller/wallet.ts b/src/background/controller/wallet.ts index 493aebb54c8..6ec32f989c9 100644 --- a/src/background/controller/wallet.ts +++ b/src/background/controller/wallet.ts @@ -3310,6 +3310,17 @@ export class WalletController extends BaseController { }; updateNotificationWinProps = notificationService.updateNotificationWinProps; + + checkNeedDisplayBlockedRequestApproval = + notificationService.checkNeedDisplayBlockedRequestApproval; + + checkNeedDisplayCancelAllApproval = + notificationService.checkNeedDisplayCancelAllApproval; + + blockedDapp = () => { + notificationService.blockedDapp(); + this.rejectAllApprovals(); + }; } const wallet = new WalletController(); diff --git a/src/background/service/notification.ts b/src/background/service/notification.ts index b280d56c59b..d93afadce8c 100644 --- a/src/background/service/notification.ts +++ b/src/background/service/notification.ts @@ -66,6 +66,15 @@ type StatsData = { // should only open one window, unfocus will close the current notification class NotificationService extends Events { currentApproval: Approval | null = null; + dappManager = new Map< + string, + { + lastRejectTimestamp: number; + lastRejectCount: number; + blockedTimestamp: number; + isBlocked: boolean; + } + >(); _approvals: Approval[] = []; notifiWindowId: null | number = null; isLocked = false; @@ -175,6 +184,7 @@ class NotificationService extends Events { const approval = this.currentApproval; + this.clearLastRejectDapp(); this.deleteApproval(approval); if (this.approvals.length > 0) { @@ -187,8 +197,8 @@ class NotificationService extends Events { }; rejectApproval = async (err?: string, stay = false, isInternal = false) => { + this.addLastRejectDapp(); const approval = this.currentApproval; - if (this.approvals.length <= 1) { await this.clear(stay); // TODO: FIXME } @@ -214,6 +224,19 @@ class NotificationService extends Events { }; requestApproval = async (data, winProps?): Promise => { + const origin = this.getOrigin(data); + if (origin) { + const dapp = this.dappManager.get(origin); + // is blocked and less 10 min + if ( + dapp?.isBlocked && + Date.now() - dapp.blockedTimestamp < 60 * 1000 * 10 + ) { + throw ethErrors.provider.userRejectedRequest( + 'User rejected the request.' + ); + } + } const currentAccount = preferenceService.getCurrentAccount(); const reportExplain = (signingTxId?: string) => { const signingTx = signingTxId @@ -344,6 +367,7 @@ class NotificationService extends Events { }; rejectAllApprovals = () => { + this.addLastRejectDapp(); this.approvals.forEach((approval) => { approval.reject && approval.reject( @@ -399,6 +423,72 @@ class NotificationService extends Events { getStatsData = () => { return this.statsData; }; + + private addLastRejectDapp() { + // not Rabby dapp + if (this.currentApproval?.data?.params?.$ctx) return; + const origin = this.getOrigin(); + if (!origin) { + return; + } + const dapp = this.dappManager.get(origin); + // same origin and less 1 min + if (dapp && Date.now() - dapp.lastRejectTimestamp < 60 * 1000) { + dapp.lastRejectCount = dapp.lastRejectCount + 1; + dapp.lastRejectTimestamp = Date.now(); + } else { + this.dappManager.set(origin, { + lastRejectTimestamp: Date.now(), + lastRejectCount: 1, + blockedTimestamp: 0, + isBlocked: false, + }); + } + } + + private clearLastRejectDapp() { + const origin = this.getOrigin(); + if (!origin) { + return; + } + this.dappManager.delete(origin); + } + + checkNeedDisplayBlockedRequestApproval = () => { + const origin = this.getOrigin(); + if (!origin) { + return false; + } + const dapp = this.dappManager.get(origin); + if (!dapp) return false; + // less 1 min and reject count more than 2 times + if ( + Date.now() - dapp.lastRejectTimestamp < 60 * 1000 && + dapp.lastRejectCount >= 2 + ) { + return true; + } + return false; + }; + checkNeedDisplayCancelAllApproval = () => { + return this.approvals.length > 1; + }; + + blockedDapp = () => { + const origin = this.getOrigin(); + if (!origin) { + return; + } + const dapp = this.dappManager.get(origin); + if (!dapp) return; + + dapp.isBlocked = true; + dapp.blockedTimestamp = Date.now(); + }; + + private getOrigin(data = this.currentApproval?.data) { + return data?.params.origin || data?.origin; + } } export default new NotificationService(); diff --git a/src/ui/assets/approval/arrow-down-blue.svg b/src/ui/assets/approval/arrow-down-blue.svg index dffa68bad80..1bb21c9cfdd 100644 --- a/src/ui/assets/approval/arrow-down-blue.svg +++ b/src/ui/assets/approval/arrow-down-blue.svg @@ -1,4 +1,3 @@ - - + + - diff --git a/src/ui/views/Approval/components/Connect/index.tsx b/src/ui/views/Approval/components/Connect/index.tsx index 1640d174a9a..538f380a3b6 100644 --- a/src/ui/views/Approval/components/Connect/index.tsx +++ b/src/ui/views/Approval/components/Connect/index.tsx @@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next'; import i18n from '@/i18n'; import { Chain } from 'background/service/openapi'; import { ChainSelector, Spin, FallbackSiteLogo } from 'ui/component'; -import { useApproval, useWallet } from 'ui/utils'; +import { useApproval, useCommonPopupView, useWallet } from 'ui/utils'; import { CHAINS_ENUM, CHAINS, @@ -27,6 +27,7 @@ import UserListDrawer from './UserListDrawer'; import IconSuccess from 'ui/assets/success.svg'; import PQueue from 'p-queue'; import { SignTestnetPermission } from './SignTestnetPermission'; +import { ReactComponent as ArrowDownSVG } from '@/ui/assets/approval/arrow-down-blue.svg'; interface ConnectProps { params: any; @@ -586,6 +587,26 @@ const Connect = ({ params: { icon, origin } }: ConnectProps) => { setRuleDrawerVisible(true); }; + const [ + displayBlockedRequestApproval, + setDisplayBlockedRequestApproval, + ] = React.useState(false); + const { activePopup, setData } = useCommonPopupView(); + + React.useEffect(() => { + wallet + .checkNeedDisplayBlockedRequestApproval() + .then(setDisplayBlockedRequestApproval); + }, []); + + const activeCancelPopup = () => { + setData({ + onCancel: handleCancel, + displayBlockedRequestApproval, + }); + activePopup('CancelConnect'); + }; + return ( @@ -709,11 +730,21 @@ const Connect = ({ params: { icon, origin } }: ConnectProps) => { diff --git a/src/ui/views/Approval/components/FooterBar/ActionsContainer.tsx b/src/ui/views/Approval/components/FooterBar/ActionsContainer.tsx index b9b187e3655..963355130ca 100644 --- a/src/ui/views/Approval/components/FooterBar/ActionsContainer.tsx +++ b/src/ui/views/Approval/components/FooterBar/ActionsContainer.tsx @@ -4,6 +4,8 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { Account } from '@/background/service/preference'; import { Chain } from '@debank/common'; +import { useCommonPopupView, useWallet } from '@/ui/utils'; +import { ReactComponent as ArrowDownSVG } from '@/ui/assets/approval/arrow-down-blue.svg'; export interface Props { onSubmit(): void; @@ -20,7 +22,39 @@ export const ActionsContainer: React.FC> = ({ children, onCancel, }) => { + const wallet = useWallet(); const { t } = useTranslation(); + const [ + displayBlockedRequestApproval, + setDisplayBlockedRequestApproval, + ] = React.useState(false); + const [ + displayCancelAllApproval, + setDisplayCancelAllApproval, + ] = React.useState(false); + const { activePopup, setData } = useCommonPopupView(); + + React.useEffect(() => { + wallet + .checkNeedDisplayBlockedRequestApproval() + .then(setDisplayBlockedRequestApproval); + wallet + .checkNeedDisplayCancelAllApproval() + .then(setDisplayCancelAllApproval); + }, []); + + const displayPopup = + displayBlockedRequestApproval || displayCancelAllApproval; + + const activeCancelPopup = () => { + setData({ + onCancel, + displayBlockedRequestApproval, + displayCancelAllApproval, + }); + activePopup('CancelApproval'); + }; + return (
{children} @@ -31,11 +65,14 @@ export const ActionsContainer: React.FC> = ({ 'hover:bg-[#8697FF1A] active:bg-[#0000001A]', 'rounded-[8px]', 'before:content-none', - 'z-10' + 'z-10', + 'flex items-center justify-center gap-2' )} - onClick={onCancel} + onClick={displayPopup ? activeCancelPopup : onCancel} > {t('global.cancelButton')} + + {displayPopup && }
); diff --git a/src/ui/views/CommonPopup/CancelApproval/CancelApproval.tsx b/src/ui/views/CommonPopup/CancelApproval/CancelApproval.tsx new file mode 100644 index 00000000000..b0398b0149a --- /dev/null +++ b/src/ui/views/CommonPopup/CancelApproval/CancelApproval.tsx @@ -0,0 +1,64 @@ +import { useCommonPopupView, useWallet } from '@/ui/utils'; +import React from 'react'; +import { CancelItem } from './CancelItem'; +import { useTranslation } from 'react-i18next'; + +export const CancelApproval = () => { + const { data, setTitle, setHeight, closePopup } = useCommonPopupView(); + const { + onCancel, + displayBlockedRequestApproval, + displayCancelAllApproval, + } = data; + const wallet = useWallet(); + const [pendingApprovalCount, setPendingApprovalCount] = React.useState(0); + const { t } = useTranslation(); + + React.useEffect(() => { + setTitle(t('page.signFooterBar.cancelTransaction')); + if (displayBlockedRequestApproval && displayCancelAllApproval) { + setHeight(288); + } else { + setHeight(244); + } + wallet.getPendingApprovalCount().then(setPendingApprovalCount); + }, [displayBlockedRequestApproval, displayCancelAllApproval]); + + const handleCancelAll = () => { + wallet.rejectAllApprovals(); + }; + + const handleBlockedRequestApproval = () => { + wallet.blockedDapp(); + }; + + const handleCancel = () => { + onCancel(); + closePopup(); + }; + + return ( +
+
+ {t('page.signFooterBar.detectedMultipleRequestsFromThisDapp')} +
+
+ + {t('page.signFooterBar.cancelCurrentTransaction')} + + {displayCancelAllApproval && ( + + {t('page.signFooterBar.cancelAll', { + count: pendingApprovalCount, + })} + + )} + {displayBlockedRequestApproval && ( + + {t('page.signFooterBar.blockDappFromSendingRequests')} + + )} +
+
+ ); +}; diff --git a/src/ui/views/CommonPopup/CancelApproval/CancelItem.tsx b/src/ui/views/CommonPopup/CancelApproval/CancelItem.tsx new file mode 100644 index 00000000000..52ddf68028c --- /dev/null +++ b/src/ui/views/CommonPopup/CancelApproval/CancelItem.tsx @@ -0,0 +1,34 @@ +import clsx from 'clsx'; +import React from 'react'; +import styled from 'styled-components'; +import IconArrowRight from '@/ui/assets/dashboard/settings/icon-right-arrow.svg'; + +export interface Props { + onClick(): void; +} + +const Styled = styled.div` + border-color: var(--r-neutral-card-2) !important; + &:hover { + border-color: var(--r-blue-default) !important; + } +`; + +export const CancelItem: React.FC = ({ children, onClick }) => { + return ( + + {children} + + + ); +}; diff --git a/src/ui/views/CommonPopup/CancelConnect/CancelConnect.tsx b/src/ui/views/CommonPopup/CancelConnect/CancelConnect.tsx new file mode 100644 index 00000000000..a230cf231c9 --- /dev/null +++ b/src/ui/views/CommonPopup/CancelConnect/CancelConnect.tsx @@ -0,0 +1,39 @@ +import { useCommonPopupView, useWallet } from '@/ui/utils'; +import React from 'react'; +import { CancelItem } from '../CancelApproval/CancelItem'; +import { useTranslation } from 'react-i18next'; + +export const CancelConnect = () => { + const { data, setTitle, setHeight } = useCommonPopupView(); + const { onCancel, displayBlockedRequestApproval } = data; + const wallet = useWallet(); + const { t } = useTranslation(); + + React.useEffect(() => { + setTitle(t('page.signFooterBar.cancelConnection')); + + setHeight(244); + }, [displayBlockedRequestApproval]); + + const handleBlockedRequestApproval = () => { + wallet.blockedDapp(); + }; + + return ( +
+
+ {t('page.signFooterBar.detectedMultipleRequestsFromThisDapp')} +
+
+ + {t('page.signFooterBar.cancelCurrentConnection')} + + {displayBlockedRequestApproval && ( + + {t('page.signFooterBar.blockDappFromSendingRequests')} + + )} +
+
+ ); +}; diff --git a/src/ui/views/CommonPopup/index.tsx b/src/ui/views/CommonPopup/index.tsx index 77e3cfd0706..dd65bfc3ac3 100644 --- a/src/ui/views/CommonPopup/index.tsx +++ b/src/ui/views/CommonPopup/index.tsx @@ -7,6 +7,8 @@ import { SwitchAddress } from './SwitchAddress'; import { SwitchChain } from './SwitchChain'; import { Ledger } from './Ledger'; import { AssetList } from './AssetList/AssetList'; +import { CancelApproval } from './CancelApproval/CancelApproval'; +import { CancelConnect } from './CancelConnect/CancelConnect'; export type CommonPopupComponentName = | 'Approval' @@ -14,23 +16,38 @@ export type CommonPopupComponentName = | 'SwitchAddress' | 'SwitchChain' | 'AssetList' - | 'Ledger'; + | 'Ledger' + | 'CancelConnect' + | 'CancelApproval'; const ComponentConfig = { AssetList: { title: null, closeable: false, + titleSize: '16px', padding: '12px 20px', }, Default: { title: undefined, closeable: true, + titleSize: '16px', padding: '20px 20px 24px', }, Approval: { closeable: false, + titleSize: '16px', maskClosable: false, }, + CancelApproval: { + padding: '8px 20px 22px', + titleSize: '20px', + closeable: true, + }, + CancelConnect: { + padding: '8px 20px 22px', + titleSize: '20px', + closeable: true, + }, }; export const CommonPopup: React.FC = () => { @@ -55,7 +72,13 @@ export const CommonPopup: React.FC = () => { {config.title} + + {config.title} + ) : null } closable={config.closeable} @@ -76,6 +99,8 @@ export const CommonPopup: React.FC = () => { {componentName === 'SwitchChain' && } {componentName === 'Ledger' && } {componentName === 'AssetList' && } + {componentName === 'CancelApproval' && } + {componentName === 'CancelConnect' && } ); };