react-native-debug-toolkit 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,41 @@
1
+ ---
2
+ description:
3
+ globs:
4
+ alwaysApply: false
5
+ ---
6
+
7
+ You are an expert in TypeScript, React Native, Expo, and Mobile App Development.
8
+
9
+ Code Style and Structure:
10
+ - Write concise, type-safe TypeScript code.
11
+ - Use functional components and hooks over class components.
12
+ - Ensure components are modular, reusable, and maintainable.
13
+ - Organize files by feature, grouping related components, hooks, and styles.
14
+
15
+ Naming Conventions:
16
+ - Use camelCase for variable and function names (e.g., `isFetchingData`, `handleUserInput`).
17
+ - Use PascalCase for component names (e.g., `UserProfile`, `ChatScreen`).
18
+
19
+ TypeScript Usage:
20
+ - Use TypeScript for all components, favoring interfaces for props and state.
21
+ - Enable strict typing in `tsconfig.json`.
22
+ - Avoid using `any`; strive for precise types.
23
+ - Utilize `React.FC` for defining functional components with props.
24
+
25
+ Performance Optimization:
26
+ - Minimize `useEffect`, `useState`, and heavy computations inside render methods.
27
+ - Use `React.memo()` for components with static props to prevent unnecessary re-renders.
28
+ - Optimize FlatLists with props like `removeClippedSubviews`, `maxToRenderPerBatch`, and `windowSize`.
29
+ - Use `getItemLayout` for FlatLists when items have a consistent size to improve performance.
30
+ - Avoid anonymous functions in `renderItem` or event handlers to prevent re-renders.
31
+
32
+ UI and Styling:
33
+ - Use consistent styling, either through `StyleSheet.create()` or Styled Components.
34
+ - Ensure responsive design by considering different screen sizes and orientations.
35
+ - Optimize image handling using libraries designed for React Native
36
+
37
+ Best Practices:
38
+ - Follow React Native's threading model to ensure smooth UI performance.
39
+ - Utilize CodePush Build and Updates for continuous deployment and Over-The-Air (OTA) updates.
40
+ - Use React Navigation for handling navigation and deep linking with best practices.
41
+
@@ -12,6 +12,7 @@ class NetworkFeature {
12
12
  this.logs = []
13
13
  this.pendingAxiosRequests = new Map()
14
14
  this.originalFetch = null
15
+ this.blacklist = [] // URL patterns to exclude from logging
15
16
 
16
17
  NetworkFeature.instance = this
17
18
  }
@@ -39,6 +40,39 @@ class NetworkFeature {
39
40
  this.pendingAxiosRequests.clear()
40
41
  }
41
42
 
