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 +85 -0
- package/Api/index.jsx +203 -0
- package/Bookmark.jsx +39 -0
- package/README.md +4 -0
- package/X.jsx +28 -0
- package/index.jsx +5 -11
- package/package.json +1 -1
- package/useApiInterceptor.js +15 -21
- package/utils/getRandomBrightColor.js +51 -0
- package/Api.jsx +0 -300
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,
|
|
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
|
-
<
|
|
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,
|
|
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
package/useApiInterceptor.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { useEffect, useState } from
|
|
2
|
-
import XHRInterceptor from
|
|
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
|
-
[
|
|
8
|
-
url.includes(
|
|
9
|
-
url.includes(
|
|
10
|
-
url.includes(
|
|
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() +
|
|
28
|
-
const second = (date.getSeconds() +
|
|
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
|
-
|
|
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 =
|
|
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
|
-
});
|