react-native-vconsole 0.3.0 → 0.4.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/README.md +17 -2
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/vconsole/VconsoleModule.kt +36 -23
- package/ios/Vconsole.mm +0 -12
- package/lib/module/VConsole.js +333 -60
- package/lib/module/VConsole.js.map +1 -1
- package/lib/module/core/xhrProxy.js +146 -8
- package/lib/module/core/xhrProxy.js.map +1 -1
- package/lib/module/index.js +0 -3
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/VConsole.d.ts +7 -2
- package/lib/typescript/src/VConsole.d.ts.map +1 -1
- package/lib/typescript/src/core/xhrProxy.d.ts +2 -1
- package/lib/typescript/src/core/xhrProxy.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +0 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +3 -1
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/VConsole.tsx +410 -67
- package/src/core/xhrProxy.ts +179 -10
- package/src/index.tsx +0 -4
- package/src/types.ts +3 -1
package/src/VConsole.tsx
CHANGED
|
@@ -4,13 +4,16 @@ import {
|
|
|
4
4
|
Clipboard,
|
|
5
5
|
Dimensions,
|
|
6
6
|
FlatList,
|
|
7
|
+
Keyboard,
|
|
7
8
|
NativeModules,
|
|
8
9
|
PanResponder,
|
|
9
10
|
Platform,
|
|
10
11
|
Pressable,
|
|
12
|
+
TextInput,
|
|
11
13
|
StatusBar,
|
|
12
14
|
StyleSheet,
|
|
13
15
|
Text,
|
|
16
|
+
ToastAndroid,
|
|
14
17
|
View,
|
|
15
18
|
ScrollView,
|
|
16
19
|
type FlatListProps,
|
|
@@ -41,7 +44,7 @@ import type {
|
|
|
41
44
|
const BUTTON_WIDTH = 88;
|
|
42
45
|
const BUTTON_HEIGHT = 36;
|
|
43
46
|
const PANEL_HEIGHT_RATIO = 7 / 9;
|
|
44
|
-
const
|
|
47
|
+
const EMPTY_EXCLUDE: VConsoleExclude = {};
|
|
45
48
|
const LOG_SUB_TABS: LogFilterTab[] = ['All', 'log', 'info', 'warn', 'error'];
|
|
46
49
|
const ROOT_TABS: VConsoleTab[] = ['Log', 'Network', 'System', 'App'];
|
|
47
50
|
|
|
@@ -61,7 +64,12 @@ type NativeModuleShape = {
|
|
|
61
64
|
|
|
62
65
|
export type VConsoleProps = {
|
|
63
66
|
enable?: boolean;
|
|
64
|
-
|
|
67
|
+
exclude?: VConsoleExclude;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
type VConsoleExclude = {
|
|
71
|
+
domains?: string[];
|
|
72
|
+
ip?: boolean;
|
|
65
73
|
};
|
|
66
74
|
|
|
67
75
|
function clamp(value: number, min: number, max: number): number {
|
|
@@ -83,6 +91,36 @@ function copyToClipboard(value: string) {
|
|
|
83
91
|
Clipboard.setString(value);
|
|
84
92
|
}
|
|
85
93
|
|
|
94
|
+
function copyToClipboardWithFeedback(value: string) {
|
|
95
|
+
copyToClipboard(value);
|
|
96
|
+
if (Platform.OS === 'android') {
|
|
97
|
+
ToastAndroid.show('Copied', ToastAndroid.SHORT);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function formatMemorySize(bytes: unknown): string {
|
|
102
|
+
if (typeof bytes !== 'number' || !Number.isFinite(bytes) || bytes < 0) {
|
|
103
|
+
return '-';
|
|
104
|
+
}
|
|
105
|
+
const mb = bytes / (1024 * 1024);
|
|
106
|
+
if (mb >= 1024) {
|
|
107
|
+
return `${(mb / 1024).toFixed(2)} GB`;
|
|
108
|
+
}
|
|
109
|
+
return `${mb.toFixed(2)} MB`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function formatLogTime(timestamp: number): string {
|
|
113
|
+
const date = new Date(timestamp);
|
|
114
|
+
if (Number.isNaN(date.getTime())) {
|
|
115
|
+
return '--:--:--.---';
|
|
116
|
+
}
|
|
117
|
+
const hh = String(date.getHours()).padStart(2, '0');
|
|
118
|
+
const mm = String(date.getMinutes()).padStart(2, '0');
|
|
119
|
+
const ss = String(date.getSeconds()).padStart(2, '0');
|
|
120
|
+
const ms = String(date.getMilliseconds()).padStart(3, '0');
|
|
121
|
+
return `${hh}:${mm}:${ss}.${ms}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
86
124
|
function prettyText(value: unknown): string {
|
|
87
125
|
if (value === undefined) {
|
|
88
126
|
return '';
|
|
@@ -97,19 +135,103 @@ function prettyText(value: unknown): string {
|
|
|
97
135
|
}
|
|
98
136
|
}
|
|
99
137
|
|
|
138
|
+
function isNetworkErrorEntry(item: NetworkEntry): boolean {
|
|
139
|
+
return item.isError === true;
|
|
140
|
+
}
|
|
141
|
+
|
|
100
142
|
function buildNetworkCopyText(item: NetworkEntry): string {
|
|
101
143
|
const status = item.status ?? '-';
|
|
102
144
|
const duration =
|
|
103
145
|
typeof item.durationMs === 'number' ? `${item.durationMs}ms` : '-';
|
|
146
|
+
const isError = isNetworkErrorEntry(item);
|
|
104
147
|
|
|
105
|
-
|
|
148
|
+
const segments = [
|
|
106
149
|
`${item.method} ${item.url}`,
|
|
107
150
|
`status ${status} duration ${duration}`,
|
|
108
151
|
`request headers\n${prettyText(item.requestHeaders)}`,
|
|
109
152
|
`request body\n${prettyText(item.requestBody)}`,
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
if (isError) {
|
|
156
|
+
segments.push(
|
|
157
|
+
`error reason\n${item.errorReason ?? 'Network request failed'}`
|
|
158
|
+
);
|
|
159
|
+
} else {
|
|
160
|
+
segments.push(`response headers\n${prettyText(item.responseHeaders)}`);
|
|
161
|
+
segments.push(`response data\n${prettyText(item.responseData)}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return segments.join('\n');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const FORBIDDEN_RETRY_HEADERS = new Set([
|
|
168
|
+
'host',
|
|
169
|
+
'content-length',
|
|
170
|
+
'accept-encoding',
|
|
171
|
+
'connection',
|
|
172
|
+
'origin',
|
|
173
|
+
'referer',
|
|
174
|
+
]);
|
|
175
|
+
|
|
176
|
+
function normalizeRetryUrl(rawUrl: string): string {
|
|
177
|
+
if (!rawUrl) {
|
|
178
|
+
return '';
|
|
179
|
+
}
|
|
180
|
+
if (/^\/\//.test(rawUrl)) {
|
|
181
|
+
return `https:${rawUrl}`;
|
|
182
|
+
}
|
|
183
|
+
return rawUrl;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function buildRetryHeaders(
|
|
187
|
+
headers: Record<string, string> | undefined
|
|
188
|
+
): Record<string, string> {
|
|
189
|
+
const nextHeaders: Record<string, string> = {};
|
|
190
|
+
if (!headers) {
|
|
191
|
+
return nextHeaders;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
Object.entries(headers).forEach(([key, value]) => {
|
|
195
|
+
if (!FORBIDDEN_RETRY_HEADERS.has(key.toLowerCase())) {
|
|
196
|
+
nextHeaders[key] = value;
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
return nextHeaders;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function buildRetryBody(payload: unknown, method: string): unknown | undefined {
|
|
203
|
+
if (method === 'GET' || method === 'HEAD' || payload == null) {
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
if (typeof payload === 'string') {
|
|
207
|
+
return payload;
|
|
208
|
+
}
|
|
209
|
+
if (typeof payload === 'number' || typeof payload === 'boolean') {
|
|
210
|
+
return String(payload);
|
|
211
|
+
}
|
|
212
|
+
if (typeof FormData !== 'undefined' && payload instanceof FormData) {
|
|
213
|
+
return payload;
|
|
214
|
+
}
|
|
215
|
+
if (
|
|
216
|
+
typeof URLSearchParams !== 'undefined' &&
|
|
217
|
+
payload instanceof URLSearchParams
|
|
218
|
+
) {
|
|
219
|
+
return payload;
|
|
220
|
+
}
|
|
221
|
+
if (typeof Blob !== 'undefined' && payload instanceof Blob) {
|
|
222
|
+
return payload;
|
|
223
|
+
}
|
|
224
|
+
if (typeof ArrayBuffer !== 'undefined' && payload instanceof ArrayBuffer) {
|
|
225
|
+
return payload;
|
|
226
|
+
}
|
|
227
|
+
if (ArrayBuffer.isView(payload)) {
|
|
228
|
+
return payload;
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
return JSON.stringify(payload);
|
|
232
|
+
} catch {
|
|
233
|
+
return String(payload);
|
|
234
|
+
}
|
|
113
235
|
}
|
|
114
236
|
|
|
115
237
|
type ObjectTreeProps = {
|
|
@@ -131,9 +253,21 @@ function ObjectTree({
|
|
|
131
253
|
|
|
132
254
|
const valueType = typeof value;
|
|
133
255
|
if (valueType !== 'object') {
|
|
256
|
+
const displayValue = getDisplayValue(value);
|
|
257
|
+
if (Platform.OS === 'android') {
|
|
258
|
+
return (
|
|
259
|
+
<Pressable
|
|
260
|
+
onLongPress={() => copyToClipboardWithFeedback(displayValue)}
|
|
261
|
+
delayLongPress={250}
|
|
262
|
+
android_ripple={{ color: '#D0D0D0' }}
|
|
263
|
+
>
|
|
264
|
+
<Text style={styles.valuePrimitive}>{displayValue}</Text>
|
|
265
|
+
</Pressable>
|
|
266
|
+
);
|
|
267
|
+
}
|
|
134
268
|
return (
|
|
135
269
|
<Text style={styles.valuePrimitive} selectable={true}>
|
|
136
|
-
{
|
|
270
|
+
{displayValue}
|
|
137
271
|
</Text>
|
|
138
272
|
);
|
|
139
273
|
}
|
|
@@ -194,7 +328,7 @@ function useFlatListRefs() {
|
|
|
194
328
|
|
|
195
329
|
export function VConsole({
|
|
196
330
|
enable = true,
|
|
197
|
-
|
|
331
|
+
exclude = EMPTY_EXCLUDE,
|
|
198
332
|
}: VConsoleProps) {
|
|
199
333
|
const nativeModule = NativeModules.Vconsole as NativeModuleShape | undefined;
|
|
200
334
|
const { width, height } = Dimensions.get('window');
|
|
@@ -227,6 +361,11 @@ export function VConsole({
|
|
|
227
361
|
const [logSubTab, setLogSubTab] = useState<LogFilterTab>('All');
|
|
228
362
|
const [logEntries, setLogEntries] = useState<LogEntry[]>([]);
|
|
229
363
|
const [networkEntries, setNetworkEntries] = useState<NetworkEntry[]>([]);
|
|
364
|
+
const [logFilterInput, setLogFilterInput] = useState('');
|
|
365
|
+
const [networkFilterInput, setNetworkFilterInput] = useState('');
|
|
366
|
+
const [debouncedLogFilter, setDebouncedLogFilter] = useState('');
|
|
367
|
+
const [debouncedNetworkFilter, setDebouncedNetworkFilter] = useState('');
|
|
368
|
+
const [keyboardHeight, setKeyboardHeight] = useState(0);
|
|
230
369
|
const [expandedMap, setExpandedMap] = useState<ExpandedMap>({});
|
|
231
370
|
const [systemInfo, setSystemInfo] = useState<SystemInfo | null>(null);
|
|
232
371
|
const [appInfo, setAppInfo] = useState<AppInfo | null>(null);
|
|
@@ -235,10 +374,14 @@ export function VConsole({
|
|
|
235
374
|
const panelTranslateY = useRef(new Animated.Value(panelHeight)).current;
|
|
236
375
|
const logListRefs = useFlatListRefs();
|
|
237
376
|
const networkListRef = useRef<FlatList<NetworkEntry>>(null);
|
|
238
|
-
const
|
|
239
|
-
() =>
|
|
240
|
-
|
|
377
|
+
const normalizedExcludeDomains = useMemo(
|
|
378
|
+
() =>
|
|
379
|
+
(exclude.domains ?? [])
|
|
380
|
+
.map((item) => item.trim().toLowerCase())
|
|
381
|
+
.filter(Boolean),
|
|
382
|
+
[exclude.domains]
|
|
241
383
|
);
|
|
384
|
+
const shouldExcludeIp = exclude.ip === true;
|
|
242
385
|
|
|
243
386
|
useEffect(() => {
|
|
244
387
|
if (!enable) {
|
|
@@ -247,7 +390,10 @@ export function VConsole({
|
|
|
247
390
|
}
|
|
248
391
|
|
|
249
392
|
installConsoleProxy();
|
|
250
|
-
installXhrProxy({
|
|
393
|
+
installXhrProxy({
|
|
394
|
+
excludeHosts: normalizedExcludeDomains,
|
|
395
|
+
excludeIp: shouldExcludeIp,
|
|
396
|
+
});
|
|
251
397
|
|
|
252
398
|
const unsubscribeLog = subscribeLogEntries(setLogEntries);
|
|
253
399
|
const unsubscribeNetwork = subscribeNetworkEntries(setNetworkEntries);
|
|
@@ -260,7 +406,7 @@ export function VConsole({
|
|
|
260
406
|
uninstallConsoleProxy();
|
|
261
407
|
uninstallXhrProxy();
|
|
262
408
|
};
|
|
263
|
-
}, [enable,
|
|
409
|
+
}, [enable, normalizedExcludeDomains, shouldExcludeIp]);
|
|
264
410
|
|
|
265
411
|
useEffect(() => {
|
|
266
412
|
dragPosition.stopAnimation((value) => {
|
|
@@ -286,6 +432,39 @@ export function VConsole({
|
|
|
286
432
|
}
|
|
287
433
|
}, [activeTab, appInfo, nativeModule, panelVisible, systemInfo]);
|
|
288
434
|
|
|
435
|
+
useEffect(() => {
|
|
436
|
+
const showEvent =
|
|
437
|
+
Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
|
|
438
|
+
const hideEvent =
|
|
439
|
+
Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';
|
|
440
|
+
|
|
441
|
+
const showSubscription = Keyboard.addListener(showEvent, (event) => {
|
|
442
|
+
setKeyboardHeight(event.endCoordinates?.height ?? 0);
|
|
443
|
+
});
|
|
444
|
+
const hideSubscription = Keyboard.addListener(hideEvent, () => {
|
|
445
|
+
setKeyboardHeight(0);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
return () => {
|
|
449
|
+
showSubscription.remove();
|
|
450
|
+
hideSubscription.remove();
|
|
451
|
+
};
|
|
452
|
+
}, []);
|
|
453
|
+
|
|
454
|
+
useEffect(() => {
|
|
455
|
+
const timer = setTimeout(() => {
|
|
456
|
+
setDebouncedLogFilter(logFilterInput);
|
|
457
|
+
}, 1000);
|
|
458
|
+
return () => clearTimeout(timer);
|
|
459
|
+
}, [logFilterInput]);
|
|
460
|
+
|
|
461
|
+
useEffect(() => {
|
|
462
|
+
const timer = setTimeout(() => {
|
|
463
|
+
setDebouncedNetworkFilter(networkFilterInput);
|
|
464
|
+
}, 1000);
|
|
465
|
+
return () => clearTimeout(timer);
|
|
466
|
+
}, [networkFilterInput]);
|
|
467
|
+
|
|
289
468
|
const panResponder = useMemo(
|
|
290
469
|
() =>
|
|
291
470
|
PanResponder.create({
|
|
@@ -339,15 +518,36 @@ export function VConsole({
|
|
|
339
518
|
});
|
|
340
519
|
};
|
|
341
520
|
|
|
521
|
+
const normalizedLogFilter = debouncedLogFilter.trim().toLowerCase();
|
|
522
|
+
const normalizedNetworkFilter = debouncedNetworkFilter.trim().toLowerCase();
|
|
523
|
+
|
|
524
|
+
const filteredLogEntries = useMemo(() => {
|
|
525
|
+
if (!normalizedLogFilter) {
|
|
526
|
+
return logEntries;
|
|
527
|
+
}
|
|
528
|
+
return logEntries.filter((item) =>
|
|
529
|
+
item.text.toLowerCase().includes(normalizedLogFilter)
|
|
530
|
+
);
|
|
531
|
+
}, [logEntries, normalizedLogFilter]);
|
|
532
|
+
|
|
533
|
+
const filteredNetworkEntries = useMemo(() => {
|
|
534
|
+
if (!normalizedNetworkFilter) {
|
|
535
|
+
return networkEntries;
|
|
536
|
+
}
|
|
537
|
+
return networkEntries.filter((item) =>
|
|
538
|
+
item.url.toLowerCase().includes(normalizedNetworkFilter)
|
|
539
|
+
);
|
|
540
|
+
}, [networkEntries, normalizedNetworkFilter]);
|
|
541
|
+
|
|
342
542
|
const logDataByTab = useMemo(
|
|
343
543
|
() => ({
|
|
344
|
-
All:
|
|
345
|
-
log:
|
|
346
|
-
info:
|
|
347
|
-
warn:
|
|
348
|
-
error:
|
|
544
|
+
All: filteredLogEntries,
|
|
545
|
+
log: filteredLogEntries.filter((item) => item.level === 'log'),
|
|
546
|
+
info: filteredLogEntries.filter((item) => item.level === 'info'),
|
|
547
|
+
warn: filteredLogEntries.filter((item) => item.level === 'warn'),
|
|
548
|
+
error: filteredLogEntries.filter((item) => item.level === 'error'),
|
|
349
549
|
}),
|
|
350
|
-
[
|
|
550
|
+
[filteredLogEntries]
|
|
351
551
|
);
|
|
352
552
|
|
|
353
553
|
const onToggleNode = (key: string) => {
|
|
@@ -376,6 +576,39 @@ export function VConsole({
|
|
|
376
576
|
networkListRef.current?.scrollToEnd({ animated: true });
|
|
377
577
|
};
|
|
378
578
|
|
|
579
|
+
const retryNetworkRequest = (item: NetworkEntry) => {
|
|
580
|
+
const method = (item.method || 'GET').toUpperCase();
|
|
581
|
+
const url = normalizeRetryUrl(item.url);
|
|
582
|
+
if (!url) {
|
|
583
|
+
console.error('[vConsole] Retry failed: empty request URL');
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
const headers = buildRetryHeaders(item.requestHeaders);
|
|
588
|
+
const body = buildRetryBody(item.requestBody, method);
|
|
589
|
+
const hasContentType = Object.keys(headers).some(
|
|
590
|
+
(key) => key.toLowerCase() === 'content-type'
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
if (
|
|
594
|
+
body &&
|
|
595
|
+
typeof body === 'string' &&
|
|
596
|
+
typeof item.requestBody === 'object' &&
|
|
597
|
+
item.requestBody !== null &&
|
|
598
|
+
!hasContentType
|
|
599
|
+
) {
|
|
600
|
+
headers['Content-Type'] = 'application/json';
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
fetch(url, {
|
|
604
|
+
method,
|
|
605
|
+
headers,
|
|
606
|
+
body: body as never,
|
|
607
|
+
}).catch((error: unknown) => {
|
|
608
|
+
console.error('[vConsole] Retry request failed', error);
|
|
609
|
+
});
|
|
610
|
+
};
|
|
611
|
+
|
|
379
612
|
const renderRootTab = (tab: VConsoleTab) => (
|
|
380
613
|
<Pressable
|
|
381
614
|
key={tab}
|
|
@@ -414,6 +647,10 @@ export function VConsole({
|
|
|
414
647
|
<View style={styles.listItemMain}>
|
|
415
648
|
<Text style={[styles.logLevelText, { color: levelTheme.color }]}>
|
|
416
649
|
[{item.level.toUpperCase()}]
|
|
650
|
+
<Text style={styles.logTimeText}>
|
|
651
|
+
{' '}
|
|
652
|
+
{formatLogTime(item.timestamp)}
|
|
653
|
+
</Text>
|
|
417
654
|
</Text>
|
|
418
655
|
<View style={styles.logPayload}>
|
|
419
656
|
{item.args.map((arg, index) => (
|
|
@@ -429,9 +666,9 @@ export function VConsole({
|
|
|
429
666
|
</View>
|
|
430
667
|
<Pressable
|
|
431
668
|
style={styles.copyButton}
|
|
432
|
-
onPress={() =>
|
|
669
|
+
onPress={() => copyToClipboardWithFeedback(item.text)}
|
|
433
670
|
>
|
|
434
|
-
<Text style={styles.copyButtonText}
|
|
671
|
+
<Text style={styles.copyButtonText}>Copy</Text>
|
|
435
672
|
</Pressable>
|
|
436
673
|
</View>
|
|
437
674
|
);
|
|
@@ -440,18 +677,31 @@ export function VConsole({
|
|
|
440
677
|
const renderNetworkItem: FlatListProps<NetworkEntry>['renderItem'] = ({
|
|
441
678
|
item,
|
|
442
679
|
}) => {
|
|
680
|
+
const isError = isNetworkErrorEntry(item);
|
|
681
|
+
const startedTime = formatLogTime(item.startedAt);
|
|
682
|
+
const finishedTime =
|
|
683
|
+
typeof item.finishedAt === 'number'
|
|
684
|
+
? formatLogTime(item.finishedAt)
|
|
685
|
+
: '-';
|
|
443
686
|
return (
|
|
444
|
-
<View
|
|
687
|
+
<View
|
|
688
|
+
style={[
|
|
689
|
+
styles.listItem,
|
|
690
|
+
isError ? { backgroundColor: LOG_THEME.error.backgroundColor } : null,
|
|
691
|
+
]}
|
|
692
|
+
>
|
|
445
693
|
<View style={styles.listItemMain}>
|
|
446
694
|
<Text style={styles.networkTitle}>
|
|
447
695
|
{item.method} {item.url}
|
|
448
696
|
</Text>
|
|
449
697
|
<Text style={styles.networkLabel}>
|
|
450
|
-
|
|
698
|
+
Time: {startedTime}
|
|
699
|
+
{finishedTime !== '-' ? ` ~ ${finishedTime}` : ''}
|
|
451
700
|
{' '}
|
|
452
701
|
Duration:{' '}
|
|
453
702
|
{typeof item.durationMs === 'number' ? `${item.durationMs}ms` : '-'}
|
|
454
703
|
</Text>
|
|
704
|
+
<Text style={styles.networkLabel}>Status: {item.status ?? '-'}</Text>
|
|
455
705
|
<View style={styles.networkBlock}>
|
|
456
706
|
<Text style={styles.networkLabel}>Request Headers</Text>
|
|
457
707
|
<ObjectTree
|
|
@@ -470,39 +720,60 @@ export function VConsole({
|
|
|
470
720
|
onToggle={onToggleNode}
|
|
471
721
|
/>
|
|
472
722
|
</View>
|
|
473
|
-
|
|
474
|
-
<
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
723
|
+
{isError ? (
|
|
724
|
+
<View style={styles.networkBlock}>
|
|
725
|
+
<Text style={[styles.networkLabel, styles.networkErrorLabel]}>
|
|
726
|
+
Error Reason
|
|
727
|
+
</Text>
|
|
728
|
+
<Text style={styles.networkErrorText}>
|
|
729
|
+
{item.errorReason ?? 'Network request failed'}
|
|
730
|
+
</Text>
|
|
731
|
+
</View>
|
|
732
|
+
) : (
|
|
733
|
+
<>
|
|
734
|
+
<View style={styles.networkBlock}>
|
|
735
|
+
<Text style={styles.networkLabel}>Response Headers</Text>
|
|
736
|
+
<ObjectTree
|
|
737
|
+
value={item.responseHeaders}
|
|
738
|
+
nodeKey={`${item.id}.responseHeaders`}
|
|
739
|
+
expandedMap={expandedMap}
|
|
740
|
+
onToggle={onToggleNode}
|
|
741
|
+
/>
|
|
742
|
+
</View>
|
|
743
|
+
<View style={styles.networkBlock}>
|
|
744
|
+
<Text style={styles.networkLabel}>Response Data</Text>
|
|
745
|
+
<ScrollView horizontal={true}>
|
|
746
|
+
<ObjectTree
|
|
747
|
+
value={item.responseData ?? ''}
|
|
748
|
+
nodeKey={`${item.id}.responseData`}
|
|
749
|
+
expandedMap={expandedMap}
|
|
750
|
+
onToggle={onToggleNode}
|
|
751
|
+
/>
|
|
752
|
+
</ScrollView>
|
|
753
|
+
</View>
|
|
754
|
+
</>
|
|
755
|
+
)}
|
|
493
756
|
</View>
|
|
494
757
|
<Pressable
|
|
495
758
|
style={styles.copyButton}
|
|
496
|
-
onPress={() =>
|
|
759
|
+
onPress={() =>
|
|
760
|
+
copyToClipboardWithFeedback(buildNetworkCopyText(item))
|
|
761
|
+
}
|
|
762
|
+
>
|
|
763
|
+
<Text style={styles.copyButtonText}>Copy</Text>
|
|
764
|
+
</Pressable>
|
|
765
|
+
<Pressable
|
|
766
|
+
style={styles.retryButton}
|
|
767
|
+
onPress={() => retryNetworkRequest(item)}
|
|
497
768
|
>
|
|
498
|
-
<Text style={styles.
|
|
769
|
+
<Text style={styles.retryButtonText}>Retry</Text>
|
|
499
770
|
</Pressable>
|
|
500
771
|
</View>
|
|
501
772
|
);
|
|
502
773
|
};
|
|
503
774
|
|
|
504
|
-
const renderLogPanel = () => (
|
|
505
|
-
<View style={styles.contentArea}>
|
|
775
|
+
const renderLogPanel = (visible: boolean) => (
|
|
776
|
+
<View style={[styles.contentArea, visible ? {} : styles.hidden]}>
|
|
506
777
|
<View style={styles.subTabRow}>
|
|
507
778
|
{LOG_SUB_TABS.map((tab) => (
|
|
508
779
|
<Pressable
|
|
@@ -540,6 +811,16 @@ export function VConsole({
|
|
|
540
811
|
</View>
|
|
541
812
|
))}
|
|
542
813
|
</View>
|
|
814
|
+
<View style={styles.filterInputWrap}>
|
|
815
|
+
<TextInput
|
|
816
|
+
style={styles.filterInput}
|
|
817
|
+
textAlignVertical="center"
|
|
818
|
+
value={logFilterInput}
|
|
819
|
+
onChangeText={setLogFilterInput}
|
|
820
|
+
placeholder="filter..."
|
|
821
|
+
placeholderTextColor="#999999"
|
|
822
|
+
/>
|
|
823
|
+
</View>
|
|
543
824
|
<View style={styles.actionsRow}>
|
|
544
825
|
{renderActionButton('Clear', () => {
|
|
545
826
|
clearLogEntries();
|
|
@@ -552,15 +833,24 @@ export function VConsole({
|
|
|
552
833
|
</View>
|
|
553
834
|
);
|
|
554
835
|
|
|
555
|
-
const renderNetworkPanel = () => (
|
|
556
|
-
<View style={styles.contentArea}>
|
|
836
|
+
const renderNetworkPanel = (visible: boolean) => (
|
|
837
|
+
<View style={[styles.contentArea, visible ? {} : styles.hidden]}>
|
|
557
838
|
<FlatList
|
|
558
839
|
ref={networkListRef}
|
|
559
|
-
data={
|
|
840
|
+
data={filteredNetworkEntries}
|
|
560
841
|
keyExtractor={(item) => `network-${item.id}`}
|
|
561
842
|
renderItem={renderNetworkItem}
|
|
562
843
|
ItemSeparatorComponent={ListSeparator}
|
|
563
844
|
/>
|
|
845
|
+
<View style={styles.filterInputWrap}>
|
|
846
|
+
<TextInput
|
|
847
|
+
style={styles.filterInput}
|
|
848
|
+
value={networkFilterInput}
|
|
849
|
+
onChangeText={setNetworkFilterInput}
|
|
850
|
+
placeholder="filter"
|
|
851
|
+
placeholderTextColor="#999999"
|
|
852
|
+
/>
|
|
853
|
+
</View>
|
|
564
854
|
<View style={styles.actionsRow}>
|
|
565
855
|
{renderActionButton('Clear', () => {
|
|
566
856
|
clearNetworkEntries();
|
|
@@ -573,32 +863,33 @@ export function VConsole({
|
|
|
573
863
|
</View>
|
|
574
864
|
);
|
|
575
865
|
|
|
576
|
-
const renderSystemPanel = () => (
|
|
577
|
-
<View style={styles.contentArea}>
|
|
866
|
+
const renderSystemPanel = (visible: boolean) => (
|
|
867
|
+
<View style={[styles.contentArea, visible ? {} : styles.hidden]}>
|
|
578
868
|
<View style={styles.infoCard}>
|
|
579
869
|
<Text style={styles.infoText}>
|
|
580
|
-
|
|
870
|
+
Brand: {systemInfo?.manufacturer ?? '-'}
|
|
581
871
|
</Text>
|
|
582
|
-
<Text style={styles.infoText}
|
|
872
|
+
<Text style={styles.infoText}>Model: {systemInfo?.model ?? '-'}</Text>
|
|
583
873
|
<Text style={styles.infoText}>
|
|
584
|
-
|
|
874
|
+
System Version: {Platform.OS === 'android' ? 'Android' : 'iOS'}{' '}
|
|
875
|
+
{systemInfo?.osVersion ?? '-'}
|
|
585
876
|
</Text>
|
|
586
877
|
{Platform.OS === 'android' ? (
|
|
587
878
|
<Text style={styles.infoText}>
|
|
588
|
-
|
|
879
|
+
Network Type: {systemInfo?.networkType ?? '-'}
|
|
589
880
|
</Text>
|
|
590
881
|
) : null}
|
|
591
882
|
{Platform.OS === 'android' ? (
|
|
592
883
|
<Text style={styles.infoText}>
|
|
593
|
-
|
|
884
|
+
Network Reachable: {systemInfo?.isNetworkReachable ?? 'unknown'}
|
|
594
885
|
</Text>
|
|
595
886
|
) : null}
|
|
596
887
|
<Text style={styles.infoText}>
|
|
597
|
-
|
|
888
|
+
Total Memory: {formatMemorySize(systemInfo?.totalMemory)}
|
|
598
889
|
</Text>
|
|
599
890
|
{Platform.OS === 'android' ? (
|
|
600
891
|
<Text style={styles.infoText}>
|
|
601
|
-
|
|
892
|
+
Available Memory: {formatMemorySize(systemInfo?.availableMemory)}
|
|
602
893
|
</Text>
|
|
603
894
|
) : null}
|
|
604
895
|
</View>
|
|
@@ -608,8 +899,8 @@ export function VConsole({
|
|
|
608
899
|
</View>
|
|
609
900
|
);
|
|
610
901
|
|
|
611
|
-
const renderAppPanel = () => (
|
|
612
|
-
<View style={styles.contentArea}>
|
|
902
|
+
const renderAppPanel = (visible: boolean) => (
|
|
903
|
+
<View style={[styles.contentArea, visible ? {} : styles.hidden]}>
|
|
613
904
|
<View style={styles.infoCard}>
|
|
614
905
|
<Text style={styles.infoText}>
|
|
615
906
|
App Version: {appInfo?.appVersion ?? '-'}
|
|
@@ -652,15 +943,16 @@ export function VConsole({
|
|
|
652
943
|
styles.panel,
|
|
653
944
|
{
|
|
654
945
|
height: panelHeight,
|
|
946
|
+
marginBottom: keyboardHeight,
|
|
655
947
|
transform: [{ translateY: panelTranslateY }],
|
|
656
948
|
},
|
|
657
949
|
]}
|
|
658
950
|
>
|
|
659
951
|
<View style={styles.topTabRow}>{ROOT_TABS.map(renderRootTab)}</View>
|
|
660
|
-
{activeTab === 'Log'
|
|
661
|
-
{activeTab === 'Network'
|
|
662
|
-
{activeTab === 'System'
|
|
663
|
-
{activeTab === 'App'
|
|
952
|
+
{renderLogPanel(activeTab === 'Log')}
|
|
953
|
+
{renderNetworkPanel(activeTab === 'Network')}
|
|
954
|
+
{renderSystemPanel(activeTab === 'System')}
|
|
955
|
+
{renderAppPanel(activeTab === 'App')}
|
|
664
956
|
</Animated.View>
|
|
665
957
|
</View>
|
|
666
958
|
) : null}
|
|
@@ -676,7 +968,7 @@ const styles = StyleSheet.create({
|
|
|
676
968
|
floatingButton: {
|
|
677
969
|
width: BUTTON_WIDTH,
|
|
678
970
|
height: BUTTON_HEIGHT,
|
|
679
|
-
borderRadius:
|
|
971
|
+
borderRadius: 8,
|
|
680
972
|
backgroundColor: '#22A455',
|
|
681
973
|
justifyContent: 'center',
|
|
682
974
|
alignItems: 'center',
|
|
@@ -726,7 +1018,7 @@ const styles = StyleSheet.create({
|
|
|
726
1018
|
},
|
|
727
1019
|
contentArea: {
|
|
728
1020
|
flex: 1,
|
|
729
|
-
paddingBottom: 16,
|
|
1021
|
+
paddingBottom: Platform.OS === 'android' ? 42 : 16,
|
|
730
1022
|
},
|
|
731
1023
|
subTabRow: {
|
|
732
1024
|
flexDirection: 'row',
|
|
@@ -779,6 +1071,11 @@ const styles = StyleSheet.create({
|
|
|
779
1071
|
fontWeight: '700',
|
|
780
1072
|
marginBottom: 4,
|
|
781
1073
|
},
|
|
1074
|
+
logTimeText: {
|
|
1075
|
+
fontSize: 11,
|
|
1076
|
+
fontWeight: '400',
|
|
1077
|
+
color: '#888888',
|
|
1078
|
+
},
|
|
782
1079
|
logPayload: {
|
|
783
1080
|
flex: 1,
|
|
784
1081
|
},
|
|
@@ -796,11 +1093,31 @@ const styles = StyleSheet.create({
|
|
|
796
1093
|
fontSize: 11,
|
|
797
1094
|
color: '#333333',
|
|
798
1095
|
},
|
|
1096
|
+
retryButton: {
|
|
1097
|
+
position: 'absolute',
|
|
1098
|
+
right: 8,
|
|
1099
|
+
top: 40,
|
|
1100
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
1101
|
+
borderColor: '#D0D0D0',
|
|
1102
|
+
borderRadius: 6,
|
|
1103
|
+
paddingVertical: 4,
|
|
1104
|
+
paddingHorizontal: 8,
|
|
1105
|
+
},
|
|
1106
|
+
retryButtonText: {
|
|
1107
|
+
fontSize: 11,
|
|
1108
|
+
color: '#333333',
|
|
1109
|
+
},
|
|
799
1110
|
valuePrimitive: {
|
|
800
1111
|
color: '#222222',
|
|
801
1112
|
fontSize: 12,
|
|
802
1113
|
flexShrink: 1,
|
|
803
1114
|
},
|
|
1115
|
+
valuePrimitiveInput: {
|
|
1116
|
+
paddingVertical: 0,
|
|
1117
|
+
paddingHorizontal: 0,
|
|
1118
|
+
margin: 0,
|
|
1119
|
+
textAlignVertical: 'top',
|
|
1120
|
+
},
|
|
804
1121
|
treeNode: {
|
|
805
1122
|
flexDirection: 'column',
|
|
806
1123
|
marginBottom: 4,
|
|
@@ -852,6 +1169,32 @@ const styles = StyleSheet.create({
|
|
|
852
1169
|
color: '#444444',
|
|
853
1170
|
marginBottom: 2,
|
|
854
1171
|
},
|
|
1172
|
+
networkErrorLabel: {
|
|
1173
|
+
color: LOG_THEME.error.color,
|
|
1174
|
+
fontWeight: '600',
|
|
1175
|
+
},
|
|
1176
|
+
networkErrorText: {
|
|
1177
|
+
color: LOG_THEME.error.color,
|
|
1178
|
+
fontSize: 12,
|
|
1179
|
+
},
|
|
1180
|
+
filterInputWrap: {
|
|
1181
|
+
borderTopWidth: StyleSheet.hairlineWidth,
|
|
1182
|
+
borderTopColor: '#E1E1E1',
|
|
1183
|
+
paddingHorizontal: 8,
|
|
1184
|
+
paddingTop: 8,
|
|
1185
|
+
paddingBottom: 6,
|
|
1186
|
+
},
|
|
1187
|
+
filterInput: {
|
|
1188
|
+
height: 34,
|
|
1189
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
1190
|
+
borderColor: '#D0D0D0',
|
|
1191
|
+
borderRadius: 8,
|
|
1192
|
+
paddingHorizontal: 10,
|
|
1193
|
+
fontSize: 12,
|
|
1194
|
+
color: '#222222',
|
|
1195
|
+
backgroundColor: '#FFFFFF',
|
|
1196
|
+
paddingVertical: 0,
|
|
1197
|
+
},
|
|
855
1198
|
actionsRow: {
|
|
856
1199
|
borderTopWidth: StyleSheet.hairlineWidth,
|
|
857
1200
|
borderTopColor: '#E1E1E1',
|