Skip to content

Commit

Permalink
Merge branch 'main' into salim/add-new-default-networks
Browse files Browse the repository at this point in the history
  • Loading branch information
salimtb authored Oct 22, 2024
2 parents d8c4a67 + 1738ea0 commit d124a74
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 42 deletions.
22 changes: 4 additions & 18 deletions app/components/Nav/Main/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ import { createStackNavigator } from '@react-navigation/stack';
import ReviewModal from '../../UI/ReviewModal';
import { useTheme } from '../../../util/theme';
import RootRPCMethodsUI from './RootRPCMethodsUI';
import { colors as importedColors } from '../../../styles/common';
import {
ToastContext,
ToastVariants,
Expand Down Expand Up @@ -82,6 +81,7 @@ import {
stopIncomingTransactionPolling,
} from '../../../util/transaction-controller';
import isNetworkUiRedesignEnabled from '../../../util/networks/isNetworkUiRedesignEnabled';
import { useConnectionHandler } from '../../../util/navigation/useConnectionHandler';

const Stack = createStackNavigator();

Expand All @@ -99,7 +99,6 @@ const createStyles = (colors) =>
});

const Main = (props) => {
const [connected, setConnected] = useState(true);
const [forceReload, setForceReload] = useState(false);
const [showRemindLaterModal, setShowRemindLaterModal] = useState(false);
const [skipCheckbox, setSkipCheckbox] = useState(false);
Expand All @@ -110,6 +109,8 @@ const Main = (props) => {
const locale = useRef(I18n.locale);
const removeConnectionStatusListener = useRef();

const { connectionChangeHandler } = useConnectionHandler(props.navigation);

const removeNotVisibleNotifications = props.removeNotVisibleNotifications;
useNotificationHandler(props.navigation);
useEnableAutomaticSecurityChecks();
Expand All @@ -133,21 +134,6 @@ const Main = (props) => {
}
}, [props.showIncomingTransactionsNetworks, props.chainId]);

const connectionChangeHandler = useCallback(
(state) => {
if (!state) return;
const { isConnected } = state;
// Show the modal once the status changes to offline
if (connected && isConnected === false) {
props.navigation.navigate('OfflineModeView');
}
if (connected !== isConnected && isConnected !== null) {
setConnected(isConnected);
}
},
[connected, setConnected, props.navigation],
);

