1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-22 17:33:23 +01:00

Fixing connect custody flow (#19802)

* Fixing connect custody flow

* Finished fixing mmi connect flow

* Fixed test

* updated snapshot

* Finished fixing tests
This commit is contained in:
Albert Olivé 2023-06-30 12:41:28 +02:00 committed by GitHub
parent 6bb5e6941d
commit 59980f1b27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 186 additions and 268 deletions

View File

@ -2368,19 +2368,38 @@ export default class MetamaskController extends EventEmitter {
...getPermissionBackgroundApiMethods(permissionController),
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
connectCustodyAddresses:
this.mmiController.connectCustodyAddresses.bind(this),
getCustodianAccounts: this.mmiController.getCustodianAccounts.bind(this),
connectCustodyAddresses: this.mmiController.connectCustodyAddresses.bind(
this.mmiController,
),
getCustodianAccounts: this.mmiController.getCustodianAccounts.bind(
this.mmiController,
),
getCustodianAccountsByAddress:
this.mmiController.getCustodianAccountsByAddress.bind(this),
this.mmiController.getCustodianAccountsByAddress.bind(
this.mmiController,
),
getCustodianTransactionDeepLink:
this.mmiController.getCustodianTransactionDeepLink.bind(this),
this.mmiController.getCustodianTransactionDeepLink.bind(
this.mmiController,
),
getCustodianConfirmDeepLink:
this.mmiController.getCustodianConfirmDeepLink.bind(this),
this.mmiController.getCustodianConfirmDeepLink.bind(this.mmiController),
getCustodianSignMessageDeepLink:
this.mmiController.getCustodianSignMessageDeepLink.bind(this),
getCustodianToken: this.mmiController.getCustodianToken.bind(this),
getCustodianJWTList: this.mmiController.getCustodianJWTList.bind(this),
this.mmiController.getCustodianSignMessageDeepLink.bind(
this.mmiController,
),
getCustodianToken: this.mmiController.getCustodianToken.bind(
this.mmiController,
),
getCustodianJWTList: this.mmiController.getCustodianJWTList.bind(
this.mmiController,
),
getAllCustodianAccountsWithToken:
this.mmiController.getAllCustodianAccountsWithToken.bind(
this.mmiController,
),
setCustodianNewRefreshToken:
this.mmiController.setCustodianNewRefreshToken.bind(this.mmiController),
setWaitForConfirmDeepLinkDialog:
this.custodyController.setWaitForConfirmDeepLinkDialog.bind(
this.custodyController,
@ -2393,8 +2412,6 @@ export default class MetamaskController extends EventEmitter {
this.custodyController.getCustodianConnectRequest.bind(
this.custodyController,
),
getAllCustodianAccountsWithToken:
this.mmiController.getAllCustodianAccountsWithToken.bind(this),
getMmiConfiguration:
this.mmiConfigurationController.getConfiguration.bind(
this.mmiConfigurationController,
@ -2415,12 +2432,10 @@ export default class MetamaskController extends EventEmitter {
this.institutionalFeaturesController.syncReportsInProgress.bind(
this.institutionalFeaturesController,
),
removeConnectInstitutionalFeature:
this.institutionalFeaturesController.removeConnectInstitutionalFeature.bind(
this.institutionalFeaturesController,
),
getComplianceHistoricalReportsByAddress:
this.institutionalFeaturesController.getComplianceHistoricalReportsByAddress.bind(
this.institutionalFeaturesController,
@ -2429,8 +2444,6 @@ export default class MetamaskController extends EventEmitter {
this.institutionalFeaturesController.removeAddTokenConnectRequest.bind(
this.institutionalFeaturesController,
),
setCustodianNewRefreshToken:
this.mmiController.setCustodianNewRefreshToken.bind(this),
///: END:ONLY_INCLUDE_IN
///: BEGIN:ONLY_INCLUDE_IN(snaps)

View File

@ -5,10 +5,10 @@ import PulseLoader from '../../../components/ui/pulse-loader';
import { CUSTODY_ACCOUNT_ROUTE } from '../../../helpers/constants/routes';
import {
AlignItems,
DISPLAY,
Display,
TextColor,
TEXT_ALIGN,
FLEX_DIRECTION,
TextAlign,
FlexDirection,
} from '../../../helpers/constants/design-system';
import { BUILT_IN_NETWORKS } from '../../../../shared/constants/network';
import { I18nContext } from '../../../contexts/i18n';
@ -23,8 +23,12 @@ import {
Button,
BUTTON_SIZES,
BUTTON_VARIANT,
Box,
} from '../../../components/component-library';
import Box from '../../../components/ui/box';
import {
complianceActivated,
getInstitutionalConnectRequests,
} from '../../../ducks/institutional/institutional';
const ConfirmAddCustodianToken = () => {
const t = useContext(I18nContext);
@ -34,59 +38,12 @@ const ConfirmAddCustodianToken = () => {
const mmiActions = mmiActionsFactory();
const mostRecentOverviewPage = useSelector(getMostRecentOverviewPage);
const connectRequests = useSelector(
(state) => state.metamask.institutionalFeatures?.connectRequests,
);
const complianceActivated = useSelector((state) =>
Boolean(state.metamask.institutionalFeatures?.complianceProjectId),
);
const connectRequests = useSelector(getInstitutionalConnectRequests);
const isComplianceActivated = useSelector(complianceActivated);
const [showMore, setShowMore] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [connectError, setConnectError] = useState('');
const handleConnectError = (e) => {
let errorMessage = e.message;
if (!errorMessage) {
errorMessage = 'Connection error';
}
setConnectError(errorMessage);
setIsLoading(false);
};
const renderSelectedToken = () => {
const connectRequest = connectRequests ? connectRequests[0] : undefined;
return (
<Box
paddingTop={2}
paddingBottom={2}
display={DISPLAY.FLEX}
flexDirection={FLEX_DIRECTION.ROW}
alignItems={AlignItems.center}
>
<Text>
{showMore && connectRequest?.token
? connectRequest?.token
: `...${connectRequest?.token.slice(-9)}`}
</Text>
{!showMore && (
<Box paddingLeft={2}>
<ButtonLink
rel="noopener noreferrer"
onClick={() => {
setShowMore(true);
}}
>
{t('showMore')}
</ButtonLink>
</Box>
)}
</Box>
);
};
const connectRequest = connectRequests ? connectRequests[0] : undefined;
if (!connectRequest) {
@ -148,7 +105,31 @@ const ConfirmAddCustodianToken = () => {
marginLeft={4}
className="add_custodian_token_confirm__token"
>
{renderSelectedToken()}
<Box
paddingTop={2}
paddingBottom={2}
display={Display.Flex}
flexDirection={FlexDirection.Row}
alignItems={AlignItems.center}
>
<Text>
{showMore && connectRequest?.token
? connectRequest?.token
: `...${connectRequest?.token.slice(-9)}`}
</Text>
{!showMore && (
<Box paddingLeft={2}>
<ButtonLink
rel="noopener noreferrer"
onClick={() => {
setShowMore(true);
}}
>
{t('showMore')}
</ButtonLink>
</Box>
)}
</Box>
</Box>
{connectRequest.apiUrl && (
<Box>
@ -168,9 +149,9 @@ const ConfirmAddCustodianToken = () => {
)}
</Box>
{!complianceActivated && (
{!isComplianceActivated && (
<Box marginTop={4} data-testid="connect-custodian-token-error">
<Text data-testid="error-message" textAlign={TEXT_ALIGN.CENTER}>
<Text data-testid="error-message" textAlign={TextAlign.Center}>
{connectError}
</Text>
</Box>
@ -180,19 +161,19 @@ const ConfirmAddCustodianToken = () => {
{isLoading ? (
<PulseLoader />
) : (
<Box display={DISPLAY.FLEX} gap={4}>
<Box display={Display.Flex} gap={4}>
<Button
block
variant={BUTTON_VARIANT.SECONDARY}
size={BUTTON_SIZES.LG}
data-testid="cancel-btn"
onClick={() => {
mmiActions.removeAddTokenConnectRequest({
onClick={async () => {
await mmiActions.removeAddTokenConnectRequest({
origin: connectRequest.origin,
apiUrl: connectRequest.apiUrl,
token: connectRequest.token,
});
history.push(mostRecentOverviewPage);
trackEvent({
category: 'MMI',
event: 'Custodian onboarding',
@ -230,17 +211,21 @@ const ConfirmAddCustodianToken = () => {
custodianName = connectRequest.environment;
}
await mmiActions.setCustodianConnectRequest({
token: connectRequest.token,
apiUrl: connectRequest.apiUrl,
custodianName,
custodianType: connectRequest.service,
});
mmiActions.removeAddTokenConnectRequest({
await dispatch(
mmiActions.setCustodianConnectRequest({
token: connectRequest.token,
apiUrl: connectRequest.apiUrl,
custodianName,
custodianType: connectRequest.service,
}),
);
await mmiActions.removeAddTokenConnectRequest({
origin: connectRequest.origin,
apiUrl: connectRequest.apiUrl,
token: connectRequest.token,
});
trackEvent({
category: 'MMI',
event: 'Custodian onboarding',
@ -250,9 +235,17 @@ const ConfirmAddCustodianToken = () => {
apiUrl: connectRequest.apiUrl,
},
});
history.push(CUSTODY_ACCOUNT_ROUTE);
} catch (e) {
handleConnectError(e);
let errorMessage = e.message;
if (!errorMessage) {
errorMessage = 'Connection error';
}
setConnectError(errorMessage);
setIsLoading(false);
}
}}
>

View File

@ -1,6 +1,24 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CustodyPage renders CustodyPage 1`] = `
<div>
<div
class="pulse-loader"
>
<div
class="pulse-loader__loading-dot-one"
/>
<div
class="pulse-loader__loading-dot-two"
/>
<div
class="pulse-loader__loading-dot-three"
/>
</div>
</div>
`;
exports[`CustodyPage renders CustodyPage 2`] = `
<div>
@ -70,115 +88,6 @@ exports[`CustodyPage renders CustodyPage 1`] = `
</ul>
</div>
</div>
</div>
`;
exports[`CustodyPage renders CustodyPage 2`] = `
<div>
<div
class="mm-box mm-box--padding-4 mm-box--display-flex mm-box--flex-direction-column mm-box--background-color-background-default"
style="box-shadow: var(--shadow-size-xs) var(--color-shadow-default);"
>
<div
class="mm-box mm-box--margin-top-4 mm-box--margin-bottom-4 mm-box--display-flex mm-box--align-items-center"
>
<button
aria-label="Back"
class="box mm-button-icon mm-button-icon--size-sm box--display-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-icon-default box--background-color-transparent box--rounded-lg"
>
<span
class="box mm-icon mm-icon--size-sm box--display-inline-block box--flex-direction-row box--color-inherit"
style="mask-image: url('./images/icons/arrow-left.svg');"
/>
</button>
<p
class="box mm-text mm-text--body-md box--flex-direction-row box--color-text-default"
>
Back
</p>
</div>
<h4
class="box mm-text mm-text--body-md box--flex-direction-row box--color-text-default"
>
<div
class="mm-box mm-box--display-flex mm-box--align-items-center"
>
<p
class="box mm-text mm-text--body-md box--margin-left-2 box--flex-direction-row box--color-text-default"
/>
</div>
</h4>
<p
class="box mm-text mm-text--body-md box--margin-top-4 box--flex-direction-row box--color-text-default"
>
Enter your token or add a new token
</p>
<div
class="mm-box mm-box--padding-bottom-7"
>
<div
class="mm-box jwt-url-form mm-box--margin-bottom-8 mm-box--display-flex mm-box--flex-direction-column mm-box--align-items-flex-start"
>
<div
class="mm-box jwt-url-form__jwt-container mm-box--margin-top-4 mm-box--margin-bottom-4 mm-box--display-flex mm-box--flex-direction-column mm-box--align-items-center mm-box--width-full"
>
<div
class="mm-box mm-box--width-full"
>
<p
class="box mm-text jwt-url-form__instruction mm-text--body-md box--margin-bottom-4 box--display-block box--flex-direction-row box--color-text-default"
>
Paste or drop your token here:
</p>
<textarea
class="jwt-url-form__input-jwt"
data-testid="jwt-input"
id="jwt-box"
>
token
</textarea>
</div>
</div>
<div
class="mm-box mm-box--width-full"
>
<p
class="box mm-text jwt-url-form__instruction mm-text--body-md box--display-block box--flex-direction-row box--color-text-default"
>
API URL
</p>
<div
class="mm-box mm-box--margin-top-4"
>
<input
class="jwt-url-form__input"
data-testid="jwt-api-url-input"
id="api-url-box"
value="url"
/>
</div>
</div>
</div>
<div
class="mm-box mm-box--padding-0 mm-box--display-flex mm-box--flex-direction-row mm-box--justify-content-center"
>
<button
class="box mm-text mm-button-base mm-button-base--size-md mm-button-base--block mm-button-secondary mm-text--body-md box--margin-right-4 box--padding-right-4 box--padding-left-4 box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-primary-default box--background-color-transparent box--rounded-pill box--border-color-primary-default box--border-style-solid box--border-width-1"
>
Cancel
</button>
<button
class="box mm-text mm-button-base mm-button-base--size-md mm-button-base--block mm-button-primary mm-text--body-md box--padding-right-4 box--padding-left-4 box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-primary-inverse box--background-color-primary-default box--rounded-pill"
data-testid="jwt-form-connect-button"
>
Connect
</button>
</div>
</div>
</div>
</div>
`;

View File

@ -44,6 +44,7 @@ import { getCurrentChainId } from '../../../selectors';
import { getMMIConfiguration } from '../../../selectors/institutional/selectors';
import CustodyAccountList from '../connect-custody/account-list';
import JwtUrlForm from '../../../components/institutional/jwt-url-form';
import PulseLoader from '../../../components/ui/pulse-loader/pulse-loader';
const CustodyPage = () => {
const t = useI18nContext();
@ -55,6 +56,7 @@ const CustodyPage = () => {
const currentChainId = useSelector(getCurrentChainId);
const { custodians } = useSelector(getMMIConfiguration);
const [loading, setLoading] = useState(true);
const [selectedAccounts, setSelectedAccounts] = useState({});
const [selectedCustodianName, setSelectedCustodianName] = useState('');
const [selectedCustodianImage, setSelectedCustodianImage] = useState(null);
@ -200,12 +202,12 @@ const CustodyPage = () => {
);
const getCustodianAccounts = useCallback(
async (token, custody, getNonImportedAccounts) => {
async (token, getNonImportedAccounts) => {
return await dispatch(
mmiActions.getCustodianAccounts(
token,
apiUrl,
custody || selectedCustodianType,
selectedCustodianType,
getNonImportedAccounts,
),
);
@ -218,12 +220,8 @@ const CustodyPage = () => {
// If you have one JWT already, but no dropdown yet, currentJwt is null!
const jwt = currentJwt || jwtList[0];
setConnectError('');
const accountsValue = await getCustodianAccounts(
jwt,
apiUrl,
selectedCustodianType,
true,
);
const accountsValue = await getCustodianAccounts(jwt, true);
setAccounts(accountsValue);
trackEvent({
category: 'MMI',
@ -245,15 +243,16 @@ const CustodyPage = () => {
handleConnectError,
jwtList,
selectedCustodianName,
selectedCustodianType,
trackEvent,
]);
useEffect(() => {
setLoading(true);
const fetchConnectRequest = async () => {
const connectRequestValue = await dispatch(
mmiActions.getCustodianConnectRequest(),
);
setChainId(parseInt(currentChainId, 16));
// check if it's empty object
@ -266,15 +265,22 @@ const CustodyPage = () => {
setSelectedCustodianType(connectRequestValue.custodianType);
setSelectedCustodianName(connectRequestValue.custodianName);
setApiUrl(connectRequestValue.apiUrl);
connect();
}
};
// call the function
fetchConnectRequest()
// make sure to catch any error
.catch(console.error);
}, [dispatch, connect, currentChainId, mmiActions]);
const handleFetchConnectRequest = async () => {
try {
await fetchConnectRequest();
setLoading(false);
} catch (error) {
console.error(error);
setLoading(false);
}
};
handleFetchConnectRequest();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
const handleNetworkChange = async () => {
@ -282,14 +288,7 @@ const CustodyPage = () => {
const jwt = currentJwt || jwtList[0];
if (jwt && jwt.length) {
setAccounts(
await getCustodianAccounts(
jwt,
apiUrl,
selectedCustodianType,
true,
),
);
setAccounts(await getCustodianAccounts(jwt, true));
}
}
};
@ -341,6 +340,10 @@ const CustodyPage = () => {
}
};
if (loading) {
return <PulseLoader />;
}
return (
<>
{connectError && (
@ -348,7 +351,6 @@ const CustodyPage = () => {
{connectError}
</Text>
)}
{selectError && (
<Text textAlign={TextAlign.Center} marginTop={3} padding={[2, 7, 5]}>
{selectError}
@ -397,7 +399,6 @@ const CustodyPage = () => {
</Box>
</Box>
) : null}
{!accounts && selectedCustodianType && (
<>
<Box
@ -485,7 +486,6 @@ const CustodyPage = () => {
</Box>
</>
)}
{accounts && accounts.length > 0 && (
<>
<Box
@ -541,6 +541,10 @@ const CustodyPage = () => {
selectedAccounts={selectedAccounts}
onAddAccounts={async () => {
try {
const selectedCustodian = custodians.find(
(custodian) => custodian.name === selectedCustodianName,
);
await dispatch(
mmiActions.connectCustodyAddresses(
selectedCustodianType,
@ -548,17 +552,7 @@ const CustodyPage = () => {
selectedAccounts,
),
);
const selectedCustodian = custodians.find(
(custodian) => custodian.name === selectedCustodianName,
);
history.push({
pathname: CUSTODY_ACCOUNT_DONE_ROUTE,
state: {
imgSrc: selectedCustodian.iconUrl,
title: t('custodianAccountAddedTitle'),
description: t('custodianAccountAddedDesc'),
},
});
trackEvent({
category: 'MMI',
event: 'Custodial accounts connected',
@ -568,6 +562,15 @@ const CustodyPage = () => {
chainId,
},
});
history.push({
pathname: CUSTODY_ACCOUNT_DONE_ROUTE,
state: {
imgSrc: selectedCustodian.iconUrl,
title: t('custodianAccountAddedTitle'),
description: t('custodianAccountAddedDesc'),
},
});
} catch (e) {
setSelectError(e.message);
}
@ -598,7 +601,6 @@ const CustodyPage = () => {
/>
</>
)}
{accounts && accounts.length === 0 && (
<Box
data-testid="custody-accounts-empty"
@ -609,11 +611,11 @@ const CustodyPage = () => {
marginBottom={2}
fontWeight={FontWeight.Bold}
color={TextColor.textDefault}
variant={TextVariant.bodySm}
variant={TextVariant.bodyLgMedium}
>
{t('allCustodianAccountsConnectedTitle')}
</Text>
<Text variant={TextVariant.bodyXs}>
<Text variant={TextVariant.bodyMd}>
{t('allCustodianAccountsConnectedSubtitle')}
</Text>

View File

@ -1,6 +1,6 @@
import React from 'react';
import configureMockStore from 'redux-mock-store';
import { fireEvent, waitFor, screen } from '@testing-library/react';
import { fireEvent, waitFor, screen, act } from '@testing-library/react';
import thunk from 'redux-thunk';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import CustodyPage from '.';
@ -16,8 +16,8 @@ const mockedGetCustodianConnectRequest = jest.fn().mockReturnValue({
custodian: 'saturn',
token: 'token',
apiUrl: 'url',
custodianType: 'JSON-RPC',
custodianName: 'Saturn',
custodianType: undefined,
custodianName: 'saturn',
});
jest.mock('../../../store/institutional/institution-background', () => ({
@ -85,29 +85,19 @@ describe('CustodyPage', function () {
});
});
it('calls getCustodianJwtList on custody select when connect btn is click', async () => {
const { getByTestId } = renderWithProvider(<CustodyPage />, store);
const custodyBtn = getByTestId('custody-connect-button');
await waitFor(() => {
fireEvent.click(custodyBtn);
it('calls getCustodianJwtList on custody select when connect btn is click and clicks connect button and shows the jwt form', async () => {
act(() => {
renderWithProvider(<CustodyPage />, store);
});
await waitFor(() => {
expect(mockedGetCustodianJWTList).toHaveBeenCalled();
});
});
it('clicks connect button and shows the jwt form', async () => {
const { getByTestId } = renderWithProvider(<CustodyPage />, store);
const custodyBtn = getByTestId('custody-connect-button');
await waitFor(() => {
const custodyBtn = screen.getByTestId('custody-connect-button');
fireEvent.click(custodyBtn);
});
await waitFor(() => {
expect(screen.getByTestId('jwt-form-connect-button')).toBeInTheDocument();
expect(mockedGetCustodianJWTList).toHaveBeenCalled();
});
});
});

View File

@ -117,7 +117,6 @@ describe('Institution Actions', () => {
'newApiUrl',
);
connectCustodyAddresses(jest.fn());
setWaitForConfirmDeepLinkDialog(jest.fn());
expect(connectCustodyAddresses).toBeDefined();
expect(setWaitForConfirmDeepLinkDialog).toBeDefined();
});

View File

@ -91,14 +91,17 @@ export function mmiActionsFactory() {
};
}
function createAction(name: string, payload: any) {
return () => {
callBackgroundMethod(name, [payload], (err) => {
if (isErrorWithMessage(err)) {
throw new Error(err.message);
function createAction(name: string, payload: any): Promise<void> {
return new Promise((resolve, reject) => {
callBackgroundMethod(name, [payload], (error) => {
if (error) {
reject(error);
return;
}
resolve();
});
};
});
}
return {
@ -190,18 +193,27 @@ export function mmiActionsFactory() {
createAction('syncReportsInProgress', { address, historicalReports }),
removeConnectInstitutionalFeature: (origin: string, projectId: string) =>
createAction('removeConnectInstitutionalFeature', { origin, projectId }),
removeAddTokenConnectRequest: (
origin: string,
apiUrl: string,
token: string,
) =>
removeAddTokenConnectRequest: ({
origin,
apiUrl,
token,
}: {
origin: string;
apiUrl: string;
token: string;
}) =>
createAction('removeAddTokenConnectRequest', { origin, apiUrl, token }),
setCustodianConnectRequest: (
token: string,
apiUrl: string,
custodianType: string,
custodianName: string,
) =>
setCustodianConnectRequest: ({
token,
apiUrl,
custodianType,
custodianName,
}: {
token: string;
apiUrl: string;
custodianType: string;
custodianName: string;
}) =>
createAsyncAction('setCustodianConnectRequest', [
{ token, apiUrl, custodianType, custodianName },
]),