react-native-debug-toolkit 0.1.1
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/README.md +108 -0
- package/android/src/main/java/com/debugtoolkit/BuildTypeModule.java +68 -0
- package/android/src/main/java/com/debugtoolkit/BuildTypePackage.java +24 -0
- package/index.d.ts +155 -0
- package/index.js +10 -0
- package/ios/BuildTypeModule.h +4 -0
- package/ios/BuildTypeModule.m +71 -0
- package/lib/DebugToolKit.js +83 -0
- package/lib/EnvironmentManager.ts +80 -0
- package/lib/features/NetworkFeature.js +330 -0
- package/lib/index.js +6 -0
- package/lib/utils/DebugConst.js +22 -0
- package/lib/views/FloatPanelView.js +491 -0
- package/lib/views/HttpLogDetails.js +666 -0
- package/lib/views/RestartModal.js +75 -0
- package/lib/views/SubViewEnvironment.js +73 -0
- package/lib/views/SubViewHTTPLogs.js +267 -0
- package/lib/views/TabView.js +66 -0
- package/package.json +39 -0
- package/react-native-debug-toolkit-0.1.1.tgz +0 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
class NetworkFeature {
|
|
4
|
+
static instance = null
|
|
5
|
+
static MAX_LOGS = 100
|
|
6
|
+
|
|
7
|
+
constructor() {
|
|
8
|
+
if (NetworkFeature.instance) {
|
|
9
|
+
return NetworkFeature.instance
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
this.logs = []
|
|
13
|
+
this.pendingAxiosRequests = new Map()
|
|
14
|
+
this.originalFetch = null
|
|
15
|
+
|
|
16
|
+
NetworkFeature.instance = this
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
setup() {
|
|
20
|
+
if (!__DEV__) {
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// this._interceptFetch()
|
|
25
|
+
return this
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getData() {
|
|
29
|
+
return this.logs
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
cleanup() {
|
|
33
|
+
if (this.originalFetch) {
|
|
34
|
+
global.fetch = this.originalFetch
|
|
35
|
+
this.originalFetch = null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this.logs = []
|
|
39
|
+
this.pendingAxiosRequests.clear()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
setupAxiosInterceptors(axiosInstance) {
|
|
43
|
+
if (!__DEV__) {
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
axiosInstance.interceptors.request.use(
|
|
48
|
+
(config) => {
|
|
49
|
+
// Generate a unique ID if one doesn't exist
|
|
50
|
+
if (!config.headers) {
|
|
51
|
+
config.headers = {}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!config.headers['X-Request-Id']) {
|
|
55
|
+
config.headers['X-Request-Id'] =
|
|
56
|
+
Date.now().toString() + Math.random().toString(36).substring(2, 10)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const trackId = config.headers['X-Request-Id']
|
|
60
|
+
this.pendingAxiosRequests.set(trackId, {
|
|
61
|
+
timestamp: new Date(),
|
|
62
|
+
startTime: Date.now(),
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
return config
|
|
66
|
+
},
|
|
67
|
+
(error) => {
|
|
68
|
+
this.logAxiosError(error)
|
|
69
|
+
return Promise.reject(error)
|
|
70
|
+
},
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
axiosInstance.interceptors.response.use(
|
|
74
|
+
(response) => {
|
|
75
|
+
this.logAxiosResponse(response)
|
|
76
|
+
return response
|
|
77
|
+
},
|
|
78
|
+
(error) => {
|
|
79
|
+
this.logAxiosError(error)
|
|
80
|
+
return Promise.reject(error)
|
|
81
|
+
},
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
logAxiosResponse(response) {
|
|
86
|
+
if (!response || !response.config || !response.config.headers) {
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const trackId = response.config.headers['X-Request-Id']
|
|
91
|
+
const pendingRequest = this.pendingAxiosRequests.get(trackId)
|
|
92
|
+
|
|
93
|
+
if (!pendingRequest) {
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (this.logs.length >= NetworkFeature.MAX_LOGS) {
|
|
98
|
+
this.logs.shift()
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Calculate duration
|
|
102
|
+
const duration = Date.now() - pendingRequest.startTime
|
|
103
|
+
|
|
104
|
+
const logEntry = {
|
|
105
|
+
timestamp: pendingRequest.timestamp,
|
|
106
|
+
duration: Math.round(duration),
|
|
107
|
+
request: {
|
|
108
|
+
url: `${response.config.baseURL || ''}${response.config.url}`,
|
|
109
|
+
method: response.config.method?.toUpperCase() || 'GET',
|
|
110
|
+
headers: response.config.headers,
|
|
111
|
+
body: response.config.data || response.config.params,
|
|
112
|
+
},
|
|
113
|
+
response: {
|
|
114
|
+
status: response.status,
|
|
115
|
+
statusText: response.statusText,
|
|
116
|
+
headers: response.headers,
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (response.data && typeof response.data === 'object') {
|
|
121
|
+
logEntry.response.success = response.data.success !== false
|
|
122
|
+
logEntry.response.data = response.data
|
|
123
|
+
} else {
|
|
124
|
+
logEntry.response.success =
|
|
125
|
+
response.status >= 200 && response.status < 300
|
|
126
|
+
|
|
127
|
+
if (response.data) {
|
|
128
|
+
logEntry.response.data = response.data
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
this.logs.push(logEntry)
|
|
133
|
+
this.pendingAxiosRequests.delete(trackId)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
logAxiosError(error) {
|
|
137
|
+
if (!error.config) {
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (!error.config.headers) {
|
|
142
|
+
error.config.headers = {}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const trackId = error.config.headers['X-Request-Id']
|
|
146
|
+
const pendingRequest = this.pendingAxiosRequests.get(trackId)
|
|
147
|
+
|
|
148
|
+
const startTime = pendingRequest
|
|
149
|
+
? pendingRequest.startTime
|
|
150
|
+
: Date.now() - 100
|
|
151
|
+
|
|
152
|
+
if (this.logs.length >= NetworkFeature.MAX_LOGS) {
|
|
153
|
+
this.logs.shift()
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const duration = Date.now() - startTime
|
|
157
|
+
|
|
158
|
+
const logEntry = {
|
|
159
|
+
timestamp: pendingRequest ? pendingRequest.timestamp : new Date(),
|
|
160
|
+
duration: Math.round(duration),
|
|
161
|
+
request: {
|
|
162
|
+
url: `${error.config.baseURL || ''}${error.config.url}`,
|
|
163
|
+
method: error.config.method?.toUpperCase() || 'GET',
|
|
164
|
+
headers: error.config.headers,
|
|
165
|
+
body: error.config.data || error.config.params,
|
|
166
|
+
},
|
|
167
|
+
error: error.message,
|
|
168
|
+
success: false,
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (error.response && error.response.data) {
|
|
172
|
+
logEntry.response = {
|
|
173
|
+
status: error.response.status,
|
|
174
|
+
statusText: error.response.statusText,
|
|
175
|
+
headers: error.response.headers,
|
|
176
|
+
data: error.response.data,
|
|
177
|
+
success: false,
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
this.logs.push(logEntry)
|
|
182
|
+
this.pendingAxiosRequests.delete(trackId)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
_interceptFetch() {
|
|
186
|
+
if (!__DEV__) {
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
this.originalFetch = global.fetch
|
|
191
|
+
|
|
192
|
+
global.fetch = async (...args) => {
|
|
193
|
+
const request = args[0]
|
|
194
|
+
const options = args[1] || {}
|
|
195
|
+
const startTime = Date.now()
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
const response = await this.originalFetch(...args)
|
|
199
|
+
this._logFetchResponse(request, options, response.clone(), startTime)
|
|
200
|
+
return response
|
|
201
|
+
} catch (error) {
|
|
202
|
+
this._logFetchError(request, options, error, startTime)
|
|
203
|
+
throw error
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
_logFetchResponse(request, options, response, startTime) {
|
|
209
|
+
if (this.logs.length >= NetworkFeature.MAX_LOGS) {
|
|
210
|
+
this.logs.shift()
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const duration = Date.now() - startTime
|
|
214
|
+
const requestUrl = typeof request === 'string' ? request : request.url
|
|
215
|
+
|
|
216
|
+
const commonData = {
|
|
217
|
+
timestamp: new Date(),
|
|
218
|
+
duration: Math.round(duration),
|
|
219
|
+
request: {
|
|
220
|
+
url: requestUrl,
|
|
221
|
+
method: options.method || 'GET',
|
|
222
|
+
headers: options.headers || {},
|
|
223
|
+
body: options.body,
|
|
224
|
+
},
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Try to parse JSON response
|
|
228
|
+
response
|
|
229
|
+
.text()
|
|
230
|
+
.then((text) => {
|
|
231
|
+
let responseData
|
|
232
|
+
let success = response.status >= 200 && response.status < 300
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
// Try to parse as JSON
|
|
236
|
+
if (text) {
|
|
237
|
+
responseData = JSON.parse(text)
|
|
238
|
+
|
|
239
|
+
// Use API success flag if available
|
|
240
|
+
if (responseData && typeof responseData.success !== 'undefined') {
|
|
241
|
+
success = !!responseData.success
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
} catch (e) {
|
|
245
|
+
// Not JSON, use text response
|
|
246
|
+
responseData = text
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const logEntry = {
|
|
250
|
+
...commonData,
|
|
251
|
+
response: {
|
|
252
|
+
status: response.status,
|
|
253
|
+
statusText: response.statusText,
|
|
254
|
+
headers: this._headersToObject(response.headers),
|
|
255
|
+
data: responseData,
|
|
256
|
+
success: success,
|
|
257
|
+
},
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
this.logs.push(logEntry)
|
|
261
|
+
})
|
|
262
|
+
.catch((err) => {
|
|
263
|
+
// Fallback for when we can't read the response body
|
|
264
|
+
const logEntry = {
|
|
265
|
+
...commonData,
|
|
266
|
+
response: {
|
|
267
|
+
status: response.status,
|
|
268
|
+
statusText: response.statusText,
|
|
269
|
+
headers: this._headersToObject(response.headers),
|
|
270
|
+
success: response.status >= 200 && response.status < 300,
|
|
271
|
+
},
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
this.logs.push(logEntry)
|
|
275
|
+
})
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
_logFetchError(request, options, error, startTime) {
|
|
279
|
+
if (this.logs.length >= NetworkFeature.MAX_LOGS) {
|
|
280
|
+
this.logs.shift()
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const duration = Date.now() - startTime
|
|
284
|
+
|
|
285
|
+
this.logs.push({
|
|
286
|
+
timestamp: new Date(),
|
|
287
|
+
duration: Math.round(duration),
|
|
288
|
+
request: {
|
|
289
|
+
url: typeof request === 'string' ? request : request.url,
|
|
290
|
+
method: options.method || 'GET',
|
|
291
|
+
headers: options.headers || {},
|
|
292
|
+
body: options.body,
|
|
293
|
+
},
|
|
294
|
+
error: error.message,
|
|
295
|
+
success: false,
|
|
296
|
+
})
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Utility to convert Headers object to plain object
|
|
300
|
+
_headersToObject(headers) {
|
|
301
|
+
const result = {}
|
|
302
|
+
|
|
303
|
+
if (headers && typeof headers.forEach === 'function') {
|
|
304
|
+
headers.forEach((value, key) => {
|
|
305
|
+
result[key] = value
|
|
306
|
+
})
|
|
307
|
+
} else if (headers) {
|
|
308
|
+
// Fallback for non-standard headers object
|
|
309
|
+
Object.keys(headers).forEach((key) => {
|
|
310
|
+
result[key] = headers[key]
|
|
311
|
+
})
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return result
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export const createNetworkFeature = () => {
|
|
319
|
+
const feature = new NetworkFeature()
|
|
320
|
+
|
|
321
|
+
return {
|
|
322
|
+
name: 'network',
|
|
323
|
+
label: 'Network Logs',
|
|
324
|
+
setup: () => feature.setup(),
|
|
325
|
+
getData: () => feature.getData(),
|
|
326
|
+
cleanup: () => feature.cleanup(),
|
|
327
|
+
setupAxiosInterceptors: (axiosInstance) =>
|
|
328
|
+
feature.setupAxiosInterceptors(axiosInstance),
|
|
329
|
+
}
|
|
330
|
+
}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Dimensions } from 'react-native';
|
|
2
|
+
|
|
3
|
+
const { width: screenWidth, height: screenHeight } = Dimensions.get('window');
|
|
4
|
+
|
|
5
|
+
export const IconRadius = 50;
|
|
6
|
+
export const PanelWidth = screenWidth * 0.85;
|
|
7
|
+
export const PanelHeight = screenHeight * 0.85;
|
|
8
|
+
|
|
9
|
+
export const DebugColors = {
|
|
10
|
+
white: '#FFFFFF',
|
|
11
|
+
blue: '#007AFF',
|
|
12
|
+
text: '#000000',
|
|
13
|
+
border: '#CCCCCC',
|
|
14
|
+
background: '#F5F5F5',
|
|
15
|
+
success: '#4CD964',
|
|
16
|
+
error: '#FF3B30',
|
|
17
|
+
warning: '#FF9500'
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const DebugImgs = {
|
|
21
|
+
iconLink: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVGhD7ZgxDoMwDEV9/xvQtReoGBASYmFhYmNiYmNhYWJiQQIJqUh8y05wlAYw+T/pD5Dg9xwnhPR6vV6v1+v1er3eGvuOb7yIhxEQvjAC4hRGQJzGCIhLGAFxGSMgbmEExC2MgLiFERC3MALiFkZA3MIIiFsYAXELIyBuYQTELYyAuIURELcwAuIWRkDcwgiIWxgBcQsjIG5hBMQtjIC4hREQtzAC4hZGQNzCCIhbGAFxCyMgbmEExC2MgLiFERC3MALiFkZA3MIIiFsYAXELIyBuYQTELYyAuIURELcwAuIWRkDcwgiIW/+PYBgfhPtL9WBrfKUAAAAASUVORK5CYII='
|
|
22
|
+
};
|