Skip to content

Commit 35b54e0

Browse files
authored
feat: Warn if the logger doesn't start due to duplicate logger (#56)
- add new forceEnable option to override default behaviour
1 parent a452565 commit 35b54e0

File tree

7 files changed

+112
-14
lines changed

7 files changed

+112
-14
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,14 @@ import NetworkLogger from 'react-native-network-logger';
120120
const MyScreen = () => <NetworkLogger sort="asc" />;
121121
```
122122

123+
#### Force Enable
124+
125+
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.
126+
127+
```ts
128+
startNetworkLogging({ forceEnable: true });
129+
```
130+
123131
#### Integrate with existing navigation
124132

125133
Use your existing back button (e.g. in your navigation header) to navigate within the network logger.

src/Logger.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import XHRInterceptor from 'react-native/Libraries/Network/XHRInterceptor';
22
import NetworkRequestInfo from './NetworkRequestInfo';
33
import { Headers, RequestMethod, StartNetworkLoggingOptions } from './types';
44
import extractHost from './utils/extractHost';
5+
import { warn } from './utils/logger';
6+
57
let nextXHRId = 0;
68

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

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

116119
enableXHRInterception = (options?: StartNetworkLoggingOptions) => {
117-
if (XHRInterceptor.isInterceptorEnabled()) {
120+
if (
121+
this.enabled ||
122+
(XHRInterceptor.isInterceptorEnabled() && !options?.forceEnable)
123+
) {
124+
if (!this.enabled) {
125+
warn(
126+
'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.'
127+
);
128+
}
118129
return;
119130
}
120131

121132
if (options?.maxRequests !== undefined) {
122133
if (typeof options.maxRequests !== 'number' || options.maxRequests < 1) {
123-
console.warn(
124-
'react-native-network-logger: maxRequests must be a number greater than 0. The logger has not been started.'
134+
warn(
135+
'maxRequests must be a number greater than 0. The logger has not been started.'
125136
);
126137
return;
127138
}
@@ -133,8 +144,8 @@ export default class Logger {
133144
!Array.isArray(options.ignoredHosts) ||
134145
typeof options.ignoredHosts[0] !== 'string'
135146
) {
136-
console.warn(
137-
'react-native-network-logger: ignoredHosts must be an array of strings. The logger has not been started.'
147+
warn(
148+
'ignoredHosts must be an array of strings. The logger has not been started.'
138149
);
139150
return;
140151
}
@@ -148,6 +159,7 @@ export default class Logger {
148159
XHRInterceptor.setResponseCallback(this.responseCallback);
149160

150161
XHRInterceptor.enableInterception();
162+
this.enabled = true;
151163
};
152164

153165
getRequests = () => {

src/__tests__/Logger.spec.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import XHRInterceptor from 'react-native/Libraries/Network/XHRInterceptor';
2+
import { warn } from '../utils/logger';
23
import Logger from '../Logger';
34

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

16+
jest.mock('../utils/logger', () => ({
17+
warn: jest.fn(() => {
18+
throw new Error('Unexpected warning');
19+
}),
20+
}));
21+
1522
beforeEach(() => {
1623
jest.clearAllMocks();
1724
});
1825

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

2331
(XHRInterceptor.isInterceptorEnabled as jest.Mock).mockReturnValueOnce(
2432
true
2533
);
2634

2735
expect(logger.enableXHRInterception()).toBeUndefined();
36+
expect(warn).toHaveBeenCalledTimes(1);
2837
expect(XHRInterceptor.isInterceptorEnabled).toHaveBeenCalledTimes(1);
2938
expect(XHRInterceptor.setOpenCallback).toHaveBeenCalledTimes(0);
3039
});
3140

41+
it('should continue if interceptor has already been enabled but forceEnable is true', () => {
42+
const logger = new Logger();
43+
44+
(XHRInterceptor.isInterceptorEnabled as jest.Mock).mockReturnValueOnce(
45+
true
46+
);
47+
48+
expect(logger.enableXHRInterception({ forceEnable: true })).toBeUndefined();
49+
expect(XHRInterceptor.isInterceptorEnabled).toHaveBeenCalledTimes(1);
50+
expect(XHRInterceptor.setOpenCallback).toHaveBeenCalledTimes(1);
51+
});
52+
3253
it('should update the maxRequests if provided', () => {
3354
const logger = new Logger();
3455

src/components/NetworkLogger.tsx

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { ThemeContext, ThemeName } from '../theme';
66
import RequestList from './RequestList';
77
import RequestDetails from './RequestDetails';
88
import { setBackHandler } from '../backHandler';
9+
import Unmounted from './Unmounted';
910

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

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

4446
logger.enableXHRInterception();
47+
setMounted(true);
4548

4649
return () => {
4750
// no-op if component is unmounted
@@ -91,15 +94,19 @@ const NetworkLogger: React.FC<Props> = ({ theme = 'light', sort = 'desc' }) => {
9194
</View>
9295
)}
9396
<View style={showDetails && !!request ? styles.hidden : styles.visible}>
94-
<RequestList
95-
requests={requests}
96-
onShowMore={showMore}
97-
showDetails={showDetails && !!request}
98-
onPressItem={(item) => {
99-
setRequest(item);
100-
setShowDetails(true);
101-
}}
102-
/>
97+
{mounted && !logger.enabled && !requests.length ? (
98+
<Unmounted />
99+
) : (
100+
<RequestList
101+
requests={requests}
102+
onShowMore={showMore}
103+
showDetails={showDetails && !!request}
104+
onPressItem={(item) => {
105+
setRequest(item);
106+
setShowDetails(true);
107+
}}
108+
/>
109+
)}
103110
</View>
104111
</View>
105112
</ThemeContext.Provider>

src/components/Unmounted.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import React from 'react';
2+
import { View, StyleSheet, Text } from 'react-native';
3+
import { Theme, useThemedStyles } from '../theme';
4+
5+
const Unmounted = () => {
6+
const styles = useThemedStyles(themedStyles);
7+
return (
8+
<View style={styles.container}>
9+
<Text style={styles.heading}>Unmounted Error</Text>
10+
<Text style={styles.body}>
11+
It looks like the network logger hasn’t been enabled yet.
12+
</Text>
13+
<Text style={styles.body}>
14+
This is likely due to you running another debugging tool that is also
15+
intercepting network requests. Either disable that or start the network
16+
logger with the option:{' '}
17+
<Text style={styles.code}>"forceEnable: true"</Text>.
18+
</Text>
19+
</View>
20+
);
21+
};
22+
23+
const themedStyles = (theme: Theme) =>
24+
StyleSheet.create({
25+
container: {
26+
padding: 15,
27+
},
28+
heading: {
29+
color: theme.colors.text,
30+
fontWeight: '600',
31+
fontSize: 25,
32+
marginBottom: 10,
33+
},
34+
body: {
35+
color: theme.colors.text,
36+
marginTop: 5,
37+
},
38+
code: {
39+
color: theme.colors.muted,
40+
},
41+
});
42+
43+
export default Unmounted;

src/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,9 @@ export type StartNetworkLoggingOptions = {
88
maxRequests?: number;
99
/** List of hosts to ignore, e.g. services.test.com */
1010
ignoredHosts?: string[];
11+
/**
12+
* Force the network logger to start even if another program is using the network interceptor
13+
* e.g. a dev/debuging program
14+
*/
15+
forceEnable?: boolean;
1116
};

src/utils/logger.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const warn = (message: string) =>
2+
console.warn(`react-native-network-logger: ${message}`);

0 commit comments

Comments
 (0)