@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.
Files changed (113) hide show
  1. package/README.md +2 -0
  2. package/dist/App.html +2 -2
  3. package/dist/assets/{App-DoHQsY5s.css → App-BrSkOkws.css} +223 -2
  4. package/dist/assets/{App-CA1Fbh0I.js → App-Kyi7zHUX.js} +8188 -2671
  5. package/dist/react-native.cjs +4 -1
  6. package/dist/react-native.js +4 -1
  7. package/dist/rozenite.json +1 -1
  8. package/dist/src/react-native/config.d.ts +20 -0
  9. package/dist/src/react-native/http/overrides-registry.d.ts +6 -0
  10. package/dist/src/react-native/http/xhr-interceptor.d.ts +7 -1
  11. package/dist/src/react-native/sse/sse-interceptor.d.ts +2 -2
  12. package/dist/src/react-native/useNetworkActivityDevTools.d.ts +2 -1
  13. package/dist/src/react-native/utils/getBlobName.d.ts +35 -0
  14. package/dist/src/react-native/utils/getFormDataEntries.d.ts +18 -0
  15. package/dist/src/shared/client.d.ts +55 -4
  16. package/dist/src/shared/sse-events.d.ts +4 -1
  17. package/dist/src/ui/components/Button.d.ts +2 -2
  18. package/dist/src/ui/components/CodeBlock.d.ts +3 -0
  19. package/dist/src/ui/components/CodeEditor.d.ts +5 -0
  20. package/dist/src/ui/components/CookieCard.d.ts +7 -0
  21. package/dist/src/ui/components/CopyRequestDropdown.d.ts +7 -0
  22. package/dist/src/ui/components/DropdownMenu.d.ts +27 -0
  23. package/dist/src/ui/components/FilterBar.d.ts +10 -0
  24. package/dist/src/ui/components/JsonTreeCopyableItem.d.ts +1 -1
  25. package/dist/src/ui/components/KeyValueGrid.d.ts +13 -0
  26. package/dist/src/ui/components/OverrideResponse.d.ts +8 -0
  27. package/dist/src/ui/components/RequestBody.d.ts +6 -0
  28. package/dist/src/ui/components/RequestList.d.ts +9 -4
  29. package/dist/src/ui/components/ScrollArea.d.ts +3 -2
  30. package/dist/src/ui/components/Section.d.ts +8 -0
  31. package/dist/src/ui/components/Separator.d.ts +2 -1
  32. package/dist/src/ui/components/Tabs.d.ts +7 -0
  33. package/dist/src/ui/state/hooks.d.ts +4 -0
  34. package/dist/src/ui/state/model.d.ts +22 -7
  35. package/dist/src/ui/state/store.d.ts +27 -3
  36. package/dist/src/ui/utils/checkRequestBodyBinary.d.ts +2 -0
  37. package/dist/src/ui/utils/escapeShellArg.d.ts +1 -0
  38. package/dist/src/ui/utils/generateCurlCommand.d.ts +2 -0
  39. package/dist/src/ui/utils/generateFetchCall.d.ts +2 -0
  40. package/dist/src/ui/utils/generateMultipartBody.d.ts +4 -0
  41. package/dist/src/utils/applyReactNativeRequestHeadersLogic.d.ts +7 -0
  42. package/dist/src/utils/applyReactNativeResponseHeadersLogic.d.ts +9 -0
  43. package/dist/src/utils/cookieParser.d.ts +6 -0
  44. package/dist/src/utils/getContentTypeMimeType.d.ts +2 -0
  45. package/dist/src/utils/getHttpHeader.d.ts +5 -0
  46. package/dist/src/utils/getHttpHeaderValueAsString.d.ts +11 -0
  47. package/dist/src/utils/getStringSizeInBytes.d.ts +1 -0
  48. package/dist/src/utils/inferContentTypeFromPostData.d.ts +2 -0
  49. package/dist/src/utils/safeStringify.d.ts +1 -0
  50. package/dist/src/utils/typeChecks.d.ts +9 -0
  51. package/dist/useNetworkActivityDevTools.cjs +337 -24
  52. package/dist/useNetworkActivityDevTools.js +338 -25
  53. package/package.json +7 -4
  54. package/react-native.ts +6 -1
  55. package/src/react-native/config.ts +43 -0
  56. package/src/react-native/http/network-inspector.ts +190 -8
  57. package/src/react-native/http/overrides-registry.ts +32 -0
  58. package/src/react-native/http/xhr-interceptor.ts +19 -2
  59. package/src/react-native/sse/sse-inspector.ts +27 -5
  60. package/src/react-native/sse/sse-interceptor.ts +26 -8
  61. package/src/react-native/useNetworkActivityDevTools.ts +86 -8
  62. package/src/react-native/utils/getBlobName.ts +45 -0
  63. package/src/react-native/utils/getFormDataEntries.ts +32 -0
  64. package/src/react-native/utils.ts +3 -3
  65. package/src/shared/client.ts +81 -4
  66. package/src/shared/sse-events.ts +4 -1
  67. package/src/ui/components/Button.tsx +1 -0
  68. package/src/ui/components/CodeBlock.tsx +19 -0
  69. package/src/ui/components/CodeEditor.tsx +26 -0
  70. package/src/ui/components/CookieCard.tsx +64 -0
  71. package/src/ui/components/CopyRequestDropdown.tsx +95 -0
  72. package/src/ui/components/DropdownMenu.tsx +206 -0
  73. package/src/ui/components/FilterBar.tsx +117 -0
  74. package/src/ui/components/Input.tsx +1 -1
  75. package/src/ui/components/JsonTree.tsx +10 -3
  76. package/src/ui/components/JsonTreeCopyableItem.tsx +14 -10
  77. package/src/ui/components/KeyValueGrid.tsx +51 -0
  78. package/src/ui/components/OverrideResponse.tsx +132 -0
  79. package/src/ui/components/RequestBody.tsx +86 -0
  80. package/src/ui/components/RequestList.tsx +74 -14
  81. package/src/ui/components/ScrollArea.tsx +1 -0
  82. package/src/ui/components/Section.tsx +46 -0
  83. package/src/ui/components/SidePanel.tsx +15 -5
  84. package/src/ui/components/Toolbar.tsx +3 -2
  85. package/src/ui/globals.css +4 -0
  86. package/src/ui/hooks/useCopyToClipboard.ts +2 -2
  87. package/src/ui/state/derived.ts +2 -0
  88. package/src/ui/state/hooks.ts +8 -0
  89. package/src/ui/state/model.ts +28 -7
  90. package/src/ui/state/store.ts +640 -500
  91. package/src/ui/tabs/CookiesTab.tsx +60 -263
  92. package/src/ui/tabs/HeadersTab.tsx +78 -89
  93. package/src/ui/tabs/RequestTab.tsx +58 -46
  94. package/src/ui/tabs/ResponseTab.tsx +98 -67
  95. package/src/ui/tabs/SSEMessagesTab.tsx +50 -39
  96. package/src/ui/utils/checkRequestBodyBinary.ts +7 -0
  97. package/src/ui/utils/escapeShellArg.ts +12 -0
  98. package/src/ui/utils/generateCurlCommand.ts +83 -0
  99. package/src/ui/utils/generateFetchCall.ts +64 -0
  100. package/src/ui/utils/generateMultipartBody.ts +19 -0
  101. package/src/ui/views/InspectorView.tsx +15 -3
  102. package/src/utils/applyReactNativeRequestHeadersLogic.ts +30 -0
  103. package/src/utils/applyReactNativeResponseHeadersLogic.ts +28 -0
  104. package/src/utils/cookieParser.ts +126 -0
  105. package/src/utils/getContentTypeMimeType.ts +17 -0
  106. package/src/utils/getHttpHeader.ts +17 -0
  107. package/src/utils/getHttpHeaderValueAsString.ts +13 -0
  108. package/src/utils/getStringSizeInBytes.ts +3 -0
  109. package/src/utils/inferContentTypeFromPostData.ts +9 -0
  110. package/src/utils/safeStringify.ts +7 -0
  111. package/src/utils/typeChecks.ts +27 -0
  112. package/dist/src/ui/utils/getHttpHeaderValue.d.ts +0 -2
  113. 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 { Badge } from '../components/Badge';
