Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: permissions system within sdk redux store #8785

Merged
merged 11 commits into from
Mar 6, 2024
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
Loading