@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,333 @@
|
|
|
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
|
+
useOverrides,
|
|
14
|
+
useSelectedRequest,
|
|
15
|
+
} from '../state/hooks';
|
|
16
|
+
import { NetworkEntry as OldNetworkEntry } from '../types';
|
|
17
|
+
import { getStatusColor } from '../utils/getStatusColor';
|
|
18
|
+
import { MessagesTab } from '../tabs/MessagesTab';
|
|
19
|
+
import { SSEMessagesTab } from '../tabs/SSEMessagesTab';
|
|
20
|
+
|
|
21
|
+
const getTypeColor = (type: string) => {
|
|
22
|
+
const colors: Record<string, string> = {
|
|
23
|
+
document: 'bg-blue-600',
|
|
24
|
+
script: 'bg-yellow-600',
|
|
25
|
+
stylesheet: 'bg-purple-600',
|
|
26
|
+
xhr: 'bg-green-600',
|
|
27
|
+
img: 'bg-pink-600',
|
|
28
|
+
font: 'bg-orange-600',
|
|
29
|
+
http: 'bg-green-600',
|
|
30
|
+
websocket: 'bg-blue-600',
|
|
31
|
+
sse: 'bg-purple-600',
|
|
32
|
+
};
|
|
33
|
+
return colors[type] || 'bg-gray-600';
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Adapter to convert new model to old format for tab components
|
|
37
|
+
const createLegacyNetworkEntry = (
|
|
38
|
+
selectedRequest: any,
|
|
39
|
+
httpDetails: any,
|
|
40
|
+
wsDetails: any
|
|
41
|
+
): OldNetworkEntry | null => {
|
|
42
|
+
if (selectedRequest.type === 'http' && httpDetails) {
|
|
43
|
+
return {
|
|
44
|
+
requestId: httpDetails.id,
|
|
45
|
+
url: httpDetails.request?.url || '',
|
|
46
|
+
method: httpDetails.request?.method || 'GET',
|
|
47
|
+
headers: httpDetails.request?.headers || {},
|
|
48
|
+
body: httpDetails.request?.body,
|
|
49
|
+
status: httpDetails.status,
|
|
50
|
+
startTime: httpDetails.timestamp,
|
|
51
|
+
endTime: httpDetails.duration
|
|
52
|
+
? httpDetails.timestamp + httpDetails.duration
|
|
53
|
+
: undefined,
|
|
54
|
+
duration: httpDetails.duration,
|
|
55
|
+
ttfb: httpDetails.ttfb,
|
|
56
|
+
type: httpDetails.resourceType,
|
|
57
|
+
initiator: httpDetails.initiator,
|
|
58
|
+
request: httpDetails.request,
|
|
59
|
+
response: httpDetails.response,
|
|
60
|
+
responseBody: httpDetails.response?.body
|
|
61
|
+
? { body: httpDetails.response.body }
|
|
62
|
+
: undefined,
|
|
63
|
+
error: httpDetails.error,
|
|
64
|
+
canceled: httpDetails.canceled,
|
|
65
|
+
size: httpDetails.size,
|
|
66
|
+
};
|
|
67
|
+
} else if (selectedRequest.type === 'websocket' && wsDetails) {
|
|
68
|
+
// For WebSocket, create a minimal entry since tabs are designed for HTTP
|
|
69
|
+
return {
|
|
70
|
+
requestId: wsDetails.id,
|
|
71
|
+
url: wsDetails.connection?.url || '',
|
|
72
|
+
method: 'WS',
|
|
73
|
+
headers: {},
|
|
74
|
+
status: wsDetails.status === 'open' ? 'finished' : 'pending',
|
|
75
|
+
startTime: wsDetails.timestamp,
|
|
76
|
+
endTime: wsDetails.duration
|
|
77
|
+
? wsDetails.timestamp + wsDetails.duration
|
|
78
|
+
: undefined,
|
|
79
|
+
duration: wsDetails.duration,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const SidePanel = () => {
|
|
86
|
+
const actions = useNetworkActivityActions();
|
|
87
|
+
const selectedRequest = useSelectedRequest();
|
|
88
|
+
const client = useNetworkActivityStore((state) => state._client);
|
|
89
|
+
const overrides = useOverrides();
|
|
90
|
+
|
|
91
|
+
const onClose = (): void => {
|
|
92
|
+
actions.setSelectedRequest(null);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Early return if no request is selected
|
|
96
|
+
if (!selectedRequest) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Get detailed information based on request type
|
|
101
|
+
const httpDetails = selectedRequest.type === 'http' ? selectedRequest : null;
|
|
102
|
+
const wsDetails =
|
|
103
|
+
selectedRequest.type === 'websocket' ? selectedRequest : null;
|
|
104
|
+
const sseDetails = selectedRequest.type === 'sse' ? selectedRequest : null;
|
|
105
|
+
|
|
106
|
+
// Extract name from the request
|
|
107
|
+
const requestName =
|
|
108
|
+
selectedRequest.type === 'http'
|
|
109
|
+
? httpDetails?.request?.url || 'Unknown'
|
|
110
|
+
: selectedRequest.type === 'websocket'
|
|
111
|
+
? wsDetails?.connection?.url || 'Unknown'
|
|
112
|
+
: sseDetails?.request?.url || 'Unknown';
|
|
113
|
+
|
|
114
|
+
// Extract status from the request
|
|
115
|
+
const requestStatus =
|
|
116
|
+
selectedRequest.type === 'http'
|
|
117
|
+
? httpDetails?.response?.status || httpDetails?.status || 'pending'
|
|
118
|
+
: selectedRequest.type === 'websocket'
|
|
119
|
+
? wsDetails?.status || 'unknown'
|
|
120
|
+
: sseDetails?.status || 'unknown';
|
|
121
|
+
|
|
122
|
+
// Create legacy network entry for tab components
|
|
123
|
+
const legacyEntry = createLegacyNetworkEntry(
|
|
124
|
+
selectedRequest,
|
|
125
|
+
httpDetails,
|
|
126
|
+
wsDetails
|
|
127
|
+
);
|
|
128
|
+
const legacyNetworkEntries = new Map<string, OldNetworkEntry>();
|
|
129
|
+
if (legacyEntry) {
|
|
130
|
+
legacyNetworkEntries.set(legacyEntry.requestId, legacyEntry);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const override = legacyEntry !== null ? overrides.get(legacyEntry.url) : null;
|
|
134
|
+
const hasResponseOverride = override && override.body ? true : false;
|
|
135
|
+
|
|
136
|
+
const getTabsListTriggers = () => {
|
|
137
|
+
if (httpDetails) {
|
|
138
|
+
return (
|
|
139
|
+
<>
|
|
140
|
+
<TabsTrigger
|
|
141
|
+
value="headers"
|
|
142
|
+
className="data-[state=active]:bg-gray-700"
|
|
143
|
+
>
|
|
144
|
+
Headers
|
|
145
|
+
</TabsTrigger>
|
|
146
|
+
<TabsTrigger
|
|
147
|
+
value="request"
|
|
148
|
+
className="data-[state=active]:bg-gray-700"
|
|
149
|
+
>
|
|
150
|
+
Request
|
|
151
|
+
</TabsTrigger>
|
|
152
|
+
<TabsTrigger
|
|
153
|
+
value="response"
|
|
154
|
+
className="data-[state=active]:bg-gray-700"
|
|
155
|
+
>
|
|
156
|
+
Response
|
|
157
|
+
{hasResponseOverride && (
|
|
158
|
+
<span className="w-2 h-2 rounded-full bg-violet-300 ms-2 inline-block"></span>
|
|
159
|
+
)}
|
|
160
|
+
</TabsTrigger>
|
|
161
|
+
<TabsTrigger
|
|
162
|
+
value="cookies"
|
|
163
|
+
className="data-[state=active]:bg-gray-700"
|
|
164
|
+
>
|
|
165
|
+
Cookies
|
|
166
|
+
</TabsTrigger>
|
|
167
|
+
<TabsTrigger
|
|
168
|
+
value="timing"
|
|
169
|
+
className="data-[state=active]:bg-gray-700"
|
|
170
|
+
>
|
|
171
|
+
Timing
|
|
172
|
+
</TabsTrigger>
|
|
173
|
+
</>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (sseDetails) {
|
|
178
|
+
return (
|
|
179
|
+
<>
|
|
180
|
+
<TabsTrigger
|
|
181
|
+
value="headers"
|
|
182
|
+
className="data-[state=active]:bg-gray-700"
|
|
183
|
+
>
|
|
184
|
+
Headers
|
|
185
|
+
</TabsTrigger>
|
|
186
|
+
<TabsTrigger
|
|
187
|
+
value="request"
|
|
188
|
+
className="data-[state=active]:bg-gray-700"
|
|
189
|
+
>
|
|
190
|
+
Request
|
|
191
|
+
</TabsTrigger>
|
|
192
|
+
<TabsTrigger
|
|
193
|
+
value="messages"
|
|
194
|
+
className="data-[state=active]:bg-gray-700"
|
|
195
|
+
>
|
|
196
|
+
Messages
|
|
197
|
+
</TabsTrigger>
|
|
198
|
+
</>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return (
|
|
203
|
+
<>
|
|
204
|
+
<TabsTrigger
|
|
205
|
+
value="messages"
|
|
206
|
+
className="data-[state=active]:bg-gray-700"
|
|
207
|
+
>
|
|
208
|
+
Messages
|
|
209
|
+
</TabsTrigger>
|
|
210
|
+
</>
|
|
211
|
+
);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const getTabsContent = () => {
|
|
215
|
+
if (httpDetails) {
|
|
216
|
+
return (
|
|
217
|
+
<>
|
|
218
|
+
<TabsContent value="headers" className="flex-1 m-0 overflow-hidden">
|
|
219
|
+
<HeadersTab selectedRequest={httpDetails} />
|
|
220
|
+
</TabsContent>
|
|
221
|
+
|
|
222
|
+
<TabsContent value="request" className="flex-1 m-0 overflow-hidden">
|
|
223
|
+
<RequestTab selectedRequest={httpDetails} />
|
|
224
|
+
</TabsContent>
|
|
225
|
+
|
|
226
|
+
<TabsContent value="response" className="flex-1 m-0 overflow-hidden">
|
|
227
|
+
<ResponseTab
|
|
228
|
+
selectedRequest={httpDetails}
|
|
229
|
+
onRequestResponseBody={(requestId) => {
|
|
230
|
+
if (client) {
|
|
231
|
+
client.send('get-response-body', {
|
|
232
|
+
requestId,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}}
|
|
236
|
+
/>
|
|
237
|
+
</TabsContent>
|
|
238
|
+
|
|
239
|
+
<TabsContent value="cookies" className="flex-1 m-0 overflow-hidden">
|
|
240
|
+
<CookiesTab selectedRequest={httpDetails} />
|
|
241
|
+
</TabsContent>
|
|
242
|
+
|
|
243
|
+
<TabsContent value="timing" className="flex-1 m-0 overflow-hidden">
|
|
244
|
+
<TimingTab selectedRequest={httpDetails} />
|
|
245
|
+
</TabsContent>
|
|
246
|
+
</>
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (wsDetails) {
|
|
251
|
+
return (
|
|
252
|
+
<>
|
|
253
|
+
<TabsContent value="messages" className="flex-1 m-0 overflow-hidden">
|
|
254
|
+
<MessagesTab selectedRequest={wsDetails} />
|
|
255
|
+
</TabsContent>
|
|
256
|
+
</>
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (sseDetails) {
|
|
261
|
+
return (
|
|
262
|
+
<>
|
|
263
|
+
<TabsContent value="headers" className="flex-1 m-0 overflow-hidden">
|
|
264
|
+
<HeadersTab selectedRequest={sseDetails} />
|
|
265
|
+
</TabsContent>
|
|
266
|
+
|
|
267
|
+
<TabsContent value="request" className="flex-1 m-0 overflow-hidden">
|
|
268
|
+
<RequestTab selectedRequest={sseDetails} />
|
|
269
|
+
</TabsContent>
|
|
270
|
+
|
|
271
|
+
<TabsContent value="messages" className="flex-1 m-0 overflow-hidden">
|
|
272
|
+
<SSEMessagesTab selectedRequest={sseDetails} />
|
|
273
|
+
</TabsContent>
|
|
274
|
+
|
|
275
|
+
<TabsContent value="cookies" className="flex-1 m-0 overflow-hidden">
|
|
276
|
+
<CookiesTab selectedRequest={sseDetails} />
|
|
277
|
+
</TabsContent>
|
|
278
|
+
</>
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
throw new Error('Invalid request type');
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
return (
|
|
286
|
+
<div className="w-1/2 flex flex-col bg-gray-900">
|
|
287
|
+
{/* Side Panel Header */}
|
|
288
|
+
<div className="flex items-center justify-between p-3 border-b border-gray-700 bg-gray-800">
|
|
289
|
+
<div className="flex items-center gap-2 min-w-0 flex-1">
|
|
290
|
+
<div
|
|
291
|
+
className={`w-3 h-3 rounded-full flex-shrink-0 ${getTypeColor(
|
|
292
|
+
selectedRequest.type
|
|
293
|
+
)}`}
|
|
294
|
+
></div>
|
|
295
|
+
<span className="font-medium truncate">{requestName}</span>
|
|
296
|
+
<Badge
|
|
297
|
+
variant="outline"
|
|
298
|
+
className={`${getStatusColor(
|
|
299
|
+
requestStatus
|
|
300
|
+
)} border-current flex-shrink-0`}
|
|
301
|
+
>
|
|
302
|
+
{requestStatus}
|
|
303
|
+
</Badge>
|
|
304
|
+
</div>
|
|
305
|
+
<Button
|
|
306
|
+
variant="ghost"
|
|
307
|
+
size="sm"
|
|
308
|
+
onClick={onClose}
|
|
309
|
+
className="h-6 w-6 p-0 text-gray-400 hover:text-blue-400 flex-shrink-0 ml-2"
|
|
310
|
+
>
|
|
311
|
+
<X className="h-4 w-4" />
|
|
312
|
+
</Button>
|
|
313
|
+
</div>
|
|
314
|
+
|
|
315
|
+
{/* Side Panel Content */}
|
|
316
|
+
<div className="flex-1 overflow-hidden">
|
|
317
|
+
<Tabs
|
|
318
|
+
key={selectedRequest.id}
|
|
319
|
+
defaultValue={
|
|
320
|
+
selectedRequest.type === 'websocket' ? 'messages' : 'headers'
|
|
321
|
+
}
|
|
322
|
+
className="h-full flex flex-col"
|
|
323
|
+
>
|
|
324
|
+
<TabsList className="grid w-full grid-cols-5 bg-gray-800 rounded-none border-b border-gray-700">
|
|
325
|
+
{getTabsListTriggers()}
|
|
326
|
+
</TabsList>
|
|
327
|
+
|
|
328
|
+
{getTabsContent()}
|
|
329
|
+
</Tabs>
|
|
330
|
+
</div>
|
|
331
|
+
</div>
|
|
332
|
+
);
|
|
333
|
+
};
|
|
@@ -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
|
+
};
|
package/src/ui/globals.css
CHANGED
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { memoize } from 'proxy-memoize';
|
|
2
|
+
import { NetworkActivityState } from './store';
|
|
3
|
+
import {
|
|
4
|
+
ProcessedRequest,
|
|
5
|
+
HttpNetworkEntry,
|
|
6
|
+
WebSocketNetworkEntry,
|
|
7
|
+
SSENetworkEntry,
|
|
8
|
+
} from './model';
|
|
9
|
+
|
|
10
|
+
export const getProcessedRequests = memoize((state: NetworkActivityState) => {
|
|
11
|
+
const { networkEntries } = state;
|
|
12
|
+
const requests: ProcessedRequest[] = [];
|
|
13
|
+
|
|
14
|
+
for (const entry of networkEntries.values()) {
|
|
15
|
+
if (entry.type === 'http') {
|
|
16
|
+
const httpEntry = entry as HttpNetworkEntry;
|
|
17
|
+
requests.push({
|
|
18
|
+
id: httpEntry.id,
|
|
19
|
+
type: 'http',
|
|
20
|
+
name: httpEntry.request.url,
|
|
21
|
+
status: httpEntry.status,
|
|
22
|
+
timestamp: httpEntry.timestamp,
|
|
23
|
+
duration: httpEntry.duration,
|
|
24
|
+
size: httpEntry.size,
|
|
25
|
+
method: httpEntry.request.method,
|
|
26
|
+
httpStatus: httpEntry.response?.status,
|
|
27
|
+
});
|
|
28
|
+
} else if (entry.type === 'websocket') {
|
|
29
|
+
const wsEntry = entry as WebSocketNetworkEntry;
|
|
30
|
+
requests.push({
|
|
31
|
+
id: wsEntry.id,
|
|
32
|
+
type: 'websocket',
|
|
33
|
+
name: wsEntry.connection.url,
|
|
34
|
+
status: wsEntry.status,
|
|
35
|
+
timestamp: wsEntry.timestamp,
|
|
36
|
+
duration: wsEntry.duration,
|
|
37
|
+
method: 'WS',
|
|
38
|
+
httpStatus: 0,
|
|
39
|
+
});
|
|
40
|
+
} else if (entry.type === 'sse') {
|
|
41
|
+
const sseEntry = entry as SSENetworkEntry;
|
|
42
|
+
requests.push({
|
|
43
|
+
id: sseEntry.id,
|
|
44
|
+
type: 'sse',
|
|
45
|
+
name: sseEntry.request.url,
|
|
46
|
+
status: sseEntry.status,
|
|
47
|
+
timestamp: sseEntry.timestamp,
|
|
48
|
+
duration: sseEntry.duration,
|
|
49
|
+
method: 'SSE',
|
|
50
|
+
httpStatus: 0,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return requests.sort((a, b) => b.timestamp - a.timestamp);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export const getSelectedRequest = memoize((state: NetworkActivityState) => {
|
|
59
|
+
const { selectedRequestId, networkEntries } = state;
|
|
60
|
+
if (!selectedRequestId) return null;
|
|
61
|
+
return networkEntries.get(selectedRequestId) || null;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export const getRequestSummary = (
|
|
65
|
+
requestId: string
|
|
66
|
+
): ((state: NetworkActivityState) => ProcessedRequest | null) =>
|
|
67
|
+
memoize((state: NetworkActivityState) => {
|
|
68
|
+
const { networkEntries } = state;
|
|
69
|
+
const entry = networkEntries.get(requestId);
|
|
70
|
+
if (!entry) return null;
|
|
71
|
+
|
|
72
|
+
if (entry.type === 'http') {
|
|
73
|
+
const httpEntry = entry as HttpNetworkEntry;
|
|
74
|
+
return {
|
|
75
|
+
id: httpEntry.id,
|
|
76
|
+
type: 'http',
|
|
77
|
+
name: httpEntry.request.url,
|
|
78
|
+
status: httpEntry.status,
|
|
79
|
+
timestamp: httpEntry.timestamp,
|
|
80
|
+
duration: httpEntry.duration,
|
|
81
|
+
size: httpEntry.size,
|
|
82
|
+
method: httpEntry.request.method,
|
|
83
|
+
httpStatus: httpEntry.response?.status || 0,
|
|
84
|
+
};
|
|
85
|
+
} else if (entry.type === 'websocket') {
|
|
86
|
+
const wsEntry = entry as WebSocketNetworkEntry;
|
|
87
|
+
return {
|
|
88
|
+
id: wsEntry.id,
|
|
89
|
+
type: 'websocket',
|
|
90
|
+
name: wsEntry.connection.url,
|
|
91
|
+
status: wsEntry.status,
|
|
92
|
+
timestamp: wsEntry.timestamp,
|
|
93
|
+
duration: wsEntry.duration,
|
|
94
|
+
method: 'WS',
|
|
95
|
+
httpStatus: 0,
|
|
96
|
+
};
|
|
97
|
+
} else if (entry.type === 'sse') {
|
|
98
|
+
const sseEntry = entry as SSENetworkEntry;
|
|
99
|
+
return {
|
|
100
|
+
id: sseEntry.id,
|
|
101
|
+
type: 'sse',
|
|
102
|
+
name: sseEntry.request.url,
|
|
103
|
+
status: sseEntry.status,
|
|
104
|
+
timestamp: sseEntry.timestamp,
|
|
105
|
+
duration: sseEntry.duration,
|
|
106
|
+
method: 'SSE',
|
|
107
|
+
httpStatus: 0,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return null;
|
|
112
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { useStore } from 'zustand';
|
|
2
|
+
import { store } from './store';
|
|
3
|
+
import type { NetworkActivityState } from './store';
|
|
4
|
+
import { getProcessedRequests, getSelectedRequest } from './derived';
|
|
5
|
+
|
|
6
|
+
export const useNetworkActivityStore = <T>(
|
|
7
|
+
selector: (state: NetworkActivityState) => T
|
|
8
|
+
): T => {
|
|
9
|
+
return useStore(store, selector);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const useProcessedRequests = () => {
|
|
13
|
+
return useNetworkActivityStore(getProcessedRequests);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const useSelectedRequest = () => {
|
|
17
|
+
return useNetworkActivityStore(getSelectedRequest);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const useHasSelectedRequest = () => {
|
|
21
|
+
return useNetworkActivityStore((state) => !!getSelectedRequest(state));
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const useSelectedRequestId = () => {
|
|
25
|
+
return useNetworkActivityStore((state) => state.selectedRequestId);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const useIsRecording = () => {
|
|
29
|
+
return useNetworkActivityStore((state) => state.isRecording);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const useNetworkActivityActions = () => {
|
|
33
|
+
return useNetworkActivityStore((state) => state.actions);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const useNetworkActivityClientManagement = () => {
|
|
37
|
+
return useNetworkActivityStore((state) => state.client);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const useWebSocketMessages = (requestId: string) => {
|
|
41
|
+
return useNetworkActivityStore(
|
|
42
|
+
(state) => state.websocketMessages.get(requestId) || []
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const useOverrides = () => {
|
|
47
|
+
return useNetworkActivityStore((state) => state.overrides);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const useClientUISettings = () => {
|
|
51
|
+
return useNetworkActivityStore((state) => state.clientUISettings);
|
|
52
|
+
};
|