react-native-vconsole 0.3.1 → 0.5.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 +16 -1
- package/lib/module/VConsole.js +301 -46
- package/lib/module/VConsole.js.map +1 -1
- package/lib/module/core/xhrProxy.js +145 -7
- package/lib/module/core/xhrProxy.js.map +1 -1
- package/lib/typescript/src/VConsole.d.ts +6 -1
- package/lib/typescript/src/VConsole.d.ts.map +1 -1
- package/lib/typescript/src/core/xhrProxy.d.ts +1 -0
- package/lib/typescript/src/core/xhrProxy.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +2 -0
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/VConsole.tsx +376 -53
- package/src/core/xhrProxy.ts +177 -8
- package/src/types.ts +2 -0
package/src/VConsole.tsx
CHANGED
|
@@ -4,10 +4,12 @@ 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,
|
|
@@ -42,9 +44,11 @@ import type {
|
|
|
42
44
|
const BUTTON_WIDTH = 88;
|
|
43
45
|
const BUTTON_HEIGHT = 36;
|
|
44
46
|
const PANEL_HEIGHT_RATIO = 7 / 9;
|
|
45
|
-
const
|
|
47
|
+
const EMPTY_EXCLUDE: VConsoleExclude = {};
|
|
46
48
|
const LOG_SUB_TABS: LogFilterTab[] = ['All', 'log', 'info', 'warn', 'error'];
|
|
47
49
|
const ROOT_TABS: VConsoleTab[] = ['Log', 'Network', 'System', 'App'];
|
|
50
|
+
const NETWORK_DURATION_WARN_THRESHOLD_MS = 1000;
|
|
51
|
+
const NETWORK_DURATION_SEVERE_THRESHOLD_MS = 3000;
|
|
48
52
|
|
|
49
53
|
const LOG_THEME = {
|
|
50
54
|
log: { backgroundColor: '#FFFFFF', color: '#111111' },
|
|
@@ -62,7 +66,12 @@ type NativeModuleShape = {
|
|
|
62
66
|
|
|
63
67
|
export type VConsoleProps = {
|
|
64
68
|
enable?: boolean;
|
|
65
|
-
exclude?:
|
|
69
|
+
exclude?: VConsoleExclude;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
type VConsoleExclude = {
|
|
73
|
+
domains?: string[];
|
|
74
|
+
ip?: boolean;
|
|
66
75
|
};
|
|
67
76
|
|
|
68
77
|
function clamp(value: number, min: number, max: number): number {
|
|
@@ -102,6 +111,18 @@ function formatMemorySize(bytes: unknown): string {
|
|
|
102
111
|
return `${mb.toFixed(2)} MB`;
|
|
103
112
|
}
|
|
104
113
|
|
|
114
|
+
function formatLogTime(timestamp: number): string {
|
|
115
|
+
const date = new Date(timestamp);
|
|
116
|
+
if (Number.isNaN(date.getTime())) {
|
|
117
|
+
return '--:--:--.---';
|
|
118
|
+
}
|
|
119
|
+
const hh = String(date.getHours()).padStart(2, '0');
|
|
120
|
+
const mm = String(date.getMinutes()).padStart(2, '0');
|
|
121
|
+
const ss = String(date.getSeconds()).padStart(2, '0');
|
|
122
|
+
const ms = String(date.getMilliseconds()).padStart(3, '0');
|
|
123
|
+
return `${hh}:${mm}:${ss}.${ms}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
105
126
|
function prettyText(value: unknown): string {
|
|
106
127
|
if (value === undefined) {
|
|
107
128
|
return '';
|
|
@@ -116,19 +137,123 @@ function prettyText(value: unknown): string {
|
|
|
116
137
|
}
|
|
117
138
|
}
|
|
118
139
|
|
|
140
|
+
function isNetworkErrorEntry(item: NetworkEntry): boolean {
|
|
141
|
+
return item.isError === true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function getNetworkItemBackgroundColor(item: NetworkEntry): string | undefined {
|
|
145
|
+
if (isNetworkErrorEntry(item)) {
|
|
146
|
+
return LOG_THEME.error.backgroundColor;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (typeof item.durationMs !== 'number') {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (item.durationMs >= NETWORK_DURATION_SEVERE_THRESHOLD_MS) {
|
|
154
|
+
return LOG_THEME.error.backgroundColor;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (item.durationMs >= NETWORK_DURATION_WARN_THRESHOLD_MS) {
|
|
158
|
+
return LOG_THEME.warn.backgroundColor;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
|
|
119
164
|
function buildNetworkCopyText(item: NetworkEntry): string {
|
|
120
165
|
const status = item.status ?? '-';
|
|
121
166
|
const duration =
|
|
122
167
|
typeof item.durationMs === 'number' ? `${item.durationMs}ms` : '-';
|
|
168
|
+
const isError = isNetworkErrorEntry(item);
|
|
123
169
|
|
|
124
|
-
|
|
170
|
+
const segments = [
|
|
125
171
|
`${item.method} ${item.url}`,
|
|
126
172
|
`status ${status} duration ${duration}`,
|
|
127
173
|
`request headers\n${prettyText(item.requestHeaders)}`,
|
|
128
174
|
`request body\n${prettyText(item.requestBody)}`,
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
if (isError) {
|
|
178
|
+
segments.push(
|
|
179
|
+
`error reason\n${item.errorReason ?? 'Network request failed'}`
|
|
180
|
+
);
|
|
181
|
+
} else {
|
|
182
|
+
segments.push(`response headers\n${prettyText(item.responseHeaders)}`);
|
|
183
|
+
segments.push(`response data\n${prettyText(item.responseData)}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return segments.join('\n');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const FORBIDDEN_RETRY_HEADERS = new Set([
|
|
190
|
+
'host',
|
|
191
|
+
'content-length',
|
|
192
|
+
'accept-encoding',
|
|
193
|
+
'connection',
|
|
194
|
+
'origin',
|
|
195
|
+
'referer',
|
|
196
|
+
]);
|
|
197
|
+
|
|
198
|
+
function normalizeRetryUrl(rawUrl: string): string {
|
|
199
|
+
if (!rawUrl) {
|
|
200
|
+
return '';
|
|
201
|
+
}
|
|
202
|
+
if (/^\/\//.test(rawUrl)) {
|
|
203
|
+
return `https:${rawUrl}`;
|
|
204
|
+
}
|
|
205
|
+
return rawUrl;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function buildRetryHeaders(
|
|
209
|
+
headers: Record<string, string> | undefined
|
|
210
|
+
): Record<string, string> {
|
|
211
|
+
const nextHeaders: Record<string, string> = {};
|
|
212
|
+
if (!headers) {
|
|
213
|
+
return nextHeaders;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
Object.entries(headers).forEach(([key, value]) => {
|
|
217
|
+
if (!FORBIDDEN_RETRY_HEADERS.has(key.toLowerCase())) {
|
|
218
|
+
nextHeaders[key] = value;
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
return nextHeaders;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function buildRetryBody(payload: unknown, method: string): unknown | undefined {
|
|
225
|
+
if (method === 'GET' || method === 'HEAD' || payload == null) {
|
|
226
|
+
return undefined;
|
|
227
|
+
}
|
|
228
|
+
if (typeof payload === 'string') {
|
|
229
|
+
return payload;
|
|
230
|
+
}
|
|
231
|
+
if (typeof payload === 'number' || typeof payload === 'boolean') {
|
|
232
|
+
return String(payload);
|
|
233
|
+
}
|
|
234
|
+
if (typeof FormData !== 'undefined' && payload instanceof FormData) {
|
|
235
|
+
return payload;
|
|
236
|
+
}
|
|
237
|
+
if (
|
|
238
|
+
typeof URLSearchParams !== 'undefined' &&
|
|
239
|
+
payload instanceof URLSearchParams
|
|
240
|
+
) {
|
|
241
|
+
return payload;
|
|
242
|
+
}
|
|
243
|
+
if (typeof Blob !== 'undefined' && payload instanceof Blob) {
|
|
244
|
+
return payload;
|
|
245
|
+
}
|
|
246
|
+
if (typeof ArrayBuffer !== 'undefined' && payload instanceof ArrayBuffer) {
|
|
247
|
+
return payload;
|
|
248
|
+
}
|
|
249
|
+
if (ArrayBuffer.isView(payload)) {
|
|
250
|
+
return payload;
|
|
251
|
+
}
|
|
252
|
+
try {
|
|
253
|
+
return JSON.stringify(payload);
|
|
254
|
+
} catch {
|
|
255
|
+
return String(payload);
|
|
256
|
+
}
|
|
132
257
|
}
|
|
133
258
|
|
|
134
259
|
type ObjectTreeProps = {
|
|
@@ -225,7 +350,7 @@ function useFlatListRefs() {
|
|
|
225
350
|
|
|
226
351
|
export function VConsole({
|
|
227
352
|
enable = true,
|
|
228
|
-
exclude =
|
|
353
|
+
exclude = EMPTY_EXCLUDE,
|
|
229
354
|
}: VConsoleProps) {
|
|
230
355
|
const nativeModule = NativeModules.Vconsole as NativeModuleShape | undefined;
|
|
231
356
|
const { width, height } = Dimensions.get('window');
|
|
@@ -258,6 +383,11 @@ export function VConsole({
|
|
|
258
383
|
const [logSubTab, setLogSubTab] = useState<LogFilterTab>('All');
|
|
259
384
|
const [logEntries, setLogEntries] = useState<LogEntry[]>([]);
|
|
260
385
|
const [networkEntries, setNetworkEntries] = useState<NetworkEntry[]>([]);
|
|
386
|
+
const [logFilterInput, setLogFilterInput] = useState('');
|
|
387
|
+
const [networkFilterInput, setNetworkFilterInput] = useState('');
|
|
388
|
+
const [debouncedLogFilter, setDebouncedLogFilter] = useState('');
|
|
389
|
+
const [debouncedNetworkFilter, setDebouncedNetworkFilter] = useState('');
|
|
390
|
+
const [keyboardHeight, setKeyboardHeight] = useState(0);
|
|
261
391
|
const [expandedMap, setExpandedMap] = useState<ExpandedMap>({});
|
|
262
392
|
const [systemInfo, setSystemInfo] = useState<SystemInfo | null>(null);
|
|
263
393
|
const [appInfo, setAppInfo] = useState<AppInfo | null>(null);
|
|
@@ -266,10 +396,14 @@ export function VConsole({
|
|
|
266
396
|
const panelTranslateY = useRef(new Animated.Value(panelHeight)).current;
|
|
267
397
|
const logListRefs = useFlatListRefs();
|
|
268
398
|
const networkListRef = useRef<FlatList<NetworkEntry>>(null);
|
|
269
|
-
const
|
|
270
|
-
() =>
|
|
271
|
-
|
|
399
|
+
const normalizedExcludeDomains = useMemo(
|
|
400
|
+
() =>
|
|
401
|
+
(exclude.domains ?? [])
|
|
402
|
+
.map((item) => item.trim().toLowerCase())
|
|
403
|
+
.filter(Boolean),
|
|
404
|
+
[exclude.domains]
|
|
272
405
|
);
|
|
406
|
+
const shouldExcludeIp = exclude.ip === true;
|
|
273
407
|
|
|
274
408
|
useEffect(() => {
|
|
275
409
|
if (!enable) {
|
|
@@ -278,7 +412,10 @@ export function VConsole({
|
|
|
278
412
|
}
|
|
279
413
|
|
|
280
414
|
installConsoleProxy();
|
|
281
|
-
installXhrProxy({
|
|
415
|
+
installXhrProxy({
|
|
416
|
+
excludeHosts: normalizedExcludeDomains,
|
|
417
|
+
excludeIp: shouldExcludeIp,
|
|
418
|
+
});
|
|
282
419
|
|
|
283
420
|
const unsubscribeLog = subscribeLogEntries(setLogEntries);
|
|
284
421
|
const unsubscribeNetwork = subscribeNetworkEntries(setNetworkEntries);
|
|
@@ -291,7 +428,7 @@ export function VConsole({
|
|
|
291
428
|
uninstallConsoleProxy();
|
|
292
429
|
uninstallXhrProxy();
|
|
293
430
|
};
|
|
294
|
-
}, [enable,
|
|
431
|
+
}, [enable, normalizedExcludeDomains, shouldExcludeIp]);
|
|
295
432
|
|
|
296
433
|
useEffect(() => {
|
|
297
434
|
dragPosition.stopAnimation((value) => {
|
|
@@ -317,6 +454,39 @@ export function VConsole({
|
|
|
317
454
|
}
|
|
318
455
|
}, [activeTab, appInfo, nativeModule, panelVisible, systemInfo]);
|
|
319
456
|
|
|
457
|
+
useEffect(() => {
|
|
458
|
+
const showEvent =
|
|
459
|
+
Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
|
|
460
|
+
const hideEvent =
|
|
461
|
+
Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';
|
|
462
|
+
|
|
463
|
+
const showSubscription = Keyboard.addListener(showEvent, (event) => {
|
|
464
|
+
setKeyboardHeight(event.endCoordinates?.height ?? 0);
|
|
465
|
+
});
|
|
466
|
+
const hideSubscription = Keyboard.addListener(hideEvent, () => {
|
|
467
|
+
setKeyboardHeight(0);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
return () => {
|
|
471
|
+
showSubscription.remove();
|
|
472
|
+
hideSubscription.remove();
|
|
473
|
+
};
|
|
474
|
+
}, []);
|
|
475
|
+
|
|
476
|
+
useEffect(() => {
|
|
477
|
+
const timer = setTimeout(() => {
|
|
478
|
+
setDebouncedLogFilter(logFilterInput);
|
|
479
|
+
}, 1000);
|
|
480
|
+
return () => clearTimeout(timer);
|
|
481
|
+
}, [logFilterInput]);
|
|
482
|
+
|
|
483
|
+
useEffect(() => {
|
|
484
|
+
const timer = setTimeout(() => {
|
|
485
|
+
setDebouncedNetworkFilter(networkFilterInput);
|
|
486
|
+
}, 1000);
|
|
487
|
+
return () => clearTimeout(timer);
|
|
488
|
+
}, [networkFilterInput]);
|
|
489
|
+
|
|
320
490
|
const panResponder = useMemo(
|
|
321
491
|
() =>
|
|
322
492
|
PanResponder.create({
|
|
@@ -370,15 +540,36 @@ export function VConsole({
|
|
|
370
540
|
});
|
|
371
541
|
};
|
|
372
542
|
|
|
543
|
+
const normalizedLogFilter = debouncedLogFilter.trim().toLowerCase();
|
|
544
|
+
const normalizedNetworkFilter = debouncedNetworkFilter.trim().toLowerCase();
|
|
545
|
+
|
|
546
|
+
const filteredLogEntries = useMemo(() => {
|
|
547
|
+
if (!normalizedLogFilter) {
|
|
548
|
+
return logEntries;
|
|
549
|
+
}
|
|
550
|
+
return logEntries.filter((item) =>
|
|
551
|
+
item.text.toLowerCase().includes(normalizedLogFilter)
|
|
552
|
+
);
|
|
553
|
+
}, [logEntries, normalizedLogFilter]);
|
|
554
|
+
|
|
555
|
+
const filteredNetworkEntries = useMemo(() => {
|
|
556
|
+
if (!normalizedNetworkFilter) {
|
|
557
|
+
return networkEntries;
|
|
558
|
+
}
|
|
559
|
+
return networkEntries.filter((item) =>
|
|
560
|
+
item.url.toLowerCase().includes(normalizedNetworkFilter)
|
|
561
|
+
);
|
|
562
|
+
}, [networkEntries, normalizedNetworkFilter]);
|
|
563
|
+
|
|
373
564
|
const logDataByTab = useMemo(
|
|
374
565
|
() => ({
|
|
375
|
-
All:
|
|
376
|
-
log:
|
|
377
|
-
info:
|
|
378
|
-
warn:
|
|
379
|
-
error:
|
|
566
|
+
All: filteredLogEntries,
|
|
567
|
+
log: filteredLogEntries.filter((item) => item.level === 'log'),
|
|
568
|
+
info: filteredLogEntries.filter((item) => item.level === 'info'),
|
|
569
|
+
warn: filteredLogEntries.filter((item) => item.level === 'warn'),
|
|
570
|
+
error: filteredLogEntries.filter((item) => item.level === 'error'),
|
|
380
571
|
}),
|
|
381
|
-
[
|
|
572
|
+
[filteredLogEntries]
|
|
382
573
|
);
|
|
383
574
|
|
|
384
575
|
const onToggleNode = (key: string) => {
|
|
@@ -407,6 +598,39 @@ export function VConsole({
|
|
|
407
598
|
networkListRef.current?.scrollToEnd({ animated: true });
|
|
408
599
|
};
|
|
409
600
|
|
|
601
|
+
const retryNetworkRequest = (item: NetworkEntry) => {
|
|
602
|
+
const method = (item.method || 'GET').toUpperCase();
|
|
603
|
+
const url = normalizeRetryUrl(item.url);
|
|
604
|
+
if (!url) {
|
|
605
|
+
console.error('[vConsole] Retry failed: empty request URL');
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const headers = buildRetryHeaders(item.requestHeaders);
|
|
610
|
+
const body = buildRetryBody(item.requestBody, method);
|
|
611
|
+
const hasContentType = Object.keys(headers).some(
|
|
612
|
+
(key) => key.toLowerCase() === 'content-type'
|
|
613
|
+
);
|
|
614
|
+
|
|
615
|
+
if (
|
|
616
|
+
body &&
|
|
617
|
+
typeof body === 'string' &&
|
|
618
|
+
typeof item.requestBody === 'object' &&
|
|
619
|
+
item.requestBody !== null &&
|
|
620
|
+
!hasContentType
|
|
621
|
+
) {
|
|
622
|
+
headers['Content-Type'] = 'application/json';
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
fetch(url, {
|
|
626
|
+
method,
|
|
627
|
+
headers,
|
|
628
|
+
body: body as never,
|
|
629
|
+
}).catch((error: unknown) => {
|
|
630
|
+
console.error('[vConsole] Retry request failed', error);
|
|
631
|
+
});
|
|
632
|
+
};
|
|
633
|
+
|
|
410
634
|
const renderRootTab = (tab: VConsoleTab) => (
|
|
411
635
|
<Pressable
|
|
412
636
|
key={tab}
|
|
@@ -445,6 +669,10 @@ export function VConsole({
|
|
|
445
669
|
<View style={styles.listItemMain}>
|
|
446
670
|
<Text style={[styles.logLevelText, { color: levelTheme.color }]}>
|
|
447
671
|
[{item.level.toUpperCase()}]
|
|
672
|
+
<Text style={styles.logTimeText}>
|
|
673
|
+
{' '}
|
|
674
|
+
{formatLogTime(item.timestamp)}
|
|
675
|
+
</Text>
|
|
448
676
|
</Text>
|
|
449
677
|
<View style={styles.logPayload}>
|
|
450
678
|
{item.args.map((arg, index) => (
|
|
@@ -471,18 +699,29 @@ export function VConsole({
|
|
|
471
699
|
const renderNetworkItem: FlatListProps<NetworkEntry>['renderItem'] = ({
|
|
472
700
|
item,
|
|
473
701
|
}) => {
|
|
702
|
+
const isError = isNetworkErrorEntry(item);
|
|
703
|
+
const backgroundColor = getNetworkItemBackgroundColor(item);
|
|
704
|
+
const startedTime = formatLogTime(item.startedAt);
|
|
705
|
+
const finishedTime =
|
|
706
|
+
typeof item.finishedAt === 'number'
|
|
707
|
+
? formatLogTime(item.finishedAt)
|
|
708
|
+
: '-';
|
|
474
709
|
return (
|
|
475
|
-
<View
|
|
710
|
+
<View
|
|
711
|
+
style={[styles.listItem, backgroundColor ? { backgroundColor } : null]}
|
|
712
|
+
>
|
|
476
713
|
<View style={styles.listItemMain}>
|
|
477
714
|
<Text style={styles.networkTitle}>
|
|
478
715
|
{item.method} {item.url}
|
|
479
716
|
</Text>
|
|
480
717
|
<Text style={styles.networkLabel}>
|
|
481
|
-
|
|
718
|
+
Time: {startedTime}
|
|
719
|
+
{finishedTime !== '-' ? ` ~ ${finishedTime}` : ''}
|
|
482
720
|
{' '}
|
|
483
721
|
Duration:{' '}
|
|
484
722
|
{typeof item.durationMs === 'number' ? `${item.durationMs}ms` : '-'}
|
|
485
723
|
</Text>
|
|
724
|
+
<Text style={styles.networkLabel}>Status: {item.status ?? '-'}</Text>
|
|
486
725
|
<View style={styles.networkBlock}>
|
|
487
726
|
<Text style={styles.networkLabel}>Request Headers</Text>
|
|
488
727
|
<ObjectTree
|
|
@@ -501,26 +740,39 @@ export function VConsole({
|
|
|
501
740
|
onToggle={onToggleNode}
|
|
502
741
|
/>
|
|
503
742
|
</View>
|
|
504
|
-
|
|
505
|
-
<
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
743
|
+
{isError ? (
|
|
744
|
+
<View style={styles.networkBlock}>
|
|
745
|
+
<Text style={[styles.networkLabel, styles.networkErrorLabel]}>
|
|
746
|
+
Error Reason
|
|
747
|
+
</Text>
|
|
748
|
+
<Text style={styles.networkErrorText}>
|
|
749
|
+
{item.errorReason ?? 'Network request failed'}
|
|
750
|
+
</Text>
|
|
751
|
+
</View>
|
|
752
|
+
) : (
|
|
753
|
+
<>
|
|
754
|
+
<View style={styles.networkBlock}>
|
|
755
|
+
<Text style={styles.networkLabel}>Response Headers</Text>
|
|
756
|
+
<ObjectTree
|
|
757
|
+
value={item.responseHeaders}
|
|
758
|
+
nodeKey={`${item.id}.responseHeaders`}
|
|
759
|
+
expandedMap={expandedMap}
|
|
760
|
+
onToggle={onToggleNode}
|
|
761
|
+
/>
|
|
762
|
+
</View>
|
|
763
|
+
<View style={styles.networkBlock}>
|
|
764
|
+
<Text style={styles.networkLabel}>Response Data</Text>
|
|
765
|
+
<ScrollView horizontal={true}>
|
|
766
|
+
<ObjectTree
|
|
767
|
+
value={item.responseData ?? ''}
|
|
768
|
+
nodeKey={`${item.id}.responseData`}
|
|
769
|
+
expandedMap={expandedMap}
|
|
770
|
+
onToggle={onToggleNode}
|
|
771
|
+
/>
|
|
772
|
+
</ScrollView>
|
|
773
|
+
</View>
|
|
774
|
+
</>
|
|
775
|
+
)}
|
|
524
776
|
</View>
|
|
525
777
|
<Pressable
|
|
526
778
|
style={styles.copyButton}
|
|
@@ -530,12 +782,18 @@ export function VConsole({
|
|
|
530
782
|
>
|
|
531
783
|
<Text style={styles.copyButtonText}>Copy</Text>
|
|
532
784
|
</Pressable>
|
|
785
|
+
<Pressable
|
|
786
|
+
style={styles.retryButton}
|
|
787
|
+
onPress={() => retryNetworkRequest(item)}
|
|
788
|
+
>
|
|
789
|
+
<Text style={styles.retryButtonText}>Retry</Text>
|
|
790
|
+
</Pressable>
|
|
533
791
|
</View>
|
|
534
792
|
);
|
|
535
793
|
};
|
|
536
794
|
|
|
537
|
-
const renderLogPanel = () => (
|
|
538
|
-
<View style={styles.contentArea}>
|
|
795
|
+
const renderLogPanel = (visible: boolean) => (
|
|
796
|
+
<View style={[styles.contentArea, visible ? {} : styles.hidden]}>
|
|
539
797
|
<View style={styles.subTabRow}>
|
|
540
798
|
{LOG_SUB_TABS.map((tab) => (
|
|
541
799
|
<Pressable
|
|
@@ -573,6 +831,16 @@ export function VConsole({
|
|
|
573
831
|
</View>
|
|
574
832
|
))}
|
|
575
833
|
</View>
|
|
834
|
+
<View style={styles.filterInputWrap}>
|
|
835
|
+
<TextInput
|
|
836
|
+
style={styles.filterInput}
|
|
837
|
+
textAlignVertical="center"
|
|
838
|
+
value={logFilterInput}
|
|
839
|
+
onChangeText={setLogFilterInput}
|
|
840
|
+
placeholder="filter..."
|
|
841
|
+
placeholderTextColor="#999999"
|
|
842
|
+
/>
|
|
843
|
+
</View>
|
|
576
844
|
<View style={styles.actionsRow}>
|
|
577
845
|
{renderActionButton('Clear', () => {
|
|
578
846
|
clearLogEntries();
|
|
@@ -585,15 +853,24 @@ export function VConsole({
|
|
|
585
853
|
</View>
|
|
586
854
|
);
|
|
587
855
|
|
|
588
|
-
const renderNetworkPanel = () => (
|
|
589
|
-
<View style={styles.contentArea}>
|
|
856
|
+
const renderNetworkPanel = (visible: boolean) => (
|
|
857
|
+
<View style={[styles.contentArea, visible ? {} : styles.hidden]}>
|
|
590
858
|
<FlatList
|
|
591
859
|
ref={networkListRef}
|
|
592
|
-
data={
|
|
860
|
+
data={filteredNetworkEntries}
|
|
593
861
|
keyExtractor={(item) => `network-${item.id}`}
|
|
594
862
|
renderItem={renderNetworkItem}
|
|
595
863
|
ItemSeparatorComponent={ListSeparator}
|
|
596
864
|
/>
|
|
865
|
+
<View style={styles.filterInputWrap}>
|
|
866
|
+
<TextInput
|
|
867
|
+
style={styles.filterInput}
|
|
868
|
+
value={networkFilterInput}
|
|
869
|
+
onChangeText={setNetworkFilterInput}
|
|
870
|
+
placeholder="filter"
|
|
871
|
+
placeholderTextColor="#999999"
|
|
872
|
+
/>
|
|
873
|
+
</View>
|
|
597
874
|
<View style={styles.actionsRow}>
|
|
598
875
|
{renderActionButton('Clear', () => {
|
|
599
876
|
clearNetworkEntries();
|
|
@@ -606,8 +883,8 @@ export function VConsole({
|
|
|
606
883
|
</View>
|
|
607
884
|
);
|
|
608
885
|
|
|
609
|
-
const renderSystemPanel = () => (
|
|
610
|
-
<View style={styles.contentArea}>
|
|
886
|
+
const renderSystemPanel = (visible: boolean) => (
|
|
887
|
+
<View style={[styles.contentArea, visible ? {} : styles.hidden]}>
|
|
611
888
|
<View style={styles.infoCard}>
|
|
612
889
|
<Text style={styles.infoText}>
|
|
613
890
|
Brand: {systemInfo?.manufacturer ?? '-'}
|
|
@@ -642,8 +919,8 @@ export function VConsole({
|
|
|
642
919
|
</View>
|
|
643
920
|
);
|
|
644
921
|
|
|
645
|
-
const renderAppPanel = () => (
|
|
646
|
-
<View style={styles.contentArea}>
|
|
922
|
+
const renderAppPanel = (visible: boolean) => (
|
|
923
|
+
<View style={[styles.contentArea, visible ? {} : styles.hidden]}>
|
|
647
924
|
<View style={styles.infoCard}>
|
|
648
925
|
<Text style={styles.infoText}>
|
|
649
926
|
App Version: {appInfo?.appVersion ?? '-'}
|
|
@@ -686,15 +963,16 @@ export function VConsole({
|
|
|
686
963
|
styles.panel,
|
|
687
964
|
{
|
|
688
965
|
height: panelHeight,
|
|
966
|
+
marginBottom: keyboardHeight,
|
|
689
967
|
transform: [{ translateY: panelTranslateY }],
|
|
690
968
|
},
|
|
691
969
|
]}
|
|
692
970
|
>
|
|
693
971
|
<View style={styles.topTabRow}>{ROOT_TABS.map(renderRootTab)}</View>
|
|
694
|
-
{activeTab === 'Log'
|
|
695
|
-
{activeTab === 'Network'
|
|
696
|
-
{activeTab === 'System'
|
|
697
|
-
{activeTab === 'App'
|
|
972
|
+
{renderLogPanel(activeTab === 'Log')}
|
|
973
|
+
{renderNetworkPanel(activeTab === 'Network')}
|
|
974
|
+
{renderSystemPanel(activeTab === 'System')}
|
|
975
|
+
{renderAppPanel(activeTab === 'App')}
|
|
698
976
|
</Animated.View>
|
|
699
977
|
</View>
|
|
700
978
|
) : null}
|
|
@@ -813,6 +1091,11 @@ const styles = StyleSheet.create({
|
|
|
813
1091
|
fontWeight: '700',
|
|
814
1092
|
marginBottom: 4,
|
|
815
1093
|
},
|
|
1094
|
+
logTimeText: {
|
|
1095
|
+
fontSize: 11,
|
|
1096
|
+
fontWeight: '400',
|
|
1097
|
+
color: '#888888',
|
|
1098
|
+
},
|
|
816
1099
|
logPayload: {
|
|
817
1100
|
flex: 1,
|
|
818
1101
|
},
|
|
@@ -830,6 +1113,20 @@ const styles = StyleSheet.create({
|
|
|
830
1113
|
fontSize: 11,
|
|
831
1114
|
color: '#333333',
|
|
832
1115
|
},
|
|
1116
|
+
retryButton: {
|
|
1117
|
+
position: 'absolute',
|
|
1118
|
+
right: 8,
|
|
1119
|
+
top: 40,
|
|
1120
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
1121
|
+
borderColor: '#D0D0D0',
|
|
1122
|
+
borderRadius: 6,
|
|
1123
|
+
paddingVertical: 4,
|
|
1124
|
+
paddingHorizontal: 8,
|
|
1125
|
+
},
|
|
1126
|
+
retryButtonText: {
|
|
1127
|
+
fontSize: 11,
|
|
1128
|
+
color: '#333333',
|
|
1129
|
+
},
|
|
833
1130
|
valuePrimitive: {
|
|
834
1131
|
color: '#222222',
|
|
835
1132
|
fontSize: 12,
|
|
@@ -892,6 +1189,32 @@ const styles = StyleSheet.create({
|
|
|
892
1189
|
color: '#444444',
|
|
893
1190
|
marginBottom: 2,
|
|
894
1191
|
},
|
|
1192
|
+
networkErrorLabel: {
|
|
1193
|
+
color: LOG_THEME.error.color,
|
|
1194
|
+
fontWeight: '600',
|
|
1195
|
+
},
|
|
1196
|
+
networkErrorText: {
|
|
1197
|
+
color: LOG_THEME.error.color,
|
|
1198
|
+
fontSize: 12,
|
|
1199
|
+
},
|
|
1200
|
+
filterInputWrap: {
|
|
1201
|
+
borderTopWidth: StyleSheet.hairlineWidth,
|
|
1202
|
+
borderTopColor: '#E1E1E1',
|
|
1203
|
+
paddingHorizontal: 8,
|
|
1204
|
+
paddingTop: 8,
|
|
1205
|
+
paddingBottom: 6,
|
|
1206
|
+
},
|
|
1207
|
+
filterInput: {
|
|
1208
|
+
height: 34,
|
|
1209
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
1210
|
+
borderColor: '#D0D0D0',
|
|
1211
|
+
borderRadius: 8,
|
|
1212
|
+
paddingHorizontal: 10,
|
|
1213
|
+
fontSize: 12,
|
|
1214
|
+
color: '#222222',
|
|
1215
|
+
backgroundColor: '#FFFFFF',
|
|
1216
|
+
paddingVertical: 0,
|
|
1217
|
+
},
|
|
895
1218
|
actionsRow: {
|
|
896
1219
|
borderTopWidth: StyleSheet.hairlineWidth,
|
|
897
1220
|
borderTopColor: '#E1E1E1',
|