import * as React from 'react'; import { useCallback, useState } from 'react'; import { DeviceEventEmitter, EmitterSubscription, Modal, NativeModules, useColorScheme, View } from 'react-native'; import { Details } from './Components/Details'; import { Main } from './Components/Main'; import { clear as clearReduxActions, reduxLoggerMiddleware, setCallback as setReduxActionsCallback, setConfig as setReduxConfig, setMaxActions as setReduxMaxActions, } from './Core/ReduxLogger'; import { RNLogger } from './Core/RNLogger'; import { ConnectionLogger } from './Core/ConnectionLogger'; import { RNRequest } from './Core/Objects/RNRequest'; import { ReduxAction } from './Core/Objects/ReduxAction'; import { NRequest } from './Core/Objects/NRequest'; import { ConnectionInfo } from './Core/Objects/ConnectionInfo'; import { ThemeContext, themes } from './Theme'; import { clearMockResponses, mockRequestWithResponse, MockResponse, resetMockResponses, setupMocks, } from './Components/Mocking/utils'; import { MockingNavigator } from './Components/Mocking'; import { DarkTheme, Provider as PaperProvider } from 'react-native-paper'; import Clipboard from '@react-native-clipboard/clipboard'; import { LaunchArguments } from 'react-native-launch-arguments'; export interface IProps { visible?: boolean; onPressClose?: () => void; enabled: boolean; disableShake?: boolean; interceptIOS?: boolean; maxRequests?: number; reduxConfig?: any; theme?: 'dark' | 'light'; showStats?: boolean; useReactotron?: boolean; loadMockPresetFromClipboard?: boolean; loadMockPresetFromInputParameters?: boolean; mockPresets?: Array; } export const reduxLogger = reduxLoggerMiddleware; export const _RNLogger = new RNLogger(); export const _ConnectionLogger = new ConnectionLogger(); const { RNNetwatch } = NativeModules; let nativeLoopStarted = false; let nativeLoop: NodeJS.Timeout; export const Netwatch: React.FC = (props: IProps) => { const [reduxActions, setReduxActions] = useState>([]); const [rnRequests, setRnRequests] = useState>([]); const [nRequests, setnRequests] = useState>([]); const [connections, setConnections] = useState>([]); const [showDetails, setShowDetails] = useState(false); const [item, setItem] = useState(new ReduxAction()); const [visible, setVisible] = useState(false); const [mockResponse, setMockResponse] = useState(); const [update, setUpdate] = useState(false); const [showMockNavigator, setShowMockNavigator] = useState(false); let colorScheme = useColorScheme() || 'light'; colorScheme = props.theme ? props.theme : colorScheme; // At this time, if it's not light, that will be dark. No other possibility const _theme = colorScheme === 'light' ? themes.light : themes.dark; // Extract data from shared pref and passed the result to the UI const getNativeRequests = useCallback((): void => { RNNetwatch.getNativeRequests((response: any) => { try { let _temp; try { _temp = JSON.parse(response.result); } catch (e) { _temp = response.result; } if (_temp && _temp instanceof Array && _temp.length > 0) { const _result = _temp.map((element: any) => { return new NRequest({ _id: element._id, readyState: 4, url: element.url, shortUrl: element.url.slice(0, 100), method: element.method, status: element.status, startTime: element.startTime, endTime: element.endTime, timeout: element.timeout, dataSent: JSON.stringify(element.dataSent, null, 2), requestHeaders: element.requestHeaders, responseHeaders: element.responseHeaders, responseContentType: element.responseContentType, responseSize: element.responseSize, responseType: element.responseType, responseURL: element.responseURL, response: JSON.stringify(element.response, null, 2), }); }); setnRequests([..._result, ...nRequests]); } } catch (error) { console.error(error.message); } }); }, [nRequests]); React.useEffect(() => { if (props.visible !== undefined) { setVisible(props.visible); } }, [props.visible]); const handleShake = useCallback(() => { if (!props.disableShake && props.onPressClose) { console.warn( 'You cannot use button and shake at the same time to avoid inconsistant state. To remove this warning, you must explicitly set props disableShake to true or remove props onPressClose.', ); return; } if (!props.disableShake && props.enabled) { setVisible(true); } }, [props.disableShake, props.enabled, props.onPressClose]); React.useEffect(() => { let subscription: EmitterSubscription | null = null; if (!props.disableShake && props.enabled) { subscription = DeviceEventEmitter.addListener('NetwatchShakeEvent', handleShake); } return () => { subscription?.remove?.(); }; }, [handleShake, props.disableShake, props.enabled]); const handleBack = () => { if (showDetails) { return setShowDetails(false); } props.onPressClose ? props.onPressClose() : setVisible(false); }; const startNativeLoop = useCallback(() => { if (props.enabled && !nativeLoopStarted) { nativeLoopStarted = true; nativeLoop = setInterval(() => { getNativeRequests(); }, 1500); } }, [getNativeRequests, props.enabled]); const stopNativeLoop = () => { nativeLoopStarted = false; clearInterval(nativeLoop); }; const clearAll = () => { _RNLogger.clear(); _ConnectionLogger.clearConnectionEvents(); clearReduxActions(); setReduxActions([]); setRnRequests([]); setnRequests([]); setConnections([]); }; React.useEffect(() => { if (!props.enabled || props.useReactotron) { clearMockResponses(); clearAll(); stopNativeLoop(); _ConnectionLogger.resetCallback(); setReduxActionsCallback(() => {}); } }, [props.enabled, props.useReactotron]); React.useEffect(() => { if (props.enabled) { if (props.interceptIOS) { RNNetwatch.startNetwatch(); } startNativeLoop(); _RNLogger.enableXHRInterception(); _RNLogger.setCallback(setRnRequests); _ConnectionLogger.setCallback(setConnections); if (props.reduxConfig) { setReduxConfig(props.reduxConfig); } setReduxMaxActions(props.maxRequests); setReduxActionsCallback(setReduxActions); } }, [ props.enabled, props.interceptIOS, props.maxRequests, props.reduxConfig, startNativeLoop, props.loadMockPresetFromClipboard, ]); React.useEffect(() => { if (props.enabled) { try { if (props.loadMockPresetFromInputParameters) { const args = LaunchArguments.value<{ netwatchMocks: MockResponse[] }>(); if (args && args.netwatchMocks && Array.isArray(args.netwatchMocks)) { args.netwatchMocks.forEach(preset => { mockRequestWithResponse(preset); }); } } else if (props.loadMockPresetFromClipboard) { Clipboard.getString().then(responses => { if (responses) { resetMockResponses(responses); } }); } else if (Array.isArray(props.mockPresets)) { props.mockPresets.forEach(preset => { mockRequestWithResponse(preset); }); } } catch (e) { console.error(e); } setupMocks(); } }, [props.enabled, props.loadMockPresetFromClipboard, props.loadMockPresetFromInputParameters, props.mockPresets]); React.useEffect(() => { if (!visible) { stopNativeLoop(); return; } startNativeLoop(); }, [startNativeLoop, visible]); if (!props.enabled) { return null; } return (
setVisible(false))} onPressDetail={setShowDetails} onPress={setItem} reduxActions={reduxActions} rnRequests={rnRequests} nRequests={nRequests} connections={connections} clearAll={clearAll} showStats={props.showStats} onShowMocksList={() => { setMockResponse(undefined); setShowMockNavigator(true); }} />
{ setMockResponse(mr); setUpdate(u); setShowMockNavigator(true); }} testId="detailScreen" onPressBack={setShowDetails} item={item} /> setShowMockNavigator(true)} > setShowMockNavigator(false)} /> ); }; Netwatch.defaultProps = { visible: false, onPressClose: undefined, enabled: true, interceptIOS: true, disableShake: false, maxRequests: 100, reduxConfig: {}, theme: 'dark', showStats: true, useReactotron: false, };