react-native-in-app-debugger 1.0.43 → 1.0.44

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.
package/Api/Row.jsx ADDED
@@ -0,0 +1,85 @@
1
+ import React, { useState } from 'react';
2
+ import { StyleSheet, TouchableOpacity, View } from 'react-native';
3
+ import Text from '../Text';
4
+ import Highlight from '../Highlight';
5
+
6
+ export const MAX_URL_LENGTH = 100;
7
+
8
+ export default ({ item, filter }) => {
9
+ const tabs = [
10
+ { value: 'Response Body' },
11
+ { value: 'Request Body', hide: !item.request.data },
12
+ { value: 'Request Header' },
13
+ ];
14
+ const [tab, setTab] = useState(tabs[0].value);
15
+ const hasResponse = item.response;
16
+ const Tab = ({ value, hide }) => {
17
+ if (hide) return null;
18
+ const isSelected = value === tab;
19
+ return (
20
+ <TouchableOpacity
21
+ activeOpacity={isSelected ? 1 : 0.7}
22
+ onPress={() => setTab(value)}
23
+ style={[styles.selectionTab, { backgroundColor: isSelected ? 'white' : undefined }]}
24
+ >
25
+ <Text
26
+ style={{
27
+ color: isSelected ? '#000' : '#ffffff88',
28
+ textAlign: 'center',
29
+ }}
30
+ >
31
+ {value}
32
+ </Text>
33
+ </TouchableOpacity>
34
+ );
35
+ };
36
+
37
+ return (
38
+ <View style={styles.container}>
39
+ {item.request.url.length > MAX_URL_LENGTH && (
40
+ <Text style={{ color: '#ffffff99', paddingVertical: 20 }}>
41
+ <Highlight text={item.request.url} filter={filter} />
42
+ </Text>
43
+ )}
44
+ <View>
45
+ <View style={{ flexDirection: 'row' }}>
46
+ {tabs.map((t) => (
47
+ <Tab key={t.value} {...t} />
48
+ ))}
49
+ </View>
50
+
51
+ {tab === tabs[0].value && hasResponse && (
52
+ <Text style={{ color: 'white' }}>
53
+ <Highlight text={JSON.stringify(item.response.data, undefined, 4)} filter={filter} />
54
+ </Text>
55
+ )}
56
+ {tab === tabs[1].value && (
57
+ <Text style={{ color: 'white' }}>
58
+ <Highlight text={JSON.stringify(item.request.data, undefined, 4)} filter={filter} />
59
+ </Text>
60
+ )}
61
+ {tab === tabs[2].value && (
62
+ <Text style={{ color: 'white' }}>
63
+ <Highlight text={JSON.stringify(item.request.headers, undefined, 4)} filter={filter} />
64
+ </Text>
65
+ )}
66
+ </View>
67
+ </View>
68
+ );
69
+ };
70
+
71
+ const styles = StyleSheet.create({
72
+ container: {
73
+ padding: 5,
74
+ backgroundColor: '#171717',
75
+ paddingTop: 10,
76
+ paddingBottom: 40,
77
+ },
78
+ selectionTab: {
79
+ borderBottomColor: 'white',
80
+ borderBottomWidth: 2,
81
+ flex: 1,
82
+ borderTopEndRadius: 10,
83
+ borderTopStartRadius: 10,
84
+ },
85
+ });
package/Api/index.jsx ADDED
@@ -0,0 +1,203 @@
1
+ import React, { useState } from 'react';
2
+ import { SectionList, TextInput, View, Alert, StyleSheet, TouchableOpacity } from 'react-native';
3
+ import Text from '../Text';
4
+ import Highlight from '../Highlight';
5
+ import Bookmark from '../Bookmark';
6
+ import getRandomBrightColor from '../utils/getRandomBrightColor';
7
+ import { MAX_URL_LENGTH } from './Row';
8
+ let Clipboard;
9
+ try {
10
+ Clipboard = require('@react-native-clipboard/clipboard')?.default;
11
+ } catch (error) {
12
+ // console.error("Error importing Clipboard:", error);
13
+ }
14
+
15
+ import Row from './Row';
16
+
17
+ const isError = (a) => a.response?.status < 200 || a.response?.status >= 400;
18
+ export default (props) => {
19
+ const [filter, setFilter] = useState('');
20
+ const [errorOnly, setErrorOnly] = useState(false);
21
+ const [expands, setExpands] = useState({});
22
+ const apis = props.apis.filter((a) => !errorOnly || isError(a));
23
+
24
+ const hasError = apis.some(isError);
25
+
26
+ return (
27
+ <>
28
+ <View style={styles.container}>
29
+ {!!apis.length && !filter && (
30
+ <TouchableOpacity
31
+ style={{ padding: 5, backgroundColor: 'white', borderRadius: 5 }}
32
+ onPress={() =>
33
+ Alert.alert('Are you sure', 'You want to clear all logs', [
34
+ { text: 'Delete', onPress: props.clear, style: 'cancel' },
35
+ { text: 'Cancel' },
36
+ ])
37
+ }
38
+ >
39
+ <Text style={{ color: 'black', fontSize: 10 }}>Clear {props.apis.length} APIs</Text>
40
+ </TouchableOpacity>
41
+ )}
42
+ {hasError && !filter && (
43
+ <TouchableOpacity style={{ padding: 5 }} onPress={() => setErrorOnly((v) => !v)}>
44
+ <Text
45
+ style={{
46
+ color: 'red',
47
+ textDecorationLine: errorOnly ? 'line-through' : undefined,
48
+ fontSize: 10,
49
+ }}
50
+ >
51
+ {apis.filter(isError).length} error
52
+ {apis.filter(isError).length > 1 ? 's' : ''}
53
+ </Text>
54
+ </TouchableOpacity>
55
+ )}
56
+ {!!props.blacklists.length && !filter && (
57
+ <TouchableOpacity
58
+ style={{ padding: 5, backgroundColor: 'white', borderRadius: 5 }}
59
+ onPress={() =>
60
+ Alert.alert('Are you sure', 'You want to clear all blacklists', [
61
+ { text: 'Clear', onPress: () => props.setBlacklists(), style: 'cancel' },
62
+ { text: 'Cancel' },
63
+ ])
64
+ }
65
+ >
66
+ <Text style={{ color: 'black', fontSize: 10 }}>Clear {props.blacklists.length} Blacklists</Text>
67
+ </TouchableOpacity>
68
+ )}
69
+
70
+ <TextInput
71
+ value={filter}
72
+ placeholder='Filter...'
73
+ clearButtonMode='always'
74
+ placeholderTextColor='grey'
75
+ style={{ paddingHorizontal: 5, color: 'white', flex: 1 }}
76
+ onChangeText={(t) => setFilter(t.toLowerCase())}
77
+ />
78
+ </View>
79
+ {!filter && !!props.maxNumOfApiToStore && apis.length >= props.maxNumOfApiToStore && (
80
+ <Text style={{ color: '#ffffff88', padding: 10 }}>Capped to only latest {props.maxNumOfApiToStore} APIs</Text>
81
+ )}
82
+ <SectionList
83
+ keyExtractor={(i) => i.id}
84
+ stickySectionHeadersEnabled
85
+ showsVerticalScrollIndicator
86
+ sections={apis
87
+ .filter((a) => !filter || JSON.stringify(a).toLowerCase().includes(filter))
88
+ .map((data) => ({ data: [data], id: data.id }))}
89
+ renderItem={(i) => (expands[i.item.id] ? <Row {...i} filter={filter} /> : <View style={{ height: 20 }} />)}
90
+ renderSectionHeader={({ section: { data } }) => {
91
+ const item = data[0];
92
+ const hasResponse = !!item.response;
93
+
94
+ const duration = item.response?.timestamp ? ~~(item.response?.timestamp - item.request.timestamp) / 1000 : 0;
95
+ const isExpand = expands[item.id];
96
+ const bookmarkColor = props.bookmarks[item.id];
97
+ const color = hasResponse ? (item.response.error ? 'red' : 'white') : 'yellow';
98
+
99
+ return (
100
+ <View style={styles.rowHeader}>
101
+ <Bookmark
102
+ color={bookmarkColor}
103
+ onPress={() => {
104
+ props.setBookmarks((v) => {
105
+ if (!bookmarkColor) return { ...v, [item.id]: getRandomBrightColor() };
106
+ const newV = { ...v };
107
+ delete newV[item.id];
108
+ return newV;
109
+ });
110
+ }}
111
+ />
112
+ <Text selectable style={{ flex: 1, color, marginVertical: 10 }}>
113
+ <Text style={{ opacity: 0.7 }}>
114
+ {item.request.method +
115
+ ` (${item.response?.status ?? 'no response'})` +
116
+ ' - ' +
117
+ item.request.time +
118
+ (hasResponse ? ' - ' + duration + ' second(s)' : '') +
119
+ '\n'}
120
+ </Text>
121
+ <Highlight text={item.request.url.slice(0, MAX_URL_LENGTH)} filter={filter} />
122
+ {item.request.url.length > MAX_URL_LENGTH && '.......'}
123
+ </Text>
124
+ <View style={{ gap: 4 }}>
125
+ <TouchableOpacity
126
+ onPress={() =>
127
+ setExpands((v) => {
128
+ if (!isExpand) return { ...v, [item.id]: true };
129
+ const newV = { ...v };
130
+ delete newV[item.id];
131
+ return newV;
132
+ })
133
+ }
134
+ style={styles.actionButton}
135
+ >
136
+ <Text style={{ color: 'black', fontSize: 10 }}>{isExpand ? 'Hide' : 'Show'}</Text>
137
+ </TouchableOpacity>
138
+ {!!Clipboard && (
139
+ <TouchableOpacity
140
+ onPress={() => {
141
+ const content = { ...item };
142
+ delete content.id;
143
+ Clipboard.setString(JSON.stringify(content, undefined, 4));
144
+ }}
145
+ style={styles.actionButton}
146
+ >
147
+ <Text style={{ color: 'black', fontSize: 10 }}>Copy</Text>
148
+ </TouchableOpacity>
149
+ )}
150
+ <TouchableOpacity
151
+ onPress={() => {
152
+ Alert.alert(
153
+ 'Are you sure',
154
+ `You want to blacklist: \n\n(${item.request.method}) ${item.request.url} \n\nwhere all history logs for this API will be removed and all future request for this API will not be recorded?`,
155
+ [
156
+ {
157
+ text: 'Blacklist',
158
+ onPress: () => props.setBlacklists({ method: item.request.method, url: item.request.url }),
159
+ style: 'cancel',
160
+ },
161
+ { text: 'Cancel' },
162
+ ],
163
+ );
164
+ }}
165
+ style={styles.actionButton}
166
+ >
167
+ <Text style={{ color: 'black', fontSize: 10 }}>Blacklist</Text>
168
+ </TouchableOpacity>
169
+ </View>
170
+ </View>
171
+ );
172
+ }}
173
+ />
174
+ </>
175
+ );
176
+ };
177
+
178
+ const styles = StyleSheet.create({
179
+ container: {
180
+ flexDirection: 'row',
181
+ paddingLeft: 5,
182
+ alignItems: 'center',
183
+ gap: 5,
184
+ },
185
+ actionButton: { backgroundColor: 'white', borderRadius: 5, padding: 4 },
186
+ rowHeader: {
187
+ flexDirection: 'row',
188
+ gap: 5,
189
+ backgroundColor: 'black',
190
+ padding: 5,
191
+ paddingTop: 10,
192
+ shadowColor: 'black',
193
+ shadowOffset: {
194
+ width: 0,
195
+ height: 2,
196
+ },
197
+ shadowRadius: 5,
198
+ shadowOpacity: 1,
199
+ // TODO shadow not working on android
200
+ elevation: 10,
201
+ zIndex: 99,
202
+ },
203
+ });
package/Bookmark.jsx ADDED
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+ import { Pressable, StyleSheet, View } from 'react-native';
3
+
4
+ export default ({ size = 10, color = '#222', onPress }) => {
5
+ const tristyle = { borderRightWidth: size / 2, borderTopWidth: size / 2, borderTopColor: color };
6
+ return (
7
+ <View>
8
+ <View style={{ width: size, height: size, backgroundColor: color }} />
9
+ <View style={{ flexDirection: 'row' }}>
10
+ <View style={[styles.triangleCorner, tristyle]} />
11
+ <View style={[styles.triangleCorner, tristyle, { transform: [{ rotate: '90deg' }] }]} />
12
+ </View>
13
+ <Pressable
14
+ onPress={onPress}
15
+ style={[
16
+ {
17
+ width: size,
18
+ height: size + size / 2,
19
+ },
20
+ styles.pressable,
21
+ ]}
22
+ />
23
+ </View>
24
+ );
25
+ };
26
+
27
+ const styles = StyleSheet.create({
28
+ triangleCorner: {
29
+ width: 0,
30
+ height: 0,
31
+ backgroundColor: 'transparent',
32
+ borderStyle: 'solid',
33
+ borderRightColor: 'transparent',
34
+ },
35
+ pressable: {
36
+ position: 'absolute',
37
+ transform: [{ scale: 1.7 }],
38
+ },
39
+ });
package/README.md CHANGED
@@ -93,3 +93,7 @@ If this library is installed, when user expand any selected API, there will be a
93
93
 