const checkInfuraAvailability = useCallback(async () => {
if (props.providerType !== 'rpc') {
try {
Expand Down Expand Up @@ -336,7 +322,7 @@ const Main = (props) => {
removeConnectionStatusListener.current();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [connectionChangeHandler]);

const termsOfUse = useCallback(async () => {
if (props.navigation) {
Expand Down
137 changes: 137 additions & 0 deletions app/components/Nav/Main/useConnectionHandler.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { renderHook, act } from '@testing-library/react-hooks';
import { useConnectionHandler } from '../../../util/navigation/useConnectionHandler';
import { MetaMetricsEvents } from '../../../components/hooks/useMetrics';

const mockTrackEvent = jest.fn();

jest.mock('../../../components/hooks/useMetrics', () => ({
useMetrics: () => ({
trackEvent: mockTrackEvent,
}),
MetaMetricsEvents: {
CONNECTION_DROPPED: 'CONNECTION_DROPPED',
CONNECTION_RESTORED: 'CONNECTION_RESTORED',
},
}));

describe('useConnectionHandler', () => {
const mockNavigation = { navigate: jest.fn() };

beforeEach(() => {
jest.useFakeTimers();
jest.clearAllMocks();
});

afterEach(() => {
jest.useRealTimers();
});

it('should not navigate to OfflineModeView immediately when connection is lost', () => {
const { result } = renderHook(() => useConnectionHandler(mockNavigation));

act(() => {
result.current.connectionChangeHandler({ isConnected: false });
});

expect(mockNavigation.navigate).not.toHaveBeenCalled();
expect(mockTrackEvent).toHaveBeenCalledWith(
MetaMetricsEvents.CONNECTION_DROPPED,
);
});

it('should navigate to OfflineModeView after sustained offline period', () => {
const { result } = renderHook(() => useConnectionHandler(mockNavigation));

act(() => {
result.current.connectionChangeHandler({ isConnected: false });
});

jest.advanceTimersByTime(3000);

expect(mockNavigation.navigate).toHaveBeenCalledWith('OfflineModeView');
expect(mockTrackEvent).toHaveBeenCalledWith(
MetaMetricsEvents.CONNECTION_DROPPED,
);
});

it('should not navigate to OfflineModeView if connection is restored within timeout', () => {
const { result } = renderHook(() => useConnectionHandler(mockNavigation));

act(() => {
result.current.connectionChangeHandler({ isConnected: false });
});

expect(mockTrackEvent).toHaveBeenCalledWith(
MetaMetricsEvents.CONNECTION_DROPPED,
);

jest.advanceTimersByTime(1000);

act(() => {
result.current.connectionChangeHandler({ isConnected: true });
});

jest.advanceTimersByTime(2000);

expect(mockNavigation.navigate).not.toHaveBeenCalled();
expect(mockTrackEvent).toHaveBeenCalledTimes(2);
expect(mockTrackEvent).toHaveBeenNthCalledWith(
2,
MetaMetricsEvents.CONNECTION_RESTORED,
);
});

it('should do nothing if state is null', () => {
const { result } = renderHook(() => useConnectionHandler(mockNavigation));

act(() => {
result.current.connectionChangeHandler(null);
});

expect(mockNavigation.navigate).not.toHaveBeenCalled();
expect(mockTrackEvent).not.toHaveBeenCalled();
});

it('should not track events if connection state does not change', () => {
const { result } = renderHook(() => useConnectionHandler(mockNavigation));

act(() => {
result.current.connectionChangeHandler({ isConnected: true });
});

expect(mockTrackEvent).not.toHaveBeenCalled();

act(() => {
result.current.connectionChangeHandler({ isConnected: true });
});

expect(mockTrackEvent).not.toHaveBeenCalled();
});

it('should clear timeout if connection is restored before navigation', () => {
const { result } = renderHook(() => useConnectionHandler(mockNavigation));

act(() => {
result.current.connectionChangeHandler({ isConnected: false });
});

expect(mockTrackEvent).toHaveBeenCalledWith(
MetaMetricsEvents.CONNECTION_DROPPED,
);

jest.advanceTimersByTime(2000);

act(() => {
result.current.connectionChangeHandler({ isConnected: true });
});

expect(mockTrackEvent).toHaveBeenCalledWith(
MetaMetricsEvents.CONNECTION_RESTORED,
);

jest.advanceTimersByTime(1000);

expect(mockNavigation.navigate).not.toHaveBeenCalled();
expect(mockTrackEvent).toHaveBeenCalledTimes(2);
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-require-imports */
/* eslint-disable import/no-commonjs */
/* eslint-disable @typescript-eslint/no-var-requires */
import React, { useRef, useCallback } from 'react';
import React, { useRef } from 'react';
import BottomSheet, {
BottomSheetRef,
} from '../../../component-library/components/BottomSheets/BottomSheet';
Expand Down Expand Up @@ -35,31 +35,29 @@ const NFTAutoDetectionModal = () => {
const chainId = useSelector(selectChainId);
const displayNftMedia = useSelector(selectDisplayNftMedia);
const { trackEvent } = useMetrics();
const enableNftDetectionAndDismissModal = useCallback(
(value: boolean) => {
if (value) {
const { PreferencesController } = Engine.context;
if (!displayNftMedia) {
PreferencesController.setDisplayNftMedia(true);
}
PreferencesController.setUseNftDetection(true);
trackEvent(MetaMetricsEvents.NFT_AUTO_DETECTION_MODAL_ENABLE, {
chainId,
});
} else {
trackEvent(MetaMetricsEvents.NFT_AUTO_DETECTION_MODAL_DISABLE, {
chainId,
});
}

if (sheetRef?.current) {
sheetRef.current.onCloseBottomSheet();
} else {
navigation.goBack();
const enableNftDetectionAndDismissModal = (value: boolean) => {
if (value) {
const { PreferencesController } = Engine.context;
if (!displayNftMedia) {
PreferencesController.setDisplayNftMedia(true);
}
},
[displayNftMedia, trackEvent, chainId, navigation],
);
PreferencesController.setUseNftDetection(true);
trackEvent(MetaMetricsEvents.NFT_AUTO_DETECTION_MODAL_ENABLE, {
chainId,
});
} else {
trackEvent(MetaMetricsEvents.NFT_AUTO_DETECTION_MODAL_DISABLE, {
chainId,
});
}

if (sheetRef?.current) {
sheetRef.current.onCloseBottomSheet();
} else {
navigation.goBack();
}
};

return (
<BottomSheet ref={sheetRef}>
Expand Down
8 changes: 8 additions & 0 deletions app/core/Analytics/MetaMetrics.events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,10 @@ enum EVENT_NAME {

// network
MULTI_RPC_MIGRATION_MODAL_ACCEPTED = 'multi_rpc_migration_modal_accepted',

// Connection
CONNECTION_DROPPED = 'Connection dropped',
CONNECTION_RESTORED = 'Connection restored',
}

enum ACTIONS {
Expand Down Expand Up @@ -854,6 +858,10 @@ const events = {
),
PRIMARY_CURRENCY_TOGGLE: generateOpt(EVENT_NAME.PRIMARY_CURRENCY_TOGGLE),
LOGIN_DOWNLOAD_LOGS: generateOpt(EVENT_NAME.LOGIN_DOWNLOAD_LOGS),

// Connection
CONNECTION_DROPPED: generateOpt(EVENT_NAME.CONNECTION_DROPPED),
CONNECTION_RESTORED: generateOpt(EVENT_NAME.CONNECTION_RESTORED),
};

/**
Expand Down
41 changes: 41 additions & 0 deletions app/util/navigation/useConnectionHandler.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useRef, useCallback } from 'react';
import {
useMetrics,
MetaMetricsEvents,
} from '../../components/hooks/useMetrics';

// TODO: Replace "any" with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const useConnectionHandler = (navigation: any) => {
const connectedRef = useRef(true);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);

const { trackEvent } = useMetrics();

const connectionChangeHandler = useCallback(
(state: { isConnected: boolean } | null) => {
if (!state) return;
const { isConnected } = state;

if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}

if (connectedRef.current !== isConnected) {
if (isConnected === false) {
trackEvent(MetaMetricsEvents.CONNECTION_DROPPED);
timeoutRef.current = setTimeout(() => {
navigation.navigate('OfflineModeView');
}, 3000);
} else {
trackEvent(MetaMetricsEvents.CONNECTION_RESTORED);
}
connectedRef.current = isConnected;
}
},
[navigation, trackEvent],
);

return { connectionChangeHandler };
};

0 comments on commit d124a74

Please sign in to comment.