@rozenite/network-activity-plugin 1.0.0-alpha.7 → 1.0.0-alpha.9
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/dist/App.html +2 -2
- package/dist/assets/{App-CIflVb88.js → App-CA1Fbh0I.js} +12009 -10809
- package/dist/assets/{App-Czu6Vt2P.css → App-DoHQsY5s.css} +43 -0
- package/dist/event-source.cjs +22 -0
- package/dist/event-source.js +23 -0
- package/dist/rozenite.json +1 -1
- package/dist/src/react-native/{network-inspector.d.ts → http/network-inspector.d.ts} +1 -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/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 +8 -4
- package/dist/src/shared/sse-events.d.ts +35 -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 +1 -1
- package/dist/src/ui/components/JsonTreeCopyableItem.d.ts +7 -0
- package/dist/src/ui/components/RequestList.d.ts +6 -26
- package/dist/src/ui/components/SidePanel.d.ts +1 -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 +17 -0
- package/dist/src/ui/state/model.d.ts +98 -0
- package/dist/src/ui/state/store.d.ts +24 -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 +6 -3
- package/dist/src/ui/utils/assert.d.ts +1 -0
- package/dist/src/ui/utils/copyToClipboard.d.ts +1 -0
- package/dist/src/ui/utils/getHttpHeaderValue.d.ts +2 -0
- package/dist/src/ui/utils/getId.d.ts +1 -0
- package/dist/src/ui/utils/getStatusColor.d.ts +1 -0
- package/dist/useNetworkActivityDevTools.cjs +433 -34
- package/dist/useNetworkActivityDevTools.js +431 -34
- package/package.json +19 -8
- package/src/react-native/{network-inspector.ts → http/network-inspector.ts} +14 -32
- 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 +117 -0
- package/src/react-native/sse/sse-interceptor.ts +162 -0
- package/src/react-native/sse/types.ts +9 -0
- package/src/react-native/useNetworkActivityDevTools.ts +75 -1
- 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 +10 -4
- package/src/shared/sse-events.ts +44 -0
- package/src/shared/websocket-events.ts +79 -0
- package/src/ui/components/Badge.tsx +1 -1
- package/src/ui/components/Button.tsx +1 -1
- package/src/ui/components/Input.tsx +1 -1
- package/src/ui/components/JsonTree.tsx +13 -0
- package/src/ui/components/JsonTreeCopyableItem.tsx +33 -0
- package/src/ui/components/RequestList.tsx +42 -123
- package/src/ui/components/ScrollArea.tsx +1 -1
- package/src/ui/components/Separator.tsx +1 -1
- package/src/ui/components/SidePanel.tsx +323 -0
- package/src/ui/components/Tabs.tsx +2 -2
- package/src/ui/components/Toolbar.tsx +45 -0
- package/src/ui/hooks/useCopyToClipboard.ts +28 -0
- package/src/ui/state/derived.ts +112 -0
- package/src/ui/state/hooks.ts +44 -0
- package/src/ui/state/model.ts +129 -0
- package/src/ui/state/store.ts +559 -0
- package/src/ui/tabs/CookiesTab.tsx +168 -179
- package/src/ui/tabs/HeadersTab.tsx +24 -31
- package/src/ui/tabs/MessagesTab.tsx +276 -0
- package/src/ui/tabs/RequestTab.tsx +28 -31
- package/src/ui/tabs/ResponseTab.tsx +10 -12
- package/src/ui/tabs/SSEMessagesTab.tsx +213 -0
- package/src/ui/tabs/TimingTab.tsx +33 -44
- package/src/ui/types.ts +6 -2
- package/src/ui/utils/assert.ts +5 -0
- package/src/ui/utils/copyToClipboard.ts +3 -0
- package/src/ui/utils/getHttpHeaderValue.ts +14 -0
- package/src/ui/utils/getId.ts +10 -0
- package/src/ui/utils/getStatusColor.ts +15 -0
- package/src/ui/views/InspectorView.tsx +24 -320
- package/tailwind.config.ts +3 -0
- package/vite.config.ts +12 -0
- /package/dist/src/react-native/{network-requests-registry.d.ts → http/network-requests-registry.d.ts} +0 -0
- /package/dist/src/react-native/{xhr-interceptor.d.ts → http/xhr-interceptor.d.ts} +0 -0
- /package/dist/src/ui/{utils.d.ts → utils/cn.d.ts} +0 -0
- /package/src/react-native/{network-requests-registry.ts → http/network-requests-registry.ts} +0 -0
- /package/src/react-native/{xhr-interceptor.ts → http/xhr-interceptor.ts} +0 -0
- /package/src/ui/{utils.ts → utils/cn.ts} +0 -0
|
@@ -1,41 +1,33 @@
|
|
|
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 { ProcessedRequest } from '../state/model';
|
|
11
12
|
import { RequestId } from '../../shared/client';
|
|
13
|
+
import {
|
|
14
|
+
useNetworkActivityActions,
|
|
15
|
+
useProcessedRequests,
|
|
16
|
+
useSelectedRequestId,
|
|
17
|
+
} from '../state/hooks';
|
|
18
|
+
import { getStatusColor } from '../utils/getStatusColor';
|
|
12
19
|
|
|
13
20
|
type NetworkRequest = {
|
|
14
|
-
id:
|
|
21
|
+
id: RequestId;
|
|
15
22
|
name: string;
|
|
16
|
-
status: number;
|
|
23
|
+
status: string | number;
|
|
17
24
|
method: string;
|
|
18
25
|
domain: string;
|
|
19
26
|
path: string;
|
|
20
27
|
size: string;
|
|
21
28
|
time: string;
|
|
22
29
|
type: string;
|
|
23
|
-
initiator: string;
|
|
24
30
|
startTime: string;
|
|
25
|
-
requestBody?: {
|
|
26
|
-
type: string;
|
|
27
|
-
data: string;
|
|
28
|
-
};
|
|
29
|
-
responseBody?: {
|
|
30
|
-
type: string;
|
|
31
|
-
data: string | null;
|
|
32
|
-
};
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
type RequestListProps = {
|
|
36
|
-
networkEntries: Map<RequestId, NetworkEntry>;
|
|
37
|
-
selectedRequestId: RequestId | null;
|
|
38
|
-
onRequestSelect: (requestId: RequestId) => void;
|
|
39
31
|
};
|
|
40
32
|
|
|
41
33
|
const formatSize = (bytes: number): string => {
|
|
@@ -67,10 +59,11 @@ const extractDomainAndPath = (
|
|
|
67
59
|
url: string
|
|
68
60
|
): { domain: string; path: string } => {
|
|
69
61
|
try {
|
|
70
|
-
const
|
|
62
|
+
const { hostname, pathname, search, hash, port } = new URL(url);
|
|
63
|
+
|
|
71
64
|
return {
|
|
72
|
-
domain:
|
|
73
|
-
path:
|
|
65
|
+
domain: `${hostname}${port ? `:${port}` : ''}`,
|
|
66
|
+
path: `${pathname}${search}${hash}`,
|
|
74
67
|
};
|
|
75
68
|
} catch {
|
|
76
69
|
return { domain: 'unknown', path: url };
|
|
@@ -88,64 +81,7 @@ const generateName = (url: string): string => {
|
|
|
88
81
|
}
|
|
89
82
|
};
|
|
90
83
|
|
|
91
|
-
const
|
|
92
|
-
if (!initiator) return 'Other';
|
|
93
|
-
if (initiator.type === 'script' && initiator.url) {
|
|
94
|
-
try {
|
|
95
|
-
const url = new URL(initiator.url);
|
|
96
|
-
const filename = url.pathname.split('/').pop() || url.hostname;
|
|
97
|
-
const line = initiator.lineNumber ? `:${initiator.lineNumber}` : '';
|
|
98
|
-
return `${filename}${line}`;
|
|
99
|
-
} catch {
|
|
100
|
-
return 'Script';
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return initiator.type || 'Other';
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
const mapResourceType = (type: string): string => {
|
|
107
|
-
const typeMap: Record<string, string> = {
|
|
108
|
-
Document: 'document',
|
|
109
|
-
Stylesheet: 'stylesheet',
|
|
110
|
-
Image: 'img',
|
|
111
|
-
Media: 'media',
|
|
112
|
-
Font: 'font',
|
|
113
|
-
Script: 'script',
|
|
114
|
-
XHR: 'xhr',
|
|
115
|
-
Fetch: 'xhr',
|
|
116
|
-
EventSource: 'eventsource',
|
|
117
|
-
WebSocket: 'websocket',
|
|
118
|
-
Manifest: 'manifest',
|
|
119
|
-
Other: 'other',
|
|
120
|
-
Ping: 'ping',
|
|
121
|
-
CSPViolationReport: 'csp',
|
|
122
|
-
Preflight: 'preflight',
|
|
123
|
-
Subresource: 'subresource',
|
|
124
|
-
};
|
|
125
|
-
return typeMap[type] || 'other';
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
const getTypeColor = (type: string) => {
|
|
129
|
-
const colors: Record<string, string> = {
|
|
130
|
-
document: 'bg-blue-600',
|
|
131
|
-
script: 'bg-yellow-600',
|
|
132
|
-
stylesheet: 'bg-purple-600',
|
|
133
|
-
xhr: 'bg-green-600',
|
|
134
|
-
img: 'bg-pink-600',
|
|
135
|
-
font: 'bg-orange-600',
|
|
136
|
-
};
|
|
137
|
-
return colors[type] || 'bg-gray-600';
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
const getStatusColor = (status: number) => {
|
|
141
|
-
if (status >= 200 && status < 300) return 'text-green-400';
|
|
142
|
-
if (status >= 300 && status < 400) return 'text-yellow-400';
|
|
143
|
-
if (status >= 400) return 'text-red-400';
|
|
144
|
-
return 'text-gray-400';
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
// Custom sorting functions
|
|
148
|
-
const sortSize = (rowA: any, rowB: any, columnId: string) => {
|
|
84
|
+
const sortSize: SortingFn<NetworkRequest> = (rowA, rowB, columnId) => {
|
|
149
85
|
const a = rowA.getValue(columnId) as string;
|
|
150
86
|
const b = rowB.getValue(columnId) as string;
|
|
151
87
|
|
|
@@ -167,7 +103,7 @@ const sortSize = (rowA: any, rowB: any, columnId: string) => {
|
|
|
167
103
|
return getNumericValue(a) - getNumericValue(b);
|
|
168
104
|
};
|
|
169
105
|
|
|
170
|
-
const sortTime = (rowA
|
|
106
|
+
const sortTime: SortingFn<NetworkRequest> = (rowA, rowB, columnId) => {
|
|
171
107
|
const a = rowA.getValue(columnId) as string;
|
|
172
108
|
const b = rowB.getValue(columnId) as string;
|
|
173
109
|
|
|
@@ -183,38 +119,24 @@ const sortTime = (rowA: any, rowB: any, columnId: string) => {
|
|
|
183
119
|
return getNumericValue(a) - getNumericValue(b);
|
|
184
120
|
};
|
|
185
121
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
networkEntries: Map<RequestId, NetworkEntry>
|
|
122
|
+
const processNetworkRequests = (
|
|
123
|
+
processedRequests: ProcessedRequest[]
|
|
189
124
|
): NetworkRequest[] => {
|
|
190
|
-
return
|
|
191
|
-
const { domain, path } = extractDomainAndPath(
|
|
192
|
-
const duration =
|
|
125
|
+
return processedRequests.map((request): NetworkRequest => {
|
|
126
|
+
const { domain, path } = extractDomainAndPath(request.name);
|
|
127
|
+
const duration = request.duration || 0;
|
|
193
128
|
|
|
194
129
|
return {
|
|
195
|
-
id:
|
|
196
|
-
name: generateName(
|
|
197
|
-
status:
|
|
198
|
-
method:
|
|
130
|
+
id: request.id,
|
|
131
|
+
name: generateName(request.name),
|
|
132
|
+
status: request.httpStatus || request.status,
|
|
133
|
+
method: request.method,
|
|
199
134
|
domain,
|
|
200
135
|
path,
|
|
201
|
-
size: formatSize(
|
|
136
|
+
size: formatSize(request.size || 0),
|
|
202
137
|
time: formatDuration(duration),
|
|
203
|
-
type:
|
|
204
|
-
|
|
205
|
-
startTime: formatStartTime(entry.startTime || 0),
|
|
206
|
-
requestBody: entry.request?.postData
|
|
207
|
-
? {
|
|
208
|
-
type: entry.request.headers['content-type'] || 'text/plain',
|
|
209
|
-
data: entry.request.postData,
|
|
210
|
-
}
|
|
211
|
-
: undefined,
|
|
212
|
-
responseBody: entry.responseBody
|
|
213
|
-
? {
|
|
214
|
-
type: entry.response?.contentType || 'application/octet-stream',
|
|
215
|
-
data: entry.responseBody.body,
|
|
216
|
-
}
|
|
217
|
-
: undefined,
|
|
138
|
+
type: request.type,
|
|
139
|
+
startTime: formatStartTime(request.timestamp),
|
|
218
140
|
};
|
|
219
141
|
});
|
|
220
142
|
};
|
|
@@ -273,16 +195,15 @@ const columns = [
|
|
|
273
195
|
}),
|
|
274
196
|
];
|
|
275
197
|
|
|
276
|
-
export const RequestList
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
const [sorting, setSorting] = React.useState<SortingState>([]);
|
|
198
|
+
export const RequestList = () => {
|
|
199
|
+
const actions = useNetworkActivityActions();
|
|
200
|
+
const processedRequests = useProcessedRequests();
|
|
201
|
+
const selectedRequestId = useSelectedRequestId();
|
|
202
|
+
const [sorting, setSorting] = useState<SortingState>([]);
|
|
282
203
|
|
|
283
|
-
const requests =
|
|
284
|
-
return
|
|
285
|
-
}, [
|
|
204
|
+
const requests = useMemo(() => {
|
|
205
|
+
return processNetworkRequests(processedRequests);
|
|
206
|
+
}, [processedRequests]);
|
|
286
207
|
|
|
287
208
|
const table = useReactTable({
|
|
288
209
|
data: requests,
|
|
@@ -295,6 +216,10 @@ export const RequestList: React.FC<RequestListProps> = ({
|
|
|
295
216
|
},
|
|
296
217
|
});
|
|
297
218
|
|
|
219
|
+
const onRequestSelect = (requestId: RequestId): void => {
|
|
220
|
+
actions.setSelectedRequest(requestId);
|
|
221
|
+
};
|
|
222
|
+
|
|
298
223
|
return (
|
|
299
224
|
<div className="flex-1 overflow-auto">
|
|
300
225
|
<table className="w-full">
|
|
@@ -366,11 +291,5 @@ export {
|
|
|
366
291
|
formatStartTime,
|
|
367
292
|
extractDomainAndPath,
|
|
368
293
|
generateName,
|
|
369
|
-
|
|
370
|
-
mapResourceType,
|
|
371
|
-
getTypeColor,
|
|
372
|
-
getStatusColor,
|
|
373
|
-
processNetworkEntries,
|
|
294
|
+
processNetworkRequests,
|
|
374
295
|
};
|
|
375
|
-
|
|
376
|
-
export type { NetworkRequest };
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import * as React from 'react';
|
|
4
4
|
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
|
|
5
5
|
|
|
6
|
-
import { cn } from '../utils';
|
|
6
|
+
import { cn } from '../utils/cn';
|
|
7
7
|
|
|
8
8
|
const ScrollArea = React.forwardRef<
|
|
9
9
|
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { Badge } from './Badge';
|
|
2
|
+
import { Button } from './Button';
|
|
3
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from './Tabs';
|
|
4
|
+
import { HeadersTab } from '../tabs/HeadersTab';
|
|
5
|
+
import { RequestTab } from '../tabs/RequestTab';
|
|
6
|
+
import { ResponseTab } from '../tabs/ResponseTab';
|
|
7
|
+
import { CookiesTab } from '../tabs/CookiesTab';
|
|
8
|
+
import { TimingTab } from '../tabs/TimingTab';
|
|
9
|
+
import { X } from 'lucide-react';
|
|
10
|
+
import {
|
|
11
|
+
useNetworkActivityActions,
|
|
12
|
+
useNetworkActivityStore,
|
|
13
|
+
useSelectedRequest,
|
|
14
|
+
} from '../state/hooks';
|
|
15
|
+
import { NetworkEntry as OldNetworkEntry } from '../types';
|
|
16
|
+
import { getStatusColor } from '../utils/getStatusColor';
|
|
17
|
+
import { MessagesTab } from '../tabs/MessagesTab';
|
|
18
|
+
import { SSEMessagesTab } from '../tabs/SSEMessagesTab';
|
|
19
|
+
|
|
20
|
+
const getTypeColor = (type: string) => {
|
|
21
|
+
const colors: Record<string, string> = {
|
|
22
|
+
document: 'bg-blue-600',
|
|
23
|
+
script: 'bg-yellow-600',
|
|
24
|
+
stylesheet: 'bg-purple-600',
|
|
25
|
+
xhr: 'bg-green-600',
|
|
26
|
+
img: 'bg-pink-600',
|
|
27
|
+
font: 'bg-orange-600',
|
|
28
|
+
http: 'bg-green-600',
|
|
29
|
+
websocket: 'bg-blue-600',
|
|
30
|
+
sse: 'bg-purple-600',
|
|
31
|
+
};
|
|
32
|
+
return colors[type] || 'bg-gray-600';
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Adapter to convert new model to old format for tab components
|
|
36
|
+
const createLegacyNetworkEntry = (
|
|
37
|
+
selectedRequest: any,
|
|
38
|
+
httpDetails: any,
|
|
39
|
+
wsDetails: any
|
|
40
|
+
): OldNetworkEntry | null => {
|
|
41
|
+
if (selectedRequest.type === 'http' && httpDetails) {
|
|
42
|
+
return {
|
|
43
|
+
requestId: httpDetails.id,
|
|
44
|
+
url: httpDetails.request?.url || '',
|
|
45
|
+
method: httpDetails.request?.method || 'GET',
|
|
46
|
+
headers: httpDetails.request?.headers || {},
|
|
47
|
+
body: httpDetails.request?.body,
|
|
48
|
+
status: httpDetails.status,
|
|
49
|
+
startTime: httpDetails.timestamp,
|
|
50
|
+
endTime: httpDetails.duration
|
|
51
|
+
? httpDetails.timestamp + httpDetails.duration
|
|
52
|
+
: undefined,
|
|
53
|
+
duration: httpDetails.duration,
|
|
54
|
+
ttfb: httpDetails.ttfb,
|
|
55
|
+
type: httpDetails.resourceType,
|
|
56
|
+
initiator: httpDetails.initiator,
|
|
57
|
+
request: httpDetails.request,
|
|
58
|
+
response: httpDetails.response,
|
|
59
|
+
responseBody: httpDetails.response?.body
|
|
60
|
+
? { body: httpDetails.response.body }
|
|
61
|
+
: undefined,
|
|
62
|
+
error: httpDetails.error,
|
|
63
|
+
canceled: httpDetails.canceled,
|
|
64
|
+
size: httpDetails.size,
|
|
65
|
+
};
|
|
66
|
+
} else if (selectedRequest.type === 'websocket' && wsDetails) {
|
|
67
|
+
// For WebSocket, create a minimal entry since tabs are designed for HTTP
|
|
68
|
+
return {
|
|
69
|
+
requestId: wsDetails.id,
|
|
70
|
+
url: wsDetails.connection?.url || '',
|
|
71
|
+
method: 'WS',
|
|
72
|
+
headers: {},
|
|
73
|
+
status: wsDetails.status === 'open' ? 'finished' : 'pending',
|
|
74
|
+
startTime: wsDetails.timestamp,
|
|
75
|
+
endTime: wsDetails.duration
|
|
76
|
+
? wsDetails.timestamp + wsDetails.duration
|
|
77
|
+
: undefined,
|
|
78
|
+
duration: wsDetails.duration,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const SidePanel = () => {
|
|
85
|
+
const actions = useNetworkActivityActions();
|
|
86
|
+
const selectedRequest = useSelectedRequest();
|
|
87
|
+
const client = useNetworkActivityStore((state) => state._client);
|
|
88
|
+
|
|
89
|
+
const onClose = (): void => {
|
|
90
|
+
actions.setSelectedRequest(null);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Early return if no request is selected
|
|
94
|
+
if (!selectedRequest) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Get detailed information based on request type
|
|
99
|
+
const httpDetails = selectedRequest.type === 'http' ? selectedRequest : null;
|
|
100
|
+
const wsDetails =
|
|
101
|
+
selectedRequest.type === 'websocket' ? selectedRequest : null;
|
|
102
|
+
const sseDetails = selectedRequest.type === 'sse' ? selectedRequest : null;
|
|
103
|
+
|
|
104
|
+
// Extract name from the request
|
|
105
|
+
const requestName =
|
|
106
|
+
selectedRequest.type === 'http'
|
|
107
|
+
? httpDetails?.request?.url || 'Unknown'
|
|
108
|
+
: selectedRequest.type === 'websocket'
|
|
109
|
+
? wsDetails?.connection?.url || 'Unknown'
|
|
110
|
+
: sseDetails?.request?.url || 'Unknown';
|
|
111
|
+
|
|
112
|
+
// Extract status from the request
|
|
113
|
+
const requestStatus =
|
|
114
|
+
selectedRequest.type === 'http'
|
|
115
|
+
? httpDetails?.response?.status || httpDetails?.status || 'pending'
|
|
116
|
+
: selectedRequest.type === 'websocket'
|
|
117
|
+
? wsDetails?.status || 'unknown'
|
|
118
|
+
: sseDetails?.status || 'unknown';
|
|
119
|
+
|
|
120
|
+
// Create legacy network entry for tab components
|
|
121
|
+
const legacyEntry = createLegacyNetworkEntry(
|
|
122
|
+
selectedRequest,
|
|
123
|
+
httpDetails,
|
|
124
|
+
wsDetails
|
|
125
|
+
);
|
|
126
|
+
const legacyNetworkEntries = new Map<string, OldNetworkEntry>();
|
|
127
|
+
if (legacyEntry) {
|
|
128
|
+
legacyNetworkEntries.set(legacyEntry.requestId, legacyEntry);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const getTabsListTriggers = () => {
|
|
132
|
+
if (httpDetails) {
|
|
133
|
+
return (
|
|
134
|
+
<>
|
|
135
|
+
<TabsTrigger
|
|
136
|
+
value="headers"
|
|
137
|
+
className="data-[state=active]:bg-gray-700"
|
|
138
|
+
>
|
|
139
|
+
Headers
|
|
140
|
+
</TabsTrigger>
|
|
141
|
+
<TabsTrigger
|
|
142
|
+
value="request"
|
|
143
|
+
className="data-[state=active]:bg-gray-700"
|
|
144
|
+
>
|
|
145
|
+
Request
|
|
146
|
+
</TabsTrigger>
|
|
147
|
+
<TabsTrigger
|
|
148
|
+
value="response"
|
|
149
|
+
className="data-[state=active]:bg-gray-700"
|
|
150
|
+
>
|
|
151
|
+
Response
|
|
152
|
+
</TabsTrigger>
|
|
153
|
+
<TabsTrigger
|
|
154
|
+
value="cookies"
|
|
155
|
+
className="data-[state=active]:bg-gray-700"
|
|
156
|
+
>
|
|
157
|
+
Cookies
|
|
158
|
+
</TabsTrigger>
|
|
159
|
+
<TabsTrigger
|
|
160
|
+
value="timing"
|
|
161
|
+
className="data-[state=active]:bg-gray-700"
|
|
162
|
+
>
|
|
163
|
+
Timing
|
|
164
|
+
</TabsTrigger>
|
|
165
|
+
</>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (sseDetails) {
|
|
170
|
+
return (
|
|
171
|
+
<>
|
|
172
|
+
<TabsTrigger
|
|
173
|
+
value="headers"
|
|
174
|
+
className="data-[state=active]:bg-gray-700"
|
|
175
|
+
>
|
|
176
|
+
Headers
|
|
177
|
+
</TabsTrigger>
|
|
178
|
+
<TabsTrigger
|
|
179
|
+
value="request"
|
|
180
|
+
className="data-[state=active]:bg-gray-700"
|
|
181
|
+
>
|
|
182
|
+
Request
|
|
183
|
+
</TabsTrigger>
|
|
184
|
+
<TabsTrigger
|
|
185
|
+
value="messages"
|
|
186
|
+
className="data-[state=active]:bg-gray-700"
|
|
187
|
+
>
|
|
188
|
+
Messages
|
|
189
|
+
</TabsTrigger>
|
|
190
|
+
</>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<>
|
|
196
|
+
<TabsTrigger
|
|
197
|
+
value="messages"
|
|
198
|
+
className="data-[state=active]:bg-gray-700"
|
|
199
|
+
>
|
|
200
|
+
Messages
|
|
201
|
+
</TabsTrigger>
|
|
202
|
+
</>
|
|
203
|
+
);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const getTabsContent = () => {
|
|
207
|
+
if (httpDetails) {
|
|
208
|
+
return (
|
|
209
|
+
<>
|
|
210
|
+
<TabsContent value="headers" className="flex-1 m-0 overflow-hidden">
|
|
211
|
+
<HeadersTab selectedRequest={httpDetails} />
|
|
212
|
+
</TabsContent>
|
|
213
|
+
|
|
214
|
+
<TabsContent value="request" className="flex-1 m-0 overflow-hidden">
|
|
215
|
+
<RequestTab selectedRequest={httpDetails} />
|
|
216
|
+
</TabsContent>
|
|
217
|
+
|
|
218
|
+
<TabsContent value="response" className="flex-1 m-0 overflow-hidden">
|
|
219
|
+
<ResponseTab
|
|
220
|
+
selectedRequest={httpDetails}
|
|
221
|
+
onRequestResponseBody={(requestId) => {
|
|
222
|
+
if (client) {
|
|
223
|
+
client.send('get-response-body', {
|
|
224
|
+
requestId,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}}
|
|
228
|
+
/>
|
|
229
|
+
</TabsContent>
|
|
230
|
+
|
|
231
|
+
<TabsContent value="cookies" className="flex-1 m-0 overflow-hidden">
|
|
232
|
+
<CookiesTab selectedRequest={httpDetails} />
|
|
233
|
+
</TabsContent>
|
|
234
|
+
|
|
235
|
+
<TabsContent value="timing" className="flex-1 m-0 overflow-hidden">
|
|
236
|
+
<TimingTab selectedRequest={httpDetails} />
|
|
237
|
+
</TabsContent>
|
|
238
|
+
</>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (wsDetails) {
|
|
243
|
+
return (
|
|
244
|
+
<>
|
|
245
|
+
<TabsContent value="messages" className="flex-1 m-0 overflow-hidden">
|
|
246
|
+
<MessagesTab selectedRequest={wsDetails} />
|
|
247
|
+
</TabsContent>
|
|
248
|
+
</>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (sseDetails) {
|
|
253
|
+
return (
|
|
254
|
+
<>
|
|
255
|
+
<TabsContent value="headers" className="flex-1 m-0 overflow-hidden">
|
|
256
|
+
<HeadersTab selectedRequest={sseDetails} />
|
|
257
|
+
</TabsContent>
|
|
258
|
+
|
|
259
|
+
<TabsContent value="request" className="flex-1 m-0 overflow-hidden">
|
|
260
|
+
<RequestTab selectedRequest={sseDetails} />
|
|
261
|
+
</TabsContent>
|
|
262
|
+
|
|
263
|
+
<TabsContent value="messages" className="flex-1 m-0 overflow-hidden">
|
|
264
|
+
<SSEMessagesTab selectedRequest={sseDetails} />
|
|
265
|
+
</TabsContent>
|
|
266
|
+
|
|
267
|
+
<TabsContent value="cookies" className="flex-1 m-0 overflow-hidden">
|
|
268
|
+
<CookiesTab selectedRequest={sseDetails} />
|
|
269
|
+
</TabsContent>
|
|
270
|
+
</>
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
throw new Error('Invalid request type');
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
return (
|
|
278
|
+
<div className="w-1/2 flex flex-col bg-gray-900">
|
|
279
|
+
{/* Side Panel Header */}
|
|
280
|
+
<div className="flex items-center justify-between p-3 border-b border-gray-700 bg-gray-800">
|
|
281
|
+
<div className="flex items-center gap-2">
|
|
282
|
+
<div
|
|
283
|
+
className={`w-3 h-3 rounded-full ${getTypeColor(
|
|
284
|
+
selectedRequest.type
|
|
285
|
+
)}`}
|
|
286
|
+
></div>
|
|
287
|
+
<span className="font-medium">{requestName}</span>
|
|
288
|
+
<Badge
|
|
289
|
+
variant="outline"
|
|
290
|
+
className={`${getStatusColor(requestStatus)} border-current`}
|
|
291
|
+
>
|
|
292
|
+
{requestStatus}
|
|
293
|
+
</Badge>
|
|
294
|
+
</div>
|
|
295
|
+
<Button
|
|
296
|
+
variant="ghost"
|
|
297
|
+
size="sm"
|
|
298
|
+
onClick={onClose}
|
|
299
|
+
className="h-6 w-6 p-0 text-gray-400 hover:text-blue-400"
|
|
300
|
+
>
|
|
301
|
+
<X className="h-4 w-4" />
|
|
302
|
+
</Button>
|
|
303
|
+
</div>
|
|
304
|
+
|
|
305
|
+
{/* Side Panel Content */}
|
|
306
|
+
<div className="flex-1 overflow-hidden">
|
|
307
|
+
<Tabs
|
|
308
|
+
key={selectedRequest.id}
|
|
309
|
+
defaultValue={
|
|
310
|
+
selectedRequest.type === 'websocket' ? 'messages' : 'headers'
|
|
311
|
+
}
|
|
312
|
+
className="h-full flex flex-col"
|
|
313
|
+
>
|
|
314
|
+
<TabsList className="grid w-full grid-cols-5 bg-gray-800 rounded-none border-b border-gray-700">
|
|
315
|
+
{getTabsListTriggers()}
|
|
316
|
+
</TabsList>
|
|
317
|
+
|
|
318
|
+
{getTabsContent()}
|
|
319
|
+
</Tabs>
|
|
320
|
+
</div>
|
|
321
|
+
</div>
|
|
322
|
+
);
|
|
323
|
+
};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import * as React from 'react';
|
|
4
4
|
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
|
5
5
|
|
|
6
|
-
import { cn } from '../utils';
|
|
6
|
+
import { cn } from '../utils/cn';
|
|
7
7
|
|
|
8
8
|
const Tabs = TabsPrimitive.Root;
|
|
9
9
|
|
|
@@ -44,7 +44,7 @@ const TabsContent = React.forwardRef<
|
|
|
44
44
|
<TabsPrimitive.Content
|
|
45
45
|
ref={ref}
|
|
46
46
|
className={cn(
|
|
47
|
-
'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
|
47
|
+
'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 min-h-0',
|
|
48
48
|
className
|
|
49
49
|
)}
|
|
50
50
|
{...props}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Button } from './Button';
|
|
2
|
+
import { Circle, Square, Trash2 } from 'lucide-react';
|
|
3
|
+
import { useIsRecording, useNetworkActivityActions } from '../state/hooks';
|
|
4
|
+
|
|
5
|
+
export const Toolbar = () => {
|
|
6
|
+
const actions = useNetworkActivityActions();
|
|
7
|
+
const isRecording = useIsRecording();
|
|
8
|
+
|
|
9
|
+
const onToggleRecording = (): void => {
|
|
10
|
+
actions.setRecording(!isRecording);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const onClearRequests = (): void => {
|
|
14
|
+
actions.clearRequests();
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className="flex items-center gap-2 p-2 border-b border-gray-700 bg-gray-800">
|
|
19
|
+
<Button
|
|
20
|
+
variant="ghost"
|
|
21
|
+
size="sm"
|
|
22
|
+
onClick={onToggleRecording}
|
|
23
|
+
className={`h-8 w-8 p-0 ${
|
|
24
|
+
isRecording
|
|
25
|
+
? 'text-red-400 hover:text-red-300'
|
|
26
|
+
: 'text-gray-400 hover:text-blue-400'
|
|
27
|
+
}`}
|
|
28
|
+
>
|
|
29
|
+
{isRecording ? (
|
|
30
|
+
<Circle className="h-4 w-4 fill-current" />
|
|
31
|
+
) : (
|
|
32
|
+
<Square className="h-4 w-4" />
|
|
33
|
+
)}
|
|
34
|
+
</Button>
|
|
35
|
+
<Button
|
|
36
|
+
variant="ghost"
|
|
37
|
+
size="sm"
|
|
38
|
+
onClick={onClearRequests}
|
|
39
|
+
className="h-8 w-8 p-0 text-gray-400 hover:text-blue-400"
|
|
40
|
+
>
|
|
41
|
+
<Trash2 className="h-4 w-4" />
|
|
42
|
+
</Button>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
import { copyToClipboard } from '../utils/copyToClipboard';
|
|
4
|
+
|
|
5
|
+
export function useCopyToClipboard() {
|
|
6
|
+
const [isCopied, setIsCopied] = useState(false);
|
|
7
|
+
|
|
8
|
+
const timeoutRef = useRef<NodeJS.Timeout>();
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
return () => clearTimeout(timeoutRef.current);
|
|
12
|
+
}, []);
|
|
13
|
+
|
|
14
|
+
const copy = useCallback(async (value: string) => {
|
|
15
|
+
try {
|
|
16
|
+
await copyToClipboard(value);
|
|
17
|
+
|
|
18
|
+
setIsCopied(true);
|
|
19
|
+
|
|
20
|
+
clearTimeout(timeoutRef.current);
|
|
21
|
+
timeoutRef.current = setTimeout(() => setIsCopied(false), 1000);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error('Failed to copy:', error);
|
|
24
|
+
}
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
return { isCopied, copy };
|
|
28
|
+
}
|