react-native-in-app-debugger 1.0.85 → 2.0.0
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 +9 -2
- package/Api/index.jsx +24 -3
- package/{useApiInterceptor.js → Api/useApiInterceptor.js} +95 -24
- package/Highlight.jsx +1 -1
- package/Mock/MockDetails.jsx +225 -0
- package/Mock/MockGroup.jsx +64 -0
- package/Mock/index.jsx +46 -0
- package/README.md +0 -1
- package/index.jsx +96 -91
- package/package.json +1 -1
package/Api/Row.jsx
CHANGED
|
@@ -51,7 +51,15 @@ export default ({ item, filter, wrap, setWrap }) => {
|
|
|
51
51
|
<Tab key={t.value} {...t} />
|
|
52
52
|
))}
|
|
53
53
|
</View>
|
|
54
|
-
<View
|
|
54
|
+
<View
|
|
55
|
+
style={{
|
|
56
|
+
flexDirection: "row",
|
|
57
|
+
justifyContent: "flex-end",
|
|
58
|
+
alignItems: "center",
|
|
59
|
+
padding: 10,
|
|
60
|
+
gap: 10,
|
|
61
|
+
}}
|
|
62
|
+
>
|
|
55
63
|
<TouchableOpacity
|
|
56
64
|
onPress={() => setWrap((v) => !v)}
|
|
57
65
|
style={{
|
|
@@ -59,7 +67,6 @@ export default ({ item, filter, wrap, setWrap }) => {
|
|
|
59
67
|
backgroundColor: wrap ? "white" : undefined,
|
|
60
68
|
borderColor: "white",
|
|
61
69
|
padding: 5,
|
|
62
|
-
margin: 10,
|
|
63
70
|
borderRadius: 10,
|
|
64
71
|
}}
|
|
65
72
|
>
|
package/Api/index.jsx
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
Alert,
|
|
7
7
|
StyleSheet,
|
|
8
8
|
TouchableOpacity,
|
|
9
|
+
ActivityIndicator,
|
|
9
10
|
} from "react-native";
|
|
10
11
|
import Text from "../Text";
|
|
11
12
|
import Highlight from "../Highlight";
|
|
@@ -131,6 +132,9 @@ export default (props) => {
|
|
|
131
132
|
</TouchableOpacity>
|
|
132
133
|
</>
|
|
133
134
|
)}
|
|
135
|
+
{apis.some((a) => !a.response) && (
|
|
136
|
+
<ActivityIndicator size="small" color="white" />
|
|
137
|
+
)}
|
|
134
138
|
<TextInput
|
|
135
139
|
value={filter}
|
|
136
140
|
placeholder="Filter..."
|
|
@@ -153,7 +157,9 @@ export default (props) => {
|
|
|
153
157
|
showsVerticalScrollIndicator
|
|
154
158
|
sections={apis
|
|
155
159
|
.filter(
|
|
156
|
-
(a) =>
|
|
160
|
+
(a) =>
|
|
161
|
+
!filter ||
|
|
162
|
+
JSON.stringify(a.request).toLowerCase().includes(filter)
|
|
157
163
|
)
|
|
158
164
|
.filter((a) => !showBookmarkOnly || props.bookmarks[a.id])
|
|
159
165
|
.map((data) => ({ data: [data], id: data.id }))}
|
|
@@ -164,8 +170,11 @@ export default (props) => {
|
|
|
164
170
|
<View style={{ height: 20 }} />
|
|
165
171
|
)
|
|
166
172
|
}
|
|
167
|
-
renderSectionHeader={({
|
|
168
|
-
|
|
173
|
+
renderSectionHeader={({
|
|
174
|
+
section: {
|
|
175
|
+
data: [item],
|
|
176
|
+
},
|
|
177
|
+
}) => {
|
|
169
178
|
const hasResponse = !!item.response;
|
|
170
179
|
|
|
171
180
|
const duration = item.response?.timestamp
|
|
@@ -194,6 +203,10 @@ export default (props) => {
|
|
|
194
203
|
}}
|
|
195
204
|
/>
|
|
196
205
|
<Text selectable style={{ flex: 1, color, marginVertical: 10 }}>
|
|
206
|
+
<Text style={{ color: "#555", fontSize: 8 }}>
|
|
207
|
+
{item.id + "\n"}
|
|
208
|
+
</Text>
|
|
209
|
+
|
|
197
210
|
<Text style={{ opacity: 0.7 }}>
|
|
198
211
|
{item.request.method +
|
|
199
212
|
` (${item.response?.status ?? "no response"})` +
|
|
@@ -262,6 +275,14 @@ export default (props) => {
|
|
|
262
275
|
>
|
|
263
276
|
<BlacklistIcon />
|
|
264
277
|
</TouchableOpacity>
|
|
278
|
+
{item.interface === "axios" && (
|
|
279
|
+
<TouchableOpacity
|
|
280
|
+
onPress={() => props.goToMock(item)}
|
|
281
|
+
style={styles.actionButton}
|
|
282
|
+
>
|
|
283
|
+
<Text style={{ color: "black", fontSize: 10 }}>Mock</Text>
|
|
284
|
+
</TouchableOpacity>
|
|
285
|
+
)}
|
|
265
286
|
</View>
|
|
266
287
|
</View>
|
|
267
288
|
);
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react';
|
|
2
2
|
// import XHRInterceptor from 'react-native/Libraries/Network/XHRInterceptor.js';
|
|
3
3
|
import axios from 'axios';
|
|
4
|
-
const overrideResponse = false; // Set to true to override the response with a random value
|
|
5
|
-
const filterNonBusinessRelatedAPI = true;
|
|
4
|
+
// const overrideResponse = false; // Set to true to override the response with a random value
|
|
5
|
+
// const filterNonBusinessRelatedAPI = true;
|
|
6
|
+
|
|
7
|
+
const originalFetch = global.fetch;
|
|
6
8
|
|
|
7
9
|
const shouldExclude = (url, method) =>
|
|
8
10
|
['HEAD'].includes(method) ||
|
|
@@ -17,8 +19,8 @@ const parse = (data) => {
|
|
|
17
19
|
return data;
|
|
18
20
|
}
|
|
19
21
|
};
|
|
20
|
-
|
|
21
|
-
export default (maxNumOfApiToStore, blacklists,
|
|
22
|
+
let interceptorId = null;
|
|
23
|
+
export default ({ maxNumOfApiToStore, blacklists, blacklistRef, mocks }) => {
|
|
22
24
|
const [apis, setApis] = useState([]);
|
|
23
25
|
const [bookmarks, setBookmarks] = useState({});
|
|
24
26
|
|
|
@@ -45,11 +47,14 @@ export default (maxNumOfApiToStore, blacklists, interceptResponse, blacklistRef)
|
|
|
45
47
|
const request = {
|
|
46
48
|
...data,
|
|
47
49
|
timestamp: performance.now(),
|
|
50
|
+
datetime: new Date().toLocaleString(),
|
|
48
51
|
time: `${hour}:${minute}:${second}`,
|
|
52
|
+
isMocked: undefined,
|
|
53
|
+
interface: undefined
|
|
49
54
|
};
|
|
50
55
|
setApis((v) => {
|
|
51
56
|
const newData = [
|
|
52
|
-
{ request, id: Date.now().toString(36) + Math.random().toString(36) },
|
|
57
|
+
{ request, id: Date.now().toString(36) + Math.random().toString(36), isMocked: !!data.isMocked, interface: data.interface },
|
|
53
58
|
...(maxNumOfApiToStore ? v.slice(0, maxNumOfApiToStore - 1) : v),
|
|
54
59
|
];
|
|
55
60
|
return newData;
|
|
@@ -82,28 +87,79 @@ export default (maxNumOfApiToStore, blacklists, interceptResponse, blacklistRef)
|
|
|
82
87
|
};
|
|
83
88
|
|
|
84
89
|
useEffect(() => {
|
|
85
|
-
const
|
|
90
|
+
const mocked = (url, method) => {
|
|
91
|
+
// console.log('xxxxxx url method', url, method);
|
|
92
|
+
// console.log('xxxxxx mocks', mocks);
|
|
93
|
+
return mocks.find(
|
|
94
|
+
(m) =>
|
|
95
|
+
m.request.url.toLowerCase() === url.toLowerCase() && m.request.method.toLowerCase() === method.toLowerCase(),
|
|
96
|
+
);
|
|
97
|
+
};
|
|
86
98
|
global.fetch = async function (...args) {
|
|
87
|
-
console.log('[Fetch Request]', args);
|
|
88
|
-
const [url, { headers, method, body }] = args;
|
|
99
|
+
// console.log('xxxxxxxx [Fetch Request]', args);
|
|
100
|
+
const [url, { headers, method, body } = { method: 'get' }] = args;
|
|
89
101
|
|
|
90
102
|
try {
|
|
91
103
|
if (!shouldExclude(url, method)) {
|
|
92
|
-
const data = body ? parse(body): undefined;
|
|
104
|
+
const data = body ? parse(body) : undefined;
|
|
105
|
+
|
|
93
106
|
makeRequest({
|
|
94
107
|
url,
|
|
95
108
|
headers,
|
|
96
109
|
method,
|
|
97
110
|
data,
|
|
111
|
+
interface: 'fetch',
|
|
98
112
|
});
|
|
99
113
|
}
|
|
114
|
+
|
|
115
|
+
// return new Response(JSON.stringify({"xxxx": "xxxxx"}), {
|
|
116
|
+
// status: 444,
|
|
117
|
+
// headers: { 'Content-Type': 'application/json' },
|
|
118
|
+
// });
|
|
119
|
+
|
|
100
120
|
const response = await originalFetch(...args);
|
|
121
|
+
// console.log('xxxxxx [Fetch Response]', response);
|
|
122
|
+
// const data = await response.json();
|
|
123
|
+
// alert('aaaa')
|
|
124
|
+
// const returnedTarget = Object.assign({}, response);
|
|
125
|
+
// const data = await returnedTarget.json();
|
|
126
|
+
|
|
127
|
+
// const reader = response.body.getReader();
|
|
128
|
+
// const decoder = new TextDecoder();
|
|
129
|
+
// let result = '';
|
|
130
|
+
|
|
131
|
+
// function read() {
|
|
132
|
+
// return reader.read().then(({ done, value }) => {
|
|
133
|
+
// if (done) {
|
|
134
|
+
// console.log('Response body:', result);
|
|
135
|
+
// return;
|
|
136
|
+
// }
|
|
137
|
+
// result += decoder.decode(value, { stream: true });
|
|
138
|
+
// return read();
|
|
139
|
+
// });
|
|
140
|
+
// }
|
|
141
|
+
|
|
142
|
+
// const data = read();
|
|
143
|
+
// alert(JSON.stringify(data));
|
|
101
144
|
|
|
102
145
|
receiveResponse({
|
|
103
|
-
config:
|
|
146
|
+
config: {
|
|
147
|
+
url: response.url,
|
|
148
|
+
headers: response.headers,
|
|
149
|
+
method: response.method || method,
|
|
150
|
+
},
|
|
104
151
|
status: response.status,
|
|
152
|
+
// data: { aaa: 'bbb' },
|
|
105
153
|
});
|
|
106
154
|
|
|
155
|
+
// console.log('xxxxxx fetch checking mock');
|
|
156
|
+
const mock = mocked(url, method);
|
|
157
|
+
|
|
158
|
+
if (mock) {
|
|
159
|
+
// console.log('xxxxxx jest mocked', mock);
|
|
160
|
+
}
|
|
161
|
+
// console.log('xxxxxx jest response', response);
|
|
162
|
+
|
|
107
163
|
// console.log('[Fetch Response]', args);
|
|
108
164
|
|
|
109
165
|
// if (overrideResponse) {
|
|
@@ -121,7 +177,11 @@ export default (maxNumOfApiToStore, blacklists, interceptResponse, blacklistRef)
|
|
|
121
177
|
} catch (error) {
|
|
122
178
|
// console.log(error);
|
|
123
179
|
// console.error('[Fetch Error]', error);
|
|
124
|
-
|
|
180
|
+
return new Response(JSON.stringify(error), {
|
|
181
|
+
status: 400,
|
|
182
|
+
headers: { 'Content-Type': 'application/json' },
|
|
183
|
+
});
|
|
184
|
+
// throw error;
|
|
125
185
|
}
|
|
126
186
|
};
|
|
127
187
|
|
|
@@ -133,11 +193,16 @@ export default (maxNumOfApiToStore, blacklists, interceptResponse, blacklistRef)
|
|
|
133
193
|
const { method, url, headers } = config;
|
|
134
194
|
if (!shouldExclude(url, method)) {
|
|
135
195
|
const data = config.data ? parse(config.data) : undefined;
|
|
196
|
+
|
|
197
|
+
const mock = mocked(url, method);
|
|
198
|
+
|
|
136
199
|
makeRequest({
|
|
137
200
|
url,
|
|
138
201
|
headers,
|
|
139
202
|
method,
|
|
140
203
|
data,
|
|
204
|
+
isMocked: !!mock,
|
|
205
|
+
interface: 'axios',
|
|
141
206
|
});
|
|
142
207
|
}
|
|
143
208
|
|
|
@@ -145,11 +210,8 @@ export default (maxNumOfApiToStore, blacklists, interceptResponse, blacklistRef)
|
|
|
145
210
|
},
|
|
146
211
|
(error) => {
|
|
147
212
|
// alert(JSON.stringify(error));
|
|
148
|
-
console.error('[Axios Request Error]', error);
|
|
149
|
-
|
|
150
|
-
// config: error.config,
|
|
151
|
-
// status: error.response?.status,
|
|
152
|
-
// });
|
|
213
|
+
// console.error('[Axios Request Error]', error);
|
|
214
|
+
|
|
153
215
|
receiveResponse({
|
|
154
216
|
config: error.config,
|
|
155
217
|
status: error.response?.status,
|
|
@@ -158,19 +220,28 @@ export default (maxNumOfApiToStore, blacklists, interceptResponse, blacklistRef)
|
|
|
158
220
|
},
|
|
159
221
|
);
|
|
160
222
|
|
|
161
|
-
axios.interceptors.response.
|
|
223
|
+
if (interceptorId) axios.interceptors.response.eject(interceptorId);
|
|
224
|
+
|
|
225
|
+
interceptorId = axios.interceptors.response.use(
|
|
162
226
|
(response) => {
|
|
163
227
|
// console.log('[Axios Response]', response);
|
|
164
228
|
|
|
165
229
|
// if (overrideResponse) {
|
|
166
230
|
// // Replace response.data with random value
|
|
167
|
-
//
|
|
231
|
+
// response.data = { message: 'Random overridden Axios response' };
|
|
232
|
+
// response.status = 200;
|
|
168
233
|
// }
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
234
|
+
// mocked(response.config.url, response.config.method)?.response?.data
|
|
235
|
+
|
|
236
|
+
// console.log('xxxxxx axios checking mock');
|
|
237
|
+
const mock = mocked(response.config.url, response.config.method);
|
|
238
|
+
|
|
239
|
+
if (mock?.response) {
|
|
240
|
+
// console.log('xxxxxx axios mocked', mock);
|
|
241
|
+
response = mock.response;
|
|
242
|
+
}
|
|
243
|
+
// console.log('xxxxxx axios response', response);
|
|
244
|
+
receiveResponse(response);
|
|
174
245
|
|
|
175
246
|
return response;
|
|
176
247
|
},
|
|
@@ -182,7 +253,7 @@ export default (maxNumOfApiToStore, blacklists, interceptResponse, blacklistRef)
|
|
|
182
253
|
return Promise.reject(error);
|
|
183
254
|
},
|
|
184
255
|
);
|
|
185
|
-
}, []);
|
|
256
|
+
}, [mocks]);
|
|
186
257
|
|
|
187
258
|
return { apis, clear: () => setApis([]), bookmarks, setBookmarks };
|
|
188
259
|
};
|
package/Highlight.jsx
CHANGED
|
@@ -3,7 +3,7 @@ import Text from './Text';
|
|
|
3
3
|
|
|
4
4
|
export default ({ text, filter, style = {} }) => {
|
|
5
5
|
if (!filter || !/^[a-zA-Z0-9./]+$/.test(filter)) return <Text style={style}>{text}</Text>;
|
|
6
|
-
const indices = [...text.matchAll(new RegExp(filter, 'gi'))].map((a) => a.index);
|
|
6
|
+
const indices = text ? [...text.matchAll(new RegExp(filter, 'gi'))].map((a) => a.index) : [];
|
|
7
7
|
if (!indices.length) return <Text style={style}>{text}</Text>;
|
|
8
8
|
return (
|
|
9
9
|
<>
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import React, { use, useEffect, useState } from 'react';
|
|
2
|
+
import { View, StyleSheet, TextInput, TouchableOpacity, Alert } from 'react-native';
|
|
3
|
+
import Text from '../Text';
|
|
4
|
+
import X from '../X';
|
|
5
|
+
|
|
6
|
+
const removeId = (key, value) => (key === 'id' ? undefined : value);
|
|
7
|
+
export default (p) => {
|
|
8
|
+
const [tab, setTab] = useState('response');
|
|
9
|
+
const [tmpResBody, setTmpResBody] = useState('');
|
|
10
|
+
const [tmpResStatus, setTmpResStatus] = useState('');
|
|
11
|
+
const [tmpMockDetails, setTmpMockDetails] = useState({});
|
|
12
|
+
|
|
13
|
+
const save = (cb) => {
|
|
14
|
+
if (tab === 'response') {
|
|
15
|
+
try {
|
|
16
|
+
if (!tmpResStatus) {
|
|
17
|
+
return Alert.alert('Sorry', 'Status code is required');
|
|
18
|
+
}
|
|
19
|
+
const tmp = JSON.parse(tmpMockDetails);
|
|
20
|
+
tmp.response.data = JSON.parse(tmpResBody);
|
|
21
|
+
tmp.response.status = parseInt(tmpResStatus, 10);
|
|
22
|
+
p.setMockDetails(tmp);
|
|
23
|
+
|
|
24
|
+
p.setMocks((v) => {
|
|
25
|
+
const index = v.findIndex((m) => m.id === p.mockDetails.id);
|
|
26
|
+
// p.setMockDetails(null);
|
|
27
|
+
if (!~index) {
|
|
28
|
+
const id = Date.now().toString(36) + Math.random().toString(36);
|
|
29
|
+
p.setMockDetails((md) => ({ ...md, id }));
|
|
30
|
+
return [...v, { ...tmp, id }];
|
|
31
|
+
}
|
|
32
|
+
v[index] = { ...v[index], ...tmp };
|
|
33
|
+
p.setMockDetails(v[index]);
|
|
34
|
+
return v;
|
|
35
|
+
});
|
|
36
|
+
// p.setMocks(v => {
|
|
37
|
+
// const tmp = [...v];
|
|
38
|
+
// for(let i = 0; i < tmp.length; i++) {
|
|
39
|
+
// if (tmp[i].id === p.mockDetails.id) {
|
|
40
|
+
// tmp[i] = { ...tmp[i], ...tmp };
|
|
41
|
+
// break;
|
|
42
|
+
// }
|
|
43
|
+
// }
|
|
44
|
+
// // v.forEach(m => m.id === p.mockDetails.id && (m.response = tmp.response));
|
|
45
|
+
// return tmp;
|
|
46
|
+
// })
|
|
47
|
+
cb?.();
|
|
48
|
+
} catch (e) {
|
|
49
|
+
return Alert.alert('Sorry', 'Please fix the response body JSON');
|
|
50
|
+
}
|
|
51
|
+
} else if (tab === 'json') {
|
|
52
|
+
cb?.();
|
|
53
|
+
} else if (tab === 'request') {
|
|
54
|
+
cb?.();
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const reset = () => {
|
|
58
|
+
if (!p.mockDetails) return;
|
|
59
|
+
setTmpMockDetails(JSON.stringify(p.mockDetails, removeId, 4));
|
|
60
|
+
setTmpResBody(JSON.stringify(p.mockDetails.response.data, removeId, 4));
|
|
61
|
+
setTmpResStatus(p.mockDetails.response.status + '' || '');
|
|
62
|
+
};
|
|
63
|
+
useEffect(reset, [p.mockDetails]);
|
|
64
|
+
const [canReset, setCanReset] = useState(false);
|
|
65
|
+
|
|
66
|
+
// const parse = (data) => {
|
|
67
|
+
// try {
|
|
68
|
+
// return JSON.parse(data);
|
|
69
|
+
// } catch (e) {
|
|
70
|
+
// // Alert.alert('Error', 'Invalid JSON');
|
|
71
|
+
// return {};
|
|
72
|
+
// }
|
|
73
|
+
// };
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
try {
|
|
77
|
+
setCanReset(JSON.stringify(p.mockDetails, removeId) != tmpMockDetails);
|
|
78
|
+
} catch (e) {
|
|
79
|
+
setCanReset(true);
|
|
80
|
+
}
|
|
81
|
+
}, [p.mockDetails, tmpMockDetails]);
|
|
82
|
+
if (!p.mockDetails) return null;
|
|
83
|
+
const isnew = !p.mocks.some((m) => m.id === p.mockDetails.id);
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<View
|
|
87
|
+
style={{
|
|
88
|
+
top: 0,
|
|
89
|
+
bottom: 0,
|
|
90
|
+
left: 0,
|
|
91
|
+
right: 0,
|
|
92
|
+
position: 'absolute',
|
|
93
|
+
zIndex: 1000,
|
|
94
|
+
backgroundColor: 'grey',
|
|
95
|
+
width: '100%',
|
|
96
|
+
height: '100%',
|
|
97
|
+
paddingTop: 50,
|
|
98
|
+
}}
|
|
99
|
+
>
|
|
100
|
+
{!isnew && <Text style={{ opacity: 0.3 }}>{p.mockDetails.id}</Text>}
|
|
101
|
+
<View style={{ flexDirection: 'row' }}>
|
|
102
|
+
<Text style={{ flex: 1 }}>{`(${p.mockDetails.request.method}) ${p.mockDetails.request.url}`}</Text>
|
|
103
|
+
<TouchableOpacity onPress={() => p.setMockDetails(null)}>
|
|
104
|
+
<X size={25} />
|
|
105
|
+
</TouchableOpacity>
|
|
106
|
+
</View>
|
|
107
|
+
<View
|
|
108
|
+
style={{
|
|
109
|
+
marginVertical: 10,
|
|
110
|
+
flexDirection: 'row',
|
|
111
|
+
justifyContent: 'space-around',
|
|
112
|
+
backgroundColor: 'black',
|
|
113
|
+
borderRadius: 5,
|
|
114
|
+
padding: 5,
|
|
115
|
+
}}
|
|
116
|
+
>
|
|
117
|
+
{['request', 'response', 'json'].map((item) => {
|
|
118
|
+
const isSelected = item === tab;
|
|
119
|
+
return (
|
|
120
|
+
<TouchableOpacity
|
|
121
|
+
style={{ backgroundColor: isSelected ? 'white' : 'black', padding: 3, borderRadius: 5 }}
|
|
122
|
+
onPress={() => {
|
|
123
|
+
save(() => setTab(item));
|
|
124
|
+
}}
|
|
125
|
+
>
|
|
126
|
+
<Text style={{ textTransform: 'uppercase', color: isSelected ? 'black' : 'white' }}>{item}</Text>
|
|
127
|
+
</TouchableOpacity>
|
|
128
|
+
);
|
|
129
|
+
})}
|
|
130
|
+
</View>
|
|
131
|
+
{tab === 'response' && (
|
|
132
|
+
<View style={{ gap: 3 }}>
|
|
133
|
+
<View style={{ flexDirection: 'row', gap: 5 }}>
|
|
134
|
+
<Text>Status</Text>
|
|
135
|
+
<TextInput
|
|
136
|
+
style={{ backgroundColor: '#ccc', width: 50, padding: 2 }}
|
|
137
|
+
inputMode='numeric'
|
|
138
|
+
onChangeText={(t) => {
|
|
139
|
+
setTmpResStatus(t);
|
|
140
|
+
// try {
|
|
141
|
+
// const newData = JSON.parse(tmpMockDetails);
|
|
142
|
+
// newData.response.status = parseInt(t, 10);
|
|
143
|
+
// setTmpMockDetails(JSON.stringify(newData, removeId, 4));
|
|
144
|
+
// } catch (e) {
|
|
145
|
+
// Alert.alert('Error', 'Invalid JSON');
|
|
146
|
+
// }
|
|
147
|
+
}}
|
|
148
|
+
value={tmpResStatus}
|
|
149
|
+
/>
|
|
150
|
+
</View>
|
|
151
|
+
|
|
152
|
+
<View style={{ flexDirection: 'row', gap: 5 }}>
|
|
153
|
+
<Text>Body</Text>
|
|
154
|
+
<TextInput
|
|
155
|
+
style={{ backgroundColor: '#ccc', height: 250, flex: 1, padding: 2 }}
|
|
156
|
+
multiline
|
|
157
|
+
onChangeText={setTmpResBody}
|
|
158
|
+
value={tmpResBody}
|
|
159
|
+
/>
|
|
160
|
+
</View>
|
|
161
|
+
</View>
|
|
162
|
+
)}
|
|
163
|
+
{tab !== 'response' && <Text style={{ marginTop: 30, textAlign: 'center' }}>To be developed later</Text>}
|
|
164
|
+
{/* {tab === 'json' && (
|
|
165
|
+
<TextInput onChangeText={setTmpMockDetails} value={tmpMockDetails} style={{ height: '40%' }} multiline />
|
|
166
|
+
)} */}
|
|
167
|
+
{tab === 'response' && (
|
|
168
|
+
<View style={{ flexDirection: 'row', justifyContent: 'space-between', padding: 10 }}>
|
|
169
|
+
{!isnew && (
|
|
170
|
+
<TouchableOpacity
|
|
171
|
+
style={styles.actionButton}
|
|
172
|
+
onPress={() => {
|
|
173
|
+
p.deleteMock(p.mockDetails.id);
|
|
174
|
+
p.setMockDetails(null);
|
|
175
|
+
}}
|
|
176
|
+
>
|
|
177
|
+
<Text style={{ fontSize: 15 }}>Delete</Text>
|
|
178
|
+
</TouchableOpacity>
|
|
179
|
+
)}
|
|
180
|
+
{canReset && (
|
|
181
|
+
<TouchableOpacity style={styles.actionButton} onPress={reset}>
|
|
182
|
+
<Text style={{ fontSize: 15 }}>Reset</Text>
|
|
183
|
+
</TouchableOpacity>
|
|
184
|
+
)}
|
|
185
|
+
{canReset && (
|
|
186
|
+
<TouchableOpacity
|
|
187
|
+
style={styles.actionButton}
|
|
188
|
+
onPress={() => {
|
|
189
|
+
// try {
|
|
190
|
+
// const tmp = JSON.parse(tmpMockDetails);
|
|
191
|
+
// if (!tmp.request) {
|
|
192
|
+
// Alert.alert('Error', 'Invalid JSON');
|
|
193
|
+
// return;
|
|
194
|
+
// }
|
|
195
|
+
// p.setMocks((v) => {
|
|
196
|
+
// const index = v.findIndex((m) => m.id === p.mockDetails.id);
|
|
197
|
+
// p.setMockDetails(null);
|
|
198
|
+
// if (!~index) return [...v, { ...tmp, id: Date.now().toString(36) + Math.random().toString(36) }];
|
|
199
|
+
// v[index] = { ...tmp, id: p.mockDetails.id };
|
|
200
|
+
// return v;
|
|
201
|
+
// });
|
|
202
|
+
// } catch (e) {
|
|
203
|
+
// Alert.alert('Error', 'Invalid JSON');
|
|
204
|
+
// }
|
|
205
|
+
save();
|
|
206
|
+
}}
|
|
207
|
+
>
|
|
208
|
+
<Text style={{ fontSize: 15 }}>Save</Text>
|
|
209
|
+
</TouchableOpacity>
|
|
210
|
+
)}
|
|
211
|
+
</View>
|
|
212
|
+
)}
|
|
213
|
+
</View>
|
|
214
|
+
);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const styles = StyleSheet.create({
|
|
218
|
+
actionButton: {
|
|
219
|
+
backgroundColor: 'white',
|
|
220
|
+
borderRadius: 5,
|
|
221
|
+
padding: 4,
|
|
222
|
+
alignItems: 'center',
|
|
223
|
+
justifyContent: 'center',
|
|
224
|
+
},
|
|
225
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { StyleSheet, TouchableHighlight, 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 (p) => {
|
|
9
|
+
const [expand, setExpand] = useState(false);
|
|
10
|
+
return (
|
|
11
|
+
<View>
|
|
12
|
+
<TouchableHighlight underlayColor='#ffffff44' onPress={() => setExpand((v) => !v)}>
|
|
13
|
+
<View
|
|
14
|
+
style={{
|
|
15
|
+
flexDirection: 'row',
|
|
16
|
+
justifyContent: 'space-between',
|
|
17
|
+
alignItems: 'center',
|
|
18
|
+
padding: 5,
|
|
19
|
+
flex: 1,
|
|
20
|
+
}}
|
|
21
|
+
>
|
|
22
|
+
<Text selectable style={{ color: 'white', marginVertical: 10 }}>
|
|
23
|
+
<Highlight
|
|
24
|
+
text={p.item.title.slice(0, MAX_URL_LENGTH) + (p.item.title.length > MAX_URL_LENGTH ? '.....' : '')}
|
|
25
|
+
filter={p.filter}
|
|
26
|
+
/>
|
|
27
|
+
</Text>
|
|
28
|
+
<View>
|
|
29
|
+
<View style={{ padding: 5, backgroundColor: '#777', borderRadius: 8 }}>
|
|
30
|
+
<Text>{p.item.data.length}</Text>
|
|
31
|
+
</View>
|
|
32
|
+
</View>
|
|
33
|
+
</View>
|
|
34
|
+
</TouchableHighlight>
|
|
35
|
+
{expand &&
|
|
36
|
+
p.item.data.map((d) => (
|
|
37
|
+
<View style={{padding: 5}} key={d.id}>
|
|
38
|
+
<Text style={{ color: '#555', fontSize: 8 }}>{d.id}</Text>
|
|
39
|
+
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
|
|
40
|
+
<Text style={{ color: 'grey' }}>{d.request.datetime}</Text>
|
|
41
|
+
<View style={{ flexDirection: 'row', gap: 5 }}>
|
|
42
|
+
<TouchableOpacity style={styles.actionButton} onPress={() => p.deleteMock(d.id)}>
|
|
43
|
+
<Text style={{ color: 'black', fontSize: 10 }}>delete</Text>
|
|
44
|
+
</TouchableOpacity>
|
|
45
|
+
<TouchableOpacity style={styles.actionButton} onPress={() => p.setMockDetails(d)}>
|
|
46
|
+
<Text style={{ color: 'black', fontSize: 10 }}>edit</Text>
|
|
47
|
+
</TouchableOpacity>
|
|
48
|
+
</View>
|
|
49
|
+
</View>
|
|
50
|
+
</View>
|
|
51
|
+
))}
|
|
52
|
+
</View>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const styles = StyleSheet.create({
|
|
57
|
+
actionButton: {
|
|
58
|
+
backgroundColor: 'white',
|
|
59
|
+
borderRadius: 5,
|
|
60
|
+
padding: 4,
|
|
61
|
+
alignItems: 'center',
|
|
62
|
+
justifyContent: 'center',
|
|
63
|
+
},
|
|
64
|
+
});
|
package/Mock/index.jsx
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { FlatList, StyleSheet, TextInput } from 'react-native';
|
|
3
|
+
import MockGroup from './MockGroup';
|
|
4
|
+
|
|
5
|
+
const format = (m) => '(' + m.request.method + ') ' + m.request.url;
|
|
6
|
+
|
|
7
|
+
export default (p) => {
|
|
8
|
+
const [filter, setFilter] = useState('');
|
|
9
|
+
|
|
10
|
+
const data = [];
|
|
11
|
+
p.mocks.forEach((m) => {
|
|
12
|
+
const tmp = format(m);
|
|
13
|
+
if (!data.some((d) => d.title === tmp)) data.push({ title: tmp, data: [m] });
|
|
14
|
+
else data.forEach((d) => d.title === tmp && d.data.push(m));
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<>
|
|
19
|
+
<TextInput
|
|
20
|
+
value={filter}
|
|
21
|
+
placeholder='Filter...'
|
|
22
|
+
placeholderTextColor='grey'
|
|
23
|
+
style={styles.textInput}
|
|
24
|
+
onChangeText={(t) => setFilter(t.toLowerCase())}
|
|
25
|
+
clearButtonMode='always'
|
|
26
|
+
/>
|
|
27
|
+
<FlatList
|
|
28
|
+
contentContainerStyle={{ padding: 5, paddingBottom: 20 }}
|
|
29
|
+
data={data.filter((l) => !filter || l.title.toLowerCase().includes(filter))}
|
|
30
|
+
showsVerticalScrollIndicator
|
|
31
|
+
keyExtractor={(i) => i.title}
|
|
32
|
+
renderItem={(pp) => <MockGroup {...pp} {...p} filter={filter} />}
|
|
33
|
+
/>
|
|
34
|
+
</>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const styles = StyleSheet.create({
|
|
39
|
+
textInput: {
|
|
40
|
+
marginHorizontal: 5,
|
|
41
|
+
padding: 5,
|
|
42
|
+
color: 'white',
|
|
43
|
+
backgroundColor: '#333',
|
|
44
|
+
borderRadius: 8,
|
|
45
|
+
},
|
|
46
|
+
});
|
package/README.md
CHANGED
|
@@ -75,7 +75,6 @@ All FlatList props should work plus props mentioned below
|
|
|
75
75
|
| `labels` | Array of strings | Array of strings you want to show of the floating debugger pill. For each strings in the array will eb displayed as a single line in the floating debugger pill | | Optional |
|
|
76
76
|
| `maxNumOfApiToStore` | integer | Number of APIs to be kept. Too much API might make the whole app lag, therefore need to trade off. Suggested value is 50 | | Optional. If not set, all APIs will be kept forever |
|
|
77
77
|
`version` | string | Any string passed here will be shown in debugger's floating panel. | | Optional. If not supplied, version number will taken automatically using React Native Device Info library. But if Device Info library is not installed, then no version will be shown if this prop is not passed.
|
|
78
|
-
`interceptResponse` | function | Callback function when receive response from any API. | | Optional
|
|
79
78
|
|
|
80
79
|
|
|
81
80
|
### Integration with Third Party Library
|
package/index.jsx
CHANGED
|
@@ -31,9 +31,11 @@ try {
|
|
|
31
31
|
import useAnimation from "./useAnimation";
|
|
32
32
|
import Variables from "./Variables";
|
|
33
33
|
import Api from "./Api";
|
|
34
|
-
import useApiInterceptor from "./useApiInterceptor";
|
|
34
|
+
import useApiInterceptor from "./Api/useApiInterceptor";
|
|
35
35
|
import useStateRef from "./useStateRef";
|
|
36
|
-
import Libs from "
|
|
36
|
+
import Libs from "./Libs";
|
|
37
|
+
import Mock from "./Mock";
|
|
38
|
+
import MockDetails from "./Mock/MockDetails";
|
|
37
39
|
|
|
38
40
|
const fontSize = 7;
|
|
39
41
|
|
|
@@ -57,16 +59,19 @@ const Label = (props) => (
|
|
|
57
59
|
/>
|
|
58
60
|
);
|
|
59
61
|
|
|
62
|
+
let cachedMocks = [];
|
|
63
|
+
LocalStorage.getItem("in-app-debugger-mocked").then((d) => {
|
|
64
|
+
if (d) cachedMocks = JSON.parse(d);
|
|
65
|
+
});
|
|
66
|
+
|
|
60
67
|
export default ({
|
|
61
68
|
variables,
|
|
62
69
|
env,
|
|
63
70
|
version = v,
|
|
64
71
|
maxNumOfApiToStore = 0,
|
|
65
72
|
labels = [],
|
|
66
|
-
interceptResponse,
|
|
67
73
|
tabs = [],
|
|
68
74
|
}) => {
|
|
69
|
-
|
|
70
75
|
const deeplinkPrefix = variables?.DEEPLINK_PREFIX;
|
|
71
76
|
const [blacklists, setB, blacklistRef] = useStateRef([]);
|
|
72
77
|
const dimension = useWindowDimensions();
|
|
@@ -87,26 +92,37 @@ export default ({
|
|
|
87
92
|
}
|
|
88
93
|
};
|
|
89
94
|
|
|
95
|
+
const [tab, setTab] = useState("api");
|
|
96
|
+
const [mocks, setMocks] = useState(cachedMocks);
|
|
97
|
+
const [mockDetails, setMockDetails] = useState();
|
|
98
|
+
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
LocalStorage?.setItem("in-app-debugger-mocked", JSON.stringify(mocks));
|
|
101
|
+
}, [mocks]);
|
|
102
|
+
|
|
90
103
|
if (LocalStorage) {
|
|
91
104
|
useEffect(() => {
|
|
92
105
|
setTimeout(() => {
|
|
93
|
-
LocalStorage.getItem("in-app-debugger-blacklist").then(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
});
|
|
106
|
+
LocalStorage.getItem("in-app-debugger-blacklist").then(
|
|
107
|
+
(d) => d && setBlacklists(JSON.parse(d))
|
|
108
|
+
);
|
|
98
109
|
}, 4000);
|
|
99
110
|
}, []);
|
|
100
111
|
}
|
|
101
112
|
|
|
102
|
-
const { apis, ...restApiInterceptor } = useApiInterceptor(
|
|
113
|
+
const { apis, ...restApiInterceptor } = useApiInterceptor({
|
|
103
114
|
maxNumOfApiToStore,
|
|
104
115
|
blacklists,
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
);
|
|
116
|
+
blacklistRef,
|
|
117
|
+
mocks,
|
|
118
|
+
});
|
|
108
119
|
|
|
109
|
-
const
|
|
120
|
+
const goToMock = (d) => {
|
|
121
|
+
setMockDetails(d);
|
|
122
|
+
//setTab('mock');
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const deleteMock = (id) => setMocks((v) => v.filter((i) => i.id !== id));
|
|
110
126
|
|
|
111
127
|
const errors = apis.filter((a) => a.response?.error).length;
|
|
112
128
|
const numPendingApiCalls = apis.filter((a) => !a.response).length;
|
|
@@ -138,6 +154,7 @@ export default ({
|
|
|
138
154
|
const CustomTabComponent = tabs.find((t) => tab === t.title)?.component;
|
|
139
155
|
const [disapear, setDisapear] = useState();
|
|
140
156
|
if (disapear) return null;
|
|
157
|
+
|
|
141
158
|
return (
|
|
142
159
|
<Animated.View
|
|
143
160
|
style={{
|
|
@@ -186,70 +203,35 @@ export default ({
|
|
|
186
203
|
))}
|
|
187
204
|
</TouchableOpacity>
|
|
188
205
|
) : (
|
|
189
|
-
|
|
190
|
-
<
|
|
191
|
-
{
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
<
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
<TouchableOpacity
|
|
213
|
-
onPress={() => setTab(item)}
|
|
214
|
-
activeOpacity={isSelected ? 1 : 0.7}
|
|
215
|
-
style={{
|
|
216
|
-
paddingHorizontal: 8,
|
|
217
|
-
borderBottomWidth: +isSelected,
|
|
218
|
-
borderColor: "white",
|
|
219
|
-
}}
|
|
220
|
-
>
|
|
221
|
-
<Text
|
|
222
|
-
style={{
|
|
223
|
-
color: "white",
|
|
224
|
-
opacity: isSelected ? 1 : 0.5,
|
|
225
|
-
textAlign: "center",
|
|
226
|
-
textTransform: "uppercase",
|
|
227
|
-
}}
|
|
228
|
-
>
|
|
229
|
-
{item}
|
|
230
|
-
{!index && !!apis.length && <Text> ({apis.length})</Text>}
|
|
231
|
-
</Text>
|
|
232
|
-
</TouchableOpacity>
|
|
233
|
-
);
|
|
234
|
-
}}
|
|
235
|
-
/>
|
|
236
|
-
{/* <View style={{ flex: 1, flexDirection: "row" }}>
|
|
237
|
-
{[
|
|
238
|
-
"api",
|
|
239
|
-
!!variables && "vars",
|
|
240
|
-
"libs",
|
|
241
|
-
...tabs.map((t) => t.title),
|
|
242
|
-
]
|
|
243
|
-
.filter(Boolean)
|
|
244
|
-
.map((t, i) => {
|
|
245
|
-
const isSelected = t === tab;
|
|
206
|
+
<>
|
|
207
|
+
<SafeAreaView style={{ flex: 1 }}>
|
|
208
|
+
<View style={styles.labelContainer}>
|
|
209
|
+
{displayLabels.map((l) => (
|
|
210
|
+
<Label key={l}>{l}</Label>
|
|
211
|
+
))}
|
|
212
|
+
</View>
|
|
213
|
+
<View style={{ flexDirection: "row", padding: 5, gap: 6 }}>
|
|
214
|
+
<FlatList
|
|
215
|
+
style={{ flex: 1 }}
|
|
216
|
+
data={[
|
|
217
|
+
"api",
|
|
218
|
+
"mock",
|
|
219
|
+
!!variables && "vars",
|
|
220
|
+
"deeplink",
|
|
221
|
+
"libs",
|
|
222
|
+
...tabs.map((t) => t.title),
|
|
223
|
+
].filter(Boolean)}
|
|
224
|
+
keyExtractor={(item) => item}
|
|
225
|
+
horizontal
|
|
226
|
+
showsHorizontalScrollIndicator={false}
|
|
227
|
+
renderItem={({ item, index }) => {
|
|
228
|
+
const isSelected = item === tab;
|
|
246
229
|
return (
|
|
247
230
|
<TouchableOpacity
|
|
248
|
-
onPress={() => setTab(
|
|
231
|
+
onPress={() => setTab(item)}
|
|
249
232
|
activeOpacity={isSelected ? 1 : 0.7}
|
|
250
|
-
key={t}
|
|
251
233
|
style={{
|
|
252
|
-
|
|
234
|
+
paddingHorizontal: 8,
|
|
253
235
|
borderBottomWidth: +isSelected,
|
|
254
236
|
borderColor: "white",
|
|
255
237
|
}}
|
|
@@ -262,26 +244,49 @@ export default ({
|
|
|
262
244
|
textTransform: "uppercase",
|
|
263
245
|
}}
|
|
264
246
|
>
|
|
265
|
-
{
|
|
266
|
-
{!
|
|
247
|
+
{item}
|
|
248
|
+
{!index && !!apis.length && (
|
|
249
|
+
<Text> ({apis.length})</Text>
|
|
250
|
+
)}
|
|
251
|
+
{index === 1 && !!mocks.length && (
|
|
252
|
+
<Text> ({mocks.length})</Text>
|
|
253
|
+
)}
|
|
267
254
|
</Text>
|
|
268
255
|
</TouchableOpacity>
|
|
269
256
|
);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
257
|
+
}}
|
|
258
|
+
/>
|
|
259
|
+
<X style={{ marginRight: 5 }} onPress={() => setIsOpen(false)} />
|
|
260
|
+
</View>
|
|
261
|
+
{tab === "vars" && <Variables variables={variables} />}
|
|
262
|
+
{tab === "mock" && (
|
|
263
|
+
<Mock {...{ mocks, setMocks, deleteMock, setMockDetails }} />
|
|
264
|
+
)}
|
|
265
|
+
{tab === "deeplink" && (
|
|
266
|
+
<Deeplink
|
|
267
|
+
deeplinkPrefix={deeplinkPrefix}
|
|
268
|
+
onClose={() => setIsOpen(false)}
|
|
269
|
+
/>
|
|
270
|
+
)}
|
|
271
|
+
{tab === "libs" && <Libs />}
|
|
272
|
+
{tab === "api" && (
|
|
273
|
+
<Api
|
|
274
|
+
{...{
|
|
275
|
+
apis,
|
|
276
|
+
setBlacklists,
|
|
277
|
+
blacklists,
|
|
278
|
+
maxNumOfApiToStore,
|
|
279
|
+
goToMock,
|
|
280
|
+
}}
|
|
281
|
+
{...restApiInterceptor}
|
|
282
|
+
/>
|
|
283
|
+
)}
|
|
284
|
+
{!!CustomTabComponent && <CustomTabComponent />}
|
|
285
|
+
</SafeAreaView>
|
|
286
|
+
<MockDetails
|
|
287
|
+
{...{ mockDetails, setMockDetails, setMocks, deleteMock, mocks }}
|
|
288
|
+
/>
|
|
289
|
+
</>
|
|
285
290
|
)}
|
|
286
291
|
</Animated.View>
|
|
287
292
|
);
|
package/package.json
CHANGED