Skip to content

Commit

Permalink
chore: merge latest main
Browse files Browse the repository at this point in the history
  • Loading branch information
jclancy93 committed Nov 1, 2024
2 parents 409fd61 + dceb552 commit 965ea18
Show file tree
Hide file tree
Showing 238 changed files with 4,670 additions and 3,108 deletions.
2 changes: 1 addition & 1 deletion .android.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export FCM_CONFIG_PROJECT_ID=
export FCM_CONFIG_STORAGE_BUCKET=
export FCM_CONFIG_MESSAGING_SENDER_ID=
export FCM_CONFIG_APP_ID=
export GOOGLE_SERVICES_B64=
export GOOGLE_SERVICES_B64_ANDROID=
#Notifications Feature Announcements
export FEATURES_ANNOUNCEMENTS_ACCESS_TOKEN=
export FEATURES_ANNOUNCEMENTS_SPACE_ID=
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ android/app/_build*

# if we ever want to add google services
android/app/google-services.json
ios/GoogleService-Info.plist

# node.js
node_modules/
Expand Down
2 changes: 1 addition & 1 deletion .ios.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ FCM_CONFIG_PROJECT_ID=
FCM_CONFIG_STORAGE_BUCKET=
FCM_CONFIG_MESSAGING_SENDER_ID=
FCM_CONFIG_APP_ID=
GOOGLE_SERVICES_B64=
GOOGLE_SERVICES_B64_IOS=
#Notifications Feature Announcements
FEATURES_ANNOUNCEMENTS_ACCESS_TOKEN=
FEATURES_ANNOUNCEMENTS_SPACE_ID=
7 changes: 4 additions & 3 deletions .js.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -68,18 +68,19 @@ export SEGMENT_FLUSH_INTERVAL="1"
export SEGMENT_FLUSH_EVENT_LIMIT="1"

# URL of security alerts API used to validate dApp requests.
export SECURITY_ALERTS_API_URL="http://localhost:3000"
export SECURITY_ALERTS_API_URL="https://security-alerts.api.cx.metamask.io"

# Temporary mechanism to enable security alerts API prior to release.
export SECURITY_ALERTS_API_ENABLED="true"
export MM_SECURITY_ALERTS_API_ENABLED="true"
# Firebase
export FCM_CONFIG_API_KEY=""
export FCM_CONFIG_AUTH_DOMAIN=""
export FCM_CONFIG_PROJECT_ID=""
export FCM_CONFIG_STORAGE_BUCKET=""
export FCM_CONFIG_MESSAGING_SENDER_ID=""
export FCM_CONFIG_APP_ID=""
export GOOGLE_SERVICES_B64=""
export GOOGLE_SERVICES_B64_ANDROID=""
export GOOGLE_SERVICES_B64_IOS=""
#Notifications Feature Announcements
export FEATURES_ANNOUNCEMENTS_ACCESS_TOKEN=
export FEATURES_ANNOUNCEMENTS_SPACE_ID=
Expand Down
23 changes: 18 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,31 @@ cd metamask-mobile

**Firebase Messaging Setup**

Before running the app, keep in mind that MetaMask uses FCM (Firebase Cloud Message) to empower communications. Based on this, as an external contributor you would preferably need to provide your own FREE Firebase project config file with a matching client for package name `io.metamask`, and update your `google-services.json` file in the `android/app` directory as well your `.env` files (`.ios.env`, `.js.env`, `.android.env`), adding `GOOGLE_SERVICES_B64` variable depending on the environment you are running the app (ios/android).
Before running the app, keep in mind that MetaMask uses FCM (Firebase Cloud Message) to empower communications. Based on this, as an external contributor you would preferably need to provide your own FREE Firebase project config file with a matching client for package name `io.metamask`, and update your `google-services.json` file in the `android/app` or `GoogleService-Info.plist` file in the `ios` directory.

ATTENTION: In case you don't provide your own Firebase project config file, you can make use of a mock file at `android/app/google-services-example.json`, following the steps below from the root of the project:
**External Contributors**
In case you don't have FCM account, you can use `./android/app/google-services-example.json` for Android or `./ios/GoogleServices/GoogleService-Info-example.plist` for iOS and follow the steps below to populate the correct environment variables in the `.env` files (`.ios.env`, `.js.env`, `.android.env`), adding `GOOGLE_SERVICES_B64_ANDROID` or `GOOGLE_SERVICES_B64_IOS` variable depending on the environment you are running the app (ios/android).

