1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 01:47:00 +01:00

Approval flow for add & switch network (#19656)

This commit is contained in:
Bernardo Garces Chapero 2023-06-29 16:51:56 +01:00 committed by GitHub
parent 48465432d6
commit a2dfe8d113
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 102 additions and 22 deletions

View File

@ -22,6 +22,8 @@ const addEthereumChain = {
findNetworkConfigurationBy: true,
setActiveNetwork: true,
requestUserApproval: true,
startApprovalFlow: true,
endApprovalFlow: true,
},
};
export default addEthereumChain;
@ -38,6 +40,8 @@ async function addEthereumChainHandler(
findNetworkConfigurationBy,
setActiveNetwork,
requestUserApproval,
startApprovalFlow,
endApprovalFlow,
},
) {
if (!req.params?.[0] || typeof req.params[0] !== 'object') {
@ -242,6 +246,9 @@ async function addEthereumChainHandler(
);
}
let networkConfigurationId;
const { id: approvalFlowId } = await startApprovalFlow();
try {
await requestUserApproval({
origin,
@ -269,6 +276,7 @@ async function addEthereumChainHandler(
// Once the network has been added, the requested is considered successful
res.result = null;
} catch (error) {
endApprovalFlow({ id: approvalFlowId });
return end(error);
}
@ -285,14 +293,24 @@ async function addEthereumChainHandler(
networkConfigurationId,
},
});
await setActiveNetwork(networkConfigurationId);
} catch (error) {
// For the purposes of this method, it does not matter if the user
// declines to switch the selected network. However, other errors indicate
// that something is wrong.
if (error.code !== errorCodes.provider.userRejectedRequest) {
return end(
error.code === errorCodes.provider.userRejectedRequest
? undefined
: error,
);
} finally {
endApprovalFlow({ id: approvalFlowId });
}
try {
await setActiveNetwork(networkConfigurationId);
} catch (error) {
return end(error);
}
}
return end();
}

View File