94
94
  <img width="535" alt="image" src="https://github.com/fattahmuhyiddeen/react-native-in-app-debugger/assets/24792201/d4f58ee3-e553-4cae-91df-ba7e26d8cd70">
95
95
 
96
+
97
+ #### React Native Async Storage (https://www.npmjs.com/package/@react-native-async-storage/async-storage)
98
+
99
+ If this library is installed, the blacklist will be persisted
package/X.jsx ADDED
@@ -0,0 +1,28 @@
1
+ import React from 'react';
2
+ import { StyleSheet, TouchableOpacity, View } from 'react-native';
3
+
4
+ export default ({ size = 20, color = 'white', onPress }) => {
5
+ const panelStyle = { top: size / 2, width: size, backgroundColor: color };
6
+ return (
7
+ <TouchableOpacity
8
+ onPress={onPress}
9
+ style={{
10
+ width: size,
11
+ height: size,
12
+ transform: [{ scale: 1.7 }],
13
+ }}
14
+ >
15
+ <View style={{ width: size, height: size, transform: [{ scale: 0.5 }] }}>
16
+ <View style={[styles.panel, panelStyle, { transform: [{ rotate: '45deg' }] }]} />
17
+ <View style={[styles.panel, panelStyle, { transform: [{ rotate: '-45deg' }] }]} />
18
+ </View>
19
+ </TouchableOpacity>
20
+ );
21
+ };
22
+
23
+ const styles = StyleSheet.create({
24
+ panel: {
25
+ height: 3,
26
+ position: 'absolute',
27
+ },
28
+ });
package/index.jsx CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  Dimensions,
9
9
  } from "react-native";