**Internal Contributors**

We should access the Firebase project config file from 1Password.

The value you should provide to `GOOGLE_SERVICES_B64_ANDROID` or `GOOGLE_SERVICES_B64_IOS` is the base64 encoded version of your Firebase project config file, which can be generated as follows:

**For Android**
```bash
echo "export GOOGLE_SERVICES_B64=\"$(base64 -w0 -i ./android/app/google-services-example.json)\"" | tee -a .js.env .ios.env .android.env
export GOOGLE_SERVICES_B64_ANDROID="$(base64 -w0 -i ./android/app/google-services-example.json)" && echo "export GOOGLE_SERVICES_B64_ANDROID=\"$GOOGLE_SERVICES_B64_ANDROID\"" | tee -a .js.env .ios.env
```

You can make usage of a mock file at `android/app/google-services-example.json`, following the same steps above from the root of the project.
**For iOS**
```bash
export GOOGLE_SERVICES_B64_IOS="$(base64 -w0 -i ./ios/GoogleServices/GoogleService-Info-example.plist)" && echo "export GOOGLE_SERVICES_B64_IOS=\"$GOOGLE_SERVICES_B64_IOS\"" | tee -a .js.env .ios.env
```

In case of any doubt, please follow the instructions in the link below to get your Firebase project config file.
[!CAUTION]
> In case you don't provide your own Firebase project config file or run the steps above, you will face the error `No matching client found for package name 'io.metamask'`.
In case of any doubt, please follow the instructions in the link below to get your Firebase project config file.
[Firebase Project Quickstart](https://firebaseopensource.com/projects/firebase/quickstart-js/messaging/readme/#getting_started)

**Install dependencies**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const FORMATTED_VALUE_PRICE_TEST_ID = 'formatted-value-price-test-id';
export const FORMATTED_PERCENTAGE_TEST_ID = 'formatted-percentage-test-id';
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import AggregatedPercentage from './AggregatedPercentage';
import { mockTheme } from '../../../../util/theme';
import { useSelector } from 'react-redux';
import { selectCurrentCurrency } from '../../../../selectors/currencyRateController';
import {
FORMATTED_VALUE_PRICE_TEST_ID,
FORMATTED_PERCENTAGE_TEST_ID,
} from './AggregatedPercentage.constants';

jest.mock('react-redux', () => ({
...jest.requireActual('react-redux'),
Expand Down Expand Up @@ -65,4 +69,22 @@ describe('AggregatedPercentage', () => {
color: mockTheme.colors.error.default,
});
});

it('renders correctly with privacy mode on', () => {
const { getByTestId } = render(
<AggregatedPercentage
ethFiat={150}
tokenFiat={200}
tokenFiat1dAgo={300}
ethFiat1dAgo={200}
privacyMode
/>,
);

const formattedPercentage = getByTestId(FORMATTED_PERCENTAGE_TEST_ID);
const formattedValuePrice = getByTestId(FORMATTED_VALUE_PRICE_TEST_ID);

expect(formattedPercentage.props.children).toBe('••••••••••');
expect(formattedValuePrice.props.children).toBe('••••••••••');
});
});
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import React from 'react';
import Text, {
import {
TextColor,
TextVariant,
} from '../../../../component-library/components/Texts/Text';
import SensitiveText from '../../../../component-library/components/Texts/SensitiveText';
import { View } from 'react-native';
import { renderFiat } from '../../../../util/number';
import { useSelector } from 'react-redux';
import { selectCurrentCurrency } from '../../../../selectors/currencyRateController';
import styleSheet from './AggregatedPercentage.styles';
import { useStyles } from '../../../hooks';
import {
FORMATTED_VALUE_PRICE_TEST_ID,
FORMATTED_PERCENTAGE_TEST_ID,
} from './AggregatedPercentage.constants';

export interface AggregatedPercentageProps {
ethFiat: number;
Expand All @@ -25,11 +30,13 @@ const AggregatedPercentage = ({
tokenFiat,
tokenFiat1dAgo,
ethFiat1dAgo,
privacyMode = false,
}: {
ethFiat: number;
tokenFiat: number;
tokenFiat1dAgo: number;
ethFiat1dAgo: number;
privacyMode?: boolean;
}) => {
const { styles } = useStyles(styleSheet, {});

Expand All @@ -46,12 +53,16 @@ const AggregatedPercentage = ({

let percentageTextColor = TextColor.Default;

if (percentageChange === 0) {
percentageTextColor = TextColor.Default;
} else if (percentageChange > 0) {
percentageTextColor = TextColor.Success;
if (!privacyMode) {
if (percentageChange === 0) {
percentageTextColor = TextColor.Default;
} else if (percentageChange > 0) {
percentageTextColor = TextColor.Success;
} else {
percentageTextColor = TextColor.Error;
}
} else {
percentageTextColor = TextColor.Error;
percentageTextColor = TextColor.Alternative;
}

const formattedPercentage = isValidAmount(percentageChange)
Expand All @@ -70,12 +81,24 @@ const AggregatedPercentage = ({

return (
<View style={styles.wrapper}>
<Text color={percentageTextColor} variant={TextVariant.BodyMDMedium}>
<SensitiveText
isHidden={privacyMode}
length="10"
color={percentageTextColor}
variant={TextVariant.BodyMDMedium}
testID={FORMATTED_VALUE_PRICE_TEST_ID}
>
{formattedValuePrice}
</Text>
<Text color={percentageTextColor} variant={TextVariant.BodyMDMedium}>
</SensitiveText>
<SensitiveText
isHidden={privacyMode}
length="10"
color={percentageTextColor}
variant={TextVariant.BodyMDMedium}
testID={FORMATTED_PERCENTAGE_TEST_ID}
>
{formattedPercentage}
</Text>
</SensitiveText>
</View>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ exports[`AggregatedPercentage should render correctly 1`] = `
"lineHeight": 22,
}
}
testID="formatted-value-price-test-id"
>
+20 USD
</Text>
Expand All @@ -36,6 +37,7 @@ exports[`AggregatedPercentage should render correctly 1`] = `
"lineHeight": 22,
}
}
testID="formatted-percentage-test-id"
>
(+11.11%)
</Text>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// External dependencies.
import React from 'react';
import { TextProps } from '../Text/Text.types';

/**
Expand Down Expand Up @@ -42,5 +43,5 @@ export interface SensitiveTextProps extends TextProps {
/**
* The text content to be displayed or hidden.
*/
children: string;
children: React.ReactNode;
}
2 changes: 1 addition & 1 deletion app/components/Base/DetailsModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Ionicons from 'react-native-vector-icons/Ionicons';
import { fontStyles } from '../../styles/common';
import Text from './Text';
import { useTheme } from '../../util/theme';
import { TransactionDetailsModalSelectorsIDs } from '../../../e2e/selectors/Modals/TransactionDetailsModal.selectors';
import { TransactionDetailsModalSelectorsIDs } from '../../../e2e/selectors/Transactions/TransactionDetailsModal.selectors';

const createStyles = (colors) =>
StyleSheet.create({
Expand Down
37 changes: 25 additions & 12 deletions app/components/Base/RemoteImage/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from 'react';
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import {
Image,
Expand Down Expand Up @@ -67,23 +67,36 @@ const RemoteImage = (props) => {
const chainId = useSelector(selectChainId);
const ticker = useSelector(selectTicker);
const networkName = useSelector(selectNetworkName);
const resolvedIpfsUrl = useMemo(() => {
try {
const url = new URL(props.source.uri);
if (url.protocol !== 'ipfs:') return false;
const ipfsUrl = getFormattedIpfsUrl(ipfsGateway, props.source.uri, false);
return ipfsUrl;
} catch {
return false;
}
}, [props.source.uri, ipfsGateway]);
const [resolvedIpfsUrl, setResolvedIpfsUrl] = useState(false);

const uri = resolvedIpfsUrl || source.uri;
const uri =
resolvedIpfsUrl ||
(source.uri === undefined || source.uri?.startsWith('ipfs')
? ''
: source.uri);

const onError = ({ nativeEvent: { error } }) => setError(error);

const [dimensions, setDimensions] = useState(null);

useEffect(() => {
resolveIpfsUrl();
async function resolveIpfsUrl() {
try {
const url = new URL(props.source.uri);
if (url.protocol !== 'ipfs:') setResolvedIpfsUrl(false);
const ipfsUrl = await getFormattedIpfsUrl(
ipfsGateway,
props.source.uri,
false,
);
setResolvedIpfsUrl(ipfsUrl);
} catch (err) {
setResolvedIpfsUrl(false);
}
}
}, [props.source.uri, ipfsGateway]);

useEffect(() => {
const calculateImageDimensions = (imageWidth, imageHeight) => {
const deviceWidth = Dimensions.get('window').width;
Expand Down
18 changes: 15 additions & 3 deletions app/components/Base/RemoteImage/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React from 'react';
import { shallow } from 'enzyme';
import RemoteImage from './';
import { getFormattedIpfsUrl } from '@metamask/assets-controllers';
import { act, render } from '@testing-library/react-native';

jest.mock('react-redux', () => ({
...jest.requireActual('react-redux'),
Expand All @@ -11,6 +13,12 @@ jest.mock('react-redux', () => ({

jest.mock('../../../components/hooks/useIpfsGateway', () => jest.fn());

jest.mock('@metamask/assets-controllers', () => ({
getFormattedIpfsUrl: jest.fn(),
}));

const mockGetFormattedIpfsUrl = getFormattedIpfsUrl as jest.Mock;

describe('RemoteImage', () => {
it('should render svg correctly', () => {
const wrapper = shallow(
Expand All @@ -34,14 +42,18 @@ describe('RemoteImage', () => {
expect(wrapper).toMatchSnapshot();
});

it('should render ipfs sources', () => {
const wrapper = shallow(
it('should render ipfs sources', async () => {
const testIpfsUri = 'ipfs://QmeE94srcYV9WwJb1p42eM4zncdLUai2N9zmMxxukoEQ23';
mockGetFormattedIpfsUrl.mockResolvedValue(testIpfsUri);
const wrapper = render(
<RemoteImage
source={{
uri: 'ipfs://QmeE94srcYV9WwJb1p42eM4zncdLUai2N9zmMxxukoEQ23',
uri: testIpfsUri,
}}
/>,
);
// eslint-disable-next-line no-empty-function
await act(async () => {});
expect(wrapper).toMatchSnapshot();
});
});
5 changes: 5 additions & 0 deletions app/components/Nav/App/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import Toast, {
ToastContext,
} from '../../../component-library/components/Toast';
import AccountSelector from '../../../components/Views/AccountSelector';
import TokenSortBottomSheet from '../../../components/UI/Tokens/TokensBottomSheet/TokenSortBottomSheet.tsx';
import AccountConnect from '../../../components/Views/AccountConnect';
import AccountPermissions from '../../../components/Views/AccountPermissions';
import { AccountPermissionsScreens } from '../../../components/Views/AccountPermissions/AccountPermissions.types';
Expand Down Expand Up @@ -426,6 +427,10 @@ const RootModalFlow = () => (
name={Routes.SHEET.NETWORK_SELECTOR}
component={NetworkSelector}
/>
<Stack.Screen
name={Routes.SHEET.TOKEN_SORT}
component={TokenSortBottomSheet}
/>
<Stack.Screen
name={Routes.SHEET.BASIC_FUNCTIONALITY}
component={BasicFunctionalityModal}
Expand Down
6 changes: 2 additions & 4 deletions app/components/UI/AccountApproval/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import PropTypes from 'prop-types';
import React, { PureComponent } from 'react';
import {
InteractionManager,
Platform,
TouchableOpacity,
View,
} from 'react-native';
Expand All @@ -21,8 +20,7 @@ import { shuffle } from 'lodash';
import URL from 'url-parse';
import AppConstants from '../../../../app/core/AppConstants';
import { CommonSelectorsIDs } from '../../../../e2e/selectors/Common.selectors';
import { ConnectAccountModalSelectorsIDs } from '../../../../e2e/selectors/Modals/ConnectAccountModal.selectors';
import generateTestId from '../../../../wdio/utils/generateTestId';
import { ConnectAccountBottomSheetSelectorsIDs } from '../../../../e2e/selectors/Browser/ConnectAccountBottomSheet.selectors';
import { withMetricsAwareness } from '../../../components/hooks/useMetrics';
import Routes from '../../../constants/navigation/Routes';
import Engine from '../../../core/Engine';
Expand Down Expand Up @@ -302,7 +300,7 @@ class AccountApproval extends PureComponent {
return (
<View
style={styles.root}
{...generateTestId(Platform, ConnectAccountModalSelectorsIDs.CONTAINER)}
testID={ConnectAccountBottomSheetSelectorsIDs.CONTAINER}
>
<TransactionHeader currentPageInformation={currentPageInformation} />

Expand Down
Loading

0 comments on commit 965ea18

Please sign in to comment.