diff --git a/packages/legacy/core/App/components/network/NetInfo.tsx b/packages/legacy/core/App/components/network/NetInfo.tsx index 0babf5342..f6757d215 100644 --- a/packages/legacy/core/App/components/network/NetInfo.tsx +++ b/packages/legacy/core/App/components/network/NetInfo.tsx @@ -1,74 +1,36 @@ -import * as React from 'react' -import { useEffect, useState } from 'react' +import { useEffect, useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Toast from 'react-native-toast-message' import { useNetwork } from '../../contexts/network' import { ToastType } from '../../components/toast/BaseToast' -import { TOKENS, useServices } from '../../container-api' const NetInfo: React.FC = () => { - const { silentAssertConnectedNetwork, assertInternetReachable, assertMediatorReachable } = useNetwork() - const [{ disableMediatorCheck }] = useServices([TOKENS.CONFIG]) + const { assertInternetReachable } = useNetwork() const { t } = useTranslation() const [hasShown, setHasShown] = useState(false) - const isConnected = silentAssertConnectedNetwork() + const showNetworkWarning = useCallback(() => { + setHasShown(true) + Toast.show({ + type: ToastType.Error, + autoHide: true, + text1: t('NetInfo.NoInternetConnectionTitle'), + }) + }, [t]) useEffect(() => { - const _showNetworkWarning = () => { - setHasShown(true) - Toast.show({ - type: ToastType.Error, - autoHide: true, - text1: t('NetInfo.NoInternetConnectionTitle'), - }) + const internetReachable = assertInternetReachable() + if (internetReachable) { + Toast.hide() } - // Network is available, do further testing according to CFG.disableMediatorCheck - if (!disableMediatorCheck) { - // Network is available - if (isConnected) { - // Check mediator socket, also assert internet reachable - assertMediatorReachable().then((status) => { - if (status) { - Toast.hide() - return - } else { - // Network is available but cannot access nediator, display toast - _showNetworkWarning() - } - }) - return - } else if (!hasShown) { - _showNetworkWarning() - } - return - } else { - // Check internetReachable by connecting test beacon urls - assertInternetReachable().then((status) => { - if (status) { - Toast.hide() - return - } else if (null === status) { - // keep silent when the internet status not yet assert - return - /* - Toast.show({ - type: ToastType.Info, - autoHide: false, - text1: "Checking internet reachable", - }) - */ - } else if (!hasShown) { - _showNetworkWarning() - } - }) + + // Strict check for false, null means the network state is not yet known + if (internetReachable === false && !hasShown) { + showNetworkWarning() } }, [ - isConnected, - disableMediatorCheck, + showNetworkWarning, assertInternetReachable, - assertMediatorReachable, - t, hasShown ]) diff --git a/packages/legacy/core/App/container-impl.ts b/packages/legacy/core/App/container-impl.ts index eac2a0fd6..0b15e9c05 100644 --- a/packages/legacy/core/App/container-impl.ts +++ b/packages/legacy/core/App/container-impl.ts @@ -62,7 +62,6 @@ export const defaultConfig: Config = { showPreface: false, disableOnboardingSkip: false, disableContactsInSettings: false, - disableMediatorCheck: false, internetReachabilityUrls: ['https://clients3.google.com/generate_204'], whereToUseWalletUrl: 'https://example.com', showScanHelp: true, diff --git a/packages/legacy/core/App/contexts/network.tsx b/packages/legacy/core/App/contexts/network.tsx index 12fd36a60..feb81ebca 100644 --- a/packages/legacy/core/App/contexts/network.tsx +++ b/packages/legacy/core/App/contexts/network.tsx @@ -1,62 +1,67 @@ import { NetInfoStateType, useNetInfo } from '@react-native-community/netinfo' -import * as React from 'react' -import { createContext, useContext, useState } from 'react' +import { createContext, useContext, useState, useCallback, PropsWithChildren } from 'react' import NetInfoModal from '../components/modals/NetInfoModal' -import { hostnameFromURL, canConnectToHost } from '../utils/network' -import { Config } from 'react-native-config' export interface NetworkContext { - silentAssertConnectedNetwork: () => boolean + silentAssertConnectedNetwork: () => boolean | null assertNetworkConnected: () => boolean displayNetInfoModal: () => void hideNetInfoModal: () => void - assertInternetReachable: () => Promise - assertMediatorReachable: () => Promise + assertInternetReachable: () => boolean | null } export const NetworkContext = createContext(null as unknown as NetworkContext) -export const NetworkProvider: React.FC = ({ children }) => { - const netInfo = useNetInfo() + +// NOTE: @react-native-community/netinfo can be configured to use whichever reachability check desired +// eg. isInternetReachable can be set to check a specific URL (like your mediator). See the docs here for more info: +// https://github.com/react-native-netinfo/react-native-netinfo?tab=readme-ov-file#configure +export const NetworkProvider = ({ children }: PropsWithChildren) => { + const { isConnected, type, isInternetReachable } = useNetInfo() const [isNetInfoModalDisplayed, setIsNetInfoModalDisplayed] = useState(false) - const displayNetInfoModal = () => { + const displayNetInfoModal = useCallback(() => { setIsNetInfoModalDisplayed(true) - } + }, []) - const hideNetInfoModal = () => { + const hideNetInfoModal = useCallback(() => { setIsNetInfoModalDisplayed(false) - } + }, []) + + /** + * Returns null until the network state is known, then returns boolean + * Useful for cases where we do not want to take action until the network state is known + * + * @returns {boolean | null} - `true` if the network is connected, `false` if not connected, + * and `null` if the network status not yet known + */ + const silentAssertConnectedNetwork = useCallback((): boolean | null => { + return type === NetInfoStateType.unknown ? null : isConnected || [NetInfoStateType.wifi, NetInfoStateType.cellular].includes(type) + }, [isConnected, type]) - const silentAssertConnectedNetwork = () => { - return netInfo.isConnected || [NetInfoStateType.wifi, NetInfoStateType.cellular].includes(netInfo.type) - } - const assertNetworkConnected = () => { - const isConnected = silentAssertConnectedNetwork() - if (!isConnected) { + /** + * Strictly asserts that the network is connected. Will return false even if + * the network state is not yet known - in this case it will also display the + * NetInfoModal + * Useful for cases where we must be sure of connectivity before proceeding + * + * @returns {boolean} - `true` if the network is checked and connected, otherwise `false` + */ + const assertNetworkConnected = useCallback(() => { + const connectionConfirmed = silentAssertConnectedNetwork() === true + if (!connectionConfirmed) { displayNetInfoModal() } - return isConnected - } - - const assertInternetReachable = async (): Promise => { - return netInfo.isInternetReachable as boolean - } - const assertMediatorReachable = async (): Promise => { - const hostname = hostnameFromURL(Config.MEDIATOR_URL!) - - if (hostname === null || hostname.length === 0) { - return false - } + return connectionConfirmed + }, [silentAssertConnectedNetwork, displayNetInfoModal]) - const nodes = [{ host: hostname, port: 443 }] - const connections = await Promise.all(nodes.map((n: { host: string; port: number }) => canConnectToHost(n))) + const assertInternetReachable = useCallback((): boolean | null => { + return isInternetReachable + }, [isInternetReachable]) - return connections.includes(true) - } return ( = ({ children }) assertNetworkConnected, displayNetInfoModal, hideNetInfoModal, - assertInternetReachable, - assertMediatorReachable + assertInternetReachable }} > {children} diff --git a/packages/legacy/core/App/types/config.ts b/packages/legacy/core/App/types/config.ts index cbcc8cd34..e5a7b7cc7 100644 --- a/packages/legacy/core/App/types/config.ts +++ b/packages/legacy/core/App/types/config.ts @@ -39,7 +39,6 @@ export interface Config { contactDetailsOptions?: ContactDetailsOptionsParams credentialHideList?: string[] disableContactsInSettings?: boolean - disableMediatorCheck?: boolean internetReachabilityUrls: string[] attemptLockoutConfig?: AttemptLockoutConfig } diff --git a/packages/legacy/core/App/utils/network.tsx b/packages/legacy/core/App/utils/network.tsx index beaf059ff..cf9de511e 100644 --- a/packages/legacy/core/App/utils/network.tsx +++ b/packages/legacy/core/App/utils/network.tsx @@ -53,27 +53,3 @@ export const fetchLedgerNodes = (indyNamespace = 'sovrin'): Array<{ host: string return nodes } - -export const hostnameFromURL = (fullUrl: string): string | null => { - try { - // Start of the hostname after "//" - const startIndex = fullUrl.indexOf('//') + 2 - - // End of the hostname (before the next '/' or '?') - let endIndex = fullUrl.indexOf('/', startIndex) - - // If no '/', look for '?' - if (endIndex === -1) { - endIndex = fullUrl.indexOf('?', startIndex) - } - - // If no '/' or '?', hostname is till the end - if (endIndex === -1) { - endIndex = fullUrl.length - } - - return fullUrl.substring(startIndex, endIndex) - } catch (error) { - return null - } -} diff --git a/packages/legacy/core/__tests__/components/NetInfo.test.tsx b/packages/legacy/core/__tests__/components/NetInfo.test.tsx new file mode 100644 index 000000000..60bb7d337 --- /dev/null +++ b/packages/legacy/core/__tests__/components/NetInfo.test.tsx @@ -0,0 +1,61 @@ +import { render, waitFor } from '@testing-library/react-native' +import React from 'react' +import Toast from 'react-native-toast-message' + +import mockNetworkContext from '../contexts/network' +import NetInfo from '../../App/components/network/NetInfo' +import toastConfig from '../../App/components/toast/ToastConfig' +import { BasicAppContext } from '../helpers/app' + +// Bifold's top offset for toasts +const topOffset = 15 + +describe('NetInfo Component', () => { + it('should not show toast when internet is reachable', async () => { + mockNetworkContext.assertInternetReachable.mockReturnValue(true) + + const { queryByText } = render( + + + + + ) + + await waitFor(async () => { + const toast = await queryByText('NetInfo.NoInternetConnectionTitle', { exact: false }) + expect(toast).toBeNull() + }) + }) + + it('should show toast when internet is not reachable', async () => { + mockNetworkContext.assertInternetReachable.mockReturnValue(false) + + const { queryByText } = render( + + + + + ) + + await waitFor(async () => { + const toast = await queryByText('NetInfo.NoInternetConnectionTitle', { exact: false }) + expect(toast).toBeTruthy() + }) + }) + + it('should not show toast when internet reachability is unknown', async () => { + mockNetworkContext.assertInternetReachable.mockReturnValue(null) + + const { queryByText } = render( + + + + + ) + + await waitFor(async () => { + const toast = await queryByText('NetInfo.NoInternetConnectionTitle', { exact: false }) + expect(toast).toBeNull() + }) + }) +}) \ No newline at end of file diff --git a/packages/legacy/core/__tests__/contexts/network.ts b/packages/legacy/core/__tests__/contexts/network.ts index d56502723..8407206f9 100644 --- a/packages/legacy/core/__tests__/contexts/network.ts +++ b/packages/legacy/core/__tests__/contexts/network.ts @@ -3,9 +3,7 @@ const networkContext = { silentAssertConnectedNetwork: jest.fn(), displayNetInfoModal: jest.fn(), hideNetInfoModal: jest.fn(), - // assertNetworkReachable: jest.fn(), assertInternetReachable: jest.fn(), - assertMediatorReachable: jest.fn(), } export default networkContext