diff --git a/packages/extension-base/src/background/handlers/Extension.ts b/packages/extension-base/src/background/handlers/Extension.ts index bbafe46f..c900a838 100644 --- a/packages/extension-base/src/background/handlers/Extension.ts +++ b/packages/extension-base/src/background/handlers/Extension.ts @@ -524,6 +524,8 @@ export default class Extension { return { list: remAuth }; } + // Reject the authorization request and add the URL to the authorized list with no keys. + // The site will not prompt for re-authorization on future visits. private rejectAuthRequest (id: string): void { const queued = this.#state.getAuthRequest(id); @@ -534,6 +536,18 @@ export default class Extension { reject(new Error('Rejected')); } + // Cancel the authorization request and do not add the URL to the authorized list. + // The site will prompt for authorization on future visits. + private cancelAuthRequest (id: string): void { + const queued = this.#state.getAuthRequest(id); + + assert(queued, 'Unable to find request'); + + const { reject } = queued; + + reject(new Error('Cancelled')); + } + private updateCurrentTabs ({ urls }: RequestActiveTabsUrlUpdate) { this.#state.updateCurrentTabsUrl(urls); } @@ -558,6 +572,9 @@ export default class Extension { case 'pri(authorize.reject)': return this.rejectAuthRequest(request as string); + case 'pri(authorize.cancel)': + return this.cancelAuthRequest(request as string); + case 'pri(authorize.requests)': return port && this.authorizeSubscribe(id, port); diff --git a/packages/extension-base/src/background/handlers/State.ts b/packages/extension-base/src/background/handlers/State.ts index 67c5e007..5529a345 100644 --- a/packages/extension-base/src/background/handlers/State.ts +++ b/packages/extension-base/src/background/handlers/State.ts @@ -273,8 +273,14 @@ export default class State { return { // eslint-disable-next-line @typescript-eslint/no-misused-promises reject: async (error: Error): Promise => { - await complete(); - reject(error); + if (error.message === 'Cancelled') { + delete this.#authRequests[id]; + this.updateIconAuth(true); + reject(new Error('Connection request was cancelled by the user.')); + } else { + await complete(); + reject(new Error('Connection request was rejected by the user.')); + } }, // eslint-disable-next-line @typescript-eslint/no-misused-promises resolve: async ({ authorizedAccounts, result }: AuthResponse): Promise => { diff --git a/packages/extension-base/src/background/types.ts b/packages/extension-base/src/background/types.ts index 9e7cc8ee..77726380 100644 --- a/packages/extension-base/src/background/types.ts +++ b/packages/extension-base/src/background/types.ts @@ -97,6 +97,7 @@ export interface RequestSignatures { 'pri(authorize.requests)': [RequestAuthorizeSubscribe, boolean, AuthorizeRequest[]]; 'pri(authorize.remove)': [string, ResponseAuthorizeList]; 'pri(authorize.reject)': [string, void]; + 'pri(authorize.cancel)': [string, void]; 'pri(authorize.update)': [RequestUpdateAuthorizedAccounts, void]; 'pri(activeTabsUrl.update)': [RequestActiveTabsUrlUpdate, void]; 'pri(connectedTabsUrl.get)': [null, ConnectedTabsUrlResponse]; diff --git a/packages/extension-ui/src/Popup/AuthManagement/AccountManagement.tsx b/packages/extension-ui/src/Popup/AuthManagement/AccountManagement.tsx index 6efb3989..0020fc27 100644 --- a/packages/extension-ui/src/Popup/AuthManagement/AccountManagement.tsx +++ b/packages/extension-ui/src/Popup/AuthManagement/AccountManagement.tsx @@ -42,41 +42,54 @@ function AccountManagement ({ className }: Props): React.ReactElement { ); return ( - <> +
-
+
- +
+ +
- +
); } export default styled(AccountManagement)` - .accountSelection{ - .accountList{ - height: 390px; - } + display: flex; + flex-direction: column; + height: 100%; + min-height: 550px; + + .content { + flex: 1; + display: flex; + flex-direction: column; + overflow: auto; } + + .footer { + background: var(--background); + flex-shrink: 0; + } + .acceptButton { width: 90%; - margin: 0.5rem auto 0; + margin: 0.5rem auto; } `; diff --git a/packages/extension-ui/src/Popup/Authorize/Request.tsx b/packages/extension-ui/src/Popup/Authorize/Request.tsx index 20dc1c28..fab25330 100644 --- a/packages/extension-ui/src/Popup/Authorize/Request.tsx +++ b/packages/extension-ui/src/Popup/Authorize/Request.tsx @@ -3,11 +3,11 @@ import type { RequestAuthorizeTab } from '@polkadot/extension-base/background/types'; -import React, { useCallback, useContext, useEffect } from 'react'; +import React, { useCallback, useContext, useEffect, useState } from 'react'; -import { AccountContext, ActionBar, ActionContext, Button, Link } from '../../components/index.js'; +import { AccountContext, ActionContext, Button } from '../../components/index.js'; import { useTranslation } from '../../hooks/index.js'; -import { approveAuthRequest, rejectAuthRequest } from '../../messaging.js'; +import { approveAuthRequest, cancelAuthRequest, rejectAuthRequest } from '../../messaging.js'; import { AccountSelection } from '../../partials/index.js'; import { styled } from '../../styled.js'; import NoAccount from './NoAccount.js'; @@ -15,15 +15,15 @@ import NoAccount from './NoAccount.js'; interface Props { authId: string; className?: string; - isFirst: boolean; request: RequestAuthorizeTab; url: string; } -function Request ({ authId, className, isFirst, request: { origin }, url }: Props): React.ReactElement { +function Request ({ authId, className, request: { origin }, url }: Props): React.ReactElement { const { accounts, selectedAccounts = [], setSelectedAccounts } = useContext(AccountContext); const { t } = useTranslation(); const onAction = useContext(ActionContext); + const [dontAskAgain, setDontAskAgain] = useState(false); useEffect(() => { const defaultAccountSelection = accounts @@ -42,13 +42,22 @@ function Request ({ authId, className, isFirst, request: { origin }, url }: Prop [authId, onAction, selectedAccounts] ); - const _onClose = useCallback( + const _onReject = useCallback( (): void => { - rejectAuthRequest(authId) + const rejectFunction = dontAskAgain ? rejectAuthRequest : cancelAuthRequest; + + rejectFunction(authId) .then(() => onAction()) .catch((error: Error) => console.error(error)); }, - [authId, onAction] + [authId, onAction, dontAskAgain] + ); + + const _onToggleDontAskAgain = useCallback( + (): void => { + setDontAskAgain((prev) => !prev); + }, + [] ); if (!accounts.length) { @@ -61,42 +70,69 @@ function Request ({ authId, className, isFirst, request: { origin }, url }: Prop origin={origin} url={url} /> - {isFirst && ( - - )} - - - {t('Reject')} - - +
+
+ + +
+
+ + +
+
+ ); } export default styled(Request)` - .acceptButton { - width: 90%; - margin: .5rem auto 0; + display: flex; + flex-direction: column; + flex: 1; + overflow-y: auto; + + .footer { + padding: 1rem 1rem 0rem 1rem; + background: var(--background); } - .rejectionButton { - margin: 0 0 15px 0; - text-decoration: underline; + .buttonContainer { + display: flex; + justify-content: space-between; + width: 100%; + margin-bottom: 0.5rem; + } - .closeLink { - margin: auto; - padding: 0; + .acceptButton, .rejectButton { + width: 48%; + height: 40px; + } + + .dontAskAgainContainer { + display: flex; + align-items: center; + justify-content: center; + text-align: center; + + input { + margin-right: 0.5rem; } } `; diff --git a/packages/extension-ui/src/Popup/Authorize/index.tsx b/packages/extension-ui/src/Popup/Authorize/index.tsx index e0f99b55..f58e72cc 100644 --- a/packages/extension-ui/src/Popup/Authorize/index.tsx +++ b/packages/extension-ui/src/Popup/Authorize/index.tsx @@ -1,7 +1,7 @@ // Copyright 2019-2024 @polkadot/extension-ui authors & contributors // SPDX-License-Identifier: Apache-2.0 -import React, { useContext } from 'react'; +import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; import { AuthorizeReqContext } from '../../components/index.js'; import { useTranslation } from '../../hooks/index.js'; @@ -16,35 +16,76 @@ interface Props { function Authorize ({ className = '' }: Props): React.ReactElement { const { t } = useTranslation(); const requests = useContext(AuthorizeReqContext); + const [currentIndex, setCurrentIndex] = useState(0); + const currentIndexRef = useRef(currentIndex); + + useEffect(() => { + if (requests.length <= currentIndexRef.current) { + setCurrentIndex((prevIndex) => Math.max(prevIndex - 1, 0)); + } + }, [requests.length]); + + const handleNext = useCallback(() => { + setCurrentIndex((prevIndex) => { + const newIndex = Math.min(prevIndex + 1, requests.length - 1); + + currentIndexRef.current = newIndex; + + return newIndex; + }); + }, [requests.length]); + + const handlePrevious = useCallback(() => { + setCurrentIndex((prevIndex) => { + const newIndex = Math.max(prevIndex - 1, 0); + + currentIndexRef.current = newIndex; + + return newIndex; + }); + }, []); return ( - <> -
-
+
+ {requests.length > 1 && ( +
+ + {`${currentIndex + 1} / ${requests.length}`} + +
+ )} + {requests.length > 0 && requests[currentIndex] && ( + - {requests.map(({ id, request, url }, index): React.ReactNode => ( - - ))} -
- + )} + ); } export default styled(Authorize)` - overflow-y: auto; - - &.lastRequest { - overflow: hidden; - } + display: flex; + flex-direction: column; + height: 100%; + overflow: auto; && { padding: 0; @@ -52,5 +93,32 @@ export default styled(Authorize)` .request { padding: 0 24px; + flex: 1; + display: flex; + flex-direction: column; + overflow: auto; + min-height: 400px; + } + + .pagination { + display: flex; + justify-content: space-between; + background: var(--background); + + button { + background: none; + border: none; + color: var(--textColor); + cursor: pointer; + padding: 0.5rem 1rem; + + &.hidden { + visibility: hidden; + } + } + + span { + align-self: center; + } } `; diff --git a/packages/extension-ui/src/messaging.ts b/packages/extension-ui/src/messaging.ts index 10ed500e..d6695f34 100644 --- a/packages/extension-ui/src/messaging.ts +++ b/packages/extension-ui/src/messaging.ts @@ -224,6 +224,10 @@ export async function rejectAuthRequest (requestId: string): Promise { return sendMessage('pri(authorize.reject)', requestId); } +export async function cancelAuthRequest (requestId: string): Promise { + return sendMessage('pri(authorize.cancel)', requestId); +} + export async function subscribeMetadataRequests (cb: (accounts: MetadataRequest[]) => void): Promise { return sendMessage('pri(metadata.requests)', null, cb); } diff --git a/packages/extension-ui/src/partials/AccountSelection.tsx b/packages/extension-ui/src/partials/AccountSelection.tsx index 4f26a729..b0e2bb12 100644 --- a/packages/extension-ui/src/partials/AccountSelection.tsx +++ b/packages/extension-ui/src/partials/AccountSelection.tsx @@ -17,7 +17,7 @@ interface Props { withWarning?: boolean; } -function AccounSelection ({ className, origin, showHidden = false, url, withWarning = true }: Props): React.ReactElement { +function AccountSelection ({ className, origin, showHidden = false, url, withWarning = true }: Props): React.ReactElement { const { t } = useTranslation(); const { accounts, hierarchy, selectedAccounts = [], setSelectedAccounts } = useContext(AccountContext); const [isIndeterminate, setIsIndeterminate] = useState(false); @@ -90,10 +90,15 @@ function AccounSelection ({ className, origin, showHidden = false, url, withWarn ); } -export default styled(AccounSelection)` +export default styled(AccountSelection)` + display: flex; + flex-direction: column; + flex: 1; + overflow: hidden; + .accountList { overflow-y: auto; - height: 270px; + min-height: 250px; } .tab-name, diff --git a/packages/extension/public/locales/en/translation.json b/packages/extension/public/locales/en/translation.json index c88e1014..e7049a2a 100644 --- a/packages/extension/public/locales/en/translation.json +++ b/packages/extension/public/locales/en/translation.json @@ -179,5 +179,7 @@ "This approval will add the metadata to your extension instance, allowing future requests to be decoded using this metadata. It will also allow the use of Ledger's Generic Polkadot App.": "", "No metadata found for this chain. You must upload the metadata to the extension in order to use Ledger.": "", "This network is not available, please report an issue to update the known chains": "", - "Ledger App": "" + "Ledger App": "", + "Don't ask again": "", + "Previous": "" }