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 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 style={{ flexDirection: "row", justifyContent: "flex-end" }}>
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) => !filter || JSON.stringify(a).toLowerCase().includes(filter)
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={({ section: { data } }) => {
168
- const item = data[0];
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, interceptResponse, blacklistRef) => {
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 originalFetch = global.fetch;
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: response,
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
- throw error;
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
- // receiveResponse({
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.use(
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
- // response.data = { message: 'Random overridden Axios response' };
231
+ // response.data = { message: 'Random overridden Axios response' };
232
+ // response.status = 200;
168
233
  // }
169
- receiveResponse({
170
- config: response.config,
171
- data: response.data,
172
- status: response.status,
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 "react-native-in-app-debugger/Libs";
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((d) => {
94
- if (d) {
95
- setBlacklists(JSON.parse(d));
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
- interceptResponse,
106
- blacklistRef
107
- );
116
+ blacklistRef,
117
+ mocks,
118
+ });
108
119
 
109
- const [tab, setTab] = useState("api");
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
- <SafeAreaView style={{ flex: 1 }}>
190
- <View style={styles.labelContainer}>
191
- {displayLabels.map((l) => (
192
- <Label key={l}>{l}</Label>
193
- ))}
194
- </View>
195
- <View style={{ flexDirection: "row", padding: 5, gap: 6 }}>
196
- <FlatList
197
- style={{flex: 1}}
198
- data={[
199
- "api",
200
- !!variables && "vars",
201
- "deeplink",
202
- "libs",
203
- ...tabs.map((t) => t.title),
204
- ]
205
- .filter(Boolean)}
206
- keyExtractor={(item) => item}
207
- horizontal
208
- showsHorizontalScrollIndicator={false}
209
- renderItem={({item, index})=>{
210
- const isSelected = item === tab;
211
- return (
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(t)}
231
+ onPress={() => setTab(item)}
249
232
  activeOpacity={isSelected ? 1 : 0.7}
250
- key={t}
251
233
  style={{
252
- flex: 1,
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
- {t}
266
- {!i && !!apis.length && <Text> ({apis.length})</Text>}
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
- </View> */}
272
- <X style={{ marginRight: 5 }} onPress={() => setIsOpen(false)} />
273
- </View>
274
- {tab === "vars" && <Variables variables={variables} />}
275
- {tab === "deeplink" && <Deeplink deeplinkPrefix={deeplinkPrefix} onClose={() => setIsOpen(false)} />}
276
- {tab === "libs" && <Libs />}
277
- {tab === "api" && (
278
- <Api
279
- {...{ apis, setBlacklists, blacklists, maxNumOfApiToStore }}
280
- {...restApiInterceptor}
281
- />
282
- )}
283
- {!!CustomTabComponent && <CustomTabComponent />}
284
- </SafeAreaView>
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-in-app-debugger",
3
- "version": "1.0.85",
3
+ "version": "2.0.0",
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": {