10
10
  import Text from "./Text";
11
+ import X from './X';
11
12
 
12
13
  let DeviceInfo;
13
14
  try {
@@ -85,7 +86,7 @@ export default ({
85
86
  },[]);
86
87
  }
87
88
 
88
- const { apis, clear } = useApiInterceptor(maxNumOfApiToStore, blacklists, interceptResponse);
89
+ const { apis, ...restApiInterceptor } = useApiInterceptor(maxNumOfApiToStore, blacklists, interceptResponse);
89
90
 
90
91
  const [tab, setTab] = useState("api");
91
92
 
@@ -188,16 +189,15 @@ export default ({
188
189
  );
189
190
  })}
190
191
  </View>
191
- <TouchableOpacity onPress={() => setIsOpen(false)}>
192
- <Text style={styles.close}>X</Text>
193
- </TouchableOpacity>
192
+ <X onPress={() => setIsOpen(false)} />
194
193
  </View>
195
194
  {tab === "variables" && !!variables && (
196
195
  <Variables variables={variables} />
197
196
  )}
198
197
  {tab === "api" && (
199
198
  <Api
200
- {...{apis, clear, setBlacklists, blacklists, maxNumOfApiToStore}}
199
+ {...{apis, setBlacklists, blacklists, maxNumOfApiToStore}}
200
+ {...restApiInterceptor}
201
201
  />
202
202
  )}
