@rozenite/network-activity-plugin 1.9.0 → 1.11.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/CHANGELOG.md +60 -0
- package/dist/devtools/App.html +2 -2
- package/dist/devtools/assets/{App-hSoryVpJ.js → App-CEESZAW_.js} +7520 -937
- package/dist/devtools/assets/{App-m6xge0az.css → App-xppYUJvX.css} +246 -2
- package/dist/react-native/chunks/boot-recording.cjs +138 -14
- package/dist/react-native/chunks/boot-recording.js +138 -14
- package/dist/react-native/chunks/get-nitro-module.cjs +4 -1
- package/dist/react-native/chunks/get-nitro-module.js +4 -1
- package/dist/react-native/chunks/useNetworkActivityDevTools.require.cjs +20 -1
- package/dist/react-native/chunks/useNetworkActivityDevTools.require.js +20 -1
- package/dist/react-native/index.d.ts +37 -1
- package/dist/rozenite.json +1 -1
- package/dist/sdk/index.d.ts +37 -1
- package/package.json +12 -7
- package/src/react-native/agent/use-network-activity-agent-tools.ts +22 -4
- package/src/react-native/http/__tests__/http-utils.test.ts +228 -0
- package/src/react-native/http/http-utils.ts +208 -25
- package/src/react-native/network-inspector.ts +2 -2
- package/src/react-native/nitro-fetch/get-nitro-module.ts +5 -1
- package/src/react-native/nitro-fetch/nitro-network-inspector.ts +8 -2
- package/src/shared/http-events.ts +40 -1
- package/src/ui/components/CodeBlock.tsx +45 -1
- package/src/ui/components/FilterBar.tsx +337 -61
- package/src/ui/components/HexView.tsx +54 -0
- package/src/ui/components/MetadataCard.tsx +95 -0
- package/src/ui/components/NetworkTimeline.tsx +422 -0
- package/src/ui/components/RequestList.tsx +19 -40
- package/src/ui/components/SidePanel.tsx +42 -1
- package/src/ui/components/Toolbar.tsx +13 -1
- package/src/ui/components/ViewToggle.tsx +44 -0
- package/src/ui/components/XmlTree.tsx +160 -0
- package/src/ui/components/__tests__/CodeBlock.test.tsx +89 -0
- package/src/ui/components/__tests__/HexView.test.tsx +41 -0
- package/src/ui/components/__tests__/MetadataCard.test.tsx +107 -0
- package/src/ui/components/__tests__/ViewToggle.test.tsx +80 -0
- package/src/ui/components/__tests__/XmlTree.test.tsx +149 -0
- package/src/ui/hooks/useNetworkActivitySessionExport.ts +39 -0
- package/src/ui/response-renderers/__tests__/binary-too-large.test.tsx +56 -0
- package/src/ui/response-renderers/__tests__/binary.test.tsx +96 -0
- package/src/ui/response-renderers/__tests__/dispatch.test.ts +124 -0
- package/src/ui/response-renderers/__tests__/html.test.tsx +101 -0
- package/src/ui/response-renderers/__tests__/image.test.tsx +73 -0
- package/src/ui/response-renderers/__tests__/json.test.tsx +95 -0
- package/src/ui/response-renderers/__tests__/svg.test.tsx +46 -0
- package/src/ui/response-renderers/__tests__/xml.test.tsx +100 -0
- package/src/ui/response-renderers/binary-too-large.tsx +36 -0
- package/src/ui/response-renderers/binary.tsx +31 -0
- package/src/ui/response-renderers/empty.tsx +14 -0
- package/src/ui/response-renderers/html.tsx +36 -0
- package/src/ui/response-renderers/image.tsx +37 -0
- package/src/ui/response-renderers/index.ts +55 -0
- package/src/ui/response-renderers/json.tsx +40 -0
- package/src/ui/response-renderers/svg.tsx +27 -0
- package/src/ui/response-renderers/text-fallback.tsx +14 -0
- package/src/ui/response-renderers/types.ts +38 -0
- package/src/ui/response-renderers/unknown.tsx +18 -0
- package/src/ui/response-renderers/xml.tsx +46 -0
- package/src/ui/state/__tests__/store.test.ts +77 -0
- package/src/ui/state/derived.ts +14 -0
- package/src/ui/state/filter.ts +49 -0
- package/src/ui/state/hooks.ts +2 -2
- package/src/ui/state/model.ts +7 -1
- package/src/ui/state/store.ts +63 -4
- package/src/ui/tabs/InitiatorTab.tsx +230 -0
- package/src/ui/tabs/ResponseTab.tsx +80 -97
- package/src/ui/tabs/__tests__/ResponseTab.test.tsx +102 -0
- package/src/ui/utils/__tests__/download.test.ts +115 -0
- package/src/ui/utils/__tests__/hex.test.ts +84 -0
- package/src/ui/utils/__tests__/requestFilters.test.ts +32 -0
- package/src/ui/utils/__tests__/sessionExport.test.ts +174 -0
- package/src/ui/utils/__tests__/symbolication.test.ts +207 -0
- package/src/ui/utils/__tests__/timelineModel.test.ts +170 -0
- package/src/ui/utils/download.ts +161 -0
- package/src/ui/utils/hex.ts +59 -0
- package/src/ui/utils/initiator.ts +136 -0
- package/src/ui/utils/requestFilters.ts +183 -0
- package/src/ui/utils/sessionExport.ts +185 -0
- package/src/ui/utils/symbolication.ts +248 -0
- package/src/ui/utils/timelineModel.ts +352 -0
- package/src/ui/views/InspectorView.tsx +43 -8
- package/src/utils/__tests__/getContentTypeMimeType.test.ts +34 -0
- package/src/utils/getContentTypeMimeType.ts +14 -0
- package/vite.config.ts +5 -1
- package/vitest.setup.ts +31 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { HttpMethod, NetworkEventSource } from '../../shared/client';
|
|
2
|
+
|
|
3
|
+
export type RequestTypeFilter = 'http' | 'websocket' | 'sse';
|
|
4
|
+
|
|
5
|
+
export type AdvancedFilterState = {
|
|
6
|
+
methods: Set<HttpMethod>;
|
|
7
|
+
sources: Set<NetworkEventSource>;
|
|
8
|
+
status: string;
|
|
9
|
+
domain: string;
|
|
10
|
+
contentType: string;
|
|
11
|
+
failedOnly: boolean;
|
|
12
|
+
inFlightOnly: boolean;
|
|
13
|
+
overriddenOnly: boolean;
|
|
14
|
+
minSize: string;
|
|
15
|
+
maxSize: string;
|
|
16
|
+
minDuration: string;
|
|
17
|
+
maxDuration: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type FilterState = {
|
|
21
|
+
text: string;
|
|
22
|
+
types: Set<RequestTypeFilter>;
|
|
23
|
+
advanced: AdvancedFilterState;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const DEFAULT_REQUEST_TYPES: RequestTypeFilter[] = [
|
|
27
|
+
'http',
|
|
28
|
+
'websocket',
|
|
29
|
+
'sse',
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
export const createDefaultFilter = (): FilterState => ({
|
|
33
|
+
text: '',
|
|
34
|
+
types: new Set(DEFAULT_REQUEST_TYPES),
|
|
35
|
+
advanced: {
|
|
36
|
+
methods: new Set(),
|
|
37
|
+
sources: new Set(),
|
|
38
|
+
status: '',
|
|
39
|
+
domain: '',
|
|
40
|
+
contentType: '',
|
|
41
|
+
failedOnly: false,
|
|
42
|
+
inFlightOnly: false,
|
|
43
|
+
overriddenOnly: false,
|
|
44
|
+
minSize: '',
|
|
45
|
+
maxSize: '',
|
|
46
|
+
minDuration: '',
|
|
47
|
+
maxDuration: '',
|
|
48
|
+
},
|
|
49
|
+
});
|
package/src/ui/state/hooks.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { NetworkActivityState } from './store';
|
|
|
4
4
|
import { getProcessedRequests, getSelectedRequest } from './derived';
|
|
5
5
|
|
|
6
6
|
export const useNetworkActivityStore = <T>(
|
|
7
|
-
selector: (state: NetworkActivityState) => T
|
|
7
|
+
selector: (state: NetworkActivityState) => T,
|
|
8
8
|
): T => {
|
|
9
9
|
return useStore(store, selector);
|
|
10
10
|
};
|
|
@@ -39,7 +39,7 @@ export const useNetworkActivityClientManagement = () => {
|
|
|
39
39
|
|
|
40
40
|
export const useWebSocketMessages = (requestId: string) => {
|
|
41
41
|
return useNetworkActivityStore(
|
|
42
|
-
(state) => state.websocketMessages.get(requestId) || []
|
|
42
|
+
(state) => state.websocketMessages.get(requestId) || [],
|
|
43
43
|
);
|
|
44
44
|
};
|
|
45
45
|
|
package/src/ui/state/model.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
HttpHeaders,
|
|
5
5
|
RequestPostData,
|
|
6
6
|
NetworkEventSource,
|
|
7
|
+
ResponseBody,
|
|
7
8
|
} from '../../shared/client';
|
|
8
9
|
|
|
9
10
|
export type RequestId = string;
|
|
@@ -21,7 +22,9 @@ export type HttpRequestData = {
|
|
|
21
22
|
|
|
22
23
|
export type HttpResponseData = {
|
|
23
24
|
type: string;
|
|
24
|
-
|
|
25
|
+
// Mirrors the bridge `ResponseBody` minus null — the store only assigns
|
|
26
|
+
// `data` when the wire body is non-null (null body → undefined response.body).
|
|
27
|
+
data: NonNullable<ResponseBody>;
|
|
25
28
|
};
|
|
26
29
|
|
|
27
30
|
export type HttpRequest = {
|
|
@@ -140,6 +143,7 @@ export type ProcessedRequest = {
|
|
|
140
143
|
id: RequestId;
|
|
141
144
|
type: NetworkEntryType;
|
|
142
145
|
source?: NetworkEventSource;
|
|
146
|
+
initiator?: Initiator;
|
|
143
147
|
name: string;
|
|
144
148
|
status: HttpStatus | WebSocketStatus | SSEStatus;
|
|
145
149
|
timestamp: Timestamp;
|
|
@@ -147,6 +151,8 @@ export type ProcessedRequest = {
|
|
|
147
151
|
size: number | null;
|
|
148
152
|
method: HttpMethod | 'WS' | 'SSE';
|
|
149
153
|
httpStatus?: number;
|
|
154
|
+
contentType?: string;
|
|
155
|
+
ttfb?: number;
|
|
150
156
|
progress?: {
|
|
151
157
|
loaded: number;
|
|
152
158
|
total: number;
|
package/src/ui/state/store.ts
CHANGED
|
@@ -19,12 +19,17 @@ import { getId } from '../utils/getId';
|
|
|
19
19
|
import { assert } from '../utils/assert';
|
|
20
20
|
import { getContentTypeMime } from '../../utils/getContentTypeMimeType';
|
|
21
21
|
import { applyReactNativeRequestHeadersLogic } from '../../utils/applyReactNativeRequestHeadersLogic';
|
|
22
|
+
import { symbolicateInitiator } from '../utils/symbolication';
|
|
22
23
|
|
|
23
24
|
const MAX_WEBSOCKET_MESSAGES_PER_CONNECTION = 32;
|
|
24
25
|
const MAX_SSE_MESSAGES_PER_CONNECTION = 32;
|
|
25
26
|
|
|
26
27
|
const STORE_VERSION = 1;
|
|
27
28
|
|
|
29
|
+
const getElapsedDuration = (endTimestamp: number, startTimestamp: number) => {
|
|
30
|
+
return Math.max(endTimestamp - startTimestamp, 0);
|
|
31
|
+
};
|
|
32
|
+
|
|
28
33
|
export interface NetworkActivityState {
|
|
29
34
|
// State
|
|
30
35
|
isRecording: boolean;
|
|
@@ -128,7 +133,10 @@ export const createNetworkActivityStore = () =>
|
|
|
128
133
|
data as NetworkActivityEventMap['recording-state'];
|
|
129
134
|
const { isRecording, _client } = get();
|
|
130
135
|
if (_client && isRecording !== eventData.isRecording) {
|
|
131
|
-
_client.send(
|
|
136
|
+
_client.send(
|
|
137
|
+
isRecording ? 'network-enable' : 'network-disable',
|
|
138
|
+
{},
|
|
139
|
+
);
|
|
132
140
|
}
|
|
133
141
|
break;
|
|
134
142
|
}
|
|
@@ -177,6 +185,39 @@ export const createNetworkActivityStore = () =>
|
|
|
177
185
|
newEntries.set(eventData.requestId, entry);
|
|
178
186
|
return { networkEntries: newEntries };
|
|
179
187
|
});
|
|
188
|
+
|
|
189
|
+
if (eventData.initiator.symbolicationStatus === 'pending') {
|
|
190
|
+
void symbolicateInitiator(eventData.initiator).then(
|
|
191
|
+
(symbolicatedInitiator) => {
|
|
192
|
+
if (!symbolicatedInitiator) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
set((state) => {
|
|
197
|
+
const entry = state.networkEntries.get(
|
|
198
|
+
eventData.requestId,
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
if (
|
|
202
|
+
!entry ||
|
|
203
|
+
(entry.type !== 'http' && entry.type !== 'sse') ||
|
|
204
|
+
entry.initiator?.symbolicationStatus !== 'pending'
|
|
205
|
+
) {
|
|
206
|
+
return {};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const updatedEntry = {
|
|
210
|
+
...entry,
|
|
211
|
+
initiator: symbolicatedInitiator,
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const newEntries = new Map(state.networkEntries);
|
|
215
|
+
newEntries.set(eventData.requestId, updatedEntry);
|
|
216
|
+
return { networkEntries: newEntries };
|
|
217
|
+
});
|
|
218
|
+
},
|
|
219
|
+
);
|
|
220
|
+
}
|
|
180
221
|
break;
|
|
181
222
|
}
|
|
182
223
|
|
|
@@ -265,6 +306,10 @@ export const createNetworkActivityStore = () =>
|
|
|
265
306
|
const updatedEntry: HttpNetworkEntry = {
|
|
266
307
|
...httpEntry,
|
|
267
308
|
status: 'failed',
|
|
309
|
+
duration: getElapsedDuration(
|
|
310
|
+
eventData.timestamp,
|
|
311
|
+
httpEntry.timestamp,
|
|
312
|
+
),
|
|
268
313
|
error: eventData.error,
|
|
269
314
|
};
|
|
270
315
|
|
|
@@ -377,7 +422,10 @@ export const createNetworkActivityStore = () =>
|
|
|
377
422
|
status: 'closed',
|
|
378
423
|
closeCode: eventData.code,
|
|
379
424
|
closeReason: eventData.reason,
|
|
380
|
-
duration:
|
|
425
|
+
duration: getElapsedDuration(
|
|
426
|
+
eventData.timestamp,
|
|
427
|
+
wsEntry.timestamp,
|
|
428
|
+
),
|
|
381
429
|
};
|
|
382
430
|
|
|
383
431
|
const newEntries = new Map(state.networkEntries);
|
|
@@ -458,6 +506,10 @@ export const createNetworkActivityStore = () =>
|
|
|
458
506
|
const updatedEntry: WebSocketNetworkEntry = {
|
|
459
507
|
...wsEntry,
|
|
460
508
|
status: 'error',
|
|
509
|
+
duration: getElapsedDuration(
|
|
510
|
+
eventData.timestamp,
|
|
511
|
+
wsEntry.timestamp,
|
|
512
|
+
),
|
|
461
513
|
error: eventData.error,
|
|
462
514
|
};
|
|
463
515
|
|
|
@@ -554,6 +606,10 @@ export const createNetworkActivityStore = () =>
|
|
|
554
606
|
const updatedEntry: SSENetworkEntry = {
|
|
555
607
|
...sseEntry,
|
|
556
608
|
status: 'error',
|
|
609
|
+
duration: getElapsedDuration(
|
|
610
|
+
eventData.timestamp,
|
|
611
|
+
sseEntry.timestamp,
|
|
612
|
+
),
|
|
557
613
|
error: eventData.error.message,
|
|
558
614
|
};
|
|
559
615
|
|
|
@@ -574,7 +630,10 @@ export const createNetworkActivityStore = () =>
|
|
|
574
630
|
const updatedEntry: SSENetworkEntry = {
|
|
575
631
|
...sseEntry,
|
|
576
632
|
status: 'closed',
|
|
577
|
-
duration:
|
|
633
|
+
duration: getElapsedDuration(
|
|
634
|
+
eventData.timestamp,
|
|
635
|
+
sseEntry.timestamp,
|
|
636
|
+
),
|
|
578
637
|
};
|
|
579
638
|
|
|
580
639
|
const newEntries = new Map(state.networkEntries);
|
|
@@ -594,7 +653,7 @@ export const createNetworkActivityStore = () =>
|
|
|
594
653
|
// Subscribe to all events using the unified handler
|
|
595
654
|
const unsubscribeFunctions = [
|
|
596
655
|
client.onMessage('recording-state', (data) =>
|
|
597
|
-
handleEvent('recording-state', data)
|
|
656
|
+
handleEvent('recording-state', data),
|
|
598
657
|
),
|
|
599
658
|
client.onMessage('client-ui-settings', (data) =>
|
|
600
659
|
handleEvent('client-ui-settings', data),
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { ScrollArea } from '../components/ScrollArea';
|
|
2
|
+
import { Section } from '../components/Section';
|
|
3
|
+
import { KeyValueGrid, type KeyValueItem } from '../components/KeyValueGrid';
|
|
4
|
+
import type { HttpNetworkEntry, SSENetworkEntry } from '../state/model';
|
|
5
|
+
import type { Initiator, InitiatorStackFrame } from '../../shared/client';
|
|
6
|
+
import {
|
|
7
|
+
formatFrameLocation,
|
|
8
|
+
formatSourcePath,
|
|
9
|
+
getBestInitiatorFrame,
|
|
10
|
+
getGeneratedFrameLocation,
|
|
11
|
+
getInitiatorLabel,
|
|
12
|
+
getInitiatorLocationLabel,
|
|
13
|
+
getSourceFrameLocation,
|
|
14
|
+
} from '../utils/initiator';
|
|
15
|
+
|
|
16
|
+
export type InitiatorTabProps = {
|
|
17
|
+
selectedRequest: HttpNetworkEntry | SSENetworkEntry;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const formatInitiatorType = (type: string) => {
|
|
21
|
+
switch (type) {
|
|
22
|
+
case 'script':
|
|
23
|
+
return 'Script';
|
|
24
|
+
case 'other':
|
|
25
|
+
return 'Other';
|
|
26
|
+
default:
|
|
27
|
+
return type;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const formatSymbolicationStatus = (initiator?: Initiator) => {
|
|
32
|
+
switch (initiator?.symbolicationStatus) {
|
|
33
|
+
case 'pending':
|
|
34
|
+
return 'Resolving source...';
|
|
35
|
+
case 'complete':
|
|
36
|
+
return 'Resolved';
|
|
37
|
+
case 'failed':
|
|
38
|
+
return 'Failed';
|
|
39
|
+
case 'unavailable':
|
|
40
|
+
return 'Unavailable';
|
|
41
|
+
default:
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const getInitiatorItems = (initiator?: Initiator): KeyValueItem[] => {
|
|
47
|
+
if (!initiator) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const sourceFrame = getBestInitiatorFrame(initiator);
|
|
52
|
+
const sourceLocation = getInitiatorLocationLabel(initiator);
|
|
53
|
+
const generatedLocation = formatFrameLocation(
|
|
54
|
+
getGeneratedFrameLocation(initiator),
|
|
55
|
+
);
|
|
56
|
+
const status = formatSymbolicationStatus(initiator);
|
|
57
|
+
|
|
58
|
+
return [
|
|
59
|
+
{
|
|
60
|
+
key: 'Type',
|
|
61
|
+
value: formatInitiatorType(initiator.type),
|
|
62
|
+
},
|
|
63
|
+
...(status
|
|
64
|
+
? [
|
|
65
|
+
{
|
|
66
|
+
key: 'Source map',
|
|
67
|
+
value: status,
|
|
68
|
+
valueClassName:
|
|
69
|
+
initiator.symbolicationStatus === 'failed'
|
|
70
|
+
? 'text-red-300'
|
|
71
|
+
: 'text-gray-300',
|
|
72
|
+
},
|
|
73
|
+
]
|
|
74
|
+
: []),
|
|
75
|
+
...(sourceFrame?.functionName
|
|
76
|
+
? [
|
|
77
|
+
{
|
|
78
|
+
key: 'Function',
|
|
79
|
+
value: sourceFrame.functionName,
|
|
80
|
+
valueClassName: 'font-mono text-blue-300',
|
|
81
|
+
},
|
|
82
|
+
]
|
|
83
|
+
: []),
|
|
84
|
+
...(sourceFrame?.url
|
|
85
|
+
? [
|
|
86
|
+
{
|
|
87
|
+
key: 'Source',
|
|
88
|
+
value: formatSourcePath(sourceFrame.url),
|
|
89
|
+
valueClassName: 'font-mono text-blue-300',
|
|
90
|
+
},
|
|
91
|
+
]
|
|
92
|
+
: []),
|
|
93
|
+
...(sourceFrame?.lineNumber !== undefined
|
|
94
|
+
? [
|
|
95
|
+
{
|
|
96
|
+
key: 'Line',
|
|
97
|
+
value: sourceFrame.lineNumber,
|
|
98
|
+
},
|
|
99
|
+
]
|
|
100
|
+
: []),
|
|
101
|
+
...(sourceFrame?.columnNumber !== undefined
|
|
102
|
+
? [
|
|
103
|
+
{
|
|
104
|
+
key: 'Column',
|
|
105
|
+
value: sourceFrame.columnNumber,
|
|
106
|
+
},
|
|
107
|
+
]
|
|
108
|
+
: []),
|
|
109
|
+
...(sourceLocation
|
|
110
|
+
? [
|
|
111
|
+
{
|
|
112
|
+
key: 'Location',
|
|
113
|
+
value: sourceLocation,
|
|
114
|
+
valueClassName: 'font-mono text-blue-300',
|
|
115
|
+
},
|
|
116
|
+
]
|
|
117
|
+
: []),
|
|
118
|
+
...(generatedLocation && generatedLocation !== sourceLocation
|
|
119
|
+
? [
|
|
120
|
+
{
|
|
121
|
+
key: 'Generated',
|
|
122
|
+
value: generatedLocation,
|
|
123
|
+
valueClassName: 'font-mono text-gray-500',
|
|
124
|
+
},
|
|
125
|
+
]
|
|
126
|
+
: []),
|
|
127
|
+
];
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const getStackFrameLocation = (frame: InitiatorStackFrame) => {
|
|
131
|
+
const sourceLocation = formatFrameLocation(getSourceFrameLocation(frame));
|
|
132
|
+
const generatedLocation = formatFrameLocation(
|
|
133
|
+
getGeneratedFrameLocation(frame),
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
if (
|
|
137
|
+
sourceLocation &&
|
|
138
|
+
generatedLocation &&
|
|
139
|
+
sourceLocation !== generatedLocation
|
|
140
|
+
) {
|
|
141
|
+
return (
|
|
142
|
+
<span>
|
|
143
|
+
{sourceLocation}
|
|
144
|
+
<span className="ml-2 text-gray-500">
|
|
145
|
+
generated {generatedLocation}
|
|
146
|
+
</span>
|
|
147
|
+
</span>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return sourceLocation ?? generatedLocation ?? 'Unknown location';
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const getStackItems = (initiator?: Initiator): KeyValueItem[] => {
|
|
155
|
+
return (
|
|
156
|
+
initiator?.stack?.map((frame, index) => {
|
|
157
|
+
return {
|
|
158
|
+
key: frame.functionName || `Frame ${index + 1}`,
|
|
159
|
+
value: getStackFrameLocation(frame),
|
|
160
|
+
keyClassName: 'font-mono',
|
|
161
|
+
valueClassName: 'font-mono text-blue-300',
|
|
162
|
+
};
|
|
163
|
+
}) ?? []
|
|
164
|
+
);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export const InitiatorTab = ({ selectedRequest }: InitiatorTabProps) => {
|
|
168
|
+
const initiator = selectedRequest.initiator;
|
|
169
|
+
const initiatorItems = getInitiatorItems(initiator);
|
|
170
|
+
const stackItems = getStackItems(initiator);
|
|
171
|
+
const initiatorLabel = getInitiatorLabel(initiator);
|
|
172
|
+
const initiatorLocation = getInitiatorLocationLabel(initiator);
|
|
173
|
+
const hasSourceMappedFrame = Boolean(
|
|
174
|
+
getSourceFrameLocation(initiator) ||
|
|
175
|
+
initiator?.stack?.some((frame) => getSourceFrameLocation(frame)),
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
return (
|
|
179
|
+
<ScrollArea className="h-full w-full">
|
|
180
|
+
<div className="p-4 space-y-4">
|
|
181
|
+
<div className="rounded-md border border-gray-700 bg-gray-800/60 p-3">
|
|
182
|
+
<div className="text-xs uppercase text-gray-500">Triggered by</div>
|
|
183
|
+
<div className="mt-1 font-mono text-sm text-blue-300">
|
|
184
|
+
{initiatorLabel ?? 'Unknown initiator'}
|
|
185
|
+
</div>
|
|
186
|
+
{initiatorLocation && initiatorLocation !== initiatorLabel && (
|
|
187
|
+
<div className="mt-1 font-mono text-xs text-gray-400 wrap-anywhere">
|
|
188
|
+
{initiatorLocation}
|
|
189
|
+
</div>
|
|
190
|
+
)}
|
|
191
|
+
</div>
|
|
192
|
+
|
|
193
|
+
<Section title="Initiator">
|
|
194
|
+
<KeyValueGrid
|
|
195
|
+
items={initiatorItems}
|
|
196
|
+
emptyMessage="No initiator metadata available"
|
|
197
|
+
/>
|
|
198
|
+
</Section>
|
|
199
|
+
|
|
200
|
+
{!hasSourceMappedFrame &&
|
|
201
|
+
initiator?.symbolicationStatus !== 'pending' && (
|
|
202
|
+
<div className="rounded-md border border-gray-700 bg-gray-800/60 p-3 text-sm text-gray-400">
|
|
203
|
+
This request only includes generated bundle location data. Metro
|
|
204
|
+
source maps were not available for this entry.
|
|
205
|
+
</div>
|
|
206
|
+
)}
|
|
207
|
+
|
|
208
|
+
{initiator?.symbolicationError && (
|
|
209
|
+
<div className="rounded-md border border-red-900/70 bg-red-950/30 p-3 text-sm text-red-200">
|
|
210
|
+
{initiator.symbolicationError}
|
|
211
|
+
</div>
|
|
212
|
+
)}
|
|
213
|
+
|
|
214
|
+
{initiator?.codeFrame && (
|
|
215
|
+
<Section title="Code Frame">
|
|
216
|
+
<pre className="overflow-auto rounded-md bg-gray-950 p-3 text-xs text-gray-300">
|
|
217
|
+
<code>{initiator.codeFrame.content}</code>
|
|
218
|
+
</pre>
|
|
219
|
+
</Section>
|
|
220
|
+
)}
|
|
221
|
+
|
|
222
|
+
{stackItems.length > 0 && (
|
|
223
|
+
<Section title="Stack Preview">
|
|
224
|
+
<KeyValueGrid items={stackItems} />
|
|
225
|
+
</Section>
|
|
226
|
+
)}
|
|
227
|
+
</div>
|
|
228
|
+
</ScrollArea>
|
|
229
|
+
);
|
|
230
|
+
};
|