Skip to content

Commit

Permalink
Merge branch 'main' into upgrade/react-native-webview
Browse files Browse the repository at this point in the history
  • Loading branch information
jpcloureiro committed Jul 1, 2024
2 parents 7b7868e + 2478527 commit 056cf00
Show file tree
Hide file tree
Showing 43 changed files with 658 additions and 239 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
///: BEGIN:ONLY_INCLUDE_IF(external-snaps)
///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps)
export const SNAP_INSTALL_FLOW = 'snap-install-flow';
export const SNAP_INSTALL_OK = 'snap-install-ok';
///: END:ONLY_INCLUDE_IF
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
///: BEGIN:ONLY_INCLUDE_IF(external-snaps)
///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps)
import { StyleSheet } from 'react-native';
import { Theme } from '../../../util/theme/models';
import Device from '../../../util/device';
Expand Down Expand Up @@ -37,6 +37,10 @@ const styleSheet = (params: { theme: Theme }) => {
snapCell: {
marginVertical: 16,
},
snapAvatar: {
alignSelf: 'center',
marginTop: 16,
},
snapPermissionContainer: {
maxHeight: 300,
borderWidth: 1,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,50 @@
///: BEGIN:ONLY_INCLUDE_IF(external-snaps)
///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps)
import React, { useEffect, useState } from 'react';
import ApprovalModal from '../ApprovalModal';
import useApprovalRequest from '../../Views/confirmations/hooks/useApprovalRequest';
import { ApprovalTypes } from '../../../core/RPCMethods/RPCMethodMiddleware';
import Logger from '../../../util/Logger';
import { SnapInstallState } from './InstallSnapApproval.types';
import {
InstallSnapConnectionRequest,
///: END:ONLY_INCLUDE_IF
///: BEGIN:ONLY_INCLUDE_IF(external-snaps)
InstallSnapError,
InstallSnapPermissionsRequest,
InstallSnapSuccess,
///: END:ONLY_INCLUDE_IF
///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps)
} from './components';
import { SNAP_INSTALL_FLOW } from './InstallSnapApproval.constants';
import { ApprovalRequest } from '@metamask/approval-controller';
import { useSelector } from 'react-redux';
import { selectSnapsMetadata } from '../../../selectors/snaps/snapController';
import {
WALLET_SNAP_PERMISSION_KEY,
stripSnapPrefix,
} from '@metamask/snaps-utils';

const InstallSnapApproval = () => {
const snapsMetadata = useSelector(selectSnapsMetadata);

const [installState, setInstallState] = useState<
SnapInstallState | undefined
>(undefined);
const [isFinished, setIsFinished] = useState<boolean>(false);
///: END:ONLY_INCLUDE_IF
///: BEGIN:ONLY_INCLUDE_IF(external-snaps)
const [installError, setInstallError] = useState<Error | undefined>(
undefined,
);
///: END:ONLY_INCLUDE_IF
///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps)
const { approvalRequest, onConfirm, onReject } = useApprovalRequest();

useEffect(() => {
if (approvalRequest) {
if (approvalRequest.type === ApprovalTypes.REQUEST_PERMISSIONS) {
if (
approvalRequest.type === ApprovalTypes.REQUEST_PERMISSIONS &&
Object.keys(approvalRequest?.requestData?.permissions).includes(
WALLET_SNAP_PERMISSION_KEY,
)
) {
setInstallState(SnapInstallState.Confirm);
} else if (
approvalRequest.type === ApprovalTypes.INSTALL_SNAP &&
Expand All @@ -41,14 +59,11 @@ const InstallSnapApproval = () => {

// TODO: Replace "any" with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const getSnapName = (request: ApprovalRequest<any>): string => {
const getSnapId = (request: ApprovalRequest<any>): string => {
// We first look for the name inside the snapId approvalRequest data
const snapId = request?.requestData?.snapId;
if (typeof snapId === 'string') {
const colonIndex = snapId.indexOf(':');
if (colonIndex !== -1) {
return snapId.substring(colonIndex + 1);
}
return snapId;
}
// If there is no snapId present in the approvalRequest data, we look for the name inside the snapIds caveat
const snapIdsCaveat =
Expand All @@ -57,15 +72,16 @@ const InstallSnapApproval = () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(c: any) => c.type === 'snapIds',
);
// return an empty string if we can't find the snap name in the approvalRequest data
return snapIdsCaveat?.value ? Object.keys(snapIdsCaveat.value)[0] : '';
return Object.keys(snapIdsCaveat.value)[0];
};

const getSnapMetadata = (snapId: string) =>
snapsMetadata[snapId] ?? { name: stripSnapPrefix(snapId) };

if (!approvalRequest) return null;

const onInstallSnapFinished = () => {
setIsFinished(true);
};
///: END:ONLY_INCLUDE_IF
///: BEGIN:ONLY_INCLUDE_IF(external-snaps)

const onPermissionsConfirm = async () => {
try {
Expand All @@ -75,54 +91,55 @@ const InstallSnapApproval = () => {
});
setInstallState(SnapInstallState.SnapInstalled);
} catch (error) {
Logger.error(
error as Error,
`${SNAP_INSTALL_FLOW} Failed to install snap`,
);
setInstallError(error as Error);
setInstallState(SnapInstallState.SnapInstallError);
}
};
///: END:ONLY_INCLUDE_IF
///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps)

if (!approvalRequest) return null;
if (!approvalRequest || installState === undefined) return null;

const snapName = getSnapName(approvalRequest);
const snapId = getSnapId(approvalRequest);
const snapName = getSnapMetadata(snapId).name;

// TODO: This component should support connecting to multiple Snaps at once.
const renderModalContent = () => {
switch (installState) {
case SnapInstallState.Confirm:
return (
<InstallSnapConnectionRequest
approvalRequest={approvalRequest}
snapId={snapId}
snapName={snapName}
onConfirm={onConfirm}
onCancel={onReject}
/>
);
///: END:ONLY_INCLUDE_IF
///: BEGIN:ONLY_INCLUDE_IF(external-snaps)
case SnapInstallState.AcceptPermissions:
return (
<InstallSnapPermissionsRequest
approvalRequest={approvalRequest}
snapId={snapId}
snapName={snapName}
onConfirm={onPermissionsConfirm}
onCancel={onReject}
/>
);
case SnapInstallState.SnapInstalled:
return (
<InstallSnapSuccess
snapName={snapName}
onConfirm={onInstallSnapFinished}
/>
);
return <InstallSnapSuccess snapName={snapName} onConfirm={onConfirm} />;
case SnapInstallState.SnapInstallError:
return (
<InstallSnapError
snapName={snapName}
onConfirm={onInstallSnapFinished}
onConfirm={onConfirm}
error={installError}
/>
);
///: END:ONLY_INCLUDE_IF
///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps)
default:
return null;
}
Expand All @@ -131,10 +148,7 @@ const InstallSnapApproval = () => {
const content = renderModalContent();

return content ? (
<ApprovalModal
isVisible={installState !== undefined && !isFinished}
onCancel={onReject}
>
<ApprovalModal isVisible={installState !== undefined} onCancel={onReject}>
{content}
</ApprovalModal>
) : null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
///: BEGIN:ONLY_INCLUDE_IF(external-snaps)
///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps)
interface InstallSnapFlowProps {
// TODO: Replace "any" with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
approvalRequest: any;
snapId: string;
snapName: string;
onConfirm: () => void;
onCancel: () => void;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
///: BEGIN:ONLY_INCLUDE_IF(external-snaps)
///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps)
import React, { useMemo } from 'react';
import { ImageSourcePropType, View } from 'react-native';
import { View } from 'react-native';
import { InstallSnapFlowProps } from '../../InstallSnapApproval.types';
import styleSheet from '../../InstallSnapApproval.styles';
import { strings } from '../../../../../../locales/i18n';
Expand All @@ -11,10 +11,6 @@ import Text, {
import TagUrl from '../../../../../component-library/components/Tags/TagUrl';
import { getUrlObj, prefixUrlWithProtocol } from '../../../../../util/browser';
import { IconName } from '../../../../../component-library/components/Icons/Icon';
import Cell, {
CellVariant,
} from '../../../../../component-library/components/Cells/Cell';
import { AvatarVariant } from '../../../../../component-library/components/Avatars/Avatar';
import {
ButtonSize,
ButtonVariants,
Expand All @@ -29,15 +25,18 @@ import {
SNAP_INSTALL_CONNECT,
SNAP_INSTALL_CONNECTION_REQUEST,
} from './InstallSnapConnectionRequest.constants';
import { useFavicon } from '../../../../hooks/useFavicon';
import { SnapAvatar } from '../../../../UI/Snaps/SnapAvatar/SnapAvatar';

const InstallSnapConnectionRequest = ({
approvalRequest,
snapId,
snapName,
onConfirm,
onCancel,
}: Pick<
InstallSnapFlowProps,
'approvalRequest' | 'onConfirm' | 'onCancel' | 'snapName'
'approvalRequest' | 'onConfirm' | 'onCancel' | 'snapId' | 'snapName'
>) => {
const { styles } = useStyles(styleSheet, {});

Expand All @@ -46,10 +45,7 @@ const InstallSnapConnectionRequest = ({
[approvalRequest.origin],
);

const favicon: ImageSourcePropType = useMemo(() => {
const iconUrl = `https://api.faviconkit.com/${origin}/50`;
return { uri: iconUrl };
}, [origin]);
const favicon = useFavicon(origin);

const urlWithProtocol = prefixUrlWithProtocol(origin);

Expand Down Expand Up @@ -85,22 +81,18 @@ const InstallSnapConnectionRequest = ({
label={urlWithProtocol}
iconName={secureIcon}
/>
<SnapAvatar
snapId={snapId}
snapName={snapName}
style={styles.snapAvatar}
/>
<SheetHeader title={strings('install_snap.title')} />
<Text style={styles.description} variant={TextVariant.BodyMD}>
{strings('install_snap.description', {
origin,
snap: snapName,
})}
</Text>
<Cell
style={styles.snapCell}
variant={CellVariant.Display}
title={snapName}
avatarProps={{
variant: AvatarVariant.Icon,
name: IconName.Snaps,
}}
/>
<View style={styles.actionContainer}>
<BottomSheetFooter
buttonsAlignment={ButtonsAlignment.Horizontal}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
///: BEGIN:ONLY_INCLUDE_IF(external-snaps)
///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps)
/* eslint-disable import/prefer-default-export */
import InstallSnapConnectionRequest from './InstallSnapConnectionRequest';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
///: BEGIN:ONLY_INCLUDE_IF(external-snaps)
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import configureMockStore from 'redux-mock-store';
import { Provider } from 'react-redux';
import InstallSnapConnectionRequest from '../InstallSnapConnectionRequest';
import {
SNAP_INSTALL_CANCEL,
Expand Down Expand Up @@ -38,6 +39,28 @@ describe('InstallSnapConnectionRequest', () => {
expectsResult: false,
};

const mockStore = configureMockStore();
const mockInitialState = {
settings: {},
engine: {
backgroundState: {
SubjectMetadataController: {
subjectMetadata: {},
},
SnapController: {
snaps: {},
},
},
},
};
const store = mockStore(mockInitialState);

// TODO: Replace "any" with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const Wrapper = ({ children }: any) => (
<Provider store={store}>{children}</Provider>
);

const onConfirm = jest.fn();
const onCancel = jest.fn();

Expand All @@ -51,8 +74,10 @@ describe('InstallSnapConnectionRequest', () => {
approvalRequest={requestPermissionsData}
onConfirm={onConfirm}
onCancel={onCancel}
snapId="npm:@metamask/bip32-example-snap"
snapName="@metamask/bip32-example-snap"
/>,
{ wrapper: Wrapper },
);
expect(getByTestId(SNAP_INSTALL_CONNECTION_REQUEST)).toBeDefined();
});
Expand All @@ -63,8 +88,10 @@ describe('InstallSnapConnectionRequest', () => {
approvalRequest={requestPermissionsData}
onConfirm={onConfirm}
onCancel={onCancel}
snapId="npm:@metamask/bip32-example-snap"
snapName="@metamask/bip32-example-snap"
/>,
{ wrapper: Wrapper },
);

fireEvent.press(getByTestId(SNAP_INSTALL_CONNECT));
Expand All @@ -77,8 +104,10 @@ describe('InstallSnapConnectionRequest', () => {
approvalRequest={requestPermissionsData}
onConfirm={onConfirm}
onCancel={onCancel}
snapId="npm:@metamask/bip32-example-snap"
snapName="@metamask/bip32-example-snap"
/>,
{ wrapper: Wrapper },
);

fireEvent.press(getByTestId(SNAP_INSTALL_CANCEL));
Expand All @@ -91,12 +120,13 @@ describe('InstallSnapConnectionRequest', () => {
approvalRequest={requestPermissionsData}
onConfirm={onConfirm}
onCancel={onCancel}
snapId="npm:@metamask/bip32-example-snap"
snapName="@metamask/bip32-example-snap"
/>,
{ wrapper: Wrapper },
);

const expectedUrl = 'https://metamask.github.io';
expect(getByText(expectedUrl)).toBeTruthy();
});
});
///: END:ONLY_INCLUDE_IF
Loading

0 comments on commit 056cf00

Please sign in to comment.