203
203
  </SafeAreaView>
@@ -225,12 +225,6 @@ const styles = StyleSheet.create({
225
225
  padding: 3,
226
226
  borderRadius: 999,
227
227
  },
228
- close: {
229
- color: "white",
230
- fontWeight: "bold",
231
- fontSize: 16,
232
- paddingHorizontal: 10,
233
- },
234
228
  labelContainer: {
235
229
  backgroundColor: "black",
236
230
  flexDirection: "row",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-in-app-debugger",
3
- "version": "1.0.43",
3
+ "version": "1.0.44",
4
4
  "description": "This library's main usage is to be used by Non-Technical testers during UAT, SIT or any testing phase.",
5
5
  "main": "index.jsx",
6
6
  "scripts": {
@@ -1,13 +1,13 @@
1
- import { useEffect, useState } from "react";
2
- import XHRInterceptor from "react-native/Libraries/Network/XHRInterceptor.js";
1
+ import { useEffect, useState } from 'react';
2
+ import XHRInterceptor from 'react-native/Libraries/Network/XHRInterceptor.js';
3
3
 
4
4
  const filterNonBusinessRelatedAPI = true;
5
5
 
6
6
  const shouldExclude = (url, method) =>
7
- ["HEAD"].includes(method) ||
8
- url.includes("codepush") ||
9
- url.includes("localhost") ||
10
- url.includes("applicationinsights.azure.com");
7
+ ['HEAD'].includes(method) ||
8
+ url.includes('codepush') ||
9
+ url.includes('localhost') ||
10
+ url.includes('applicationinsights.azure.com');
11
11
 
12
12
  const parse = (data) => {
13
13
  try {
@@ -19,13 +19,14 @@ const parse = (data) => {
19
19
 
20
20
  export default (maxNumOfApiToStore, blacklists, interceptResponse) => {
21
21
  const [apis, setApis] = useState([]);
22
+ const [bookmarks, setBookmarks] = useState({});
22
23
 
23
24
  const makeRequest = (data) => {
24
- if (blacklists.some(b => b.url === data.url && b.method === data.method)) return;
25
+ if (blacklists.some((b) => b.url === data.url && b.method === data.method)) return;
25
26
  const date = new Date();
26
27
  let hour = date.getHours();
27
- const minute = (date.getMinutes() + "").padStart(2, "0");
28
- const second = (date.getSeconds() + "").padStart(2, "0");
28
+ const minute = (date.getMinutes() + '').padStart(2, '0');
29
+ const second = (date.getSeconds() + '').padStart(2, '0');
29
30
 
30
31
  const request = {
31
32
  ...data,
@@ -42,10 +43,8 @@ export default (maxNumOfApiToStore, blacklists, interceptResponse) => {
42
43
  };
43
44
 
44
45
  useEffect(() => {
45
- setApis((v) => (
46
- v.filter(v => !blacklists.some(b => b.url === v.request.url && b.method === v.request.method))
47
- ))
48
- }, [blacklists])
46
+ setApis((v) => v.filter((v) => !blacklists.some((b) => b.url === v.request.url && b.method === v.request.method)));
47
+ }, [blacklists]);
49
48
 
50
49
  const receiveResponse = (data) => {
51
50
  const error = data.status < 200 || data.status >= 400;
@@ -54,12 +53,7 @@ export default (maxNumOfApiToStore, blacklists, interceptResponse) => {
54
53
  const oldData = [...v];
55
54
  for (let i = 0; i < oldData.length; i++) {
56
55
  const old = oldData[i];
57
- if (
58
- old.response ||
59
- old.request.url !== data.config.url ||
60
- old.request.method !== data.config.method
61
- )
62
- continue;
56
+ if (old.response || old.request.url !== data.config.url || old.request.method !== data.config.method) continue;
63
57
 
64
58
  oldData[i].response = {
65
59
  ...data,
@@ -77,7 +71,7 @@ export default (maxNumOfApiToStore, blacklists, interceptResponse) => {
77
71
  XHRInterceptor.enableInterception();
78
72
  // console.log('API interceptor status', XHRInterceptor.isInterceptorEnabled());
79
73
  XHRInterceptor.setSendCallback((...obj) => {
80
- obj[1].responseType = "text";
74
+ obj[1].responseType = 'text';
81
75
  const data = parse(obj[0]);
82
76
 
83
77
  const { _method: method, _url: url, _headers: headers } = obj[1];
@@ -112,5 +106,5 @@ export default (maxNumOfApiToStore, blacklists, interceptResponse) => {
112
106
  });
113
107
  }, []);
114
108
 
115
- return { apis, clear: () => setApis([]) };
109
+ return { apis, clear: () => setApis([]), bookmarks, setBookmarks };
116
110
  };
@@ -0,0 +1,51 @@
1
+ export default () => {
2
+ let h = Math.floor(Math.random() * 360); // Hue: 0 to 360 degrees
3
+ let s = Math.floor(Math.random() * 51) + 50; // Saturation: 50% to 100%
4
+ let l = Math.floor(Math.random() * 41) + 50; // Lightness: 50% to 90%
5
+
6
+ return hslToRgb(h, s, l);
7
+ };
8
+
9
+ function hslToRgb(h, s, l) {
10
+ s /= 100;
11
+ l /= 100;
12
+
13
+ let c = (1 - Math.abs(2 * l - 1)) * s;
14
+ let x = c * (1 - Math.abs(((h / 60) % 2) - 1));
15
+ let m = l - c / 2;
16
+ let r = 0,
17
+ g = 0,
18
+ b = 0;
19
+
20
+ if (0 <= h && h < 60) {
21
+ r = c;
22
+ g = x;
23
+ b = 0;
24
+ } else if (60 <= h && h < 120) {
25
+ r = x;
26
+ g = c;
27
+ b = 0;
28
+ } else if (120 <= h && h < 180) {
29
+ r = 0;
30
+ g = c;
31
+ b = x;
32
+ } else if (180 <= h && h < 240) {
33
+ r = 0;
34
+ g = x;
35
+ b = c;
36
+ } else if (240 <= h && h < 300) {
37
+ r = x;
38
+ g = 0;
39
+ b = c;
40
+ } else if (300 <= h && h < 360) {
41
+ r = c;
42
+ g = 0;
43
+ b = x;
44
+ }
45
+
46
+ r = Math.round((r + m) * 255);
47
+ g = Math.round((g + m) * 255);
48
+ b = Math.round((b + m) * 255);
49
+
50
+ return `rgb(${r}, ${g}, ${b})`;
51
+ }
package/Api.jsx DELETED
@@ -1,300 +0,0 @@
1
- import React, { useState } from "react";
2
- import {
3
- SectionList,
4
- TextInput,
5
- View,
6
- Alert,
7
- StyleSheet,
8
- TouchableOpacity,
9
- } from "react-native";
10
- import Text from "./Text";
11
- import Highlight from "./Highlight";
12
- let Clipboard;
13
- try {
14
- Clipboard = require("@react-native-clipboard/clipboard")?.default;
15
- } catch (error) {
16
- // console.error("Error importing Clipboard:", error);
17
- }
18
-
19
- const MAX_URL_LENGTH = 100;
20
-
21
- const Row = ({ item, filter }) => {
22
- const tabs = [
23
- { value: "Response Body" },
24
- { value: "Request Body", hide: !item.request.data },
25
- { value: "Request Header" },
26
- ];
27
- const [tab, setTab] = useState(tabs[0].value);
28
- const hasResponse = item.response;
29
- const Tab = ({ value, hide }) => {
30
- if (hide) return null;
31
- const isSelected = value === tab;
32
- return (
33
- <TouchableOpacity
34
- activeOpacity={isSelected ? 1 : 0.7}
35
- onPress={() => setTab(value)}
36
- style={[
37
- styles.selectionTab,
38
- { backgroundColor: isSelected ? "white" : undefined },
39
- ]}
40
- >
41
- <Text
42
- style={{
43
- color: isSelected ? "#000" : "#ffffff88",
44
- textAlign: "center",
45
- }}
46
- >
47
- {value}
48
- </Text>
49
- </TouchableOpacity>
50
- );
51
- };
52
-
53
- return (
54
- <View style={styles.details}>
55
- {item.request.url.length > MAX_URL_LENGTH && (
56
- <Text style={{ color: "#ffffff99", paddingVertical: 20 }}>
57
- <Highlight text={item.request.url} filter={filter} />
58
- </Text>
59
- )}
60
- <View>
61
- <View style={{ flexDirection: "row" }}>
62
- {tabs.map((t) => <Tab key={t.value} {...t} />)}
63
- </View>
64
-
65
- {tab === tabs[0].value && hasResponse && (
66
- <Text style={{ color: "white" }}>
67
- <Highlight
68
- text={JSON.stringify(item.response.data, undefined, 4)}
69
- filter={filter}
70
- />
71
- </Text>
72
- )}
73
- {tab === tabs[1].value && (
74
- <Text style={{ color: "white" }}>
75
- <Highlight
76
- text={JSON.stringify(item.request.data, undefined, 4)}
77
- filter={filter}
78
- />
79
- </Text>
80
- )}
81
- {tab === tabs[2].value && (
82
- <Text style={{ color: "white" }}>
83
- <Highlight
84
- text={JSON.stringify(item.request.headers, undefined, 4)}
85
- filter={filter}
86
- />
87
- </Text>
88
- )}
89
- </View>
90
- </View>
91
- );
92
- };
93
-
94
- const isError = (a) => a.response?.status < 200 || a.response?.status >= 400;
95
- export default (props) => {
96
- const [filter, setFilter] = useState("");
97
- const [errorOnly, setErrorOnly] = useState(false);
98
- const [expands, setExpands] = useState({});
99
- const apis = props.apis.filter((a) => !errorOnly || isError(a));
100
-
101
- const hasError = apis.some(isError);
102
-
103
- return (
104
- <>
105
- <View style={styles.container}>
106
- {!!apis.length && !filter && (
107
- <TouchableOpacity
108
- style={{ padding: 5, backgroundColor: "white", borderRadius: 5 }}
109
- onPress={() =>
110
- Alert.alert("Are you sure", "You want to clear all logs", [
111
- { text: "Delete", onPress: props.clear, style: "cancel" },
112
- { text: "Cancel" },
113
- ])
114
- }
115
- >
116
- <Text style={{ color: "black", fontSize: 10 }}>
117
- Clear {props.apis.length} APIs
118
- </Text>
119
- </TouchableOpacity>
120
- )}
121
- {hasError && !filter && (
122
- <TouchableOpacity
123
- style={{ padding: 5 }}
124
- onPress={() => setErrorOnly((v) => !v)}
125
- >
126
- <Text
127
- style={{
128
- color: "red",
129
- textDecorationLine: errorOnly ? "line-through" : undefined,
130
- fontSize: 10,
131
- }}
132
- >
133
- {apis.filter(isError).length} error
134
- {apis.filter(isError).length > 1 ? "s" : ""}
135
- </Text>
136
- </TouchableOpacity>
137
- )}
138
- {!!props.blacklists.length && !filter && (
139
- <TouchableOpacity
140
- style={{ padding: 5, backgroundColor: "white", borderRadius: 5 }}
141
- onPress={() =>
142
- Alert.alert("Are you sure", "You want to clear all blacklists", [
143
- { text: "Clear", onPress: () => props.setBlacklists(), style: "cancel" },
144
- { text: "Cancel" },
145
- ])
146
- }
147
- >
148
- <Text style={{ color: "black", fontSize: 10 }}>
149
- Clear {props.blacklists.length} Blacklists
150
- </Text>
151
- </TouchableOpacity>
152
- )}
153
-
154
- <TextInput
155
- value={filter}
156
- placeholder="Filter..."
157
- clearButtonMode="always"
158
- placeholderTextColor="grey"
159
- style={{ paddingHorizontal: 5, color: "white", flex: 1 }}
160
- onChangeText={(t) => setFilter(t.toLowerCase())}
161
- />
162
- </View>
163
- {!filter &&
164
- !!props.maxNumOfApiToStore &&
165
- apis.length >= props.maxNumOfApiToStore && (
166
- <Text style={{ color: "#ffffff88", padding: 10 }}>
167
- Capped to only latest {props.maxNumOfApiToStore} APIs
168
- </Text>
169
- )}
170
- <SectionList
171
- keyExtractor={(i) => i.id}
172
- stickySectionHeadersEnabled
173
- showsVerticalScrollIndicator
174
- sections={apis
175
- .filter((a) =>
176
- !filter || JSON.stringify(a).toLowerCase().includes(filter)
177
- )
178
- .map((data) => ({ data: [data], id: data.id }))}
179
- renderItem={(i) =>
180
- expands[i.item.id] ? (
181
- <Row {...i} filter={filter} />
182
- ) : (
183
- <View style={{ height: 20 }} />
184
- )
185
- }
186
- renderSectionHeader={({ section: { data } }) => {
187
- const item = data[0];
188
- const hasResponse = !!item.response;
189
-
190
- const duration = item.response?.timestamp ? ~~(item.response?.timestamp - item.request.timestamp) / 1000 : 0;
191
- const isExpand = expands[item.id];
192
- const color = hasResponse ? item.response.error ? "red" : "white" : "yellow";
193
-
194
- return (
195
- <View style={styles.rowHeader}>
196
- <Text
197
- selectable
198
- style={{flex: 1, color, marginVertical: 10}}
199
- >
200
- <Text style={{ opacity: 0.7 }}>
201
- {item.request.method +
202
- ` (${item.response?.status ?? "no response"})` +
203
- " - " +
204
- item.request.time +
205
- (hasResponse ? " - " + duration + " second(s)" : "") +
206
- "\n"}
207
- </Text>
208
- <Highlight
209
- text={item.request.url.slice(0, MAX_URL_LENGTH)}
210
- filter={filter}
211
- />
212
- {item.request.url.length > MAX_URL_LENGTH && "......."}
213
- </Text>
214
- <View style={{ gap: 4 }}>
215
- <TouchableOpacity
216
- onPress={() =>
217
- setExpands((v) => {
218
- if (!isExpand) return { ...v, [item.id]: true };
219
- const newV = { ...v };
220
- delete newV[item.id];
221
- return newV;
222
- })
223
- }
224
- style={styles.actionButton}
225
- >
226
- <Text style={{ color: "black", fontSize: 10 }}>
227
- {isExpand ? "Hide" : "Show"}
228
- </Text>
229
- </TouchableOpacity>
230
- {!!Clipboard && (
231
- <TouchableOpacity
232
- onPress={() => {
233
- const content = { ...item };
234
- delete content.id;
235
- Clipboard.setString(JSON.stringify(content, undefined, 4));
236
- }}
237
- style={styles.actionButton}
238
- >
239
- <Text style={{ color: "black", fontSize: 10 }}>Copy</Text>
240
- </TouchableOpacity>
241
- )}
242
- <TouchableOpacity
243
- onPress={() => {
244
- Alert.alert("Are you sure", `You want to blacklist: \n\n(${item.request.method}) ${item.request.url} \n\nwhere all history logs for this API will be removed and all future request for this API will not be recorded?`, [
245
- { text: "Blacklist", onPress: () => props.setBlacklists({method: item.request.method, url: item.request.url}), style: "cancel" },
246
- { text: "Cancel" },
247
- ])
248
- }}
249
- style={styles.actionButton}
250
- >
251
- <Text style={{ color: "black", fontSize: 10 }}>Blacklist</Text>
252
- </TouchableOpacity>
253
- </View>
254
- </View>
255
- );
256
- }}
257
- />
258
- </>
259
- );
260
- };
261
-
262
- const styles = StyleSheet.create({
263
- container: {
264
- flexDirection: "row",
265
- paddingLeft: 5,
266
- alignItems: "center",
267
- gap: 5,
268
- },
269
- details: {
270
- padding: 5,
271
- backgroundColor: "#171717",
272
- paddingTop: 10,
273
- paddingBottom: 40,
274
- },
275
- actionButton: { backgroundColor: "white", borderRadius: 5, padding: 4 },
276
- rowHeader: {
277
- flexDirection: "row",
278
- gap: 5,
279
- backgroundColor: "black",
280
- padding: 5,
281
- paddingTop: 10,
282
- shadowColor: "black",
283
- shadowOffset: {
284
- width: 0,
285
- height: 2,
286
- },
287
- shadowRadius: 5,
288
- shadowOpacity: 1,
289
- // TODO shadow not working on android
290
- elevation: 10,
291
- zIndex: 99,
292
- },
293
- selectionTab: {
294
- borderBottomColor: "white",
295
- borderBottomWidth: 2,
296
- flex: 1,
297
- borderTopEndRadius: 10,
298
- borderTopStartRadius: 10,
299
- },
300
- });