43
+ // Check if a URL matches any pattern in the blacklist
44
+ isUrlBlacklisted(url) {
45
+ if (!url) return false
46
+
47
+ return this.blacklist.some(pattern => {
48
+ if (pattern instanceof RegExp) {
49
+ return pattern.test(url)
50
+ }
51
+ return url.includes(pattern)
52
+ })
53
+ }
54
+
55
+ // Add a URL pattern to blacklist
56
+ addUrlToBlacklist(pattern) {
57
+ if (!this.blacklist.some(p =>
58
+ (p instanceof RegExp && pattern instanceof RegExp) ?
59
+ p.toString() === pattern.toString() : p === pattern)) {
60
+ this.blacklist.push(pattern)
61
+ }
62
+ }
63
+
64
+ // Remove a URL pattern from blacklist
65
+ removeUrlFromBlacklist(pattern) {
66
+ this.blacklist = this.blacklist.filter(p =>
67
+ (p instanceof RegExp && pattern instanceof RegExp) ?
68
+ p.toString() !== pattern.toString() : p !== pattern)
69
+ }
70
+
71
+ // Clear all patterns from blacklist
72
+ clearBlacklist() {
73
+ this.blacklist = []
74
+ }
75
+
42
76
  setupAxiosInterceptors(axiosInstance) {
43
77
  if (!__DEV__) {
44
78
  return
@@ -94,6 +128,14 @@ class NetworkFeature {
94
128
  return
95
129
  }
96
130
 
131
+ const url = `${response.config.baseURL || ''}${response.config.url}`
132
+
133
+ // Skip logging if URL is blacklisted
134
+ if (this.isUrlBlacklisted(url)) {
135
+ this.pendingAxiosRequests.delete(trackId)
136
+ return
137
+ }
138
+
97
139
  if (this.logs.length >= NetworkFeature.MAX_LOGS) {
98
140
  this.logs.shift()
99
141
  }
@@ -105,7 +147,7 @@ class NetworkFeature {
105
147
  timestamp: pendingRequest.timestamp,
106
148
  duration: Math.round(duration),
107
149
  request: {
108
- url: `${response.config.baseURL || ''}${response.config.url}`,
150
+ url: url,
109
151
  method: response.config.method?.toUpperCase() || 'GET',
110
152
  headers: response.config.headers,
111
153
  body: response.config.data || response.config.params,
@@ -148,6 +190,14 @@ class NetworkFeature {
148
190
  const startTime = pendingRequest
149
191
  ? pendingRequest.startTime
150
192
  : Date.now() - 100
193
+
194
+ const url = `${error.config.baseURL || ''}${error.config.url}`
195
+
196
+ // Skip logging if URL is blacklisted
197
+ if (this.isUrlBlacklisted(url)) {
198
+ this.pendingAxiosRequests.delete(trackId)
199
+ return
200
+ }
151
201
 
152
202
  if (this.logs.length >= NetworkFeature.MAX_LOGS) {
153
203
  this.logs.shift()
@@ -159,7 +209,7 @@ class NetworkFeature {
159
209
  timestamp: pendingRequest ? pendingRequest.timestamp : new Date(),
160
210
  duration: Math.round(duration),
161
211
  request: {
162
- url: `${error.config.baseURL || ''}${error.config.url}`,
212
+ url: url,
163
213
  method: error.config.method?.toUpperCase() || 'GET',
164
214
  headers: error.config.headers,
165
215
  body: error.config.data || error.config.params,
@@ -206,12 +256,18 @@ class NetworkFeature {
206
256
  }
207
257
 
208
258
  _logFetchResponse(request, options, response, startTime) {
259
+ const requestUrl = typeof request === 'string' ? request : request.url
260
+
261
+ // Skip logging if URL is blacklisted
262
+ if (this.isUrlBlacklisted(requestUrl)) {
263
+ return
264
+ }
265
+
209
266
  if (this.logs.length >= NetworkFeature.MAX_LOGS) {
210
267
  this.logs.shift()
211
268
  }
212
269
 
213
270
  const duration = Date.now() - startTime
214
- const requestUrl = typeof request === 'string' ? request : request.url
215
271
 
216
272
  const commonData = {
217
273
  timestamp: new Date(),
@@ -276,6 +332,13 @@ class NetworkFeature {
276
332
  }
277
333
 
278
334
  _logFetchError(request, options, error, startTime) {
335
+ const requestUrl = typeof request === 'string' ? request : request.url
336
+
337
+ // Skip logging if URL is blacklisted
338
+ if (this.isUrlBlacklisted(requestUrl)) {
339
+ return
340
+ }
341
+
279
342
  if (this.logs.length >= NetworkFeature.MAX_LOGS) {
280
343
  this.logs.shift()
281
344
  }
@@ -286,7 +349,7 @@ class NetworkFeature {
286
349
  timestamp: new Date(),
287
350
  duration: Math.round(duration),
288
351
  request: {
289
- url: typeof request === 'string' ? request : request.url,
352
+ url: requestUrl,
290
353
  method: options.method || 'GET',
291
354
  headers: options.headers || {},
292
355
  body: options.body,
@@ -326,5 +389,10 @@ export const createNetworkFeature = () => {
326
389
  cleanup: () => feature.cleanup(),
327
390
  setupAxiosInterceptors: (axiosInstance) =>
328
391
  feature.setupAxiosInterceptors(axiosInstance),
392
+ // Expose blacklist management methods
393
+ addUrlToBlacklist: (pattern) => feature.addUrlToBlacklist(pattern),
394
+ removeUrlFromBlacklist: (pattern) => feature.removeUrlFromBlacklist(pattern),
395
+ clearBlacklist: () => feature.clearBlacklist(),
396
+ getBlacklist: () => feature.blacklist,
329
397
  }
330
398
  }
@@ -1,8 +1,28 @@
1
1
  import React, { useState, useCallback } from 'react'
2
2
  import { View, Text, StyleSheet, Clipboard, Dimensions } from 'react-native'
3
3
  import { ScrollView, Pressable } from 'react-native'
4
-
4
+ import JSONTree from 'react-native-json-tree'
5
5
  const { width: SCREEN_WIDTH } = Dimensions.get('window')
6
+ const theme = {
7
+ scheme: 'monokai',
8
+ author: 'wimer hazenberg (http://www.monokai.nl)',
9
+ base00: '#272822',
10
+ base01: '#383830',
11
+ base02: '#49483e',
12
+ base03: '#75715e',
13
+ base04: '#a59f85',
14
+ base05: '#f8f8f2',
15
+ base06: '#f5f4f1',
16
+ base07: '#f9f8f5',
17
+ base08: '#f92672',
18
+ base09: '#fd971f',
19
+ base0A: '#f4bf75',
20
+ base0B: '#a6e22e',
21
+ base0C: '#a1efe4',
22
+ base0D: '#66d9ef',
23
+ base0E: '#ae81ff',
24
+ base0F: '#cc6633'
25
+ };
6
26
 
7
27
  const CopyButton = ({ text, style }) => {
8
28
  const [copied, setCopied] = useState(false)
@@ -38,15 +58,18 @@ const CollapsibleSection = ({ title, children, initiallyExpanded = false }) => {
38
58
 
39
59
  const LongTextContent = ({ text }) => {
40
60
  return (
41
- <ScrollView
42
- style={styles.longTextContainer}
43
- contentContainerStyle={{ flexGrow: 1 }}
44
- showsVerticalScrollIndicator={true}
45
- nestedScrollEnabled={true}>
46
- <Text style={styles.jsonString} selectable={true}>
47
- {text}
48
- </Text>
49
- </ScrollView>
61
+ <View style={styles.longTextWrapper}>
62
+ <ScrollView
63
+ style={styles.longTextContainer}
64
+ contentContainerStyle={styles.longTextContent}
65
+ showsVerticalScrollIndicator={true}
66
+ nestedScrollEnabled={true}
67
+ bounces={false}>
68
+ <Text style={styles.jsonString} selectable={true}>
69
+ {text}
70
+ </Text>
71
+ </ScrollView>
72
+ </View>
50
73
  )
51
74
  }
52
75
 
@@ -283,8 +306,7 @@ const HttpLogDetails = ({ log }) => {
283
306
  contentContainerStyle={styles.contentContainer}
284
307
  showsVerticalScrollIndicator={true}
285
308
  scrollEventThrottle={16}
286
- keyboardShouldPersistTaps='handled'
287
- nestedScrollEnabled={true}>
309
+ keyboardShouldPersistTaps='handled'>
288
310
  <View style={styles.header}>
289
311
  <View style={styles.headerInfo}>
290
312
  <Text style={styles.url} numberOfLines={2} selectable={true}>
@@ -299,6 +321,7 @@ const HttpLogDetails = ({ log }) => {
299
321
  {(request.method || 'GET').toUpperCase()}
300
322
  </Text>
301
323
  {status && <ApiStatus status={status} success={success} />}
324
+ {duration && <Text style={styles.duration}>(duration:{duration}ms)</Text>}
302
325
  </View>
303
326
  </View>
304
327
  <CopyButton text={request.url || ''} />
@@ -320,16 +343,28 @@ const HttpLogDetails = ({ log }) => {
320
343
  }
321
344
  />
322
345
  </View>
323
- <ScrollView style={styles.dataContent} contentContainerStyle={{ flexGrow: 1 }} nestedScrollEnabled={true}>
324
- <JSONValue value={requestData} maxExpandLevel={1} />
325
- </ScrollView>
346
+ <View style={styles.dataContentWrapper}>
347
+ <ScrollView
348
+ style={styles.dataContent}
349
+ nestedScrollEnabled={true}
350
+ bounces={false}
351
+ showsVerticalScrollIndicator={true}>
352
+ <JSONValue value={requestData} maxExpandLevel={1} />
353
+ </ScrollView>
354
+ </View>
326
355
  </View>
327
356
  )}
328
357
 
329
358
  <CollapsibleSection title='Headers'>
330
- <ScrollView style={styles.dataContent} contentContainerStyle={{ flexGrow: 1 }} nestedScrollEnabled={true}>
331
- <JSONValue value={request.headers || {}} maxExpandLevel={0} />
332
- </ScrollView>
359
+ <View style={styles.dataContentWrapper}>
360
+ <ScrollView
361
+ style={styles.dataContent}
362
+ nestedScrollEnabled={true}
363
+ bounces={false}
364
+ showsVerticalScrollIndicator={true}>
365
+ <JSONValue value={request.headers || {}} maxExpandLevel={0} />
366
+ </ScrollView>
367
+ </View>
333
368
  </CollapsibleSection>
334
369
  </View>
335
370
  </CollapsibleSection>
@@ -338,12 +373,12 @@ const HttpLogDetails = ({ log }) => {
338
373
  title={`Response ${responseSize}`}
339
374
  initiallyExpanded={true}>
340
375
  <View style={styles.content}>
341
- <View style={styles.row}>
342
- <Text style={styles.label}>Status:{status || (success === false ? 'Error' : 'Unknown')}
376
+ {/* <View style={styles.row}>
377
+ <Text style={styles.label}>Status: {status || (success === false ? 'Error' : 'Unknown')}
343
378
  {status && response.statusText ? ` (${response.statusText})` : ''}
344
- {duration && ` Duration: (${duration}ms)`}
379
+ {duration && ` Duration: (${duration}ms)`}
345
380
  </Text>
346
- </View>
381
+ </View> */}
347
382
 
348
383
  {error && (
349
384
  <View style={styles.errorSection}>
@@ -366,24 +401,28 @@ const HttpLogDetails = ({ log }) => {
366
401
  }
367
402
  />
368
403
  </View>
404
+ <View style={styles.dataContentWrapper}>
405
+ <ScrollView
406
+ style={styles.dataContent}
407
+ nestedScrollEnabled={true}
408
+ bounces={false}
409
+ showsVerticalScrollIndicator={true}>
410
+ <JSONTree data={responseData} theme={theme} invertTheme={true} />
411
+ </ScrollView>
412
+ </View>
413
+ </View>
414
+ )}
415
+
416
+ <CollapsibleSection title='Headers'>
417
+ <View style={styles.dataContentWrapper}>
369
418
  <ScrollView
370
419
  style={styles.dataContent}
371
- contentContainerStyle={{ flexGrow: 1 }}
372
420
  nestedScrollEnabled={true}
421
+ bounces={false}
373
422
  showsVerticalScrollIndicator={true}>
374
- <JSONValue value={responseData} maxExpandLevel={1} />
423
+ <JSONValue value={response.headers || {}} maxExpandLevel={0} />
375
424
  </ScrollView>
376
425
  </View>
377
- )}
378
-
379
- <CollapsibleSection title='Headers'>
380
- <ScrollView
381
- style={styles.dataContent}
382
- contentContainerStyle={{ flexGrow: 1 }}
383
- nestedScrollEnabled={true}
384
- showsVerticalScrollIndicator={true}>
385
- <JSONValue value={response.headers || {}} maxExpandLevel={0} />
386
- </ScrollView>
387
426
  </CollapsibleSection>
388
427
  </View>
389
428
  </CollapsibleSection>
@@ -395,10 +434,16 @@ const HttpLogDetails = ({ log }) => {
395
434
  <Text style={styles.codeBlockLabel}>Debug with cURL</Text>
396
435
  <CopyButton text={generateCurl()} />
397
436
  </View>
398
- <View style={styles.codeBlock}>
399
- <Text style={styles.codeText} selectable={true}>
400
- {generateCurl()}
401
- </Text>
437
+ <View style={styles.dataContentWrapper}>
438
+ <ScrollView
439
+ style={styles.codeBlock}
440
+ nestedScrollEnabled={true}
441
+ bounces={false}
442
+ showsVerticalScrollIndicator={true}>
443
+ <Text style={styles.codeText} selectable={true}>
444
+ {generateCurl()}
445
+ </Text>
446
+ </ScrollView>
402
447
  </View>
403
448
  </View>
404
449
  </View>
@@ -559,6 +604,9 @@ const styles = StyleSheet.create({
559
604
  fontWeight: 'bold',
560
605
  color: '#666',
561
606
  },
607
+ dataContentWrapper: {
608
+ flex: 1,
609
+ },
562
610
  dataContent: {
563
611
  flex: 1,
564
612
  backgroundColor: '#f8f9fa',
@@ -582,6 +630,7 @@ const styles = StyleSheet.create({
582
630
  fontWeight: 'bold',
583
631
  },
584
632
  codeBlock: {
633
+ flex: 1,
585
634
  backgroundColor: '#f8f9fa',
586
635
  padding: 10,
587
636
  borderRadius: 4,
@@ -662,9 +711,13 @@ const styles = StyleSheet.create({
662
711
  color: '#666',
663
712
  },
664
713
  longTextContainer: {
665
- maxHeight: 200,
714
+ flex: 1,
666
715
  paddingHorizontal: 8,
667
716
  paddingVertical: 5,
717
+ backgroundColor: '#f8f9fa',
718
+ },
719
+ longTextContent: {
720
+ paddingBottom: 10,
668
721
  },
669
722
  })
670
723
 
@@ -5,7 +5,6 @@ import {
5
5
  StyleSheet,
6
6
  FlatList,
7
7
  TouchableOpacity,
8
- Modal,
9
8
  } from 'react-native'
10
9
  import HttpLogDetails from './HttpLogDetails'
11
10
 
@@ -76,6 +75,44 @@ const SubViewHTTPLogs = ({ logs = [] }) => {
76
75
  )
77
76
  }
78
77
 
78
+ // If a log is selected, show the details view with a back button
79
+ if (selectedLog) {
80
+ return (
81
+ <View style={styles.container}>
82
+ <View style={styles.detailsHeader}>
83
+ <TouchableOpacity
84
+ style={styles.backButton}
85
+ onPress={() => setSelectedLog(null)}>
86
+ <Text style={styles.backButtonText}>← Back</Text>
87
+ </TouchableOpacity>
88
+ <View style={styles.headerMethodStatus}>
89
+ <Text
90
+ style={[
91
+ styles.headerMethod,
92
+ { color: getMethodColor(selectedLog.request?.method) },
93
+ ]}>
94
+ {(selectedLog.request?.method || 'GET').toUpperCase()}
95
+ </Text>
96
+ <Text
97
+ style={[
98
+ styles.headerStatus,
99
+ {
100
+ color:
101
+ selectedLog.response?.status >= 400
102
+ ? '#ff4444'
103
+ : '#00C851',
104
+ },
105
+ ]}>
106
+ {selectedLog.response?.status || 'Error'}
107
+ </Text>
108
+ </View>
109
+ </View>
110
+ <HttpLogDetails log={selectedLog} />
111
+ </View>
112
+ )
113
+ }
114
+
115
+ // Otherwise show the list view
79
116
  return (
80
117
  <View style={styles.container}>
81
118
  {logs.length === 0 ? (
@@ -88,56 +125,6 @@ const SubViewHTTPLogs = ({ logs = [] }) => {
88
125
  style={styles.list}
89
126
  />
90
127
  )}
91
-
92
- <Modal
93
- visible={!!selectedLog}
94
- animationType='slide'
95
- transparent={true}
96
- onRequestClose={() => setSelectedLog(null)}>
97
- {selectedLog && (
98
- <TouchableOpacity
99
- style={styles.modalContainer}
100
- activeOpacity={1}
101
- onPress={() => setSelectedLog(null)}>
102
- <TouchableOpacity
103
- style={styles.modalContent}
104
- activeOpacity={1}
105
- onPress={(e) => e.stopPropagation()}>
106
- <View style={styles.modalHeader}>
107
- <View style={styles.modalHeaderContent}>
108
- <View style={styles.modalMethodStatus}>
109
- <Text
110
- style={[
111
- styles.modalMethod,
112
- { color: getMethodColor(selectedLog.request?.method) },
113
- ]}>
114
- {(selectedLog.request?.method || 'GET').toUpperCase()}
115
- </Text>
116
- <Text
117
- style={[
118
- styles.modalStatus,
119
- {
120
- color:
121
- selectedLog.response?.status >= 400
122
- ? '#ff4444'
123
- : '#00C851',
124
- },
125
- ]}>
126
- {selectedLog.response?.status || 'Error'}
127
- </Text>
128
- </View>
129
- </View>
130
- <TouchableOpacity
131
- style={styles.closeButton}
132
- onPress={() => setSelectedLog(null)}>
133
- <Text style={styles.closeButtonText}>×</Text>
134
- </TouchableOpacity>
135
- </View>
136
- <HttpLogDetails log={selectedLog} />
137
- </TouchableOpacity>
138
- </TouchableOpacity>
139
- )}
140
- </Modal>
141
128
  </View>
142
129
  )
143
130
  }
@@ -211,20 +198,7 @@ const styles = StyleSheet.create({
211
198
  fontSize: 12,
212
199
  marginLeft: 8,
213
200
  },
214
- modalContainer: {
215
- flex: 1,
216
- backgroundColor: 'rgba(0,0,0,0.5)',
217
- justifyContent: 'flex-end',
218
- alignItems: 'center',
219
- },
220
- modalContent: {
221
- width: '100%',
222
- height: '90%',
223
- backgroundColor: '#fff',
224
- borderRadius: 10,
225
- overflow: 'hidden',
226
- },
227
- modalHeader: {
201
+ detailsHeader: {
228
202
  flexDirection: 'row',
229
203
  alignItems: 'center',
230
204
  padding: 15,
@@ -232,36 +206,30 @@ const styles = StyleSheet.create({
232
206
  borderBottomColor: '#eee',
233
207
  backgroundColor: '#f8f9fa',
234
208
  },
235
- modalHeaderContent: {
236
- flex: 1,
209
+ backButton: {
210
+ paddingHorizontal: 10,
211
+ paddingVertical: 5,
212
+ borderRadius: 4,
213
+ backgroundColor: '#eee',
237
214
  marginRight: 10,
238
215
  },
239
- modalMethodStatus: {
216
+ backButtonText: {
217
+ color: '#333',
218
+ fontWeight: '500',
219
+ },
220
+ headerMethodStatus: {
240
221
  flexDirection: 'row',
241
222
  alignItems: 'center',
242
223
  },
243
- modalMethod: {
224
+ headerMethod: {
244
225
  fontSize: 16,
245
226
  fontWeight: 'bold',
246
227
  marginRight: 10,
247
228
  },
248
- modalStatus: {
229
+ headerStatus: {
249
230
  fontSize: 14,
250
231
  fontWeight: '600',
251
232
  },
252
- closeButton: {
253
- width: 30,
254
- height: 30,
255
- alignItems: 'center',
256
- justifyContent: 'center',
257
- borderRadius: 15,
258
- backgroundColor: '#eee',
259
- },
260
- closeButtonText: {
261
- fontSize: 20,
262
- color: '#666',
263
- lineHeight: 20,
264
- },
265
233
  })
266
234
 
267
235
  export default SubViewHTTPLogs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-debug-toolkit",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "A simple yet powerful debugging toolkit for React Native with a convenient floating UI for development",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -23,7 +23,8 @@
23
23
  "author": "zcj",
24
24
  "license": "MIT",
25
25
  "dependencies": {
26
- "react-native-root-siblings": "^4.0.0"
26
+ "react-native-root-siblings": "^4.0.0",
27
+ "react-native-json-tree": "^1.5.0"
27
28
  },
28
29
  "peerDependencies": {
29
30
  "react": "*",