Skip to content

Commit

Permalink
feat: permissions system within sdk redux store (#8785)
Browse files Browse the repository at this point in the history
## **Description**

- Migrate away from Default Preferences to manage persisted session
state.
- Migrate walletconnect to permission system
- Connection flow optimization for ios mobile

Grouped within a single PR as these features are tightly integrated
together.
 
## **Related issues**

Fixes:

## **Manual testing steps**

1. Go to this page...
2.
3.

## **Screenshots/Recordings**

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

### **Before**

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

### **After**

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

## **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**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] 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 Mar 6, 2024
1 parent 1097298 commit 8299ec9
Show file tree
Hide file tree
Showing 47 changed files with 608 additions and 673 deletions.
194 changes: 194 additions & 0 deletions app/actions/sdk/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import type { Action as ReduxAction } from 'redux';
import { ConnectionProps } from '../../core/SDKConnect/Connection';
import { ApprovedHosts, SDKSessions } from '../../core/SDKConnect/SDKConnect';
import { WC2Metadata } from './state';

export enum ActionType {
WC2_METADATA = 'WC2_METADATA',
RESET_CONNECTIONS = 'RESET_CONNECTIONS',
UPDATE_CONNECTION = 'UPDATE_CONNECTION',
REMOVE_CONNECTION = 'REMOVE_CONNECTION',
ADD_CONNECTION = 'ADD_CONNECTION',
DISCONNECT_ALL = 'DISCONNECT_ALL',
REMOVE_APPROVED_HOST = 'REMOVE_APPROVWED_HOST',
SET_APPROVED_HOST = 'SET_APPROVED_HOST',
RESET_APPROVED_HOSTS = 'RESET_APPROVED_HOSTS',
SET_CONNECTED = 'SET_CONNECTED',
UPDATE_ANDROID_CONNECTION = 'UPDATE_ANDROID_CONNECTION',
REMOVE_ANDROID_CONNECTION = 'REMOVE_ANDROID_CONNECTION',
RESET_ANDROID_CONNECTIONS = 'RESET_ANDROID_CONNECTIONS',
}

export type DisconnectAll = ReduxAction<ActionType.DISCONNECT_ALL>;

export interface UpdateConnection
extends ReduxAction<ActionType.UPDATE_CONNECTION> {
channelId: string;
connection: ConnectionProps;
}

export interface ResetConnection
extends ReduxAction<ActionType.RESET_CONNECTIONS> {
connections: SDKSessions;
}

export interface RemoveConnection
extends ReduxAction<ActionType.REMOVE_CONNECTION> {
channelId: string;
}

export interface AddConnection extends ReduxAction<ActionType.ADD_CONNECTION> {
channelId: string;
connection: ConnectionProps;
}

export interface RemoveApprovedHost
extends ReduxAction<ActionType.REMOVE_APPROVED_HOST> {
channelId: string;
}

export interface SetApprovedHost
extends ReduxAction<ActionType.SET_APPROVED_HOST> {
channelId: string;
validUntil: number;
}

export interface ResetApprovedHosts
extends ReduxAction<ActionType.RESET_APPROVED_HOSTS> {
approvedHosts: ApprovedHosts;
}

export interface UpdateAndroidConnection
extends ReduxAction<ActionType.UPDATE_ANDROID_CONNECTION> {
channelId: string;
connection: ConnectionProps;
}

export interface RemoveAndroidConnection
extends ReduxAction<ActionType.REMOVE_ANDROID_CONNECTION> {
channelId: string;
}

export interface ResetAndroidConnections
extends ReduxAction<ActionType.RESET_ANDROID_CONNECTIONS> {
connections: SDKSessions;
}

export interface SetConnected extends ReduxAction<ActionType.SET_CONNECTED> {
channelId: string;
connected: boolean;
}

export interface UpdateWC2Metadata
extends ReduxAction<ActionType.WC2_METADATA> {
metadata?: WC2Metadata;
}

export type Action =
| UpdateConnection
| DisconnectAll
| RemoveConnection
| AddConnection
| ResetConnection
| RemoveApprovedHost
| SetApprovedHost
| ResetApprovedHosts
| UpdateWC2Metadata
| UpdateAndroidConnection
| RemoveAndroidConnection
| ResetAndroidConnections
| SetConnected;

export const disconnectAll = (): DisconnectAll => ({
type: ActionType.DISCONNECT_ALL,
});

export const updateWC2Metadata = (
metadata: WC2Metadata,
): UpdateWC2Metadata => ({
type: ActionType.WC2_METADATA,
metadata,
});

export const updateConnection = (
channelId: string,
connection: ConnectionProps,
): UpdateConnection => ({
type: ActionType.UPDATE_CONNECTION,
channelId,
connection,
});

export const removeConnection = (channelId: string): RemoveConnection => ({
type: ActionType.REMOVE_CONNECTION,
channelId,
});

export const addConnection = (
channelId: string,
connection: ConnectionProps,
): AddConnection => ({
type: ActionType.ADD_CONNECTION,
channelId,
connection,
});

export const resetConnections = (
connections: SDKSessions,
): ResetConnection => ({
type: ActionType.RESET_CONNECTIONS,
connections,
});

export const removeApprovedHost = (channelId: string): RemoveApprovedHost => ({
type: ActionType.REMOVE_APPROVED_HOST,
channelId,
});

export const setApprovedHost = (
channelId: string,
validUntil: number,
): SetApprovedHost => ({
type: ActionType.SET_APPROVED_HOST,
channelId,
validUntil,
});

export const resetApprovedHosts = (
approvedHosts: ApprovedHosts,
): ResetApprovedHosts => ({
type: ActionType.RESET_APPROVED_HOSTS,
approvedHosts,
});

export const updateAndroidConnection = (
channelId: string,
connection: ConnectionProps,
): UpdateAndroidConnection => ({
type: ActionType.UPDATE_ANDROID_CONNECTION,
channelId,
connection,
});

export const removeAndroidConnection = (
channelId: string,
): RemoveAndroidConnection => ({
type: ActionType.REMOVE_ANDROID_CONNECTION,
channelId,
});

export const resetAndroidConnections = (
connections: SDKSessions,
): ResetAndroidConnections => ({
type: ActionType.RESET_ANDROID_CONNECTIONS,
connections,
});

export const setConnected = (
channelId: string,
connected: boolean,
): SetConnected => ({
type: ActionType.SET_CONNECTED,
channelId,
connected,
});
14 changes: 14 additions & 0 deletions app/actions/sdk/state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ApprovedHosts, SDKSessions } from '../../core/SDKConnect/SDKConnect';
export interface WC2Metadata {
id: string;
url: string;
name: string;
icon: string;
}
export interface SDKState {
connections: SDKSessions;
approvedHosts: ApprovedHosts;
androidConnections: SDKSessions;
// Link to metadata of last created wallet connect session.
wc2Metadata?: WC2Metadata;
}
49 changes: 34 additions & 15 deletions app/components/Views/AccountConnect/AccountConnect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import SDKConnect from '../../../core/SDKConnect/SDKConnect';
import AppConstants from '../../../../app/core/AppConstants';
import { trackDappVisitedEvent } from '../../../util/metrics';
import { useMetrics } from '../../../components/hooks/useMetrics';
import { RootState } from '../../../../app/reducers';

