@rozenite/network-activity-plugin 1.0.0-alpha.8 → 1.0.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 +2 -0
- package/dist/App.html +2 -2
- package/dist/assets/{App-R2ZMH9wJ.css → App-BrSkOkws.css} +269 -2
- package/dist/assets/{App-lNMijPJ4.js → App-C6wCDVkW.js} +17485 -10814
- package/dist/event-source.cjs +22 -0
- package/dist/event-source.js +23 -0
- package/dist/react-native.cjs +4 -1
- package/dist/react-native.js +4 -1
- package/dist/rozenite.json +1 -1
- package/dist/src/react-native/config.d.ts +20 -0
- package/dist/src/react-native/{network-inspector.d.ts → http/network-inspector.d.ts} +1 -1
- package/dist/src/react-native/http/overrides-registry.d.ts +6 -0
- package/dist/src/react-native/{xhr-interceptor.d.ts → http/xhr-interceptor.d.ts} +7 -1
- package/dist/src/react-native/sse/event-source.d.ts +2 -0
- package/dist/src/react-native/sse/sse-inspector.d.ts +9 -0
- package/dist/src/react-native/sse/sse-interceptor.d.ts +36 -0
- package/dist/src/react-native/sse/types.d.ts +6 -0
- package/dist/src/react-native/useNetworkActivityDevTools.d.ts +2 -1
- package/dist/src/react-native/utils/getBlobName.d.ts +35 -0
- package/dist/src/react-native/utils/getFormDataEntries.d.ts +18 -0
- package/dist/src/react-native/utils.d.ts +6 -0
- package/dist/src/react-native/websocket/websocket-inspector.d.ts +9 -0
- package/dist/src/react-native/websocket/websocket-interceptor.d.ts +74 -0
- package/dist/src/shared/client.d.ts +53 -6
- package/dist/src/shared/sse-events.d.ts +38 -0
- package/dist/src/shared/websocket-events.d.ts +60 -0
- package/dist/src/ui/components/Badge.d.ts +1 -1
- package/dist/src/ui/components/Button.d.ts +2 -2
- package/dist/src/ui/components/CodeBlock.d.ts +3 -0
- package/dist/src/ui/components/CodeEditor.d.ts +5 -0
- package/dist/src/ui/components/CookieCard.d.ts +7 -0
- package/dist/src/ui/components/CopyRequestDropdown.d.ts +7 -0
- package/dist/src/ui/components/DropdownMenu.d.ts +27 -0
- package/dist/src/ui/components/FilterBar.d.ts +10 -0
- package/dist/src/ui/components/JsonTreeCopyableItem.d.ts +7 -0
- package/dist/src/ui/components/KeyValueGrid.d.ts +13 -0
- package/dist/src/ui/components/OverrideResponse.d.ts +8 -0
- package/dist/src/ui/components/RequestBody.d.ts +6 -0
- package/dist/src/ui/components/RequestList.d.ts +13 -28
- package/dist/src/ui/components/ScrollArea.d.ts +3 -2
- package/dist/src/ui/components/Section.d.ts +8 -0
- package/dist/src/ui/components/Separator.d.ts +2 -1
- package/dist/src/ui/components/SidePanel.d.ts +1 -0
- package/dist/src/ui/components/Tabs.d.ts +7 -0
- package/dist/src/ui/components/Toolbar.d.ts +1 -0
- package/dist/src/ui/hooks/useCopyToClipboard.d.ts +4 -0
- package/dist/src/ui/state/derived.d.ts +5 -0
- package/dist/src/ui/state/hooks.d.ts +21 -0
- package/dist/src/ui/state/model.d.ts +103 -0
- package/dist/src/ui/state/store.d.ts +48 -0
- package/dist/src/ui/tabs/CookiesTab.d.ts +3 -6
- package/dist/src/ui/tabs/HeadersTab.d.ts +3 -15
- package/dist/src/ui/tabs/MessagesTab.d.ts +5 -0
- package/dist/src/ui/tabs/RequestTab.d.ts +2 -7
- package/dist/src/ui/tabs/ResponseTab.d.ts +2 -8
- package/dist/src/ui/tabs/SSEMessagesTab.d.ts +5 -0
- package/dist/src/ui/tabs/TimingTab.d.ts +3 -5
- package/dist/src/ui/types.d.ts +4 -1
- package/dist/src/ui/utils/assert.d.ts +1 -0
- package/dist/src/ui/utils/checkRequestBodyBinary.d.ts +2 -0
- package/dist/src/ui/utils/copyToClipboard.d.ts +1 -0
- package/dist/src/ui/utils/escapeShellArg.d.ts +1 -0
- package/dist/src/ui/utils/generateCurlCommand.d.ts +2 -0
- package/dist/src/ui/utils/generateFetchCall.d.ts +2 -0
- package/dist/src/ui/utils/generateMultipartBody.d.ts +4 -0
- package/dist/src/ui/utils/getId.d.ts +1 -0
- package/dist/src/ui/utils/getStatusColor.d.ts +1 -0
- package/dist/src/utils/applyReactNativeRequestHeadersLogic.d.ts +7 -0
- package/dist/src/utils/applyReactNativeResponseHeadersLogic.d.ts +9 -0
- package/dist/src/utils/cookieParser.d.ts +6 -0
- package/dist/src/utils/getContentTypeMimeType.d.ts +2 -0
- package/dist/src/utils/getHttpHeader.d.ts +5 -0
- package/dist/src/utils/getHttpHeaderValueAsString.d.ts +11 -0
- package/dist/src/utils/getStringSizeInBytes.d.ts +1 -0
- package/dist/src/utils/inferContentTypeFromPostData.d.ts +2 -0
- package/dist/src/utils/safeStringify.d.ts +1 -0
- package/dist/src/utils/typeChecks.d.ts +9 -0
- package/dist/useNetworkActivityDevTools.cjs +724 -40
- package/dist/useNetworkActivityDevTools.js +723 -41
- package/package.json +22 -8
- package/react-native.ts +6 -1
- package/src/react-native/config.ts +43 -0
- package/src/react-native/http/network-inspector.ts +388 -0
- package/src/react-native/http/overrides-registry.ts +32 -0
- package/src/react-native/{xhr-interceptor.ts → http/xhr-interceptor.ts} +19 -2
- package/src/react-native/{xml-request.d.ts → http/xml-request.d.ts} +1 -0
- package/src/react-native/sse/event-source.ts +25 -0
- package/src/react-native/sse/sse-inspector.ts +139 -0
- package/src/react-native/sse/sse-interceptor.ts +180 -0
- package/src/react-native/sse/types.ts +9 -0
- package/src/react-native/useNetworkActivityDevTools.ts +156 -4
- package/src/react-native/utils/getBlobName.ts +45 -0
- package/src/react-native/utils/getFormDataEntries.ts +32 -0
- package/src/react-native/utils.ts +43 -0
- package/src/react-native/websocket/websocket-inspector.ts +180 -0
- package/src/react-native/websocket/websocket-interceptor.d.ts +4 -0
- package/src/react-native/websocket/websocket-interceptor.ts +166 -0
- package/src/shared/client.ts +79 -6
- package/src/shared/sse-events.ts +47 -0
- package/src/shared/websocket-events.ts +79 -0
- package/src/ui/components/Button.tsx +1 -0
- package/src/ui/components/CodeBlock.tsx +19 -0
- package/src/ui/components/CodeEditor.tsx +26 -0
- package/src/ui/components/CookieCard.tsx +64 -0
- package/src/ui/components/CopyRequestDropdown.tsx +95 -0
- package/src/ui/components/DropdownMenu.tsx +206 -0
- package/src/ui/components/FilterBar.tsx +117 -0
- package/src/ui/components/Input.tsx +1 -1
- package/src/ui/components/JsonTree.tsx +20 -0
- package/src/ui/components/JsonTreeCopyableItem.tsx +37 -0
- package/src/ui/components/KeyValueGrid.tsx +51 -0
- package/src/ui/components/OverrideResponse.tsx +132 -0
- package/src/ui/components/RequestBody.tsx +86 -0
- package/src/ui/components/RequestList.tsx +101 -131
- package/src/ui/components/ScrollArea.tsx +1 -0
- package/src/ui/components/Section.tsx +46 -0
- package/src/ui/components/SidePanel.tsx +333 -0
- package/src/ui/components/Tabs.tsx +1 -1
- package/src/ui/components/Toolbar.tsx +45 -0
- package/src/ui/globals.css +4 -0
- package/src/ui/hooks/useCopyToClipboard.ts +28 -0
- package/src/ui/state/derived.ts +112 -0
- package/src/ui/state/hooks.ts +52 -0
- package/src/ui/state/model.ts +140 -0
- package/src/ui/state/store.ts +669 -0
- package/src/ui/tabs/CookiesTab.tsx +61 -278
- package/src/ui/tabs/HeadersTab.tsx +85 -103
- package/src/ui/tabs/MessagesTab.tsx +276 -0
- package/src/ui/tabs/RequestTab.tsx +58 -51
- package/src/ui/tabs/ResponseTab.tsx +101 -74
- package/src/ui/tabs/SSEMessagesTab.tsx +224 -0
- package/src/ui/tabs/TimingTab.tsx +30 -43
- package/src/ui/types.ts +4 -1
- package/src/ui/utils/assert.ts +5 -0
- package/src/ui/utils/checkRequestBodyBinary.ts +7 -0
- package/src/ui/utils/copyToClipboard.ts +3 -0
- package/src/ui/utils/escapeShellArg.ts +12 -0
- package/src/ui/utils/generateCurlCommand.ts +83 -0
- package/src/ui/utils/generateFetchCall.ts +64 -0
- package/src/ui/utils/generateMultipartBody.ts +19 -0
- package/src/ui/utils/getId.ts +10 -0
- package/src/ui/utils/getStatusColor.ts +15 -0
- package/src/ui/views/InspectorView.tsx +35 -319
- package/src/utils/applyReactNativeRequestHeadersLogic.ts +30 -0
- package/src/utils/applyReactNativeResponseHeadersLogic.ts +28 -0
- package/src/utils/cookieParser.ts +126 -0
- package/src/utils/getContentTypeMimeType.ts +17 -0
- package/src/utils/getHttpHeader.ts +17 -0
- package/src/utils/getHttpHeaderValueAsString.ts +13 -0
- package/src/utils/getStringSizeInBytes.ts +3 -0
- package/src/utils/inferContentTypeFromPostData.ts +9 -0
- package/src/utils/safeStringify.ts +7 -0
- package/src/utils/typeChecks.ts +27 -0
- package/tailwind.config.ts +3 -0
- package/vite.config.ts +12 -0
- package/dist/src/ui/utils/getHttpHeaderValue.d.ts +0 -2
- package/src/react-native/network-inspector.ts +0 -247
- package/src/ui/utils/getHttpHeaderValue.ts +0 -14
- /package/dist/src/react-native/{network-requests-registry.d.ts → http/network-requests-registry.d.ts} +0 -0
- /package/src/react-native/{network-requests-registry.ts → http/network-requests-registry.ts} +0 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { useRef, useState } from 'react';
|
|
2
|
+
import { HttpNetworkEntry } from '../state/model';
|
|
3
|
+
import { Section } from '../components/Section';
|
|
4
|
+
import { KeyValueGrid } from '../components/KeyValueGrid';
|
|
5
|
+
import { useNetworkActivityActions } from '../state/hooks';
|
|
6
|
+
import { CodeEditor } from '../components/CodeEditor';
|
|
7
|
+
import { RequestOverride } from '../../shared/client';
|
|
8
|
+
import { Button } from './Button';
|
|
9
|
+
import { Check, CircleSlash2 } from 'lucide-react';
|
|
10
|
+
|
|
11
|
+
export type OverrideResponseProps = {
|
|
12
|
+
selectedRequest: HttpNetworkEntry;
|
|
13
|
+
initialOverride: RequestOverride | undefined;
|
|
14
|
+
onClear: () => void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const OverrideResponse = ({
|
|
18
|
+
selectedRequest,
|
|
19
|
+
initialOverride,
|
|
20
|
+
onClear,
|
|
21
|
+
}: OverrideResponseProps) => {
|
|
22
|
+
const actions = useNetworkActivityActions();
|
|
23
|
+
const [savedOverride, setSavedOverride] = useState<
|
|
24
|
+
RequestOverride | undefined
|
|
25
|
+
>(initialOverride);
|
|
26
|
+
const [editedBody, setEditedBody] = useState<string | undefined>(
|
|
27
|
+
initialOverride?.body
|
|
28
|
+
);
|
|
29
|
+
const [editedStatus, setEditedStatus] = useState<number | undefined>(
|
|
30
|
+
initialOverride?.status
|
|
31
|
+
);
|
|
32
|
+
const responseEditorRef = useRef<HTMLPreElement>(null);
|
|
33
|
+
const responseBody = selectedRequest.response?.body;
|
|
34
|
+
|
|
35
|
+
const saveOverride = () => {
|
|
36
|
+
if (editedBody === undefined && editedStatus === undefined) return;
|
|
37
|
+
|
|
38
|
+
const newOverrideData = {
|
|
39
|
+
body: editedBody,
|
|
40
|
+
status: editedStatus,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
setSavedOverride(newOverrideData);
|
|
44
|
+
actions.addOverride(selectedRequest.request.url, newOverrideData);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const clearOverride = () => {
|
|
48
|
+
setSavedOverride(undefined);
|
|
49
|
+
setEditedBody(undefined);
|
|
50
|
+
actions.clearOverride(selectedRequest.request.url);
|
|
51
|
+
onClear();
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
if (!responseBody || responseBody.data === null) {
|
|
55
|
+
return (
|
|
56
|
+
<div className="text-sm text-gray-400">
|
|
57
|
+
No response body available for this request
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const { type } = responseBody;
|
|
63
|
+
|
|
64
|
+
const hasChanges =
|
|
65
|
+
editedBody !== savedOverride?.body ||
|
|
66
|
+
editedStatus !== savedOverride?.status;
|
|
67
|
+
|
|
68
|
+
const overrideActions = (
|
|
69
|
+
<>
|
|
70
|
+
<Button
|
|
71
|
+
variant="ghost"
|
|
72
|
+
size="xs"
|
|
73
|
+
className="text-violet-300 hover:text-violet-300 ms-2"
|
|
74
|
+
onClick={clearOverride}
|
|
75
|
+
>
|
|
76
|
+
<CircleSlash2 className="h-2 w-2" />
|
|
77
|
+
Clear override
|
|
78
|
+
</Button>
|
|
79
|
+
|
|
80
|
+
<Button
|
|
81
|
+
variant="ghost"
|
|
82
|
+
size="xs"
|
|
83
|
+
className="text-violet-300 hover:text-violet-300"
|
|
84
|
+
onClick={saveOverride}
|
|
85
|
+
disabled={!hasChanges}
|
|
86
|
+
>
|
|
87
|
+
<Check className="h-2 w-2" />
|
|
88
|
+
{hasChanges ? 'Save override' : 'Saved'}
|
|
89
|
+
</Button>
|
|
90
|
+
</>
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
if (savedOverride !== undefined) {
|
|
94
|
+
return (
|
|
95
|
+
<Section
|
|
96
|
+
title="Response Body"
|
|
97
|
+
collapsible={false}
|
|
98
|
+
action={overrideActions}
|
|
99
|
+
>
|
|
100
|
+
<div className="space-y-4">
|
|
101
|
+
<KeyValueGrid
|
|
102
|
+
items={[
|
|
103
|
+
{
|
|
104
|
+
key: 'Content-Type',
|
|
105
|
+
value: type,
|
|
106
|
+
valueClassName: 'text-blue-400',
|
|
107
|
+
},
|
|
108
|
+
]}
|
|
109
|
+
/>
|
|
110
|
+
|
|
111
|
+
<div className="grid grid-cols-[minmax(7rem,25%)_minmax(3rem,1fr)] gap-x-2 gap-y-2 text-sm">
|
|
112
|
+
<span className={'text-gray-400 wrap-anywhere'}>Status Code</span>
|
|
113
|
+
<input
|
|
114
|
+
type="number"
|
|
115
|
+
value={editedStatus}
|
|
116
|
+
onChange={(e) => {
|
|
117
|
+
setEditedStatus(parseInt(e.target.value));
|
|
118
|
+
}}
|
|
119
|
+
className="max-w-24 font-mono text-gray-300 whitespace-pre-wrap bg-gray-800 p-1 rounded-md border border-gray-700 overflow-x-auto wrap-anywhere ring-offset-blue-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
120
|
+
/>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<CodeEditor
|
|
124
|
+
data={savedOverride?.body}
|
|
125
|
+
ref={responseEditorRef}
|
|
126
|
+
onInput={(e) => setEditedBody(e.currentTarget.innerText)}
|
|
127
|
+
/>
|
|
128
|
+
</div>
|
|
129
|
+
</Section>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { HttpRequestData } from '../state/model';
|
|
2
|
+
import { KeyValueGrid, KeyValueItem } from './KeyValueGrid';
|
|
3
|
+
import { CodeBlock } from './CodeBlock';
|
|
4
|
+
import { JsonTree } from './JsonTree';
|
|
5
|
+
import {
|
|
6
|
+
RequestBinaryPostData,
|
|
7
|
+
RequestFormDataPostData,
|
|
8
|
+
} from '../../shared/client';
|
|
9
|
+
|
|
10
|
+
type RequestBodyProps = {
|
|
11
|
+
data: HttpRequestData['data'];
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const getFormDataBinaryEntries = (
|
|
15
|
+
key: string,
|
|
16
|
+
value: RequestBinaryPostData['value']
|
|
17
|
+
): KeyValueItem[] => {
|
|
18
|
+
return [
|
|
19
|
+
{
|
|
20
|
+
key,
|
|
21
|
+
value: <span className="text-blue-400">[binary]</span>,
|
|
22
|
+
},
|
|
23
|
+
...getBinaryEntries(value).map((item) => ({
|
|
24
|
+
...item,
|
|
25
|
+
key: ` └─ ${item.key}`,
|
|
26
|
+
keyClassName: 'whitespace-pre',
|
|
27
|
+
})),
|
|
28
|
+
];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const getBinaryEntries = (
|
|
32
|
+
value: RequestBinaryPostData['value']
|
|
33
|
+
): KeyValueItem[] => {
|
|
34
|
+
const { size, type, name } = value;
|
|
35
|
+
|
|
36
|
+
const items: KeyValueItem[] = [];
|
|
37
|
+
|
|
38
|
+
if (name) {
|
|
39
|
+
items.push({ key: 'Name', value: name });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (type) {
|
|
43
|
+
items.push({ key: 'Type', value: type });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
items.push({ key: 'Size', value: `${size} bytes` });
|
|
47
|
+
|
|
48
|
+
return items;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const getFormDataEntries = (value: RequestFormDataPostData['value']) =>
|
|
52
|
+
Object.entries(value).flatMap(([key, { value, type }]) => {
|
|
53
|
+
if (type === 'binary') {
|
|
54
|
+
return getFormDataBinaryEntries(key, value);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return [{ key, value }];
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export const RequestBody = ({ data }: RequestBodyProps) => {
|
|
61
|
+
const { type: dataType, value } = data;
|
|
62
|
+
|
|
63
|
+
if (dataType === 'text') {
|
|
64
|
+
try {
|
|
65
|
+
const jsonData = JSON.parse(value);
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<CodeBlock>
|
|
69
|
+
<JsonTree data={jsonData} />
|
|
70
|
+
</CodeBlock>
|
|
71
|
+
);
|
|
72
|
+
} catch {
|
|
73
|
+
return <CodeBlock>{value}</CodeBlock>;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (dataType === 'form-data') {
|
|
78
|
+
return <KeyValueGrid items={getFormDataEntries(value)} />;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (dataType === 'binary') {
|
|
82
|
+
return <KeyValueGrid items={getBinaryEntries(value)} />;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return null;
|
|
86
|
+
};
|
|
@@ -1,42 +1,38 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useMemo, useState } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
createColumnHelper,
|
|
4
4
|
flexRender,
|
|
5
5
|
getCoreRowModel,
|
|
6
6
|
getSortedRowModel,
|
|
7
|
+
SortingFn,
|
|
7
8
|
SortingState,
|
|
8
9
|
useReactTable,
|
|
9
10
|
} from '@tanstack/react-table';
|
|
10
|
-
import {
|
|
11
|
-
import { RequestId } from '../../shared/client';
|
|
12
|
-
import {
|
|
11
|
+
import { ProcessedRequest } from '../state/model';
|
|
12
|
+
import { RequestId, RequestOverride } from '../../shared/client';
|
|
13
|
+
import {
|
|
14
|
+
useNetworkActivityActions,
|
|
15
|
+
useOverrides,
|
|
16
|
+
useProcessedRequests,
|
|
17
|
+
useSelectedRequestId,
|
|
18
|
+
useClientUISettings,
|
|
19
|
+
} from '../state/hooks';
|
|
20
|
+
import { getStatusColor } from '../utils/getStatusColor';
|
|
21
|
+
import { FilterState } from './FilterBar';
|
|
22
|
+
import { isNumber } from '../../utils/typeChecks';
|
|
13
23
|
|
|
14
24
|
type NetworkRequest = {
|
|
15
|
-
id:
|
|
25
|
+
id: RequestId;
|
|
16
26
|
name: string;
|
|
17
|
-
status: number;
|
|
27
|
+
status: string | number;
|
|
18
28
|
method: string;
|
|
19
29
|
domain: string;
|
|
20
30
|
path: string;
|
|
21
31
|
size: string;
|
|
22
32
|
time: string;
|
|
23
33
|
type: string;
|
|
24
|
-
initiator: string;
|
|
25
34
|
startTime: string;
|
|
26
|
-
|
|
27
|
-
type: string;
|
|
28
|
-
data: string;
|
|
29
|
-
};
|
|
30
|
-
responseBody?: {
|
|
31
|
-
type: string;
|
|
32
|
-
data: string | null;
|
|
33
|
-
};
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
type RequestListProps = {
|
|
37
|
-
networkEntries: Map<RequestId, NetworkEntry>;
|
|
38
|
-
selectedRequestId: RequestId | null;
|
|
39
|
-
onRequestSelect: (requestId: RequestId) => void;
|
|
35
|
+
hasOverride: boolean;
|
|
40
36
|
};
|
|
41
37
|
|
|
42
38
|
const formatSize = (bytes: number): string => {
|
|
@@ -68,85 +64,30 @@ const extractDomainAndPath = (
|
|
|
68
64
|
url: string
|
|
69
65
|
): { domain: string; path: string } => {
|
|
70
66
|
try {
|
|
71
|
-
const
|
|
67
|
+
const { hostname, pathname, search, hash, port } = new URL(url);
|
|
68
|
+
|
|
72
69
|
return {
|
|
73
|
-
domain:
|
|
74
|
-
path:
|
|
70
|
+
domain: `${hostname}${port ? `:${port}` : ''}`,
|
|
71
|
+
path: `${pathname}${search}${hash}`,
|
|
75
72
|
};
|
|
76
73
|
} catch {
|
|
77
74
|
return { domain: 'unknown', path: url };
|
|
78
75
|
}
|
|
79
76
|
};
|
|
80
77
|
|
|
81
|
-
const generateName = (url: string): string => {
|
|
78
|
+
const generateName = (url: string, showEntirePathName = false): string => {
|
|
82
79
|
try {
|
|
83
80
|
const urlObj = new URL(url);
|
|
84
81
|
const pathname = urlObj.pathname;
|
|
85
|
-
const filename = pathname.split('/').pop();
|
|
82
|
+
const filename = showEntirePathName ? undefined : pathname.split('/').pop();
|
|
83
|
+
|
|
86
84
|
return filename || pathname || urlObj.hostname;
|
|
87
85
|
} catch {
|
|
88
86
|
return url;
|
|
89
87
|
}
|
|
90
88
|
};
|
|
91
89
|
|
|
92
|
-
const
|
|
93
|
-
if (!initiator) return 'Other';
|
|
94
|
-
if (initiator.type === 'script' && initiator.url) {
|
|
95
|
-
try {
|
|
96
|
-
const url = new URL(initiator.url);
|
|
97
|
-
const filename = url.pathname.split('/').pop() || url.hostname;
|
|
98
|
-
const line = initiator.lineNumber ? `:${initiator.lineNumber}` : '';
|
|
99
|
-
return `${filename}${line}`;
|
|
100
|
-
} catch {
|
|
101
|
-
return 'Script';
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return initiator.type || 'Other';
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
const mapResourceType = (type: string): string => {
|
|
108
|
-
const typeMap: Record<string, string> = {
|
|
109
|
-
Document: 'document',
|
|
110
|
-
Stylesheet: 'stylesheet',
|
|
111
|
-
Image: 'img',
|
|
112
|
-
Media: 'media',
|
|
113
|
-
Font: 'font',
|
|
114
|
-
Script: 'script',
|
|
115
|
-
XHR: 'xhr',
|
|
116
|
-
Fetch: 'xhr',
|
|
117
|
-
EventSource: 'eventsource',
|
|
118
|
-
WebSocket: 'websocket',
|
|
119
|
-
Manifest: 'manifest',
|
|
120
|
-
Other: 'other',
|
|
121
|
-
Ping: 'ping',
|
|
122
|
-
CSPViolationReport: 'csp',
|
|
123
|
-
Preflight: 'preflight',
|
|
124
|
-
Subresource: 'subresource',
|
|
125
|
-
};
|
|
126
|
-
return typeMap[type] || 'other';
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
const getTypeColor = (type: string) => {
|
|
130
|
-
const colors: Record<string, string> = {
|
|
131
|
-
document: 'bg-blue-600',
|
|
132
|
-
script: 'bg-yellow-600',
|
|
133
|
-
stylesheet: 'bg-purple-600',
|
|
134
|
-
xhr: 'bg-green-600',
|
|
135
|
-
img: 'bg-pink-600',
|
|
136
|
-
font: 'bg-orange-600',
|
|
137
|
-
};
|
|
138
|
-
return colors[type] || 'bg-gray-600';
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
const getStatusColor = (status: number) => {
|
|
142
|
-
if (status >= 200 && status < 300) return 'text-green-400';
|
|
143
|
-
if (status >= 300 && status < 400) return 'text-yellow-400';
|
|
144
|
-
if (status >= 400) return 'text-red-400';
|
|
145
|
-
return 'text-gray-400';
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
// Custom sorting functions
|
|
149
|
-
const sortSize = (rowA: any, rowB: any, columnId: string) => {
|
|
90
|
+
const sortSize: SortingFn<NetworkRequest> = (rowA, rowB, columnId) => {
|
|
150
91
|
const a = rowA.getValue(columnId) as string;
|
|
151
92
|
const b = rowB.getValue(columnId) as string;
|
|
152
93
|
|
|
@@ -168,7 +109,7 @@ const sortSize = (rowA: any, rowB: any, columnId: string) => {
|
|
|
168
109
|
return getNumericValue(a) - getNumericValue(b);
|
|
169
110
|
};
|
|
170
111
|
|
|
171
|
-
const sortTime = (rowA
|
|
112
|
+
const sortTime: SortingFn<NetworkRequest> = (rowA, rowB, columnId) => {
|
|
172
113
|
const a = rowA.getValue(columnId) as string;
|
|
173
114
|
const b = rowB.getValue(columnId) as string;
|
|
174
115
|
|
|
@@ -184,38 +125,28 @@ const sortTime = (rowA: any, rowB: any, columnId: string) => {
|
|
|
184
125
|
return getNumericValue(a) - getNumericValue(b);
|
|
185
126
|
};
|
|
186
127
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
128
|
+
const processNetworkRequests = (
|
|
129
|
+
processedRequests: ProcessedRequest[],
|
|
130
|
+
overrides: Map<string, RequestOverride>,
|
|
131
|
+
showEntirePathAsName = false
|
|
190
132
|
): NetworkRequest[] => {
|
|
191
|
-
return
|
|
192
|
-
const { domain, path } = extractDomainAndPath(
|
|
193
|
-
const duration =
|
|
133
|
+
return processedRequests.map((request): NetworkRequest => {
|
|
134
|
+
const { domain, path } = extractDomainAndPath(request.name);
|
|
135
|
+
const duration = request.duration || 0;
|
|
136
|
+
const hasOverride = overrides.has(request.name);
|
|
194
137
|
|
|
195
138
|
return {
|
|
196
|
-
id:
|
|
197
|
-
name: generateName(
|
|
198
|
-
status:
|
|
199
|
-
method:
|
|
139
|
+
id: request.id,
|
|
140
|
+
name: generateName(request.name, showEntirePathAsName),
|
|
141
|
+
status: request.httpStatus || request.status,
|
|
142
|
+
method: request.method,
|
|
200
143
|
domain,
|
|
201
144
|
path,
|
|
202
|
-
size: formatSize(
|
|
145
|
+
size: isNumber(request.size) ? formatSize(request.size) : '—',
|
|
203
146
|
time: formatDuration(duration),
|
|
204
|
-
type:
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
requestBody: entry.request?.postData
|
|
208
|
-
? {
|
|
209
|
-
type: getHttpHeaderValue(entry.request.headers, 'content-type') || 'text/plain',
|
|
210
|
-
data: entry.request.postData,
|
|
211
|
-
}
|
|
212
|
-
: undefined,
|
|
213
|
-
responseBody: entry.responseBody
|
|
214
|
-
? {
|
|
215
|
-
type: entry.response?.contentType || 'application/octet-stream',
|
|
216
|
-
data: entry.responseBody.body,
|
|
217
|
-
}
|
|
218
|
-
: undefined,
|
|
147
|
+
type: request.type,
|
|
148
|
+
startTime: formatStartTime(request.timestamp),
|
|
149
|
+
hasOverride: hasOverride,
|
|
219
150
|
};
|
|
220
151
|
});
|
|
221
152
|
};
|
|
@@ -231,8 +162,14 @@ const columns = [
|
|
|
231
162
|
}),
|
|
232
163
|
columnHelper.accessor('name', {
|
|
233
164
|
header: 'Name',
|
|
234
|
-
cell: ({ getValue }) => (
|
|
235
|
-
<div className="flex-1 min-w-0 truncate"
|
|
165
|
+
cell: ({ row, getValue }) => (
|
|
166
|
+
<div className="flex-1 min-w-0 truncate" title={row.original.path}>
|
|
167
|
+
{getValue()}
|
|
168
|
+
|
|
169
|
+
{row.original.hasOverride && (
|
|
170
|
+
<span className="w-2 h-2 rounded-full bg-violet-300 ms-2 inline-block"></span>
|
|
171
|
+
)}
|
|
172
|
+
</div>
|
|
236
173
|
),
|
|
237
174
|
sortingFn: 'alphanumeric',
|
|
238
175
|
}),
|
|
@@ -262,28 +199,63 @@ const columns = [
|
|
|
262
199
|
}),
|
|
263
200
|
columnHelper.accessor('size', {
|
|
264
201
|
header: 'Size',
|
|
265
|
-
cell: ({ getValue }) =>
|
|
202
|
+
cell: ({ getValue }) => (
|
|
203
|
+
<div className="text-gray-300 whitespace-nowrap">{getValue()}</div>
|
|
204
|
+
),
|
|
266
205
|
size: 80,
|
|
267
206
|
sortingFn: sortSize,
|
|
268
207
|
}),
|
|
269
208
|
columnHelper.accessor('time', {
|
|
270
209
|
header: 'Time',
|
|
271
|
-
cell: ({ getValue }) =>
|
|
210
|
+
cell: ({ getValue }) => (
|
|
211
|
+
<div className="text-gray-300 whitespace-nowrap">{getValue()}</div>
|
|
212
|
+
),
|
|
272
213
|
size: 80,
|
|
273
214
|
sortingFn: sortTime,
|
|
274
215
|
}),
|
|
275
216
|
];
|
|
276
217
|
|
|
277
|
-
export
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
onRequestSelect,
|
|
281
|
-
}) => {
|
|
282
|
-
const [sorting, setSorting] = React.useState<SortingState>([]);
|
|
218
|
+
export type RequestListProps = {
|
|
219
|
+
filter: FilterState;
|
|
220
|
+
};
|
|
283
221
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
222
|
+
export const RequestList = ({ filter }: RequestListProps) => {
|
|
223
|
+
const actions = useNetworkActivityActions();
|
|
224
|
+
const processedRequests = useProcessedRequests();
|
|
225
|
+
const selectedRequestId = useSelectedRequestId();
|
|
226
|
+
const [sorting, setSorting] = useState<SortingState>([]);
|
|
227
|
+
const overrides = useOverrides();
|
|
228
|
+
const clientUISettings = useClientUISettings();
|
|
229
|
+
|
|
230
|
+
// Filter requests based on current filter state
|
|
231
|
+
const filteredRequests = useMemo(() => {
|
|
232
|
+
return processedRequests.filter((request) => {
|
|
233
|
+
// Type filter
|
|
234
|
+
if (!filter.types.has(request.type)) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Text filter
|
|
239
|
+
if (filter.text) {
|
|
240
|
+
const searchText = filter.text.toLowerCase();
|
|
241
|
+
const searchableFields = [
|
|
242
|
+
request.name,
|
|
243
|
+
request.method,
|
|
244
|
+
request.status.toString(),
|
|
245
|
+
]
|
|
246
|
+
.join(' ')
|
|
247
|
+
.toLowerCase();
|
|
248
|
+
|
|
249
|
+
return searchableFields.includes(searchText);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return true;
|
|
253
|
+
});
|
|
254
|
+
}, [processedRequests, filter]);
|
|
255
|
+
|
|
256
|
+
const requests = useMemo(() => {
|
|
257
|
+
return processNetworkRequests(filteredRequests, overrides, clientUISettings?.showUrlAsName);
|
|
258
|
+
}, [filteredRequests, overrides, clientUISettings?.showUrlAsName]);
|
|
287
259
|
|
|
288
260
|
const table = useReactTable({
|
|
289
261
|
data: requests,
|
|
@@ -296,6 +268,10 @@ export const RequestList: React.FC<RequestListProps> = ({
|
|
|
296
268
|
},
|
|
297
269
|
});
|
|
298
270
|
|
|
271
|
+
const onRequestSelect = (requestId: RequestId): void => {
|
|
272
|
+
actions.setSelectedRequest(requestId);
|
|
273
|
+
};
|
|
274
|
+
|
|
299
275
|
return (
|
|
300
276
|
<div className="flex-1 overflow-auto">
|
|
301
277
|
<table className="w-full">
|
|
@@ -367,11 +343,5 @@ export {
|
|
|
367
343
|
formatStartTime,
|
|
368
344
|
extractDomainAndPath,
|
|
369
345
|
generateName,
|
|
370
|
-
|
|
371
|
-
mapResourceType,
|
|
372
|
-
getTypeColor,
|
|
373
|
-
getStatusColor,
|
|
374
|
-
processNetworkEntries,
|
|
346
|
+
processNetworkRequests,
|
|
375
347
|
};
|
|
376
|
-
|
|
377
|
-
export type { NetworkRequest };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { cn } from '../utils/cn';
|
|
3
|
+
|
|
4
|
+
export type SectionProps = {
|
|
5
|
+
title: string;
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
collapsible?: boolean;
|
|
8
|
+
action?: React.ReactNode;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const Section = ({
|
|
12
|
+
title,
|
|
13
|
+
children,
|
|
14
|
+
collapsible = true,
|
|
15
|
+
action,
|
|
16
|
+
}: SectionProps) => {
|
|
17
|
+
const [isCollapsed, setIsCollapsed] = useState(false);
|
|
18
|
+
|
|
19
|
+
const isChildrenVisible = !collapsible || !isCollapsed;
|
|
20
|
+
|
|
21
|
+
const handleCollapseSection = () => {
|
|
22
|
+
setIsCollapsed((prevState) => !prevState);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const headerClassName = `flex items-center w-full text-left text-sm text-gray-300 mb-2 ${
|
|
26
|
+
collapsible ? 'hover:text-white' : 'cursor-default'
|
|
27
|
+
}`;
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div>
|
|
31
|
+
<button
|
|
32
|
+
onClick={collapsible ? handleCollapseSection : undefined}
|
|
33
|
+
className={headerClassName}
|
|
34
|
+
tabIndex={collapsible ? 0 : -1}
|
|
35
|
+
>
|
|
36
|
+
{collapsible && (
|
|
37
|
+
<span className={cn('mr-2', { 'rotate-90': !isCollapsed })}>▶</span>
|
|
38
|
+
)}
|
|
39
|
+
<span className="font-medium me-auto">{title}</span>
|
|
40
|
+
|
|
41
|
+
{action}
|
|
42
|
+
</button>
|
|
43
|
+
{isChildrenVisible && children}
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
};
|