diff --git a/app/actions/infuraAvailability/index.js b/app/actions/infuraAvailability/index.js new file mode 100644 index 00000000000..7fd1b8ec23d --- /dev/null +++ b/app/actions/infuraAvailability/index.js @@ -0,0 +1,13 @@ +import { INFURA_AVAILABILITY_BLOCKED, INFURA_AVAILABILITY_NOT_BLOCKED } from '../../reducers/infuraAvailability'; + +export function setInfuraAvailabilityBlocked() { + return { + type: INFURA_AVAILABILITY_BLOCKED + }; +} + +export function setInfuraAvailabilityNotBlocked() { + return { + type: INFURA_AVAILABILITY_NOT_BLOCKED + }; +} diff --git a/app/components/Nav/Main/index.js b/app/components/Nav/Main/index.js index 86af6b065cc..214f63f3b32 100644 --- a/app/components/Nav/Main/index.js +++ b/app/components/Nav/Main/index.js @@ -63,6 +63,7 @@ import SwapsLiveness from '../../UI/Swaps/SwapsLiveness'; import Analytics from '../../../core/Analytics'; import { ANALYTICS_EVENT_OPTS } from '../../../util/analytics'; import BigNumber from 'bignumber.js'; +import { setInfuraAvailabilityBlocked, setInfuraAvailabilityNotBlocked } from '../../../actions/infuraAvailability'; const styles = StyleSheet.create({ flex: { @@ -79,9 +80,8 @@ const styles = StyleSheet.create({ margin: 0 } }); - const Main = props => { - const [connected, setConnected] = useState(false); + const [connected, setConnected] = useState(true); const [forceReload, setForceReload] = useState(false); const [signMessage, setSignMessage] = useState(false); const [signMessageParams, setSignMessageParams] = useState({ data: '' }); @@ -138,14 +138,37 @@ const Main = props => { const connectionChangeHandler = useCallback( state => { // Show the modal once the status changes to offline - if (connected && !state.isConnected) { + if (connected && state && !state.isConnected) { props.navigation.navigate('OfflineModeView'); + setConnected(state.isConnected); } - setConnected(state.isConnected); }, [connected, props.navigation] ); + const checkInfuraAvailability = useCallback(async () => { + if (props.providerType !== 'rpc') { + try { + const { TransactionController } = Engine.context; + await util.query(TransactionController.ethQuery, 'blockNumber', []); + props.setInfuraAvailabilityNotBlocked(); + } catch (e) { + if (e.message === AppConstants.ERRORS.INFURA_BLOCKED_MESSAGE) { + props.navigation.navigate('OfflineModeView'); + props.setInfuraAvailabilityBlocked(); + } + } + } else { + props.setInfuraAvailabilityNotBlocked(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + props.navigation, + props.providerType, + props.setInfuraAvailabilityBlocked, + props.setInfuraAvailabilityNotBlocked + ]); + const initializeWalletConnect = () => { WalletConnect.hub.on('walletconnectSessionRequest', peerInfo => { setWalletConnectRequest(true); @@ -592,7 +615,7 @@ const Main = props => { removeNotificationById: props.removeNotificationById }); pollForIncomingTransactions(); - + checkInfuraAvailability(); removeConnectionStatusListener.current = NetInfo.addEventListener(connectionChangeHandler); }, 1000); @@ -709,7 +732,19 @@ Main.propTypes = { /** * Selected address */ - selectedAddress: PropTypes.string + selectedAddress: PropTypes.string, + /** + * Network provider type + */ + providerType: PropTypes.string, + /** + * Dispatch infura availability blocked + */ + setInfuraAvailabilityBlocked: PropTypes.func, + /** + * Dispatch infura availability not blocked + */ + setInfuraAvailabilityNotBlocked: PropTypes.func }; const mapStateToProps = state => ({ @@ -720,7 +755,8 @@ const mapStateToProps = state => ({ isPaymentRequest: state.transaction.paymentRequest, dappTransactionModalVisible: state.modals.dappTransactionModalVisible, approveModalVisible: state.modals.approveModalVisible, - swapsTransactions: state.engine.backgroundState.TransactionController.swapsTransactions || {} + swapsTransactions: state.engine.backgroundState.TransactionController.swapsTransactions || {}, + providerType: state.engine.backgroundState.NetworkController.provider.type }); const mapDispatchToProps = dispatch => ({ @@ -731,7 +767,9 @@ const mapDispatchToProps = dispatch => ({ hideCurrentNotification: () => dispatch(hideCurrentNotification()), removeNotificationById: id => dispatch(removeNotificationById(id)), toggleDappTransactionModal: (show = null) => dispatch(toggleDappTransactionModal(show)), - toggleApproveModal: show => dispatch(toggleApproveModal(show)) + toggleApproveModal: show => dispatch(toggleApproveModal(show)), + setInfuraAvailabilityBlocked: () => dispatch(setInfuraAvailabilityBlocked()), + setInfuraAvailabilityNotBlocked: () => dispatch(setInfuraAvailabilityNotBlocked()) }); export default connect( diff --git a/app/components/Views/OfflineMode/__snapshots__/index.test.js.snap b/app/components/Views/OfflineMode/__snapshots__/index.test.js.snap index 1542afc8c51..0305a7201aa 100644 --- a/app/components/Views/OfflineMode/__snapshots__/index.test.js.snap +++ b/app/components/Views/OfflineMode/__snapshots__/index.test.js.snap @@ -1,82 +1,109 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`OfflineMode should render correctly 1`] = ` - - + - You're offline - Check your internet connection and try again + Unable to connect to the blockchain host. - + + Try again - - + + `; diff --git a/app/components/Views/OfflineMode/index.js b/app/components/Views/OfflineMode/index.js index 6b9596e240e..b3d747bbcd7 100644 --- a/app/components/Views/OfflineMode/index.js +++ b/app/components/Views/OfflineMode/index.js @@ -1,100 +1,112 @@ 'use strict'; -import React, { PureComponent } from 'react'; -import { SafeAreaView, Image, Text, View, StyleSheet } from 'react-native'; +import React from 'react'; +import { SafeAreaView, Image, View, StyleSheet } from 'react-native'; +import Text from '../../Base/Text'; import NetInfo from '@react-native-community/netinfo'; -import { colors } from '../../../styles/common'; +import { baseStyles, colors, fontStyles } from '../../../styles/common'; import PropTypes from 'prop-types'; import { strings } from '../../../../locales/i18n'; import StyledButton from '../../UI/StyledButton'; import { getOfflineModalNavbar } from '../../UI/Navbar'; import AndroidBackHandler from '../AndroidBackHandler'; import Device from '../../../util/Device'; +import AppConstants from '../../../core/AppConstants'; +import { connect } from 'react-redux'; +import { getInfuraBlockedSelector } from '../../../reducers/infuraAvailability'; const styles = StyleSheet.create({ container: { - flex: 1, - backgroundColor: colors.white - }, - innerView: { flex: 1 }, frame: { width: 200, height: 200, alignSelf: 'center', - justifyContent: 'center', - marginTop: 80, - marginBottom: 10 + marginTop: 60 }, content: { - width: 300, - height: 125, - alignSelf: 'center', - justifyContent: 'center' - }, - text: { flex: 1, - fontSize: 12, - color: colors.fontPrimary, - textAlign: 'center', - justifyContent: 'center' + marginHorizontal: 18, + justifyContent: 'center', + marginVertical: 30 }, title: { - fontSize: 17, + fontSize: 18, color: colors.fontPrimary, - textAlign: 'center', - justifyContent: 'center', - marginBottom: 10 + marginBottom: 10, + ...fontStyles.bold }, - button: { - alignSelf: 'center', - width: 150, - height: 50 + text: { + fontSize: 12, + color: colors.fontPrimary, + ...fontStyles.normal + }, + buttonContainer: { + marginHorizontal: 18 } }); const astronautImage = require('../../../images/astronaut.png'); // eslint-disable-line import/no-commonjs -/** - * View that wraps the Offline mode screen - */ -export default class OfflineMode extends PureComponent { - static navigationOptions = ({ navigation }) => getOfflineModalNavbar(navigation); +const OfflineMode = ({ navigation, infuraBlocked }) => { + const netinfo = NetInfo.useNetInfo(); - static propTypes = { - /** - * Object that represents the navigator - */ - navigation: PropTypes.object + const tryAgain = () => { + if (netinfo?.isConnected) { + navigation.pop(); + } }; - goBack = () => { - this.props.navigation.goBack(); + const learnMore = () => { + navigation.navigate('SimpleWebview', { url: AppConstants.URLS.CONNECTIVITY_ISSUES }); }; - tryAgain = () => { - NetInfo.isConnected.fetch().then(isConnected => { - if (isConnected) { - this.props.navigation.pop(); - } - }); + const action = () => { + if (infuraBlocked) { + learnMore(); + } else { + tryAgain(); + } }; - render() { - return ( - - - - - {strings('offline_mode.title')} - {strings('offline_mode.text')} - - {strings('offline_mode.try_again')} - - - - {Device.isAndroid() && } + return ( + + + + + + {strings('offline_mode.title')} + + + {strings(`offline_mode.text`)} + + + + + {strings(`offline_mode.${infuraBlocked ? 'learn_more' : 'try_again'}`)} + + - ); - } -} + {Device.isAndroid() && } + + ); +}; + +OfflineMode.navigationOptions = ({ navigation }) => getOfflineModalNavbar(navigation); + +OfflineMode.propTypes = { + /** + * Object that represents the navigator + */ + navigation: PropTypes.object, + /** + * Whether infura was blocked or not + */ + infuraBlocked: PropTypes.bool +}; + +const mapStateToProps = state => ({ + infuraBlocked: getInfuraBlockedSelector(state) +}); + +export default connect(mapStateToProps)(OfflineMode); diff --git a/app/components/Views/OfflineMode/index.test.js b/app/components/Views/OfflineMode/index.test.js index 53b1297aa0f..ab24f9fbf8c 100644 --- a/app/components/Views/OfflineMode/index.test.js +++ b/app/components/Views/OfflineMode/index.test.js @@ -1,10 +1,20 @@ import React from 'react'; import { shallow } from 'enzyme'; +import configureMockStore from 'redux-mock-store'; import OfflineMode from './'; +const mockStore = configureMockStore(); + describe('OfflineMode', () => { it('should render correctly', () => { - const wrapper = shallow( false }} />); - expect(wrapper).toMatchSnapshot(); + const initialState = { + infuraAvailability: { + isBlocked: false + } + }; + const wrapper = shallow( false }} />, { + context: { store: mockStore(initialState) } + }); + expect(wrapper.dive()).toMatchSnapshot(); }); }); diff --git a/app/core/AppConstants.js b/app/core/AppConstants.js index 48c3d4ca9ff..8e7a51578fd 100644 --- a/app/core/AppConstants.js +++ b/app/core/AppConstants.js @@ -66,6 +66,10 @@ export default { MAX_SAFE_CHAIN_ID: 4503599627370476, URLS: { TERMS_AND_CONDITIONS: 'https://consensys.net/terms-of-use/', - PRIVACY_POLICY: 'https://consensys.net/privacy-policy/' + PRIVACY_POLICY: 'https://consensys.net/privacy-policy/', + CONNECTIVITY_ISSUES: 'https://metamask.zendesk.com/hc/en-us/articles/360059386712' + }, + ERRORS: { + INFURA_BLOCKED_MESSAGE: 'EthQuery - RPC Error - This service is not available in your country' } }; diff --git a/app/reducers/index.js b/app/reducers/index.js index e8085a142b5..66484349551 100644 --- a/app/reducers/index.js +++ b/app/reducers/index.js @@ -13,6 +13,7 @@ import onboardingReducer from './onboarding'; import fiatOrders from './fiatOrders'; import swapsReducer from './swaps'; import notificationReducer from './notification'; +import infuraAvailabilityReducer from './infuraAvailability'; import { combineReducers } from 'redux'; const rootReducer = combineReducers({ @@ -30,7 +31,8 @@ const rootReducer = combineReducers({ onboarding: onboardingReducer, notification: notificationReducer, swaps: swapsReducer, - fiatOrders + fiatOrders, + infuraAvailability: infuraAvailabilityReducer }); export default rootReducer; diff --git a/app/reducers/infuraAvailability/index.js b/app/reducers/infuraAvailability/index.js new file mode 100644 index 00000000000..4079763a530 --- /dev/null +++ b/app/reducers/infuraAvailability/index.js @@ -0,0 +1,26 @@ +const initialState = { + isBlocked: false +}; + +export const INFURA_AVAILABILITY_BLOCKED = 'INFURA_AVAILABILITY_BLOCKED'; +export const INFURA_AVAILABILITY_NOT_BLOCKED = 'INFURA_AVAILABILITY_NOT_BLOCKED'; + +export const getInfuraBlockedSelector = state => state.infuraAvailability?.isBlocked; + +const infuraAvailabilityReducer = (state = initialState, action) => { + switch (action.type) { + case INFURA_AVAILABILITY_BLOCKED: + return { + ...state, + isBlocked: true + }; + case INFURA_AVAILABILITY_NOT_BLOCKED: + return { + ...state, + isBlocked: false + }; + default: + return state; + } +}; +export default infuraAvailabilityReducer; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 6e70a3a7975..5b829048f67 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -634,7 +634,7 @@ SPEC CHECKSUMS: Branch: 49c609eb0ac0130b8491d0923a9298714731bd0d CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 CocoaLibEvent: 2fab71b8bd46dd33ddb959f7928ec5909f838e3f - DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2 + DoubleConversion: cde416483dac037923206447da6e1454df403714 FBLazyVector: 3bb422f41b18121b71783a905c10e58606f7dc3e FBReactNativeSpec: f2c97f2529dd79c083355182cc158c9f98f4bd6e Flipper: be611d4b742d8c87fbae2ca5f44603a02539e365 @@ -645,7 +645,7 @@ SPEC CHECKSUMS: Flipper-RSocket: a3acb8812d6adf127deb0a5edae2793b97e6b641 FlipperKit: ab353d41aea8aae2ea6daaf813e67496642f3d7d Folly: b73c3869541e86821df3c387eb0af5f65addfab4 - glog: 1f3da668190260b06b429bb211bfbee5cd790c28 + glog: 40a13f7840415b9a77023fbcae0f1e6f43192af3 lottie-ios: a50d5c0160425cd4b01b852bb9578963e6d92d31 lottie-react-native: 7ca15c46249b61e3f9ffcf114cb4123e907a2156 OpenSSL-Universal: ff34003318d5e1163e9529b08470708e389ffcdd diff --git a/locales/languages/en.json b/locales/languages/en.json index 5f5168fa389..f2807a857bf 100644 --- a/locales/languages/en.json +++ b/locales/languages/en.json @@ -1248,8 +1248,9 @@ }, "offline_mode": { "title": "You're offline", - "text": "Check your internet connection and try again", - "try_again": "Try again" + "text": "Unable to connect to the blockchain host.", + "try_again": "Try again", + "learn_more": "Learn more" }, "walletconnect_return_modal": { "title": "You're all set!", diff --git a/locales/languages/es-OLD.json b/locales/languages/es-OLD.json index 82cff406943..f4d0d25e9ae 100644 --- a/locales/languages/es-OLD.json +++ b/locales/languages/es-OLD.json @@ -1088,7 +1088,7 @@ }, "offline_mode": { "title": "Sin Conexión", - "text": "Revisa tu conexión de internet e intenta nuevamente", + "text": "No se puede conectar al host de blockchain.", "try_again": "Intentar de nuevo" }, "walletconnect_return_modal": { diff --git a/locales/languages/es.json b/locales/languages/es.json index c8b74a5f16d..d9e9c954ae1 100644 --- a/locales/languages/es.json +++ b/locales/languages/es.json @@ -1292,7 +1292,7 @@ }, "offline_mode": { "title": "Está desconectado", - "text": "Compruebe la conexión a Internet y vuelva a intentarlo", + "text": "No se puede conectar al host de blockchain.", "try_again": "Vuelva a intentarlo" }, "payment_channel_request": { diff --git a/locales/languages/hi-in.json b/locales/languages/hi-in.json index f567a98a491..f1ea43747bd 100644 --- a/locales/languages/hi-in.json +++ b/locales/languages/hi-in.json @@ -1288,7 +1288,7 @@ }, "offline_mode": { "title": "आप ऑफ़लाइन हैं", - "text": "अपना इंटरनेट कनेक्शन जाँचें और पुनः प्रयास करें", + "text": "ब्लॉकचैन होस्ट से कनेक्ट करने में असमर्थ।", "try_again": "पुनः प्रयास करें" }, "payment_channel_request": { diff --git a/locales/languages/id-id.json b/locales/languages/id-id.json index 317a524de5b..bd98a57fc64 100644 --- a/locales/languages/id-id.json +++ b/locales/languages/id-id.json @@ -1288,7 +1288,7 @@ }, "offline_mode": { "title": "Anda sedang offline", - "text": "Periksa koneksi internet Anda dan coba lagi", + "text": "Tidak dapat terhubung ke host blockchain.", "try_again": "Coba lagi" }, "payment_channel_request": { diff --git a/locales/languages/ja-jp.json b/locales/languages/ja-jp.json index 268607c8df7..8bade3dfb1f 100644 --- a/locales/languages/ja-jp.json +++ b/locales/languages/ja-jp.json @@ -1288,7 +1288,7 @@ }, "offline_mode": { "title": "オフラインです", - "text": "インターネット接続を確認して、もう一度実行してください", + "text": "ブロックチェーンホストに接続できません。", "try_again": "再試行" }, "payment_channel_request": { diff --git a/locales/languages/ko-kr.json b/locales/languages/ko-kr.json index 71a333aa5cf..4ef4f20c992 100644 --- a/locales/languages/ko-kr.json +++ b/locales/languages/ko-kr.json @@ -1288,7 +1288,7 @@ }, "offline_mode": { "title": "오프라인 상태입니다.", - "text": "인터넷 연결을 확인하고 다시 시도하세요.", + "text": "블록 체인 호스트에 연결할 수 없습니다.", "try_again": "다시 시도" }, "payment_channel_request": { diff --git a/locales/languages/pt-br.json b/locales/languages/pt-br.json index 999173788c9..f47d2e8f816 100644 --- a/locales/languages/pt-br.json +++ b/locales/languages/pt-br.json @@ -1288,7 +1288,7 @@ }, "offline_mode": { "title": "Você está offline", - "text": "Verifique a conexão com a internet e tente novamente", + "text": "Não foi possível conectar ao host blockchain.", "try_again": "Tente novamente" }, "payment_channel_request": { diff --git a/locales/languages/ru-ru.json b/locales/languages/ru-ru.json index 06c33e2121c..15144420bfb 100644 --- a/locales/languages/ru-ru.json +++ b/locales/languages/ru-ru.json @@ -1288,7 +1288,7 @@ }, "offline_mode": { "title": "Вы не в сети", - "text": "Проверьте подключение к интернету и попробуйте еще раз", + "text": "Невозможно подключиться к хосту блокчейна.", "try_again": "Попробуйте еще раз" }, "payment_channel_request": { diff --git a/locales/languages/vi-vn.json b/locales/languages/vi-vn.json index 4a45d238326..22bf24e6d65 100644 --- a/locales/languages/vi-vn.json +++ b/locales/languages/vi-vn.json @@ -1288,7 +1288,7 @@ }, "offline_mode": { "title": "Bạn đang không kết nối mạng", - "text": "Hãy kiểm tra kết nối internet của bạn và thử lại", + "text": "Không thể kết nối với máy chủ lưu trữ chuỗi khối.", "try_again": "Thử lại" }, "payment_channel_request": { diff --git a/package.json b/package.json index 2a2044d6ce9..f4f4102bf76 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "@react-native-community/clipboard": "^1.2.2", "@react-native-community/cookies": "^4.0.1", "@react-native-community/masked-view": "^0.1.10", - "@react-native-community/netinfo": "4.1.5", + "@react-native-community/netinfo": "6.0.0", "@react-native-community/viewpager": "^3.3.0", "@rnhooks/keyboard": "^0.0.3", "@sentry/integrations": "5.13.0", diff --git a/yarn.lock b/yarn.lock index 560079f002f..a3edfa1fd3d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1840,10 +1840,10 @@ resolved "https://registry.yarnpkg.com/@react-native-community/masked-view/-/masked-view-0.1.10.tgz#5dda643e19e587793bc2034dd9bf7398ad43d401" integrity sha512-rk4sWFsmtOw8oyx8SD3KSvawwaK7gRBSEIy2TAwURyGt+3TizssXP1r8nx3zY+R7v2vYYHXZ+k2/GULAT/bcaQ== -"@react-native-community/netinfo@4.1.5": - version "4.1.5" - resolved "https://registry.yarnpkg.com/@react-native-community/netinfo/-/netinfo-4.1.5.tgz#4bb44842db6a1a18f00a0f061b0e3dcc638f67dd" - integrity sha512-lagdZr9UiVAccNXYfTEj+aUcPCx9ykbMe9puffeIyF3JsRuMmlu3BjHYx1klUHX7wNRmFNC8qVP0puxUt1sZ0A== +"@react-native-community/netinfo@6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@react-native-community/netinfo/-/netinfo-6.0.0.tgz#2a4d7190b508dd0c2293656c9c1aa068f6f60a71" + integrity sha512-Z9M8VGcF2IZVOo2x+oUStvpCW/8HjIRi4+iQCu5n+PhC7OqCQX58KYAzdBr///alIfRXiu6oMb+lK+rXQH1FvQ== "@react-native-community/viewpager@^2.0.1": version "2.0.2"