@ -3909,6 +3909,16 @@ export default class MetamaskController extends EventEmitter {
this.approvalController.addAndShowApprovalRequest.bind(
this.approvalController,
),
startApprovalFlow: this.approvalController.startFlow.bind(
this.approvalController,
),
endApprovalFlow: this.approvalController.endFlow.bind(
this.approvalController,
),
setApprovalFlowLoadingText:
this.approvalController.setFlowLoadingText.bind(
this.approvalController,
),
sendMetrics: this.metaMetricsController.trackEvent.bind(
this.metaMetricsController,
),

View File

@ -100,7 +100,7 @@
"resolutions": {
"@babel/core": "patch:@babel/core@npm%3A7.21.5#./.yarn/patches/@babel-core-npm-7.21.5-c72c337956.patch",
"@babel/runtime": "patch:@babel/runtime@npm%3A7.18.9#./.yarn/patches/@babel-runtime-npm-7.18.9-28ca6b5f61.patch",
"@metamask/approval-controller": "^3.0.0",
"@metamask/approval-controller": "^3.3.0",
"@types/react": "^16.9.53",
"analytics-node/axios": "^0.21.2",
"ganache-core/lodash": "^4.17.21",
@ -224,7 +224,7 @@
"@metamask-institutional/transaction-update": "^0.1.21",
"@metamask/address-book-controller": "^3.0.0",
"@metamask/announcement-controller": "^4.0.0",
"@metamask/approval-controller": "^3.1.0",
"@metamask/approval-controller": "^3.3.0",
"@metamask/assets-controllers": "^9.2.0",
"@metamask/base-controller": "^3.0.0",
"@metamask/browser-passworder": "^4.1.0",

View File

@ -32,11 +32,14 @@ import {
///: END:ONLY_INCLUDE_IN
getUnapprovedTemplatedConfirmations,
getUnapprovedTxCount,
getApprovalFlows,
getTotalUnapprovedCount,
} from '../../selectors';
import NetworkDisplay from '../../components/app/network-display/network-display';
import Callout from '../../components/ui/callout';
import SiteOrigin from '../../components/ui/site-origin';
import { Icon, IconName } from '../../components/component-library';
import Loading from '../../components/ui/loading-screen';
///: BEGIN:ONLY_INCLUDE_IN(snaps)
import SnapAuthorshipHeader from '../../components/app/snaps/snap-authorship-header';
import { getSnapName } from '../../helpers/utils/util';
@ -176,6 +179,9 @@ export default function ConfirmationPage({
isEqual,
);
const unapprovedTxsCount = useSelector(getUnapprovedTxCount);
const approvalFlows = useSelector(getApprovalFlows, isEqual);
const totalUnapprovedCount = useSelector(getTotalUnapprovedCount);
const [approvalFlowLoadingText, setApprovalFlowLoadingText] = useState(null);
const [currentPendingConfirmation, setCurrentPendingConfirmation] =
useState(0);
const pendingConfirmation = pendingConfirmations[currentPendingConfirmation];
@ -256,20 +262,36 @@ export default function ConfirmationPage({
// viewed index, reset the index.
if (
pendingConfirmations.length === 0 &&
(approvalFlows.length === 0 || totalUnapprovedCount !== 0) &&
redirectToHomeOnZeroConfirmations
) {
history.push(DEFAULT_ROUTE);
} else if (pendingConfirmations.length <= currentPendingConfirmation) {
} else if (
pendingConfirmations.length &&
pendingConfirmations.length <= currentPendingConfirmation
) {
setCurrentPendingConfirmation(pendingConfirmations.length - 1);
}
}, [
pendingConfirmations,
approvalFlows,
totalUnapprovedCount,
history,
currentPendingConfirmation,
redirectToHomeOnZeroConfirmations,
]);
useEffect(() => {
const childFlow = approvalFlows[approvalFlows.length - 1];
setApprovalFlowLoadingText(childFlow?.loadingText ?? null);
}, [approvalFlows]);
if (!pendingConfirmation) {
if (approvalFlows.length > 0) {
return <Loading loadingMessage={approvalFlowLoadingText} />;
}
return null;
}

View File

@ -33,6 +33,7 @@ const mockBaseStore = {
pendingApprovals: {
[mockApprovalId]: mockApproval,
},
approvalFlows: [{ id: mockApprovalId, loadingText: null }],
subjectMetadata: {},
providerConfig: {
type: 'rpc',

View File

@ -33,6 +33,7 @@ const mockBaseStore = {
pendingApprovals: {
[mockApprovalId]: mockApproval,
},
approvalFlows: [],
subjectMetadata: {},
providerConfig: {
type: 'rpc',

View File

@ -84,6 +84,7 @@ import FlaskHomeFooter from './flask/flask-home-footer.component';
function shouldCloseNotificationPopup({
isNotification,
totalUnapprovedCount,
hasApprovalFlows,
isSigningQRHardwareTransaction,
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
waitForConfirmDeepLinkDialog,
@ -93,6 +94,7 @@ function shouldCloseNotificationPopup({
let shouldCLose =
isNotification &&
totalUnapprovedCount === 0 &&
!hasApprovalFlows &&
!isSigningQRHardwareTransaction;
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
@ -139,6 +141,7 @@ export default class Home extends PureComponent {
originOfCurrentTab: PropTypes.string,
disableWeb3ShimUsageAlert: PropTypes.func.isRequired,
pendingConfirmations: PropTypes.arrayOf(PropTypes.object).isRequired,
hasApprovalFlows: PropTypes.bool.isRequired,
infuraBlocked: PropTypes.bool.isRequired,
showWhatsNewPopup: PropTypes.bool.isRequired,
hideWhatsNewPopup: PropTypes.func.isRequired,
@ -287,6 +290,7 @@ export default class Home extends PureComponent {
showAwaitingSwapScreen,
swapsFetchParams,
pendingConfirmations,
hasApprovalFlows,
} = this.props;
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
@ -307,7 +311,7 @@ export default class Home extends PureComponent {
history.push(CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE);
} else if (hasWatchNftPendingApprovals) {
history.push(CONFIRM_ADD_SUGGESTED_NFT_ROUTE);
} else if (pendingConfirmations.length > 0) {
} else if (pendingConfirmations.length > 0 || hasApprovalFlows) {
history.push(CONFIRMATION_V_NEXT_ROUTE);
}
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)

View File

@ -36,6 +36,7 @@ import {
getRemoveNftMessage,
getSuggestedTokens,
getSuggestedNfts,
getApprovalFlows,
} from '../../selectors';
import {
@ -137,6 +138,7 @@ const mapStateToProps = (state) => {
selectedAddress,
firstPermissionsRequestId,
totalUnapprovedCount,
hasApprovalFlows: getApprovalFlows(state).length > 0,
connectedStatusPopoverHasBeenShown,
defaultHomeActiveTabName,
firstTimeFlowType,

View File

@ -106,6 +106,7 @@ describe('Routes Component', () => {
swapsFeatureIsLive: true,
},
pendingApprovals: {},
approvalFlows: [],
announcements: {},
},
send: {

View File

@ -1,5 +1,5 @@
import { ApprovalType } from '@metamask/controller-utils';
import { hasPendingApprovals } from './approvals';
import { getApprovalFlows, hasPendingApprovals } from './approvals';
describe('approval selectors', () => {
const mockedState = {
@ -13,6 +13,7 @@ describe('approval selectors', () => {
type: ApprovalType.WatchAsset,
requestData: {},
requestState: null,
expectsResult: false,
},
'2': {
id: '2',
@ -21,13 +22,19 @@ describe('approval selectors', () => {
type: ApprovalType.Transaction,
requestData: {},
requestState: null,
expectsResult: false,
},
},
unapprovedTxs: {
'2': {
approvalFlows: [
{
id: '1',
loadingText: 'loadingText1',
},
{
id: '2',
loadingText: 'loadingText2',
},
},
],
},
};
@ -47,4 +54,12 @@ describe('approval selectors', () => {
expect(result).toBe(false);
});
});
describe('getApprovalFlows', () => {
it('should return existing approval flows', () => {
const result = getApprovalFlows(mockedState);
expect(result).toStrictEqual(mockedState.metamask.approvalFlows);
});
});
});

View File

@ -1,13 +1,10 @@
import { ApprovalControllerState } from '@metamask/approval-controller';
import { ApprovalType } from '@metamask/controller-utils';
import { TransactionMeta } from '../../shared/constants/transaction';
type ApprovalsMetaMaskState = {
metamask: {
pendingApprovals: ApprovalControllerState['pendingApprovals'];
unapprovedTxs: {
[transactionId: string]: TransactionMeta;
};
approvalFlows: ApprovalControllerState['approvalFlows'];
};
};
@ -46,3 +43,7 @@ export const getApprovalRequestsByType = (
return pendingApprovalRequests;
};
export function getApprovalFlows(state: ApprovalsMetaMaskState) {
return state.metamask.approvalFlows;
}

View File

@ -35,6 +35,7 @@ import {
getPermittedAccountsForCurrentTab,
getSelectedAddress,
hasTransactionPendingApprovals,
getApprovalFlows,
///: BEGIN:ONLY_INCLUDE_IN(snaps)
getNotifications,
///: END:ONLY_INCLUDE_IN
@ -2382,9 +2383,12 @@ export function closeCurrentNotificationWindow(): ThunkAction<
AnyAction
> {
return (_, getState) => {
const state = getState();
const approvalFlows = getApprovalFlows(state);
if (
getEnvironmentType() === ENVIRONMENT_TYPE_NOTIFICATION &&
!hasTransactionPendingApprovals(getState())
!hasTransactionPendingApprovals(state) &&
approvalFlows.length === 0
) {
closeNotificationPopup();
}

View File

@ -69,6 +69,7 @@ interface TemporaryBackgroundState {
networkId: string | null;
networkStatus: NetworkStatus;
pendingApprovals: ApprovalControllerState['pendingApprovals'];
approvalFlows: ApprovalControllerState['approvalFlows'];
knownMethodData?: {
[fourBytePrefix: string]: Record<string, unknown>;
};

View File

@ -3857,16 +3857,16 @@ __metadata:
languageName: node
linkType: hard
"@metamask/approval-controller@npm:^3.0.0":
version: 3.1.0
resolution: "@metamask/approval-controller@npm:3.1.0"
"@metamask/approval-controller@npm:^3.3.0":
version: 3.3.0
resolution: "@metamask/approval-controller@npm:3.3.0"
dependencies:
"@metamask/base-controller": ^3.0.0
"@metamask/utils": ^5.0.2
eth-rpc-errors: ^4.0.2
immer: ^9.0.6
nanoid: ^3.1.31
checksum: 2043e62e8815a600e839617b4df26515fc33f655e21562dc230cd6dbfc4677e955e1e45a5df8fbb2def2122b3578f6a632acb939f8175419febb1471d0c48ce0
checksum: 1fa6111a897d6f4aa369fd1a669fb5e16558277019f9d6c61449aea0d7b2672a62c189e5b3d9e84ab6e4d5826932c78d2bdb0f05aafb184a4ff07903c46abf2c
languageName: node
linkType: hard
@ -24482,7 +24482,7 @@ __metadata:
"@metamask-institutional/transaction-update": ^0.1.21
"@metamask/address-book-controller": ^3.0.0
"@metamask/announcement-controller": ^4.0.0
"@metamask/approval-controller": ^3.1.0
"@metamask/approval-controller": ^3.3.0
"@metamask/assets-controllers": ^9.2.0
"@metamask/auto-changelog": ^2.1.0
"@metamask/base-controller": ^3.0.0