@rozenite/network-activity-plugin 1.0.0-alpha.9 → 1.1.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-DoHQsY5s.css → App-BrSkOkws.css} +223 -2
- package/dist/assets/{App-CA1Fbh0I.js → App-Kyi7zHUX.js} +8188 -2671
- 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/http/overrides-registry.d.ts +6 -0
- package/dist/src/react-native/http/xhr-interceptor.d.ts +7 -1
- package/dist/src/react-native/sse/sse-interceptor.d.ts +2 -2
- 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/shared/client.d.ts +55 -4
- package/dist/src/shared/sse-events.d.ts +4 -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 +1 -1
- 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 +9 -4
- 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/Tabs.d.ts +7 -0
- package/dist/src/ui/state/hooks.d.ts +4 -0
- package/dist/src/ui/state/model.d.ts +22 -7
- package/dist/src/ui/state/store.d.ts +27 -3
- package/dist/src/ui/utils/checkRequestBodyBinary.d.ts +2 -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/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 +337 -24
- package/dist/useNetworkActivityDevTools.js +338 -25
- package/package.json +7 -4
- package/react-native.ts +6 -1
- package/src/react-native/config.ts +43 -0
- package/src/react-native/http/network-inspector.ts +190 -8
- package/src/react-native/http/overrides-registry.ts +32 -0
- package/src/react-native/http/xhr-interceptor.ts +19 -2
- package/src/react-native/sse/sse-inspector.ts +27 -5
- package/src/react-native/sse/sse-interceptor.ts +26 -8
- package/src/react-native/useNetworkActivityDevTools.ts +86 -8
- package/src/react-native/utils/getBlobName.ts +45 -0
- package/src/react-native/utils/getFormDataEntries.ts +32 -0
- package/src/react-native/utils.ts +3 -3
- package/src/shared/client.ts +81 -4
- package/src/shared/sse-events.ts +4 -1
- 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 +10 -3
- package/src/ui/components/JsonTreeCopyableItem.tsx +14 -10
- 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 +74 -14
- package/src/ui/components/ScrollArea.tsx +1 -0
- package/src/ui/components/Section.tsx +46 -0
- package/src/ui/components/SidePanel.tsx +15 -5
- package/src/ui/components/Toolbar.tsx +3 -2
- package/src/ui/globals.css +4 -0
- package/src/ui/hooks/useCopyToClipboard.ts +2 -2
- package/src/ui/state/derived.ts +2 -0
- package/src/ui/state/hooks.ts +8 -0
- package/src/ui/state/model.ts +28 -7
- package/src/ui/state/store.ts +640 -500
- package/src/ui/tabs/CookiesTab.tsx +60 -263
- package/src/ui/tabs/HeadersTab.tsx +78 -89
- package/src/ui/tabs/RequestTab.tsx +58 -46
- package/src/ui/tabs/ResponseTab.tsx +98 -67
- package/src/ui/tabs/SSEMessagesTab.tsx +50 -39
- package/src/ui/utils/checkRequestBodyBinary.ts +7 -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/views/InspectorView.tsx +15 -3
- 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/dist/src/ui/utils/getHttpHeaderValue.d.ts +0 -2
- package/src/ui/utils/getHttpHeaderValue.ts +0 -14
|
@@ -1,278 +1,75 @@
|
|
|
1
|
+
import React from 'react';
|
|
1
2
|
import { ScrollArea } from '../components/ScrollArea';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
3
|
+
import { Section } from '../components/Section';
|
|
4
|
+
import { CookieCard } from '../components/CookieCard';
|
|
5
|
+
import {
|
|
6
|
+
parseRequestCookiesFromHeaders,
|
|
7
|
+
parseResponseCookiesFromHeaders,
|
|
8
|
+
} from '../../utils/cookieParser';
|
|
4
9
|
import { HttpNetworkEntry, SSENetworkEntry } from '../state/model';
|
|
5
10
|
|
|
6
|
-
type Cookie = {
|
|
7
|
-
name: string;
|
|
8
|
-
value: string;
|
|
9
|
-
domain?: string;
|
|
10
|
-
path?: string;
|
|
11
|
-
expires?: string;
|
|
12
|
-
maxAge?: string;
|
|
13
|
-
secure?: boolean;
|
|
14
|
-
httpOnly?: boolean;
|
|
15
|
-
sameSite?: string;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
11
|
export type CookiesTabProps = {
|
|
19
12
|
selectedRequest: HttpNetworkEntry | SSENetworkEntry;
|
|
20
13
|
};
|
|
21
14
|
|
|
22
|
-
const parseCookieString = (cookieString: string): Cookie[] => {
|
|
23
|
-
if (!cookieString) return [];
|
|
24
|
-
|
|
25
|
-
return cookieString
|
|
26
|
-
.split(';')
|
|
27
|
-
.map((cookieStr) => {
|
|
28
|
-
const [nameValue, ...attributes] = cookieStr.trim().split(';');
|
|
29
|
-
const [name, value] = nameValue.split('=');
|
|
30
|
-
|
|
31
|
-
const cookieObj: Cookie = {
|
|
32
|
-
name: name?.trim() || '',
|
|
33
|
-
value: value?.trim() || '',
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
// Parse attributes
|
|
37
|
-
attributes.forEach((attr) => {
|
|
38
|
-
const [attrName, attrValue] = attr.trim().split('=');
|
|
39
|
-
const lowerAttrName = attrName.toLowerCase();
|
|
40
|
-
|
|
41
|
-
switch (lowerAttrName) {
|
|
42
|
-
case 'domain':
|
|
43
|
-
cookieObj.domain = attrValue;
|
|
44
|
-
break;
|
|
45
|
-
case 'path':
|
|
46
|
-
cookieObj.path = attrValue;
|
|
47
|
-
break;
|
|
48
|
-
case 'expires':
|
|
49
|
-
cookieObj.expires = attrValue;
|
|
50
|
-
break;
|
|
51
|
-
case 'max-age':
|
|
52
|
-
cookieObj.maxAge = attrValue;
|
|
53
|
-
break;
|
|
54
|
-
case 'secure':
|
|
55
|
-
cookieObj.secure = true;
|
|
56
|
-
break;
|
|
57
|
-
case 'httponly':
|
|
58
|
-
cookieObj.httpOnly = true;
|
|
59
|
-
break;
|
|
60
|
-
case 'samesite':
|
|
61
|
-
cookieObj.sameSite = attrValue;
|
|
62
|
-
break;
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
return cookieObj;
|
|
67
|
-
})
|
|
68
|
-
.filter((cookieObj) => cookieObj.name); // Filter out empty cookies
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const extractCookiesFromHeaders = (
|
|
72
|
-
headers: HttpHeaders
|
|
73
|
-
): {
|
|
74
|
-
requestCookies: Cookie[];
|
|
75
|
-
responseCookies: Cookie[];
|
|
76
|
-
} => {
|
|
77
|
-
const requestCookies: Cookie[] = [];
|
|
78
|
-
const responseCookies: Cookie[] = [];
|
|
79
|
-
|
|
80
|
-
Object.entries(headers).forEach(([key, value]) => {
|
|
81
|
-
const lowerKey = key.toLowerCase();
|
|
82
|
-
|
|
83
|
-
if (lowerKey === 'cookie') {
|
|
84
|
-
// Cookie header contains all cookies in one string
|
|
85
|
-
requestCookies.push(...parseCookieString(value));
|
|
86
|
-
} else if (lowerKey === 'set-cookie') {
|
|
87
|
-
// Set-Cookie header contains one cookie with attributes
|
|
88
|
-
const cookies = parseCookieString(value);
|
|
89
|
-
responseCookies.push(...cookies);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
return { requestCookies, responseCookies };
|
|
94
|
-
};
|
|
95
|
-
|
|
96
15
|
export const CookiesTab = ({ selectedRequest }: CookiesTabProps) => {
|
|
16
|
+
const requestHeaders = selectedRequest.request?.headers;
|
|
17
|
+
const responseHeaders = selectedRequest.response?.headers;
|
|
18
|
+
|
|
19
|
+
const { requestCookies, responseCookies } = React.useMemo(() => {
|
|
20
|
+
return {
|
|
21
|
+
requestCookies: parseRequestCookiesFromHeaders(requestHeaders || {}),
|
|
22
|
+
responseCookies: parseResponseCookiesFromHeaders(responseHeaders || {}),
|
|
23
|
+
};
|
|
24
|
+
}, [requestHeaders, responseHeaders]);
|
|
25
|
+
|
|
26
|
+
const hasRequestCookies = requestCookies.length > 0;
|
|
27
|
+
const hasResponseCookies = responseCookies.length > 0;
|
|
28
|
+
|
|
29
|
+
if (!hasRequestCookies && !hasResponseCookies) {
|
|
30
|
+
return (
|
|
31
|
+
<ScrollArea className="h-full w-full">
|
|
32
|
+
<div className="p-4">
|
|
33
|
+
<div className="text-sm text-gray-400">
|
|
34
|
+
No cookies for this request
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</ScrollArea>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
97
41
|
return (
|
|
98
42
|
<ScrollArea className="h-full w-full">
|
|
99
43
|
<div className="p-4">
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (!hasRequestCookies && !hasResponseCookies) {
|
|
113
|
-
return (
|
|
114
|
-
<div className="text-sm text-gray-400">
|
|
115
|
-
No cookies for this request
|
|
44
|
+
<div className="space-y-6">
|
|
45
|
+
{hasRequestCookies && (
|
|
46
|
+
<Section title={`Request Cookies (${requestCookies.length})`}>
|
|
47
|
+
<div className="space-y-2">
|
|
48
|
+
{requestCookies.map((cookie, index) => (
|
|
49
|
+
<CookieCard
|
|
50
|
+
key={`request-${index}`}
|
|
51
|
+
cookie={cookie}
|
|
52
|
+
keyClassName="text-blue-400"
|
|
53
|
+
/>
|
|
54
|
+
))}
|
|
116
55
|
</div>
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
<
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
<div className="flex items-center justify-between mb-2">
|
|
135
|
-
<span className="text-sm font-medium text-blue-400">
|
|
136
|
-
{cookie.name}
|
|
137
|
-
</span>
|
|
138
|
-
<div className="flex items-center gap-2">
|
|
139
|
-
{cookie.secure && (
|
|
140
|
-
<Badge
|
|
141
|
-
variant="outline"
|
|
142
|
-
className="text-xs text-yellow-400 border-yellow-400"
|
|
143
|
-
>
|
|
144
|
-
Secure
|
|
145
|
-
</Badge>
|
|
146
|
-
)}
|
|
147
|
-
{cookie.httpOnly && (
|
|
148
|
-
<Badge
|
|
149
|
-
variant="outline"
|
|
150
|
-
className="text-xs text-purple-400 border-purple-400"
|
|
151
|
-
>
|
|
152
|
-
HttpOnly
|
|
153
|
-
</Badge>
|
|
154
|
-
)}
|
|
155
|
-
</div>
|
|
156
|
-
</div>
|
|
157
|
-
<div className="text-sm text-gray-300 mb-2">
|
|
158
|
-
{cookie.value}
|
|
159
|
-
</div>
|
|
160
|
-
<div className="grid grid-cols-2 gap-4 text-xs text-gray-400">
|
|
161
|
-
{cookie.domain && (
|
|
162
|
-
<div>
|
|
163
|
-
<span className="font-medium">Domain:</span>{' '}
|
|
164
|
-
{cookie.domain}
|
|
165
|
-
</div>
|
|
166
|
-
)}
|
|
167
|
-
{cookie.path && (
|
|
168
|
-
<div>
|
|
169
|
-
<span className="font-medium">Path:</span>{' '}
|
|
170
|
-
{cookie.path}
|
|
171
|
-
</div>
|
|
172
|
-
)}
|
|
173
|
-
{cookie.expires && (
|
|
174
|
-
<div>
|
|
175
|
-
<span className="font-medium">Expires:</span>{' '}
|
|
176
|
-
{cookie.expires}
|
|
177
|
-
</div>
|
|
178
|
-
)}
|
|
179
|
-
{cookie.maxAge && (
|
|
180
|
-
<div>
|
|
181
|
-
<span className="font-medium">Max-Age:</span>{' '}
|
|
182
|
-
{cookie.maxAge}
|
|
183
|
-
</div>
|
|
184
|
-
)}
|
|
185
|
-
{cookie.sameSite && (
|
|
186
|
-
<div>
|
|
187
|
-
<span className="font-medium">SameSite:</span>{' '}
|
|
188
|
-
{cookie.sameSite}
|
|
189
|
-
</div>
|
|
190
|
-
)}
|
|
191
|
-
</div>
|
|
192
|
-
</div>
|
|
193
|
-
))}
|
|
194
|
-
</div>
|
|
195
|
-
</div>
|
|
196
|
-
)}
|
|
197
|
-
|
|
198
|
-
{/* Response Cookies */}
|
|
199
|
-
{hasResponseCookies && (
|
|
200
|
-
<div>
|
|
201
|
-
<h4 className="text-sm font-medium text-gray-300 mb-3">
|
|
202
|
-
Response Cookies ({responseCookies.length})
|
|
203
|
-
</h4>
|
|
204
|
-
<div className="space-y-2">
|
|
205
|
-
{responseCookies.map((cookie, index) => (
|
|
206
|
-
<div
|
|
207
|
-
key={`response-${index}`}
|
|
208
|
-
className="bg-gray-800 border border-gray-700 rounded p-3"
|
|
209
|
-
>
|
|
210
|
-
<div className="flex items-center justify-between mb-2">
|
|
211
|
-
<span className="text-sm font-medium text-green-400">
|
|
212
|
-
{cookie.name}
|
|
213
|
-
</span>
|
|
214
|
-
<div className="flex items-center gap-2">
|
|
215
|
-
{cookie.secure && (
|
|
216
|
-
<Badge
|
|
217
|
-
variant="outline"
|
|
218
|
-
className="text-xs text-yellow-400 border-yellow-400"
|
|
219
|
-
>
|
|
220
|
-
Secure
|
|
221
|
-
</Badge>
|
|
222
|
-
)}
|
|
223
|
-
{cookie.httpOnly && (
|
|
224
|
-
<Badge
|
|
225
|
-
variant="outline"
|
|
226
|
-
className="text-xs text-purple-400 border-purple-400"
|
|
227
|
-
>
|
|
228
|
-
HttpOnly
|
|
229
|
-
</Badge>
|
|
230
|
-
)}
|
|
231
|
-
</div>
|
|
232
|
-
</div>
|
|
233
|
-
<div className="text-sm text-gray-300 mb-2">
|
|
234
|
-
{cookie.value}
|
|
235
|
-
</div>
|
|
236
|
-
<div className="grid grid-cols-2 gap-4 text-xs text-gray-400">
|
|
237
|
-
{cookie.domain && (
|
|
238
|
-
<div>
|
|
239
|
-
<span className="font-medium">Domain:</span>{' '}
|
|
240
|
-
{cookie.domain}
|
|
241
|
-
</div>
|
|
242
|
-
)}
|
|
243
|
-
{cookie.path && (
|
|
244
|
-
<div>
|
|
245
|
-
<span className="font-medium">Path:</span>{' '}
|
|
246
|
-
{cookie.path}
|
|
247
|
-
</div>
|
|
248
|
-
)}
|
|
249
|
-
{cookie.expires && (
|
|
250
|
-
<div>
|
|
251
|
-
<span className="font-medium">Expires:</span>{' '}
|
|
252
|
-
{cookie.expires}
|
|
253
|
-
</div>
|
|
254
|
-
)}
|
|
255
|
-
{cookie.maxAge && (
|
|
256
|
-
<div>
|
|
257
|
-
<span className="font-medium">Max-Age:</span>{' '}
|
|
258
|
-
{cookie.maxAge}
|
|
259
|
-
</div>
|
|
260
|
-
)}
|
|
261
|
-
{cookie.sameSite && (
|
|
262
|
-
<div>
|
|
263
|
-
<span className="font-medium">SameSite:</span>{' '}
|
|
264
|
-
{cookie.sameSite}
|
|
265
|
-
</div>
|
|
266
|
-
)}
|
|
267
|
-
</div>
|
|
268
|
-
</div>
|
|
269
|
-
))}
|
|
270
|
-
</div>
|
|
271
|
-
</div>
|
|
272
|
-
)}
|
|
273
|
-
</div>
|
|
274
|
-
);
|
|
275
|
-
})()}
|
|
56
|
+
</Section>
|
|
57
|
+
)}
|
|
58
|
+
|
|
59
|
+
{hasResponseCookies && (
|
|
60
|
+
<Section title={`Response Cookies (${responseCookies.length})`}>
|
|
61
|
+
<div className="space-y-2">
|
|
62
|
+
{responseCookies.map((cookie, index) => (
|
|
63
|
+
<CookieCard
|
|
64
|
+
key={`response-${index}`}
|
|
65
|
+
cookie={cookie}
|
|
66
|
+
keyClassName="text-green-400"
|
|
67
|
+
/>
|
|
68
|
+
))}
|
|
69
|
+
</div>
|
|
70
|
+
</Section>
|
|
71
|
+
)}
|
|
72
|
+
</div>
|
|
276
73
|
</div>
|
|
277
74
|
</ScrollArea>
|
|
278
75
|
);
|
|
@@ -1,109 +1,98 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
2
|
import { ScrollArea } from '../components/ScrollArea';
|
|
3
|
+
import { Section } from '../components/Section';
|
|
4
|
+
import { KeyValueGrid, KeyValueItem } from '../components/KeyValueGrid';
|
|
3
5
|
import { HttpNetworkEntry, SSENetworkEntry } from '../state/model';
|
|
4
6
|
import { getStatusColor } from '../utils/getStatusColor';
|
|
7
|
+
import { CopyRequestDropdown } from '../components/CopyRequestDropdown';
|
|
8
|
+
import { HttpHeaders } from '../../shared/client';
|
|
5
9
|
|
|
6
10
|
export type HeadersTabProps = {
|
|
7
11
|
selectedRequest: HttpNetworkEntry | SSENetworkEntry;
|
|
8
12
|
};
|
|
9
13
|
|
|
14
|
+
function getHeadersItems(headers?: HttpHeaders): KeyValueItem[] {
|
|
15
|
+
if (!headers) return [];
|
|
16
|
+
|
|
17
|
+
return Object.entries(headers).reduce<KeyValueItem[]>((acc, [key, value]) => {
|
|
18
|
+
if (Array.isArray(value)) {
|
|
19
|
+
acc.push(
|
|
20
|
+
...value.map((item) => ({ key: key.toLowerCase(), value: item }))
|
|
21
|
+
);
|
|
22
|
+
} else {
|
|
23
|
+
acc.push({ key: key.toLowerCase(), value: value });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return acc;
|
|
27
|
+
}, []);
|
|
28
|
+
}
|
|
29
|
+
|
|
10
30
|
export const HeadersTab = ({ selectedRequest }: HeadersTabProps) => {
|
|
11
|
-
const
|
|
12
|
-
const { hostname, port, pathname } = new URL(selectedRequest.request.url);
|
|
31
|
+
const requestBody = selectedRequest.request.body;
|
|
13
32
|
|
|
14
|
-
|
|
15
|
-
|
|
33
|
+
const generalItems: KeyValueItem[] = useMemo(
|
|
34
|
+
() => [
|
|
35
|
+
{
|
|
36
|
+
key: 'Request URL',
|
|
37
|
+
value: selectedRequest.request.url,
|
|
38
|
+
valueClassName: 'text-blue-400',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
key: 'Request Method',
|
|
42
|
+
value: selectedRequest.request.method,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
key: 'Status Code',
|
|
46
|
+
value: selectedRequest.response?.status ?? 'Pending',
|
|
47
|
+
valueClassName: getStatusColor(selectedRequest.response?.status ?? 0),
|
|
48
|
+
},
|
|
49
|
+
...(requestBody
|
|
50
|
+
? [
|
|
51
|
+
{
|
|
52
|
+
key: 'Content-Type',
|
|
53
|
+
value: requestBody.type,
|
|
54
|
+
valueClassName: 'text-blue-400',
|
|
55
|
+
},
|
|
56
|
+
]
|
|
57
|
+
: []),
|
|
58
|
+
],
|
|
59
|
+
[selectedRequest]
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const responseHeadersItems = useMemo(
|
|
63
|
+
() => getHeadersItems(selectedRequest.response?.headers),
|
|
64
|
+
[selectedRequest]
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const requestHeadersItems = useMemo(
|
|
68
|
+
() => getHeadersItems(selectedRequest.request.headers),
|
|
69
|
+
[selectedRequest]
|
|
70
|
+
);
|
|
16
71
|
|
|
17
72
|
return (
|
|
18
73
|
<ScrollArea className="h-full w-full">
|
|
19
74
|
<div className="p-4 space-y-4">
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
<span className="text-blue-400">
|
|
26
|
-
{url}
|
|
27
|
-
</span>
|
|
28
|
-
</div>
|
|
29
|
-
<div className="flex">
|
|
30
|
-
<span className="w-32 text-gray-400">Request Method:</span>
|
|
31
|
-
<span>{selectedRequest.request.method}</span>
|
|
32
|
-
</div>
|
|
33
|
-
<div className="flex">
|
|
34
|
-
<span className="w-32 text-gray-400">Status Code:</span>
|
|
35
|
-
<span
|
|
36
|
-
className={getStatusColor(
|
|
37
|
-
selectedRequest.response?.status ?? 0
|
|
38
|
-
)}
|
|
39
|
-
>
|
|
40
|
-
{selectedRequest.response?.status ?? 'Pending'}
|
|
41
|
-
</span>
|
|
42
|
-
</div>
|
|
43
|
-
{selectedRequest.request.body && (
|
|
44
|
-
<div className="flex">
|
|
45
|
-
<span className="w-32 text-gray-400">Content-Type:</span>
|
|
46
|
-
<span className="text-blue-400">
|
|
47
|
-
{selectedRequest.request.body.type}
|
|
48
|
-
</span>
|
|
49
|
-
</div>
|
|
50
|
-
)}
|
|
51
|
-
</div>
|
|
52
|
-
</div>
|
|
75
|
+
<CopyRequestDropdown selectedRequest={selectedRequest} />
|
|
76
|
+
|
|
77
|
+
<Section title="General">
|
|
78
|
+
<KeyValueGrid items={generalItems} />
|
|
79
|
+
</Section>
|
|
53
80
|
|
|
54
|
-
<
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (responseHeaders && Object.keys(responseHeaders).length > 0) {
|
|
62
|
-
return Object.entries(responseHeaders).map(([key, value]) => (
|
|
63
|
-
<div key={key} className="flex">
|
|
64
|
-
<span className="w-32 text-gray-400">
|
|
65
|
-
{key.toLowerCase()}:
|
|
66
|
-
</span>
|
|
67
|
-
<span className="flex-1 break-all">{value}</span>
|
|
68
|
-
</div>
|
|
69
|
-
));
|
|
70
|
-
} else {
|
|
71
|
-
return (
|
|
72
|
-
<div className="text-gray-500 italic">
|
|
73
|
-
No response headers available
|
|
74
|
-
</div>
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
})()}
|
|
78
|
-
</div>
|
|
79
|
-
</div>
|
|
81
|
+
<Section title="Response Headers">
|
|
82
|
+
<KeyValueGrid
|
|
83
|
+
items={responseHeadersItems}
|
|
84
|
+
emptyMessage="No response headers available"
|
|
85
|
+
className="font-mono"
|
|
86
|
+
/>
|
|
87
|
+
</Section>
|
|
80
88
|
|
|
81
|
-
<
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (requestHeaders && Object.keys(requestHeaders).length > 0) {
|
|
89
|
-
return Object.entries(requestHeaders).map(([key, value]) => (
|
|
90
|
-
<div key={key} className="flex">
|
|
91
|
-
<span className="w-32 text-gray-400">
|
|
92
|
-
{key.toLowerCase()}:
|
|
93
|
-
</span>
|
|
94
|
-
<span className="flex-1 break-all">{value}</span>
|
|
95
|
-
</div>
|
|
96
|
-
));
|
|
97
|
-
} else {
|
|
98
|
-
return (
|
|
99
|
-
<div className="text-gray-500 italic">
|
|
100
|
-
No request headers available
|
|
101
|
-
</div>
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
})()}
|
|
105
|
-
</div>
|
|
106
|
-
</div>
|
|
89
|
+
<Section title="Request Headers">
|
|
90
|
+
<KeyValueGrid
|
|
91
|
+
items={requestHeadersItems}
|
|
92
|
+
emptyMessage="No request headers available"
|
|
93
|
+
className="font-mono"
|
|
94
|
+
/>
|
|
95
|
+
</Section>
|
|
107
96
|
</div>
|
|
108
97
|
</ScrollArea>
|
|
109
98
|
);
|
|
@@ -1,66 +1,78 @@
|
|
|
1
1
|
import { ScrollArea } from '../components/ScrollArea';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import {
|
|
3
|
+
HttpNetworkEntry,
|
|
4
|
+
HttpRequestData,
|
|
5
|
+
SSENetworkEntry,
|
|
6
|
+
} from '../state/model';
|
|
7
|
+
import { KeyValueGrid } from '../components/KeyValueGrid';
|
|
8
|
+
import { Section } from '../components/Section';
|
|
9
|
+
import { useMemo } from 'react';
|
|
10
|
+
import { RequestBody } from '../components/RequestBody';
|
|
5
11
|
|
|
6
12
|
export type RequestTabProps = {
|
|
7
13
|
selectedRequest: HttpNetworkEntry | SSENetworkEntry;
|
|
8
14
|
};
|
|
9
15
|
|
|
16
|
+
const getRequestBodySectionTitle = (body: HttpRequestData) => {
|
|
17
|
+
const baseTitle = 'Request Body';
|
|
18
|
+
|
|
19
|
+
switch (body.data.type) {
|
|
20
|
+
case 'form-data':
|
|
21
|
+
return `${baseTitle} (FormData)`;
|
|
22
|
+
|
|
23
|
+
case 'binary':
|
|
24
|
+
return `${baseTitle} (Binary)`;
|
|
25
|
+
|
|
26
|
+
default:
|
|
27
|
+
return baseTitle;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
10
31
|
export const RequestTab = ({ selectedRequest }: RequestTabProps) => {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
const { type, data } = selectedRequest.request.body;
|
|
32
|
+
const queryParams = useMemo(() => {
|
|
33
|
+
const { searchParams } = new URL(selectedRequest.request.url);
|
|
14
34
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
{data}
|
|
28
|
-
</pre>
|
|
29
|
-
);
|
|
30
|
-
}
|
|
35
|
+
return Array.from(searchParams.entries()).map(([key, value]) => ({
|
|
36
|
+
key,
|
|
37
|
+
value,
|
|
38
|
+
}));
|
|
39
|
+
}, [selectedRequest.request.url]);
|
|
40
|
+
|
|
41
|
+
const requestBody = selectedRequest.request.body;
|
|
42
|
+
const hasQueryParams = queryParams.length > 0;
|
|
43
|
+
|
|
44
|
+
const renderQueryParams = () => {
|
|
45
|
+
if (!hasQueryParams) {
|
|
46
|
+
return null;
|
|
31
47
|
}
|
|
32
48
|
|
|
33
|
-
// For non-JSON content types, use the existing pre tag
|
|
34
49
|
return (
|
|
35
|
-
<
|
|
36
|
-
{
|
|
37
|
-
</
|
|
50
|
+
<Section title={`Query Parameters (${queryParams.length})`}>
|
|
51
|
+
<KeyValueGrid items={queryParams} />
|
|
52
|
+
</Section>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const renderRequestBody = () => {
|
|
57
|
+
if (!requestBody) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<Section title={getRequestBodySectionTitle(requestBody)}>
|
|
63
|
+
<RequestBody data={requestBody.data} />
|
|
64
|
+
</Section>
|
|
38
65
|
);
|
|
39
66
|
};
|
|
40
67
|
|
|
41
68
|
return (
|
|
42
69
|
<ScrollArea className="h-full w-full">
|
|
43
|
-
<div className="p-4">
|
|
44
|
-
{
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
<h4 className="text-sm font-medium text-gray-300 mb-2">
|
|
48
|
-
Request Body
|
|
49
|
-
</h4>
|
|
50
|
-
<div className="text-sm mb-2">
|
|
51
|
-
<span className="text-gray-400">Content-Type: </span>
|
|
52
|
-
<span className="text-blue-400">
|
|
53
|
-
{selectedRequest.request.body.type}
|
|
54
|
-
</span>
|
|
55
|
-
</div>
|
|
56
|
-
</div>
|
|
57
|
-
<div>{renderRequestBody()}</div>
|
|
58
|
-
</div>
|
|
59
|
-
) : (
|
|
70
|
+
<div className="p-4 space-y-4">
|
|
71
|
+
{renderQueryParams()}
|
|
72
|
+
{renderRequestBody()}
|
|
73
|
+
{!hasQueryParams && !requestBody && (
|
|
60
74
|
<div className="text-sm text-gray-400">
|
|
61
|
-
|
|
62
|
-
? "GET requests don't have a request body"
|
|
63
|
-
: 'No request body for this request'}
|
|
75
|
+
No request body or query params for this request
|
|
64
76
|
</div>
|
|
65
77
|
)}
|
|
66
78
|
</div>
|