const AccountConnect = (props: AccountConnectProps) => {
const Engine = UntypedEngine as any;
Expand Down Expand Up @@ -92,28 +93,46 @@ const AccountConnect = (props: AccountConnectProps) => {
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 accountsLength = useSelector(selectAccountsLength);

const faviconSource = useFavicon(origin);
const [hostname, setHostname] = useState<string>(origin);
const urlWithProtocol = prefixUrlWithProtocol(hostname);
const sdkConnection = SDKConnect.getInstance().getConnection({ channelId });
// Last wallet connect session metadata
const wc2Metadata = useSelector((state: RootState) => state.sdk.wc2Metadata);

const dappIconUrl = sdkConnection.originatorInfo?.icon;
const faviconSource = useFavicon(origin);

const actualIcon = useMemo(
() => (dappIconUrl ? { uri: dappIconUrl } : faviconSource),
[dappIconUrl, faviconSource],
);

const secureIcon = useMemo(
() =>
(getUrlObj(origin) as URLParse<string>).protocol === 'https:'
(getUrlObj(hostname) as URLParse<string>).protocol === 'https:'
? IconName.Lock
: IconName.LockSlash,
[origin],
[hostname],
);

const accountsLength = useSelector(selectAccountsLength);
const loadHostname = useCallback(async () => {
if (sdkConnection) {
const _hostname = (
sdkConnection?.originatorInfo?.url ?? metadataOrigin
).replace(AppConstants.MM_SDK.SDK_REMOTE_ORIGIN, '');
return _hostname;
}

return wc2Metadata?.url ?? channelId;
}, [channelId, metadataOrigin, sdkConnection, wc2Metadata]);

// Retrieve hostname info based on channelId
useEffect(() => {
loadHostname().then(setHostname);
}, [hostname, setHostname, loadHostname]);

// Refreshes selected addresses based on the addition and removal of accounts.
useEffect(() => {
Expand Down Expand Up @@ -350,7 +369,7 @@ const AccountConnect = (props: AccountConnectProps) => {
onUserAction={setUserIntent}
defaultSelectedAccount={defaultSelectedAccount}
isLoading={isLoading}
favicon={faviconSource}
favicon={actualIcon}
secureIcon={secureIcon}
urlWithProtocol={urlWithProtocol}
/>
Expand All @@ -362,11 +381,11 @@ const AccountConnect = (props: AccountConnectProps) => {
isLoading,
setScreen,
setSelectedAddresses,
faviconSource,
actualIcon,
secureIcon,
sdkConnection,
urlWithProtocol,
setUserIntent,
sdkConnection,
]);

const renderSingleConnectSelectorScreen = useCallback(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import React, { useMemo, useRef } from 'react';

// External dependencies
import { ThemeColors } from '@metamask/design-tokens/dist/js/themes/types';
import { ThemeTypography } from '@metamask/design-tokens/dist/js/typography';
import type { ThemeColors } from '@metamask/design-tokens/dist/types/js/themes/types';
import type { ThemeTypography } from '@metamask/design-tokens/dist/types/js/typography';
import { useNavigation } from '@react-navigation/native';
import { StyleSheet, View } from 'react-native';
import { EdgeInsets, useSafeAreaInsets } from 'react-native-safe-area-context';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ThemeColors } from '@metamask/design-tokens/dist/js/themes/types';
import { ThemeTypography } from '@metamask/design-tokens/dist/js/typography';
import type { ThemeColors } from '@metamask/design-tokens/dist/types/js/themes/types';
import type { ThemeTypography } from '@metamask/design-tokens/dist/types/js/typography';
import React, { useEffect, useState } from 'react';
import { StyleSheet, TextStyle, View } from 'react-native';
import { EdgeInsets, useSafeAreaInsets } from 'react-native-safe-area-context';
Expand Down
11 changes: 5 additions & 6 deletions app/components/Views/SDKSessionsManager/SDKSessionItem.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ThemeColors } from '@metamask/design-tokens/dist/js/themes/types';
import { ThemeTypography } from '@metamask/design-tokens/dist/js/typography';
import type { ThemeColors } from '@metamask/design-tokens/dist/types/js/themes/types';
import type { ThemeTypography } from '@metamask/design-tokens/dist/types/js/typography';
import { useNavigation } from '@react-navigation/native';
import React, { useCallback, useEffect, useState } from 'react';
import { StyleSheet, TextStyle, View } from 'react-native';
Expand Down Expand Up @@ -30,9 +30,9 @@ interface SDKSessionViewProps {
connection: {
id: ConnectionProps['id'];
originatorInfo?: ConnectionProps['originatorInfo'];
connected?: ConnectionProps['connected'];
};
trigger?: number; // used to force refresh fetching permitted accounts
connected?: boolean;
}

const createStyles = (
Expand Down Expand Up @@ -85,7 +85,6 @@ const createStyles = (

export const SDKSessionItem = ({
connection,
connected = false,
trigger,
}: SDKSessionViewProps) => {
const safeAreaInsets = useSafeAreaInsets();
Expand All @@ -99,7 +98,7 @@ export const SDKSessionItem = ({
>([]);

DevLogger.log(
`Rendering SDKSessionItem connected=${connected} ${connection.id}`,
`Rendering SDKSessionItem connected=${connection.connected} ${connection.id}`,
);
useEffect(() => {
let _sessionName = connection.id;
Expand Down Expand Up @@ -154,7 +153,7 @@ export const SDKSessionItem = ({
<BadgeWrapper
style={styles.selfCenter}
badgeElement={
connected ? (
connection.connected ? (
<Badge
variant={BadgeVariant.Status}
state={BadgeStatusState.Active}
Expand Down
Loading

0 comments on commit 8299ec9

Please sign in to comment.