Skip to content

Commit

Permalink
feat: sdk permissions system integration (#8531)
Browse files Browse the repository at this point in the history
## **Description**

Changes to include permissions system within sdk workflows (js +
android). WalletConnect will require a separate PR and migrate away from
persisting connection in DefaultPreferences.

- new connection modal flow (allow multiple account selection)
- new sessions management ui

Design:

https://www.figma.com/file/piME5CaTYUz07Gc7Ye7c22/Deeplinking--SDK?type=design&node-id=1633-2285&mode=design&t=egikpsEVgt3xcMNg-0


https://app.zenhub.com/workspaces/metamask-mobile-5f984938ddc0e4001d4b79cb/issues/gh/metamask/mobile-planning/868

- added online indication in connection list

## **Related issues**

Fixes:

## **Manual testing steps**

* See QA comments

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->

### **Before**

<!-- [screenshots/recordings] -->

### **After**


![image](https://github.com/MetaMask/metamask-mobile/assets/107169956/71f7e59b-8401-4974-90cf-5af9855d9877)

![image](https://github.com/MetaMask/metamask-mobile/assets/107169956/d2e09508-2154-4e68-95af-77d01322df17)

## **Pre-merge author checklist**

- [x] I’ve followed [MetaMask Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've clearly explained what problem this PR is solving and how it
is solved.
- [x] I've linked related issues
- [x] I've included manual testing steps
- [x] I've included screenshots/recordings if applicable
- [x] I’ve included tests if applicable
- [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
- [x] I’ve properly set the pull request status:
  - [x] In case it's not yet "ready for review", I've set it to "draft".
- [x] In case it's "ready for review", I've changed it from "draft" to
"non-draft".

## **Pre-merge reviewer checklist**

- [x] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [x] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
  • Loading branch information
abretonc7s authored Feb 27, 2024
1 parent 442d9ee commit 386d9be
Show file tree
Hide file tree
Showing 48 changed files with 1,250 additions and 402 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const BottomSheet = forwardRef<BottomSheetRef, BottomSheetProps>(
isInteractable = true,
shouldNavigateBack = true,
isFullscreen = false,
customMarginTop,
...props
},
ref,
Expand Down Expand Up @@ -105,6 +106,7 @@ const BottomSheet = forwardRef<BottomSheetRef, BottomSheetProps>(
isInteractable={isInteractable}
onClose={onCloseCB}
onOpen={onOpenCB}
customMarginTop={customMarginTop}
ref={bottomSheetDialogRef}
isFullscreen={isFullscreen}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ Boolean that indicates if sheet is swippable. This affects whether or not tappin
| :-------------------------------------------------- | :------------------------------------------------------ | :----------------------------------------------------- |
| boolean | No | true |


### `customMarginTop`

Configure height of the modal by setting the distance between top of modal and top of screen.
| <span style="color:gray;font-size:14px">TYPE</span> | <span style="color:gray;font-size:14px">REQUIRED</span> | <span style="color:gray;font-size:14px">DEFAULT</span> |
| :-------------------------------------------------- | :------------------------------------------------------ | :----------------------------------------------------- |
| number | No | 250

### `children`

Content to wrap in sheet.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ const BottomSheetDialog = forwardRef<
isInteractable = true,
onClose,
onOpen,
customMarginTop,
...props
},
ref,
Expand All @@ -68,9 +69,10 @@ const BottomSheetDialog = forwardRef<
useSafeAreaInsets();
const { y: frameY } = useSafeAreaFrame();
const { height: screenHeight } = useWindowDimensions();
const marginTop = customMarginTop ?? DEFAULT_BOTTOMSHEETDIALOG_MARGINTOP;
const maxSheetHeight = isFullscreen
? screenHeight - screenTopPadding
: screenHeight - screenTopPadding - DEFAULT_BOTTOMSHEETDIALOG_MARGINTOP;
: screenHeight - screenTopPadding - marginTop;
const { styles } = useStyles(styleSheet, {
maxSheetHeight,
screenBottomPadding,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ export interface BottomSheetDialogProps extends ViewProps {
* Optional callback that gets triggered when sheet is opened.
*/
onOpen?: (hasPendingAction?: boolean) => void;
/**
* Customize the top margin of the sheet.
*/
customMarginTop?: number;
}

export interface BottomSheetDialogRef {
Expand Down
2 changes: 2 additions & 0 deletions app/component-library/components/Tags/TagUrl/TagUrl.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ const styleSheet = (params: { theme: Theme; vars: TagUrlStyleSheetVars }) => {
) as ViewStyle,
favicon: {
marginRight: 8,
width: 24,
height: 24,
},
label: {
color: colors.text.alternative,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ exports[`TagUrl should render correctly 1`] = `
size="32"
style={
Object {
"height": 24,
"marginRight": 8,
"width": 24,
}
}
variant="Favicon"
Expand Down
10 changes: 10 additions & 0 deletions app/components/Nav/App/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ import AsyncStorage from '../../../store/async-storage-wrapper';
import ShowIpfsGatewaySheet from '../../Views/ShowIpfsGatewaySheet/ShowIpfsGatewaySheet';
import ShowDisplayNftMediaSheet from '../../Views/ShowDisplayMediaNFTSheet/ShowDisplayNFTMediaSheet';
import AmbiguousAddressSheet from '../../../../app/components/Views/Settings/Contacts/AmbiguousAddressSheet/AmbiguousAddressSheet';
import SDKDisconnectModal from '../../../../app/components/Views/SDKDisconnectModal/SDKDisconnectModal';
import SDKSessionModal from '../../../../app/components/Views/SDKSessionModal/SDKSessionModal';
import { MetaMetrics } from '../../../core/Analytics';
import trackErrorAsAnalytics from '../../../util/metrics/TrackError/trackErrorAsAnalytics';

Expand Down Expand Up @@ -552,6 +554,14 @@ const App = ({ userLoggedIn }) => {
name={Routes.SHEET.SDK_FEEDBACK}
component={SDKFeedbackModal}
/>
<Stack.Screen
name={Routes.SHEET.SDK_MANAGE_CONNECTIONS}
component={SDKSessionModal}
/>
<Stack.Screen
name={Routes.SHEET.SDK_DISCONNECT}
component={SDKDisconnectModal}
/>
<Stack.Screen
name={Routes.SHEET.ACCOUNT_CONNECT}
component={AccountConnect}
Expand Down
47 changes: 41 additions & 6 deletions app/components/Views/AccountConnect/AccountConnect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ import AccountConnectSingleSelector from './AccountConnectSingleSelector';
import AccountConnectMultiSelector from './AccountConnectMultiSelector';
import useFavicon from '../../hooks/useFavicon/useFavicon';
import URLParse from 'url-parse';
import SDKConnect from '../../../core/SDKConnect/SDKConnect';
import AppConstants from '../../../../app/core/AppConstants';
import { trackDappVisitedEvent } from '../../../util/metrics';
import { useMetrics } from '../../../components/hooks/useMetrics';

Expand Down Expand Up @@ -84,11 +86,23 @@ const AccountConnect = (props: AccountConnectProps) => {
? AvatarAccountType.Blockies
: AvatarAccountType.JazzIcon,
);

const { id: channelId, origin: metadataOrigin } = hostInfo.metadata as {
id: string;
origin: string;
};

// Extract connection info from sdk
// FIXME should be replaced by passing dynamic parameters to the PermissionController
// TODO: Retrive wallet connect connection info from channelId
const sdkConnection = SDKConnect.getInstance().getConnection({ channelId });
const hostname = (
sdkConnection?.originatorInfo?.url ?? metadataOrigin
).replace(AppConstants.MM_SDK.SDK_REMOTE_ORIGIN, '');

const origin: string = useSelector(getActiveTabUrl, isEqual);

const faviconSource = useFavicon(origin);

const hostname = hostInfo.metadata.origin;
const urlWithProtocol = prefixUrlWithProtocol(hostname);

const secureIcon = useMemo(
Expand Down Expand Up @@ -117,13 +131,25 @@ const AccountConnect = (props: AccountConnectProps) => {
const cancelPermissionRequest = useCallback(
(requestId) => {
Engine.context.PermissionController.rejectPermissionsRequest(requestId);
if (channelId && accountsLength === 0) {
// Remove Potential SDK connection
SDKConnect.getInstance().removeChannel({
channelId,
sendTerminate: true,
});
}

trackEvent(MetaMetricsEvents.CONNECT_REQUEST_CANCELLED, {
number_of_accounts: accountsLength,
source: 'permission system',
});
},
[Engine.context.PermissionController, accountsLength, trackEvent],
[
Engine.context.PermissionController,
accountsLength,
channelId,
trackEvent,
],
);

const triggerDappVisitedEvent = useCallback(
Expand All @@ -141,10 +167,11 @@ const AccountConnect = (props: AccountConnectProps) => {
...hostInfo,
metadata: {
...hostInfo.metadata,
origin: hostname,
origin: metadataOrigin,
},
approvedAccounts: selectedAccounts,
};

const connectedAccountLength = selectedAccounts.length;
const activeAddress = selectedAccounts[0].address;
const activeAccountName = getAccountNameWithENS({
Expand Down Expand Up @@ -199,11 +226,11 @@ const AccountConnect = (props: AccountConnectProps) => {
hostInfo,
accounts,
ensByAccountAddress,
hostname,
accountAvatarType,
Engine.context.PermissionController,
toastRef,
accountsLength,
metadataOrigin,
triggerDappVisitedEvent,
trackEvent,
]);
Expand Down Expand Up @@ -318,6 +345,7 @@ const AccountConnect = (props: AccountConnectProps) => {
return (
<AccountConnectSingle
onSetSelectedAddresses={setSelectedAddresses}
connection={sdkConnection}
onSetScreen={setScreen}
onUserAction={setUserIntent}
defaultSelectedAccount={defaultSelectedAccount}
Expand All @@ -338,6 +366,7 @@ const AccountConnect = (props: AccountConnectProps) => {
secureIcon,
urlWithProtocol,
setUserIntent,
sdkConnection,
]);

const renderSingleConnectSelectorScreen = useCallback(
Expand Down Expand Up @@ -376,6 +405,7 @@ const AccountConnect = (props: AccountConnectProps) => {
urlWithProtocol={urlWithProtocol}
onUserAction={setUserIntent}
onBack={() => setScreen(AccountConnectScreens.SingleConnect)}
connection={sdkConnection}
/>
),
[
Expand All @@ -388,6 +418,7 @@ const AccountConnect = (props: AccountConnectProps) => {
faviconSource,
urlWithProtocol,
secureIcon,
sdkConnection,
],
);

Expand All @@ -408,7 +439,11 @@ const AccountConnect = (props: AccountConnectProps) => {
]);

return (
<BottomSheet onClose={handleSheetDismiss} ref={sheetRef}>
<BottomSheet
onClose={handleSheetDismiss}
customMarginTop={150}
ref={sheetRef}
>
{renderConnectScreens()}
</BottomSheet>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ const styleSheet = (params: { theme: Theme }) => {
const { colors } = params.theme;

return StyleSheet.create({
container: {
height: '100%',
},
body: {
paddingHorizontal: 16,
},
Expand All @@ -32,6 +35,20 @@ const styleSheet = (params: { theme: Theme }) => {
selectAllButton: {
marginBottom: 16,
},
sdkInfoContainer: {
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: 10,
paddingHorizontal: 16,
marginBottom: -16,
},
sdkInfoDivier: {
borderTopWidth: 1,
borderTopColor: colors.border.muted,
height: 1,
width: '100%',
},
disabled: {
opacity: 0.5,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
// Third party dependencies.
import React, { useCallback, useState } from 'react';
import { View, Platform } from 'react-native';
import { Platform, View } from 'react-native';

// External dependencies.
import SheetHeader from '../../../../component-library/components/Sheet/SheetHeader';
import { strings } from '../../../../../locales/i18n';
import TagUrl from '../../../../component-library/components/Tags/TagUrl';
import Text from '../../../../component-library/components/Texts/Text';
import { useStyles } from '../../../../component-library/hooks';
import { ACCOUNT_APPROVAL_SELECT_ALL_BUTTON } from '../../../../../wdio/screen-objects/testIDs/Components/AccountApprovalModal.testIds';
import generateTestId from '../../../../../wdio/utils/generateTestId';
import Button, {
ButtonSize,
ButtonVariants,
ButtonWidthTypes,
} from '../../../../component-library/components/Buttons/Button';
import AccountSelectorList from '../../../UI/AccountSelectorList';
import SheetHeader from '../../../../component-library/components/Sheet/SheetHeader';
import TagUrl from '../../../../component-library/components/Tags/TagUrl';
import Text, {
TextColor,
} from '../../../../component-library/components/Texts/Text';
import { useStyles } from '../../../../component-library/hooks';
import { USER_INTENT } from '../../../../constants/permissions';
import generateTestId from '../../../../../wdio/utils/generateTestId';
import { ACCOUNT_APPROVAL_SELECT_ALL_BUTTON } from '../../../../../wdio/screen-objects/testIDs/Components/AccountApprovalModal.testIds';
import AccountSelectorList from '../../../UI/AccountSelectorList';

// Internal dependencies.
import { ConnectAccountModalSelectorsIDs } from '../../../../../e2e/selectors/Modals/ConnectAccountModal.selectors';
import { ACCOUNT_LIST_ADD_BUTTON_ID } from '../../../../../wdio/screen-objects/testIDs/Components/AccountListComponent.testIds';
import AddAccountActions from '../../AddAccountActions';
import styleSheet from './AccountConnectMultiSelector.styles';
import {
AccountConnectMultiSelectorProps,
AccountConnectMultiSelectorScreens,
} from './AccountConnectMultiSelector.types';
import AddAccountActions from '../../AddAccountActions';
import { ACCOUNT_LIST_ADD_BUTTON_ID } from '../../../../../wdio/screen-objects/testIDs/Components/AccountListComponent.testIds';
import { ConnectAccountModalSelectorsIDs } from '../../../../../e2e/selectors/Modals/ConnectAccountModal.selectors';

const AccountConnectMultiSelector = ({
accounts,
Expand All @@ -39,6 +41,7 @@ const AccountConnectMultiSelector = ({
secureIcon,
isAutoScrollEnabled = true,
urlWithProtocol,
connection,
onBack,
}: AccountConnectMultiSelectorProps) => {
const { styles } = useStyles(styleSheet, {});
Expand Down Expand Up @@ -147,7 +150,7 @@ const AccountConnectMultiSelector = ({

const renderAccountConnectMultiSelector = useCallback(
() => (
<>
<View style={styles.container}>
<SheetHeader
title={strings('accounts.connect_accounts_title')}
onBack={onBack}
Expand Down Expand Up @@ -175,6 +178,15 @@ const AccountConnectMultiSelector = ({
isRemoveAccountEnabled
isAutoScrollEnabled={isAutoScrollEnabled}
/>
{connection?.originatorInfo?.apiVersion && (
<View style={styles.sdkInfoContainer}>
<View style={styles.sdkInfoDivier} />
<Text color={TextColor.Muted}>
SDK {connection?.originatorInfo?.platform} v
{connection?.originatorInfo?.apiVersion}
</Text>
</View>
)}
<View style={styles.addAccountButtonContainer}>
<Button
variant={ButtonVariants.Link}
Expand All @@ -188,7 +200,7 @@ const AccountConnectMultiSelector = ({
/>
</View>
<View style={styles.body}>{renderCtaButtons()}</View>
</>
</View>
),
[
accounts,
Expand All @@ -207,6 +219,10 @@ const AccountConnectMultiSelector = ({
styles.body,
styles.description,
urlWithProtocol,
connection,
styles.sdkInfoContainer,
styles.container,
styles.sdkInfoDivier,
onBack,
],
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export enum AccountConnectMultiSelectorScreens {
}

// External dependencies.
import { ConnectionProps } from '../../../../core/SDKConnect/Connection';
import { UseAccounts } from '../../../hooks/useAccounts';
import { IconName } from '../../../../component-library/components/Icons/Icon';
import { USER_INTENT } from '../../../../constants/permissions';
Expand All @@ -27,4 +28,5 @@ export interface AccountConnectMultiSelectorProps extends UseAccounts {
secureIcon: IconName;
isAutoScrollEnabled?: boolean;
onBack: () => void;
connection?: ConnectionProps;
}
Loading

0 comments on commit 386d9be

Please sign in to comment.