react-native-inapp-inspector 2.0.4 → 2.0.6

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.
Files changed (108) hide show
  1. package/android/.gradle/8.9/checksums/checksums.lock +0 -0
  2. package/android/.gradle/8.9/checksums/md5-checksums.bin +0 -0
  3. package/android/.gradle/8.9/checksums/sha1-checksums.bin +0 -0
  4. package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
  5. package/android/.gradle/8.9/executionHistory/executionHistory.lock +0 -0
  6. package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
  7. package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
  8. package/android/.gradle/8.9/gc.properties +0 -0
  9. package/android/.gradle/9.2.0/checksums/checksums.lock +0 -0
  10. package/android/.gradle/9.2.0/fileChanges/last-build.bin +0 -0
  11. package/android/.gradle/9.2.0/fileHashes/fileHashes.bin +0 -0
  12. package/android/.gradle/9.2.0/fileHashes/fileHashes.lock +0 -0
  13. package/android/.gradle/9.2.0/gc.properties +0 -0
  14. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  15. package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
  16. package/android/.gradle/vcs-1/gc.properties +0 -0
  17. package/android/build/reports/problems/problems-report.html +659 -0
  18. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.kt +11 -0
  19. package/dist/commonjs/components/Inspector/AuditTab.d.ts +3 -0
  20. package/dist/commonjs/components/Inspector/AuditTab.js +741 -0
  21. package/dist/commonjs/components/Inspector/DeviceTab.d.ts +3 -0
  22. package/dist/commonjs/components/Inspector/DeviceTab.js +717 -0
  23. package/dist/commonjs/components/Inspector/InspectorHeader.js +115 -75
  24. package/dist/commonjs/components/Inspector/MainScreen.js +16 -2
  25. package/dist/commonjs/components/Inspector/NetworkDetail.js +15 -0
  26. package/dist/commonjs/components/Inspector/NetworkTab.js +317 -12
  27. package/dist/commonjs/components/Inspector/NpmUpdateToast.js +88 -39
  28. package/dist/commonjs/components/Inspector/PerformanceHud.d.ts +7 -0
  29. package/dist/commonjs/components/Inspector/PerformanceHud.js +210 -0
  30. package/dist/commonjs/components/Inspector/PerformanceTab.js +30 -18
  31. package/dist/commonjs/components/Inspector/RequestReplayModal.d.ts +9 -0
  32. package/dist/commonjs/components/Inspector/RequestReplayModal.js +792 -0
  33. package/dist/commonjs/components/Inspector/RequestReplayView.d.ts +8 -0
  34. package/dist/commonjs/components/Inspector/RequestReplayView.js +871 -0
  35. package/dist/commonjs/components/Inspector/SettingsPanel.js +847 -370
  36. package/dist/commonjs/components/Inspector/StorageTab.d.ts +3 -0
  37. package/dist/commonjs/components/Inspector/StorageTab.js +610 -0
  38. package/dist/commonjs/components/Inspector/TabBar.js +23 -2
  39. package/dist/commonjs/components/Inspector/UpdateAvailableModal.d.ts +8 -0
  40. package/dist/commonjs/components/Inspector/UpdateAvailableModal.js +469 -0
  41. package/dist/commonjs/components/NetworkIcons.d.ts +12 -0
  42. package/dist/commonjs/components/NetworkIcons.js +72 -3
  43. package/dist/commonjs/constants/version.d.ts +1 -1
  44. package/dist/commonjs/constants/version.js +1 -1
  45. package/dist/commonjs/customHooks/storageInspector.d.ts +13 -0
  46. package/dist/commonjs/customHooks/storageInspector.js +179 -0
  47. package/dist/commonjs/helpers/deviceInfo.d.ts +4 -0
  48. package/dist/commonjs/helpers/deviceInfo.js +94 -0
  49. package/dist/commonjs/helpers/index.d.ts +2 -0
  50. package/dist/commonjs/helpers/index.js +12 -1
  51. package/dist/commonjs/helpers/packageAuditor.d.ts +5 -0
  52. package/dist/commonjs/helpers/packageAuditor.js +182 -0
  53. package/dist/commonjs/helpers/searchQueryParser.d.ts +8 -3
  54. package/dist/commonjs/helpers/searchQueryParser.js +85 -31
  55. package/dist/commonjs/helpers/telemetry.js +4 -4
  56. package/dist/commonjs/i18n/locales/en.json +3 -2
  57. package/dist/commonjs/index.d.ts +1 -0
  58. package/dist/commonjs/index.js +171 -16
  59. package/dist/commonjs/native/NativeInspector.d.ts +1 -0
  60. package/dist/commonjs/types/enums.d.ts +7 -0
  61. package/dist/commonjs/types/enums.js +7 -0
  62. package/dist/commonjs/types/interfaces.d.ts +82 -0
  63. package/dist/esm/components/Inspector/AuditTab.d.ts +3 -0
  64. package/dist/esm/components/Inspector/AuditTab.js +702 -0
  65. package/dist/esm/components/Inspector/DeviceTab.d.ts +3 -0
  66. package/dist/esm/components/Inspector/DeviceTab.js +678 -0
  67. package/dist/esm/components/Inspector/InspectorHeader.js +115 -75
  68. package/dist/esm/components/Inspector/MainScreen.js +16 -2
  69. package/dist/esm/components/Inspector/NetworkDetail.js +16 -1
  70. package/dist/esm/components/Inspector/NetworkTab.js +317 -12
  71. package/dist/esm/components/Inspector/NpmUpdateToast.js +88 -39
  72. package/dist/esm/components/Inspector/PerformanceHud.d.ts +7 -0
  73. package/dist/esm/components/Inspector/PerformanceHud.js +170 -0
  74. package/dist/esm/components/Inspector/PerformanceTab.js +31 -19
  75. package/dist/esm/components/Inspector/RequestReplayModal.d.ts +9 -0
  76. package/dist/esm/components/Inspector/RequestReplayModal.js +752 -0
  77. package/dist/esm/components/Inspector/RequestReplayView.d.ts +8 -0
  78. package/dist/esm/components/Inspector/RequestReplayView.js +831 -0
  79. package/dist/esm/components/Inspector/SettingsPanel.js +848 -371
  80. package/dist/esm/components/Inspector/StorageTab.d.ts +3 -0
  81. package/dist/esm/components/Inspector/StorageTab.js +571 -0
  82. package/dist/esm/components/Inspector/TabBar.js +24 -3
  83. package/dist/esm/components/Inspector/UpdateAvailableModal.d.ts +8 -0
  84. package/dist/esm/components/Inspector/UpdateAvailableModal.js +432 -0
  85. package/dist/esm/components/NetworkIcons.d.ts +12 -0
  86. package/dist/esm/components/NetworkIcons.js +57 -0
  87. package/dist/esm/constants/version.d.ts +1 -1
  88. package/dist/esm/constants/version.js +1 -1
  89. package/dist/esm/customHooks/storageInspector.d.ts +13 -0
  90. package/dist/esm/customHooks/storageInspector.js +170 -0
  91. package/dist/esm/helpers/deviceInfo.d.ts +4 -0
  92. package/dist/esm/helpers/deviceInfo.js +89 -0
  93. package/dist/esm/helpers/index.d.ts +2 -0
  94. package/dist/esm/helpers/index.js +10 -0
  95. package/dist/esm/helpers/packageAuditor.d.ts +5 -0
  96. package/dist/esm/helpers/packageAuditor.js +173 -0
  97. package/dist/esm/helpers/searchQueryParser.d.ts +8 -3
  98. package/dist/esm/helpers/searchQueryParser.js +85 -31
  99. package/dist/esm/helpers/telemetry.js +4 -4
  100. package/dist/esm/i18n/locales/en.json +3 -2
  101. package/dist/esm/index.d.ts +1 -0
  102. package/dist/esm/index.js +167 -15
  103. package/dist/esm/native/NativeInspector.d.ts +1 -0
  104. package/dist/esm/types/enums.d.ts +7 -0
  105. package/dist/esm/types/enums.js +7 -0
  106. package/dist/esm/types/interfaces.d.ts +82 -0
  107. package/ios/NetworkInspectorModule.mm +12 -5
  108. package/package.json +4 -1
