Skip to content

Commit

Permalink
feat: Warn if the logger doesn't start due to duplicate logger (#56)
Browse files Browse the repository at this point in the history
- add new forceEnable option to override default behaviour
  • Loading branch information
alexbrazier committed Aug 24, 2021
1 parent a452565 commit 35b54e0
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 14 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,14 @@ import NetworkLogger from 'react-native-network-logger';
const MyScreen = () => <NetworkLogger sort="asc" />;
```

#### Force Enable

If you are running another network logging interceptor, e.g. Reactotron, the logger will not start as only one can be run at once. You can override this behaviour and force the logger to start by using the `forceEnable` option.

```ts
startNetworkLogging({ forceEnable: true });
```

#### Integrate with existing navigation

Use your existing back button (e.g. in your navigation header) to navigate within the network logger.
Expand Down
22 changes: 17 additions & 5 deletions src/Logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import XHRInterceptor from 'react-native/Libraries/Network/XHRInterceptor';
import NetworkRequestInfo from './NetworkRequestInfo';
import { Headers, RequestMethod, StartNetworkLoggingOptions } from './types';
import extractHost from './utils/extractHost';
import { warn } from './utils/logger';

let nextXHRId = 0;

type XHR = {
Expand All @@ -14,6 +16,7 @@ export default class Logger {
private xhrIdMap: { [key: number]: number } = {};
private maxRequests: number = 500;
private ignoredHosts: Set<string> | undefined;
public enabled = false;

// eslint-disable-next-line @typescript-eslint/no-unused-vars
callback = (requests: any[]) => {};
Expand Down Expand Up @@ -114,14 +117,22 @@ export default class Logger {
};

enableXHRInterception = (options?: StartNetworkLoggingOptions) => {
if (XHRInterceptor.isInterceptorEnabled()) {
if (
this.enabled ||
(XHRInterceptor.isInterceptorEnabled() && !options?.forceEnable)
) {
if (!this.enabled) {
warn(
'network interceptor has not been enabled as another interceptor is already running (e.g. another debugging program). Use option `forceEnable: true` to override this behaviour.'
);
}
return;
}

if (options?.maxRequests !== undefined) {
if (typeof options.maxRequests !== 'number' || options.maxRequests < 1) {
console.warn(
'react-native-network-logger: maxRequests must be a number greater than 0. The logger has not been started.'
warn(
'maxRequests must be a number greater than 0. The logger has not been started.'
);
return;
}
Expand All @@ -133,8 +144,8 @@ export default class Logger {
!Array.isArray(options.ignoredHosts) ||
typeof options.ignoredHosts[0] !== 'string'
) {
console.warn(
'react-native-network-logger: ignoredHosts must be an array of strings. The logger has not been started.'
warn(
'ignoredHosts must be an array of strings. The logger has not been started.'
);
return;
}
Expand All @@ -148,6 +159,7 @@ export default class Logger {
XHRInterceptor.setResponseCallback(this.responseCallback);

XHRInterceptor.enableInterception();
this.enabled = true;
};

getRequests = () => {
Expand Down
21 changes: 21 additions & 0 deletions src/__tests__/Logger.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import XHRInterceptor from 'react-native/Libraries/Network/XHRInterceptor';
import { warn } from '../utils/logger';
import Logger from '../Logger';

jest.mock('react-native/Libraries/Blob/FileReader', () => ({}));
Expand All @@ -12,23 +13,43 @@ jest.mock('react-native/Libraries/Network/XHRInterceptor', () => ({
enableInterception: jest.fn(),
}));

jest.mock('../utils/logger', () => ({
warn: jest.fn(() => {
throw new Error('Unexpected warning');
}),
}));

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

describe('enableXHRInterception', () => {
it('should do nothing if interceptor has already been enabled', () => {
(warn as jest.Mock).mockImplementationOnce(() => {});
const logger = new Logger();

(XHRInterceptor.isInterceptorEnabled as jest.Mock).mockReturnValueOnce(
true
);

expect(logger.enableXHRInterception()).toBeUndefined();
expect(warn).toHaveBeenCalledTimes(1);
expect(XHRInterceptor.isInterceptorEnabled).toHaveBeenCalledTimes(1);
expect(XHRInterceptor.setOpenCallback).toHaveBeenCalledTimes(0);
});

it('should continue if interceptor has already been enabled but forceEnable is true', () => {
const logger = new Logger();

(XHRInterceptor.isInterceptorEnabled as jest.Mock).mockReturnValueOnce(
true
);

expect(logger.enableXHRInterception({ forceEnable: true })).toBeUndefined();
expect(XHRInterceptor.isInterceptorEnabled).toHaveBeenCalledTimes(1);
expect(XHRInterceptor.setOpenCallback).toHaveBeenCalledTimes(1);
});

it('should update the maxRequests if provided', () => {
const logger = new Logger();

Expand Down
25 changes: 16 additions & 9 deletions src/components/NetworkLogger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ThemeContext, ThemeName } from '../theme';
import RequestList from './RequestList';
import RequestDetails from './RequestDetails';
import { setBackHandler } from '../backHandler';
import Unmounted from './Unmounted';

interface Props {
theme?: ThemeName;
Expand All @@ -25,6 +26,7 @@ const NetworkLogger: React.FC<Props> = ({ theme = 'light', sort = 'desc' }) => {
);
const [request, setRequest] = useState<NetworkRequestInfo>();
const [showDetails, _setShowDetails] = useState(false);
const [mounted, setMounted] = useState(false);

const setShowDetails = useCallback((shouldShow: boolean) => {
_setShowDetails(shouldShow);
Expand All @@ -42,6 +44,7 @@ const NetworkLogger: React.FC<Props> = ({ theme = 'light', sort = 'desc' }) => {
});

logger.enableXHRInterception();
setMounted(true);

return () => {
// no-op if component is unmounted
Expand Down Expand Up @@ -91,15 +94,19 @@ const NetworkLogger: React.FC<Props> = ({ theme = 'light', sort = 'desc' }) => {
</View>
)}
<View style={showDetails && !!request ? styles.hidden : styles.visible}>
<RequestList
requests={requests}
onShowMore={showMore}
showDetails={showDetails && !!request}
onPressItem={(item) => {
setRequest(item);
setShowDetails(true);
}}
/>
{mounted && !logger.enabled && !requests.length ? (
<Unmounted />
) : (
<RequestList
requests={requests}
onShowMore={showMore}
showDetails={showDetails && !!request}
onPressItem={(item) => {
setRequest(item);
setShowDetails(true);
}}
/>
)}
</View>
</View>
</ThemeContext.Provider>
Expand Down
43 changes: 43 additions & 0 deletions src/components/Unmounted.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';
import { View, StyleSheet, Text } from 'react-native';
import { Theme, useThemedStyles } from '../theme';

const Unmounted = () => {
const styles = useThemedStyles(themedStyles);
return (
<View style={styles.container}>
<Text style={styles.heading}>Unmounted Error</Text>
<Text style={styles.body}>
It looks like the network logger hasn’t been enabled yet.
</Text>
<Text style={styles.body}>
This is likely due to you running another debugging tool that is also
intercepting network requests. Either disable that or start the network
logger with the option:{' '}
<Text style={styles.code}>"forceEnable: true"</Text>.
</Text>
</View>
);
};

const themedStyles = (theme: Theme) =>
StyleSheet.create({
container: {
padding: 15,
},
heading: {
color: theme.colors.text,
fontWeight: '600',
fontSize: 25,
marginBottom: 10,
},
body: {
color: theme.colors.text,
marginTop: 5,
},
code: {
color: theme.colors.muted,
},
});

export default Unmounted;
5 changes: 5 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,9 @@ export type StartNetworkLoggingOptions = {
maxRequests?: number;
/** List of hosts to ignore, e.g. services.test.com */
ignoredHosts?: string[];
/**
* Force the network logger to start even if another program is using the network interceptor
* e.g. a dev/debuging program
*/
forceEnable?: boolean;
};
2 changes: 2 additions & 0 deletions src/utils/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const warn = (message: string) =>
console.warn(`react-native-network-logger: ${message}`);

0 comments on commit 35b54e0

Please sign in to comment.