3
- import { HttpHeaders } from '../../shared/client';
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
- // Extract cookies from request and response headers separately
102
- const requestHeaders = selectedRequest.request?.headers || {};
103
- const responseHeaders = selectedRequest.response?.headers || {};
104
-
105
- const { requestCookies } = extractCookiesFromHeaders(requestHeaders);
106
- const { responseCookies } =
107
- extractCookiesFromHeaders(responseHeaders);
108
-
109
- const hasRequestCookies = requestCookies.length > 0;
110
- const hasResponseCookies = responseCookies.length > 0;
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
- return (
121
- <div className="space-y-6">
122
- {/* Request Cookies */}
123
- {hasRequestCookies && (
124
- <div>
125
- <h4 className="text-sm font-medium text-gray-300 mb-3">
126
- Request Cookies ({requestCookies.length})
127
- </h4>
128
- <div className="space-y-2">
129
- {requestCookies.map((cookie, index) => (
130
- <div
131
- key={`request-${index}`}
132
- className="bg-gray-800 border border-gray-700 rounded p-3"
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 url = useMemo(() => {
12
- const { hostname, port, pathname } = new URL(selectedRequest.request.url);
31
+ const requestBody = selectedRequest.request.body;
13
32
 
14
- return `${hostname}${port ? `:${port}` : ''}${pathname}`;
15
- }, [selectedRequest.request.url]);
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
- <div>
21
- <h4 className="text-sm font-medium text-gray-300 mb-2">General</h4>
22
- <div className="space-y-1 text-sm">
23
- <div className="flex">
24
- <span className="w-32 text-gray-400">Request URL:</span>
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
- <div>
55
- <h4 className="text-sm font-medium text-gray-300 mb-2">
56
- Response Headers
57
- </h4>
58
- <div className="space-y-1 text-sm font-mono">
59
- {(() => {
60
- const responseHeaders = selectedRequest.response?.headers;
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
- <div>
82
- <h4 className="text-sm font-medium text-gray-300 mb-2">
83
- Request Headers
84
- </h4>
85
- <div className="space-y-1 text-sm font-mono">
86
- {(() => {
87
- const requestHeaders = selectedRequest.request.headers;
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 { JsonTree } from '../components/JsonTree';
3
- import { HttpNetworkEntry, SSENetworkEntry } from '../state/model';
4
- import { assert } from '../utils/assert';
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 renderRequestBody = () => {
12
- assert(!!selectedRequest.request.body, 'Request body is required');
13
- const { type, data } = selectedRequest.request.body;
32
+ const queryParams = useMemo(() => {
33
+ const { searchParams } = new URL(selectedRequest.request.url);
14
34
 
15
- if (type === 'application/json') {
16
- try {
17
- const jsonData = JSON.parse(data);
18
- return (
19
- <div className="bg-gray-800 p-3 rounded border border-gray-700">
20
- <JsonTree data={jsonData} />
21
- </div>
22
- );
23
- } catch {
24
- // Fallback to pre tag if JSON parsing fails
25
- return (
26
- <pre className="text-sm font-mono text-gray-300 whitespace-pre-wrap bg-gray-800 p-3 rounded border border-gray-700 overflow-x-auto">
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
- <pre className="text-sm font-mono text-gray-300 whitespace-pre-wrap bg-gray-800 p-3 rounded border border-gray-700 overflow-x-auto">
36
- {data}
37
- </pre>
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
- {selectedRequest.request.body ? (
45
- <div className="space-y-4">
46
- <div>
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
- {selectedRequest.request.method === 'GET'
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>