@@ -0,0 +1,831 @@
1
+ import React, { useState } from 'react';
2
+ import { ActivityIndicator, Alert, Platform, ScrollView, StyleSheet, Text, TextInput, View, } from 'react-native';
3
+ import TouchableScale from '../TouchableScale';
4
+ import CopyButton from '../CopyButton';
5
+ import JsonViewer from '../JsonViewer';
6
+ import { AppColors } from '../../styles/AppColors';
7
+ import { AppFonts } from '../../styles/AppFonts';
8
+ import { METHOD_COLORS } from '../../constants';
9
+ import { getSize } from '../../helpers';
10
+ import { showToast } from '../../helpers/toast';
11
+ import { ClearIcon, HeadersIcon, RequestIcon, ResponseIcon, SearchIcon, TerminalIcon, WhiteBackNavigation, } from '../NetworkIcons';
12
+ const HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
13
+ export const RequestReplayView = ({ request, onBack, }) => {
14
+ const [method, setMethod] = useState(request.method?.toUpperCase() || 'GET');
15
+ const [url, setUrl] = useState(request.url || '');
16
+ const [activeTab, setActiveTab] = useState('headers');
17
+ // Initialize headers list from request
18
+ const [headers, setHeaders] = useState(() => {
19
+ const rawHeaders = request.requestHeaders || request.headers || {};
20
+ const list = [];
21
+ Object.entries(rawHeaders).forEach(([k, v], idx) => {
22
+ list.push({
23
+ id: `h-${idx}-${Date.now()}`,
24
+ key: k,
25
+ value: typeof v === 'string' ? v : JSON.stringify(v),
26
+ enabled: true,
27
+ });
28
+ });
29
+ if (list.length === 0) {
30
+ list.push({
31
+ id: `h-init-1`,
32
+ key: 'Content-Type',
33
+ value: 'application/json',
34
+ enabled: true,
35
+ });
36
+ }
37
+ return list;
38
+ });
39
+ // Request Body
40
+ const [requestBody, setRequestBody] = useState(() => {
41
+ const raw = request.request ?? request.requestBody;
42
+ if (raw == null)
43
+ return '';
44
+ if (typeof raw === 'string') {
45
+ try {
46
+ return JSON.stringify(JSON.parse(raw), null, 2);
47
+ }
48
+ catch {
49
+ return raw;
50
+ }
51
+ }
52
+ try {
53
+ return JSON.stringify(raw, null, 2);
54
+ }
55
+ catch {
56
+ return String(raw);
57
+ }
58
+ });
59
+ const [isExecuting, setIsExecuting] = useState(false);
60
+ const [executionResult, setExecutionResult] = useState(null);
61
+ const [responseSearch, setResponseSearch] = useState('');
62
+ // Add new header
63
+ const handleAddHeader = () => {
64
+ setHeaders(prev => [
65
+ ...prev,
66
+ {
67
+ id: `h-new-${Date.now()}-${Math.random().toString(36).substr(2, 4)}`,
68
+ key: '',
69
+ value: '',
70
+ enabled: true,
71
+ },
72
+ ]);
73
+ };
74
+ // Toggle header enabled
75
+ const handleToggleHeader = (id) => {
76
+ setHeaders(prev => prev.map(h => (h.id === id ? { ...h, enabled: !h.enabled } : h)));
77
+ };
78
+ // Update header key
79
+ const handleUpdateHeaderKey = (id, key) => {
80
+ setHeaders(prev => prev.map(h => (h.id === id ? { ...h, key } : h)));
81
+ };
82
+ // Update header value
83
+ const handleUpdateHeaderValue = (id, value) => {
84
+ setHeaders(prev => prev.map(h => (h.id === id ? { ...h, value } : h)));
85
+ };
86
+ // Delete header
87
+ const handleDeleteHeader = (id) => {
88
+ setHeaders(prev => prev.filter(h => h.id !== id));
89
+ };
90
+ // Prettify JSON Body
91
+ const handlePrettifyBody = () => {
92
+ try {
93
+ const parsed = JSON.parse(requestBody);
94
+ setRequestBody(JSON.stringify(parsed, null, 2));
95
+ showToast('JSON formatted');
96
+ }
97
+ catch (e) {
98
+ Alert.alert('Invalid JSON', e.message || 'Cannot parse body as valid JSON');
99
+ }
100
+ };
101
+ // Reset to original
102
+ const handleResetToOriginal = () => {
103
+ setMethod(request.method?.toUpperCase() || 'GET');
104
+ setUrl(request.url || '');
105
+ const rawHeaders = request.requestHeaders || request.headers || {};
106
+ const list = [];
107
+ Object.entries(rawHeaders).forEach(([k, v], idx) => {
108
+ list.push({
109
+ id: `h-${idx}-${Date.now()}`,
110
+ key: k,
111
+ value: typeof v === 'string' ? v : JSON.stringify(v),
112
+ enabled: true,
113
+ });
114
+ });
115
+ setHeaders(list);
116
+ const raw = request.request ?? request.requestBody;
117
+ if (raw == null) {
118
+ setRequestBody('');
119
+ }
120
+ else if (typeof raw === 'string') {
121
+ try {
122
+ setRequestBody(JSON.stringify(JSON.parse(raw), null, 2));
123
+ }
124
+ catch {
125
+ setRequestBody(raw);
126
+ }
127
+ }
128
+ else {
129
+ try {
130
+ setRequestBody(JSON.stringify(raw, null, 2));
131
+ }
132
+ catch {
133
+ setRequestBody(String(raw));
134
+ }
135
+ }
136
+ setExecutionResult(null);
137
+ showToast('Reset to original request');
138
+ };
139
+ // Execute API Request
140
+ const handleExecute = async () => {
141
+ if (!url.trim()) {
142
+ Alert.alert('Missing URL', 'Please enter a valid request URL');
143
+ return;
144
+ }
145
+ setIsExecuting(true);
146
+ const startTime = Date.now();
147
+ try {
148
+ // Build Headers Record
149
+ const reqHeaders = {};
150
+ headers.forEach(h => {
151
+ if (h.enabled && h.key.trim()) {
152
+ reqHeaders[h.key.trim()] = h.value;
153
+ }
154
+ });
155
+ const options = {
156
+ method,
157
+ headers: reqHeaders,
158
+ };
159
+ if (method !== 'GET' && requestBody.trim().length > 0) {
160
+ options.body = requestBody;
161
+ }
162
+ const res = await fetch(url.trim(), options);
163
+ const duration = Date.now() - startTime;
164
+ const resHeaders = {};
165
+ res.headers.forEach((val, key) => {
166
+ resHeaders[key] = val;
167
+ });
168
+ const rawText = await res.text();
169
+ const sizeBytes = new Blob([rawText]).size || rawText.length;
170
+ setExecutionResult({
171
+ status: res.status,
172
+ statusText: res.statusText || (res.ok ? 'OK' : 'Error'),
173
+ duration,
174
+ headers: resHeaders,
175
+ body: rawText,
176
+ sizeBytes,
177
+ isError: !res.ok,
178
+ });
179
+ setActiveTab('response');
180
+ showToast(`Finished: ${res.status} in ${duration}ms`);
181
+ }
182
+ catch (err) {
183
+ const duration = Date.now() - startTime;
184
+ setExecutionResult({
185
+ status: 0,
186
+ statusText: err.message || 'Network Request Failed',
187
+ duration,
188
+ headers: {},
189
+ body: JSON.stringify({
190
+ error: true,
191
+ name: err.name || 'FetchError',
192
+ message: err.message || 'Network Request Failed',
193
+ timestamp: new Date().toISOString(),
194
+ }, null, 2),
195
+ sizeBytes: 0,
196
+ isError: true,
197
+ });
198
+ setActiveTab('response');
199
+ showToast('Network error during replay');
200
+ }
201
+ finally {
202
+ setIsExecuting(false);
203
+ }
204
+ };
205
+ return (<View style={styles.pageContainer}>
206
+ {/* ─── 1. PAGE HEADER ────────────────────────────────────────── */}
207
+ <View style={styles.headerBar}>
208
+ <TouchableScale onPress={onBack} style={styles.backBtn} hitSlop={8}>
209
+ <WhiteBackNavigation size={16} color={AppColors.purple}/>
210
+ <Text style={styles.backBtnText}>Details</Text>
211
+ </TouchableScale>
212
+
213
+ <View style={styles.headerTitleBox}>
214
+ <View style={styles.headerTag}>
215
+ <Text style={styles.headerTagText}>ISOLATED REPLAY ENGINE</Text>
216
+ </View>
217
+ <Text style={styles.headerTitle} numberOfLines={1}>
218
+ Edit & Resend
219
+ </Text>
220
+ </View>
221
+
222
+ <TouchableScale onPress={handleResetToOriginal} style={styles.resetBtn} hitSlop={8}>
223
+ <Text style={styles.resetBtnText}>Reset</Text>
224
+ </TouchableScale>
225
+ </View>
226
+
227
+ <ScrollView style={styles.scrollContent} contentContainerStyle={styles.scrollInner} keyboardShouldPersistTaps="handled" showsVerticalScrollIndicator={false}>
228
+ {/* ─── 2. METHOD & URL BAR ──────────────────────────────────── */}
229
+ <View style={styles.urlSection}>
230
+ <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.methodList}>
231
+ {HTTP_METHODS.map(m => {
232
+ const isSelected = method === m;
233
+ const mColor = METHOD_COLORS[m] || AppColors.purple;
234
+ return (<TouchableScale key={m} onPress={() => setMethod(m)} style={[
235
+ styles.methodPill,
236
+ isSelected && {
237
+ backgroundColor: mColor,
238
+ borderColor: mColor,
239
+ },
240
+ ]}>
241
+ <Text style={[
242
+ styles.methodPillText,
243
+ isSelected && { color: AppColors.white },
244
+ ]}>
245
+ {m}
246
+ </Text>
247
+ </TouchableScale>);
248
+ })}
249
+ </ScrollView>
250
+
251
+ <View style={styles.urlInputRow}>
252
+ <TextInput style={styles.urlInput} value={url} onChangeText={setUrl} placeholder="https://api.example.com/endpoint..." placeholderTextColor={AppColors.grayTextWeak} autoCapitalize="none" autoCorrect={false} selectTextOnFocus/>
253
+ {url.length > 0 && (<TouchableScale onPress={() => setUrl('')} style={styles.urlClearBtn} hitSlop={8}>
254
+ <ClearIcon size={14} color={AppColors.grayTextWeak}/>
255
+ </TouchableScale>)}
256
+ </View>
257
+ </View>
258
+
259
+ {/* ─── 3. SEGMENTED TABS (Headers | Body | Response) ────────── */}
260
+ <View style={styles.tabBar}>
261
+ <TouchableScale onPress={() => setActiveTab('headers')} style={[
262
+ styles.tabBtn,
263
+ activeTab === 'headers' && styles.tabBtnActive,
264
+ ]}>
265
+ <HeadersIcon size={12} color={activeTab === 'headers'
266
+ ? AppColors.purple
267
+ : AppColors.grayTextWeak}/>
268
+ <Text style={[
269
+ styles.tabBtnText,
270
+ activeTab === 'headers' && styles.tabBtnTextActive,
271
+ ]}>
272
+ Headers ({headers.filter(h => h.enabled && h.key.trim()).length})
273
+ </Text>
274
+ </TouchableScale>
275
+
276
+ {method !== 'GET' && (<TouchableScale onPress={() => setActiveTab('body')} style={[
277
+ styles.tabBtn,
278
+ activeTab === 'body' && styles.tabBtnActive,
279
+ ]}>
280
+ <RequestIcon size={12} color={activeTab === 'body'
281
+ ? AppColors.purple
282
+ : AppColors.grayTextWeak}/>
283
+ <Text style={[
284
+ styles.tabBtnText,
285
+ activeTab === 'body' && styles.tabBtnTextActive,
286
+ ]}>
287
+ Body {requestBody.trim().length > 0 ? '●' : ''}
288
+ </Text>
289
+ </TouchableScale>)}
290
+
291
+ <TouchableScale onPress={() => setActiveTab('response')} style={[
292
+ styles.tabBtn,
293
+ activeTab === 'response' && styles.tabBtnActive,
294
+ ]}>
295
+ <ResponseIcon size={12} color={activeTab === 'response'
296
+ ? AppColors.purple
297
+ : AppColors.grayTextWeak}/>
298
+ <Text style={[
299
+ styles.tabBtnText,
300
+ activeTab === 'response' && styles.tabBtnTextActive,
301
+ ]}>
302
+ Response{' '}
303
+ {executionResult ? `(${executionResult.status})` : ''}
304
+ </Text>
305
+ </TouchableScale>
306
+ </View>
307
+
308
+ {/* ─── 4. TAB PANELS ────────────────────────────────────────── */}
309
+
310
+ {/* TAB A: HEADERS EDITOR */}
311
+ {activeTab === 'headers' && (<View style={styles.panelContainer}>
312
+ <View style={styles.sectionHeaderRow}>
313
+ <Text style={styles.sectionTitle}>HTTP HEADERS</Text>
314
+ <TouchableScale onPress={handleAddHeader} style={styles.addHeaderBtn} hitSlop={6}>
315
+ <Text style={styles.addHeaderBtnText}>+ Add Header</Text>
316
+ </TouchableScale>
317
+ </View>
318
+
319
+ {headers.map(item => (<View key={item.id} style={styles.headerRowItem}>
320
+ <TouchableScale onPress={() => handleToggleHeader(item.id)} style={[
321
+ styles.checkbox,
322
+ item.enabled && styles.checkboxActive,
323
+ ]} hitSlop={6}>
324
+ {item.enabled && <Text style={styles.checkmark}>✓</Text>}
325
+ </TouchableScale>
326
+
327
+ <TextInput style={[
328
+ styles.headerKeyInput,
329
+ !item.enabled && styles.disabledInput,
330
+ ]} value={item.key} onChangeText={txt => handleUpdateHeaderKey(item.id, txt)} placeholder="Key (e.g. Authorization)" placeholderTextColor={AppColors.grayTextWeak} autoCapitalize="none" autoCorrect={false}/>
331
+
332
+ <TextInput style={[
333
+ styles.headerValueInput,
334
+ !item.enabled && styles.disabledInput,
335
+ ]} value={item.value} onChangeText={txt => handleUpdateHeaderValue(item.id, txt)} placeholder="Value" placeholderTextColor={AppColors.grayTextWeak} autoCapitalize="none" autoCorrect={false}/>
336
+
337
+ <TouchableScale onPress={() => handleDeleteHeader(item.id)} style={styles.deleteHeaderBtn} hitSlop={6}>
338
+ <Text style={styles.deleteHeaderText}>✕</Text>
339
+ </TouchableScale>
340
+ </View>))}
341
+ </View>)}
342
+
343
+ {/* TAB B: REQUEST BODY */}
344
+ {activeTab === 'body' && method !== 'GET' && (<View style={styles.panelContainer}>
345
+ <View style={styles.sectionHeaderRow}>
346
+ <Text style={styles.sectionTitle}>JSON / RAW PAYLOAD</Text>
347
+ <View style={{ flexDirection: 'row', gap: 8 }}>
348
+ <TouchableScale onPress={handlePrettifyBody} style={styles.formatBtn} hitSlop={6}>
349
+ <Text style={styles.formatBtnText}>✨ Prettify JSON</Text>
350
+ </TouchableScale>
351
+ <TouchableScale onPress={() => setRequestBody('')} style={styles.clearBodyBtn} hitSlop={6}>
352
+ <Text style={styles.clearBodyBtnText}>Clear</Text>
353
+ </TouchableScale>
354
+ </View>
355
+ </View>
356
+
357
+ <TextInput style={styles.bodyTextInput} value={requestBody} onChangeText={setRequestBody} placeholder='{\n "key": "value"\n}' placeholderTextColor={AppColors.grayTextWeak} multiline numberOfLines={10} autoCapitalize="none" autoCorrect={false}/>
358
+ </View>)}
359
+
360
+ {/* TAB C: RESPONSE VIEWER */}
361
+ {activeTab === 'response' && (<View style={styles.panelContainer}>
362
+ {executionResult ? (<View>
363
+ {/* Status Bar */}
364
+ <View style={styles.responseSummaryRow}>
365
+ <View style={[
366
+ styles.resStatusBadge,
367
+ {
368
+ backgroundColor: executionResult.isError
369
+ ? `${AppColors.errorColor}18`
370
+ : `${AppColors.greenColor}18`,
371
+ borderColor: executionResult.isError
372
+ ? `${AppColors.errorColor}40`
373
+ : `${AppColors.greenColor}40`,
374
+ },
375
+ ]}>
376
+ <View style={{
377
+ width: 6,
378
+ height: 6,
379
+ borderRadius: 3,
380
+ backgroundColor: executionResult.isError
381
+ ? AppColors.errorColor
382
+ : AppColors.greenColor,
383
+ }}/>
384
+ <Text style={[
385
+ styles.resStatusText,
386
+ {
387
+ color: executionResult.isError
388
+ ? AppColors.errorColor
389
+ : AppColors.greenColor,
390
+ },
391
+ ]}>
392
+ {executionResult.status} {executionResult.statusText}
393
+ </Text>
394
+ </View>
395
+
396
+ <View style={styles.resMetaItem}>
397
+ <Text style={styles.resMetaText}>
398
+ ⏱ {executionResult.duration}ms
399
+ </Text>
400
+ </View>
401
+
402
+ <View style={styles.resMetaItem}>
403
+ <Text style={styles.resMetaText}>
404
+ 📦 {getSize(executionResult.body)}
405
+ </Text>
406
+ </View>
407
+ </View>
408
+
409
+ {/* Search in response */}
410
+ <View style={styles.responseSearchBar}>
411
+ <View style={styles.searchIconBox}>
412
+ <SearchIcon size={12} color={AppColors.grayTextWeak}/>
413
+ </View>
414
+ <TextInput style={styles.responseSearchInput} value={responseSearch} onChangeText={setResponseSearch} placeholder="Search response..." placeholderTextColor={AppColors.grayTextWeak} autoCapitalize="none" autoCorrect={false}/>
415
+ {responseSearch.length > 0 && (<TouchableScale onPress={() => setResponseSearch('')} style={{ padding: 4 }}>
416
+ <ClearIcon size={12} color={AppColors.grayTextWeak}/>
417
+ </TouchableScale>)}
418
+
419
+ <CopyButton value={executionResult.body} label="Copy Body" iconType="copy"/>
420
+ </View>
421
+
422
+ {/* Response Body Output */}
423
+ <View style={styles.responseBox}>
424
+ <JsonViewer data={executionResult.body} search={responseSearch}/>
425
+ </View>
426
+ </View>) : (<View style={styles.emptyResponseBox}>
427
+ <TerminalIcon size={32} color={AppColors.grayTextWeak}/>
428
+ <Text style={styles.emptyResponseTitle}>
429
+ No execution yet
430
+ </Text>
431
+ <Text style={styles.emptyResponseSub}>
432
+ Tap "Send Request" below to trigger this API and view the live response.
433
+ </Text>
434
+ </View>)}
435
+ </View>)}
436
+ </ScrollView>
437
+
438
+ {/* ─── 5. STICKY BOTTOM ACTION BAR ───────────────────────────── */}
439
+ <View style={styles.footerBar}>
440
+ <TouchableScale onPress={handleExecute} disabled={isExecuting} style={[
441
+ styles.sendBtn,
442
+ isExecuting && { backgroundColor: AppColors.grayBorderSecondary },
443
+ ]}>
444
+ {isExecuting ? (<ActivityIndicator size="small" color={AppColors.white}/>) : (<>
445
+ <Text style={styles.sendBtnIcon}>⚡</Text>
446
+ <Text style={styles.sendBtnText}>Send Request</Text>
447
+ </>)}
448
+ </TouchableScale>
449
+ </View>
450
+ </View>);
451
+ };
452
+ const styles = StyleSheet.create({
453
+ pageContainer: {
454
+ flex: 1,
455
+ backgroundColor: AppColors.contentBg,
456
+ },
457
+ headerBar: {
458
+ flexDirection: 'row',
459
+ alignItems: 'center',
460
+ justifyContent: 'space-between',
461
+ paddingHorizontal: 14,
462
+ paddingTop: 10,
463
+ paddingBottom: 10,
464
+ borderBottomWidth: 1,
465
+ borderBottomColor: AppColors.grayBorderSecondary,
466
+ backgroundColor: AppColors.primaryLight,
467
+ },
468
+ backBtn: {
469
+ flexDirection: 'row',
470
+ alignItems: 'center',
471
+ gap: 4,
472
+ paddingHorizontal: 8,
473
+ paddingVertical: 5,
474
+ borderRadius: 8,
475
+ backgroundColor: `${AppColors.purple}14`,
476
+ borderWidth: 1,
477
+ borderColor: `${AppColors.purple}25`,
478
+ },
479
+ backBtnText: {
480
+ fontFamily: AppFonts.interBold,
481
+ fontSize: 12,
482
+ color: AppColors.purple,
483
+ },
484
+ headerTitleBox: {
485
+ alignItems: 'center',
486
+ },
487
+ headerTag: {
488
+ backgroundColor: `${AppColors.purple}14`,
489
+ paddingHorizontal: 6,
490
+ paddingVertical: 2,
491
+ borderRadius: 4,
492
+ marginBottom: 2,
493
+ },
494
+ headerTagText: {
495
+ fontFamily: AppFonts.interBold,
496
+ fontSize: 9,
497
+ color: AppColors.purple,
498
+ letterSpacing: 0.5,
499
+ },
500
+ headerTitle: {
501
+ fontFamily: AppFonts.interBold,
502
+ fontSize: 14.5,
503
+ color: AppColors.primaryBlack,
504
+ },
505
+ resetBtn: {
506
+ paddingHorizontal: 10,
507
+ paddingVertical: 5,
508
+ borderRadius: 8,
509
+ backgroundColor: AppColors.grayBorderSecondary,
510
+ },
511
+ resetBtnText: {
512
+ fontFamily: AppFonts.interMedium,
513
+ fontSize: 11.5,
514
+ color: AppColors.grayText,
515
+ },
516
+ scrollContent: {
517
+ flex: 1,
518
+ },
519
+ scrollInner: {
520
+ padding: 14,
521
+ paddingBottom: 24,
522
+ },
523
+ urlSection: {
524
+ backgroundColor: AppColors.primaryLight,
525
+ borderRadius: 12,
526
+ padding: 10,
527
+ borderWidth: 1,
528
+ borderColor: AppColors.grayBorderSecondary,
529
+ marginBottom: 12,
530
+ },
531
+ methodList: {
532
+ gap: 6,
533
+ paddingBottom: 8,
534
+ },
535
+ methodPill: {
536
+ paddingHorizontal: 10,
537
+ paddingVertical: 4.5,
538
+ borderRadius: 6,
539
+ backgroundColor: AppColors.contentBg,
540
+ borderWidth: 1,
541
+ borderColor: AppColors.grayBorderSecondary,
542
+ },
543
+ methodPillText: {
544
+ fontFamily: AppFonts.interBold,
545
+ fontSize: 11,
546
+ color: AppColors.grayText,
547
+ },
548
+ urlInputRow: {
549
+ flexDirection: 'row',
550
+ alignItems: 'center',
551
+ backgroundColor: AppColors.contentBg,
552
+ borderRadius: 8,
553
+ borderWidth: 1,
554
+ borderColor: AppColors.grayBorderSecondary,
555
+ paddingHorizontal: 10,
556
+ },
557
+ urlInput: {
558
+ flex: 1,
559
+ fontFamily: AppFonts.interMedium,
560
+ fontSize: 12.5,
561
+ color: AppColors.primaryBlack,
562
+ paddingVertical: Platform.OS === 'ios' ? 8 : 6,
563
+ },
564
+ urlClearBtn: {
565
+ padding: 4,
566
+ },
567
+ tabBar: {
568
+ flexDirection: 'row',
569
+ backgroundColor: AppColors.grayBorderSecondary,
570
+ borderRadius: 10,
571
+ padding: 3,
572
+ gap: 4,
573
+ marginBottom: 12,
574
+ },
575
+ tabBtn: {
576
+ flex: 1,
577
+ flexDirection: 'row',
578
+ alignItems: 'center',
579
+ justifyContent: 'center',
580
+ paddingVertical: 6,
581
+ borderRadius: 7,
582
+ gap: 4,
583
+ },
584
+ tabBtnActive: {
585
+ backgroundColor: AppColors.primaryLight,
586
+ shadowColor: AppColors.black,
587
+ shadowOpacity: 0.08,
588
+ shadowRadius: 2,
589
+ shadowOffset: { width: 0, height: 1 },
590
+ },
591
+ tabBtnText: {
592
+ fontFamily: AppFonts.interMedium,
593
+ fontSize: 11.5,
594
+ color: AppColors.grayTextWeak,
595
+ },
596
+ tabBtnTextActive: {
597
+ fontFamily: AppFonts.interBold,
598
+ color: AppColors.purple,
599
+ },
600
+ panelContainer: {
601
+ backgroundColor: AppColors.primaryLight,
602
+ borderRadius: 12,
603
+ borderWidth: 1,
604
+ borderColor: AppColors.grayBorderSecondary,
605
+ padding: 12,
606
+ },
607
+ sectionHeaderRow: {
608
+ flexDirection: 'row',
609
+ justifyContent: 'space-between',
610
+ alignItems: 'center',
611
+ marginBottom: 10,
612
+ },
613
+ sectionTitle: {
614
+ fontFamily: AppFonts.interBold,
615
+ fontSize: 10.5,
616
+ color: AppColors.grayTextWeak,
617
+ letterSpacing: 0.5,
618
+ },
619
+ addHeaderBtn: {
620
+ backgroundColor: `${AppColors.purple}14`,
621
+ paddingHorizontal: 8,
622
+ paddingVertical: 3.5,
623
+ borderRadius: 6,
624
+ },
625
+ addHeaderBtnText: {
626
+ fontFamily: AppFonts.interBold,
627
+ fontSize: 10.5,
628
+ color: AppColors.purple,
629
+ },
630
+ headerRowItem: {
631
+ flexDirection: 'row',
632
+ alignItems: 'center',
633
+ gap: 6,
634
+ marginBottom: 8,
635
+ },
636
+ checkbox: {
637
+ width: 18,
638
+ height: 18,
639
+ borderRadius: 4,
640
+ borderWidth: 1.5,
641
+ borderColor: AppColors.grayBorderSecondary,
642
+ alignItems: 'center',
643
+ justifyContent: 'center',
644
+ backgroundColor: AppColors.contentBg,
645
+ },
646
+ checkboxActive: {
647
+ backgroundColor: AppColors.purple,
648
+ borderColor: AppColors.purple,
649
+ },
650
+ checkmark: {
651
+ color: AppColors.white,
652
+ fontSize: 10,
653
+ fontFamily: AppFonts.interBold,
654
+ },
655
+ headerKeyInput: {
656
+ flex: 1.2,
657
+ backgroundColor: AppColors.contentBg,
658
+ borderRadius: 6,
659
+ borderWidth: 1,
660
+ borderColor: AppColors.grayBorderSecondary,
661
+ paddingHorizontal: 8,
662
+ paddingVertical: Platform.OS === 'ios' ? 6 : 4,
663
+ fontFamily: AppFonts.interMedium,
664
+ fontSize: 11,
665
+ color: AppColors.primaryBlack,
666
+ },
667
+ headerValueInput: {
668
+ flex: 1.8,
669
+ backgroundColor: AppColors.contentBg,
670
+ borderRadius: 6,
671
+ borderWidth: 1,
672
+ borderColor: AppColors.grayBorderSecondary,
673
+ paddingHorizontal: 8,
674
+ paddingVertical: Platform.OS === 'ios' ? 6 : 4,
675
+ fontFamily: AppFonts.interMedium,
676
+ fontSize: 11,
677
+ color: AppColors.primaryBlack,
678
+ },
679
+ disabledInput: {
680
+ opacity: 0.45,
681
+ },
682
+ deleteHeaderBtn: {
683
+ padding: 6,
684
+ },
685
+ deleteHeaderText: {
686
+ fontSize: 12,
687
+ color: AppColors.errorColor,
688
+ },
689
+ formatBtn: {
690
+ backgroundColor: `${AppColors.purple}14`,
691
+ paddingHorizontal: 8,
692
+ paddingVertical: 3.5,
693
+ borderRadius: 6,
694
+ },
695
+ formatBtnText: {
696
+ fontFamily: AppFonts.interBold,
697
+ fontSize: 10.5,
698
+ color: AppColors.purple,
699
+ },
700
+ clearBodyBtn: {
701
+ backgroundColor: AppColors.grayBorderSecondary,
702
+ paddingHorizontal: 8,
703
+ paddingVertical: 3.5,
704
+ borderRadius: 6,
705
+ },
706
+ clearBodyBtnText: {
707
+ fontFamily: AppFonts.interMedium,
708
+ fontSize: 10.5,
709
+ color: AppColors.grayText,
710
+ },
711
+ bodyTextInput: {
712
+ backgroundColor: AppColors.contentBg,
713
+ borderRadius: 8,
714
+ borderWidth: 1,
715
+ borderColor: AppColors.grayBorderSecondary,
716
+ padding: 10,
717
+ fontFamily: AppFonts.interRegular,
718
+ fontSize: 12,
719
+ color: AppColors.primaryBlack,
720
+ minHeight: 180,
721
+ textAlignVertical: 'top',
722
+ },
723
+ responseSummaryRow: {
724
+ flexDirection: 'row',
725
+ alignItems: 'center',
726
+ gap: 8,
727
+ marginBottom: 10,
728
+ flexWrap: 'wrap',
729
+ },
730
+ resStatusBadge: {
731
+ flexDirection: 'row',
732
+ alignItems: 'center',
733
+ gap: 5,
734
+ paddingHorizontal: 8,
735
+ paddingVertical: 4,
736
+ borderRadius: 6,
737
+ borderWidth: 1,
738
+ },
739
+ resStatusText: {
740
+ fontFamily: AppFonts.interBold,
741
+ fontSize: 11,
742
+ },
743
+ resMetaItem: {
744
+ backgroundColor: AppColors.contentBg,
745
+ paddingHorizontal: 8,
746
+ paddingVertical: 4,
747
+ borderRadius: 6,
748
+ borderWidth: 1,
749
+ borderColor: AppColors.grayBorderSecondary,
750
+ },
751
+ resMetaText: {
752
+ fontFamily: AppFonts.interMedium,
753
+ fontSize: 11,
754
+ color: AppColors.grayText,
755
+ },
756
+ responseSearchBar: {
757
+ flexDirection: 'row',
758
+ alignItems: 'center',
759
+ backgroundColor: AppColors.contentBg,
760
+ borderRadius: 8,
761
+ borderWidth: 1,
762
+ borderColor: AppColors.grayBorderSecondary,
763
+ paddingHorizontal: 8,
764
+ paddingVertical: 4,
765
+ marginBottom: 8,
766
+ gap: 6,
767
+ },
768
+ searchIconBox: {
769
+ marginRight: 2,
770
+ },
771
+ responseSearchInput: {
772
+ flex: 1,
773
+ fontFamily: AppFonts.interRegular,
774
+ fontSize: 11.5,
775
+ color: AppColors.primaryBlack,
776
+ paddingVertical: 2,
777
+ },
778
+ responseBox: {
779
+ backgroundColor: AppColors.contentBg,
780
+ borderRadius: 8,
781
+ borderWidth: 1,
782
+ borderColor: AppColors.grayBorderSecondary,
783
+ overflow: 'hidden',
784
+ },
785
+ emptyResponseBox: {
786
+ alignItems: 'center',
787
+ justifyContent: 'center',
788
+ paddingVertical: 32,
789
+ paddingHorizontal: 20,
790
+ gap: 6,
791
+ },
792
+ emptyResponseTitle: {
793
+ fontFamily: AppFonts.interBold,
794
+ fontSize: 13,
795
+ color: AppColors.primaryBlack,
796
+ marginTop: 4,
797
+ },
798
+ emptyResponseSub: {
799
+ fontFamily: AppFonts.interRegular,
800
+ fontSize: 11.5,
801
+ color: AppColors.grayTextWeak,
802
+ textAlign: 'center',
803
+ lineHeight: 16,
804
+ },
805
+ footerBar: {
806
+ paddingHorizontal: 14,
807
+ paddingVertical: 10,
808
+ borderTopWidth: 1,
809
+ borderTopColor: AppColors.grayBorderSecondary,
810
+ backgroundColor: AppColors.primaryLight,
811
+ },
812
+ sendBtn: {
813
+ backgroundColor: AppColors.purple,
814
+ flexDirection: 'row',
815
+ alignItems: 'center',
816
+ justifyContent: 'center',
817
+ paddingVertical: 12,
818
+ borderRadius: 10,
819
+ gap: 6,
820
+ },
821
+ sendBtnIcon: {
822
+ fontSize: 14,
823
+ color: AppColors.white,
824
+ },
825
+ sendBtnText: {
826
+ fontFamily: AppFonts.interBold,
827
+ fontSize: 14,
828
+ color: AppColors.white,
829
+ },
830
+ });
831
+ export default RequestReplayView;