@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.
Files changed (160) hide show
  1. package/README.md +2 -0
  2. package/dist/App.html +2 -2
  3. package/dist/assets/{App-R2ZMH9wJ.css → App-BrSkOkws.css} +269 -2
  4. package/dist/assets/{App-lNMijPJ4.js → App-C6wCDVkW.js} +17485 -10814
  5. package/dist/event-source.cjs +22 -0
  6. package/dist/event-source.js +23 -0
  7. package/dist/react-native.cjs +4 -1
  8. package/dist/react-native.js +4 -1
  9. package/dist/rozenite.json +1 -1
  10. package/dist/src/react-native/config.d.ts +20 -0
  11. package/dist/src/react-native/{network-inspector.d.ts → http/network-inspector.d.ts} +1 -1
  12. package/dist/src/react-native/http/overrides-registry.d.ts +6 -0
  13. package/dist/src/react-native/{xhr-interceptor.d.ts → http/xhr-interceptor.d.ts} +7 -1
  14. package/dist/src/react-native/sse/event-source.d.ts +2 -0
  15. package/dist/src/react-native/sse/sse-inspector.d.ts +9 -0
  16. package/dist/src/react-native/sse/sse-interceptor.d.ts +36 -0
  17. package/dist/src/react-native/sse/types.d.ts +6 -0
  18. package/dist/src/react-native/useNetworkActivityDevTools.d.ts +2 -1
  19. package/dist/src/react-native/utils/getBlobName.d.ts +35 -0
  20. package/dist/src/react-native/utils/getFormDataEntries.d.ts +18 -0
  21. package/dist/src/react-native/utils.d.ts +6 -0
  22. package/dist/src/react-native/websocket/websocket-inspector.d.ts +9 -0
  23. package/dist/src/react-native/websocket/websocket-interceptor.d.ts +74 -0
  24. package/dist/src/shared/client.d.ts +53 -6
  25. package/dist/src/shared/sse-events.d.ts +38 -0
  26. package/dist/src/shared/websocket-events.d.ts +60 -0
  27. package/dist/src/ui/components/Badge.d.ts +1 -1
  28. package/dist/src/ui/components/Button.d.ts +2 -2
  29. package/dist/src/ui/components/CodeBlock.d.ts +3 -0
  30. package/dist/src/ui/components/CodeEditor.d.ts +5 -0
  31. package/dist/src/ui/components/CookieCard.d.ts +7 -0
  32. package/dist/src/ui/components/CopyRequestDropdown.d.ts +7 -0
  33. package/dist/src/ui/components/DropdownMenu.d.ts +27 -0
  34. package/dist/src/ui/components/FilterBar.d.ts +10 -0
  35. package/dist/src/ui/components/JsonTreeCopyableItem.d.ts +7 -0
  36. package/dist/src/ui/components/KeyValueGrid.d.ts +13 -0
  37. package/dist/src/ui/components/OverrideResponse.d.ts +8 -0
  38. package/dist/src/ui/components/RequestBody.d.ts +6 -0
  39. package/dist/src/ui/components/RequestList.d.ts +13 -28
  40. package/dist/src/ui/components/ScrollArea.d.ts +3 -2
  41. package/dist/src/ui/components/Section.d.ts +8 -0
  42. package/dist/src/ui/components/Separator.d.ts +2 -1
  43. package/dist/src/ui/components/SidePanel.d.ts +1 -0
  44. package/dist/src/ui/components/Tabs.d.ts +7 -0
  45. package/dist/src/ui/components/Toolbar.d.ts +1 -0
  46. package/dist/src/ui/hooks/useCopyToClipboard.d.ts +4 -0
  47. package/dist/src/ui/state/derived.d.ts +5 -0
  48. package/dist/src/ui/state/hooks.d.ts +21 -0
  49. package/dist/src/ui/state/model.d.ts +103 -0
  50. package/dist/src/ui/state/store.d.ts +48 -0
  51. package/dist/src/ui/tabs/CookiesTab.d.ts +3 -6
  52. package/dist/src/ui/tabs/HeadersTab.d.ts +3 -15
  53. package/dist/src/ui/tabs/MessagesTab.d.ts +5 -0
  54. package/dist/src/ui/tabs/RequestTab.d.ts +2 -7
  55. package/dist/src/ui/tabs/ResponseTab.d.ts +2 -8
  56. package/dist/src/ui/tabs/SSEMessagesTab.d.ts +5 -0
  57. package/dist/src/ui/tabs/TimingTab.d.ts +3 -5
  58. package/dist/src/ui/types.d.ts +4 -1
  59. package/dist/src/ui/utils/assert.d.ts +1 -0
  60. package/dist/src/ui/utils/checkRequestBodyBinary.d.ts +2 -0
  61. package/dist/src/ui/utils/copyToClipboard.d.ts +1 -0
  62. package/dist/src/ui/utils/escapeShellArg.d.ts +1 -0
  63. package/dist/src/ui/utils/generateCurlCommand.d.ts +2 -0
  64. package/dist/src/ui/utils/generateFetchCall.d.ts +2 -0
  65. package/dist/src/ui/utils/generateMultipartBody.d.ts +4 -0
  66. package/dist/src/ui/utils/getId.d.ts +1 -0
  67. package/dist/src/ui/utils/getStatusColor.d.ts +1 -0
  68. package/dist/src/utils/applyReactNativeRequestHeadersLogic.d.ts +7 -0
  69. package/dist/src/utils/applyReactNativeResponseHeadersLogic.d.ts +9 -0
  70. package/dist/src/utils/cookieParser.d.ts +6 -0
  71. package/dist/src/utils/getContentTypeMimeType.d.ts +2 -0
  72. package/dist/src/utils/getHttpHeader.d.ts +5 -0
  73. package/dist/src/utils/getHttpHeaderValueAsString.d.ts +11 -0
  74. package/dist/src/utils/getStringSizeInBytes.d.ts +1 -0
  75. package/dist/src/utils/inferContentTypeFromPostData.d.ts +2 -0
  76. package/dist/src/utils/safeStringify.d.ts +1 -0
  77. package/dist/src/utils/typeChecks.d.ts +9 -0
  78. package/dist/useNetworkActivityDevTools.cjs +724 -40
  79. package/dist/useNetworkActivityDevTools.js +723 -41
  80. package/package.json +22 -8
  81. package/react-native.ts +6 -1
  82. package/src/react-native/config.ts +43 -0
  83. package/src/react-native/http/network-inspector.ts +388 -0
  84. package/src/react-native/http/overrides-registry.ts +32 -0
  85. package/src/react-native/{xhr-interceptor.ts → http/xhr-interceptor.ts} +19 -2
  86. package/src/react-native/{xml-request.d.ts → http/xml-request.d.ts} +1 -0
  87. package/src/react-native/sse/event-source.ts +25 -0
  88. package/src/react-native/sse/sse-inspector.ts +139 -0
  89. package/src/react-native/sse/sse-interceptor.ts +180 -0
  90. package/src/react-native/sse/types.ts +9 -0
  91. package/src/react-native/useNetworkActivityDevTools.ts +156 -4
  92. package/src/react-native/utils/getBlobName.ts +45 -0
  93. package/src/react-native/utils/getFormDataEntries.ts +32 -0
  94. package/src/react-native/utils.ts +43 -0
  95. package/src/react-native/websocket/websocket-inspector.ts +180 -0
  96. package/src/react-native/websocket/websocket-interceptor.d.ts +4 -0
  97. package/src/react-native/websocket/websocket-interceptor.ts +166 -0
  98. package/src/shared/client.ts +79 -6
  99. package/src/shared/sse-events.ts +47 -0
  100. package/src/shared/websocket-events.ts +79 -0
  101. package/src/ui/components/Button.tsx +1 -0
  102. package/src/ui/components/CodeBlock.tsx +19 -0
  103. package/src/ui/components/CodeEditor.tsx +26 -0
  104. package/src/ui/components/CookieCard.tsx +64 -0
  105. package/src/ui/components/CopyRequestDropdown.tsx +95 -0
  106. package/src/ui/components/DropdownMenu.tsx +206 -0
  107. package/src/ui/components/FilterBar.tsx +117 -0
  108. package/src/ui/components/Input.tsx +1 -1
  109. package/src/ui/components/JsonTree.tsx +20 -0
  110. package/src/ui/components/JsonTreeCopyableItem.tsx +37 -0
  111. package/src/ui/components/KeyValueGrid.tsx +51 -0
  112. package/src/ui/components/OverrideResponse.tsx +132 -0
  113. package/src/ui/components/RequestBody.tsx +86 -0
  114. package/src/ui/components/RequestList.tsx +101 -131
  115. package/src/ui/components/ScrollArea.tsx +1 -0
  116. package/src/ui/components/Section.tsx +46 -0
  117. package/src/ui/components/SidePanel.tsx +333 -0
  118. package/src/ui/components/Tabs.tsx +1 -1
  119. package/src/ui/components/Toolbar.tsx +45 -0
  120. package/src/ui/globals.css +4 -0
  121. package/src/ui/hooks/useCopyToClipboard.ts +28 -0
  122. package/src/ui/state/derived.ts +112 -0
  123. package/src/ui/state/hooks.ts +52 -0
  124. package/src/ui/state/model.ts +140 -0
  125. package/src/ui/state/store.ts +669 -0
  126. package/src/ui/tabs/CookiesTab.tsx +61 -278
  127. package/src/ui/tabs/HeadersTab.tsx +85 -103
  128. package/src/ui/tabs/MessagesTab.tsx +276 -0
  129. package/src/ui/tabs/RequestTab.tsx +58 -51
  130. package/src/ui/tabs/ResponseTab.tsx +101 -74
  131. package/src/ui/tabs/SSEMessagesTab.tsx +224 -0
  132. package/src/ui/tabs/TimingTab.tsx +30 -43
  133. package/src/ui/types.ts +4 -1
  134. package/src/ui/utils/assert.ts +5 -0
  135. package/src/ui/utils/checkRequestBodyBinary.ts +7 -0
  136. package/src/ui/utils/copyToClipboard.ts +3 -0
  137. package/src/ui/utils/escapeShellArg.ts +12 -0
  138. package/src/ui/utils/generateCurlCommand.ts +83 -0
  139. package/src/ui/utils/generateFetchCall.ts +64 -0
  140. package/src/ui/utils/generateMultipartBody.ts +19 -0
  141. package/src/ui/utils/getId.ts +10 -0
  142. package/src/ui/utils/getStatusColor.ts +15 -0
  143. package/src/ui/views/InspectorView.tsx +35 -319
  144. package/src/utils/applyReactNativeRequestHeadersLogic.ts +30 -0
  145. package/src/utils/applyReactNativeResponseHeadersLogic.ts +28 -0
  146. package/src/utils/cookieParser.ts +126 -0
  147. package/src/utils/getContentTypeMimeType.ts +17 -0
  148. package/src/utils/getHttpHeader.ts +17 -0
  149. package/src/utils/getHttpHeaderValueAsString.ts +13 -0
  150. package/src/utils/getStringSizeInBytes.ts +3 -0
  151. package/src/utils/inferContentTypeFromPostData.ts +9 -0
  152. package/src/utils/safeStringify.ts +7 -0
  153. package/src/utils/typeChecks.ts +27 -0
  154. package/tailwind.config.ts +3 -0
  155. package/vite.config.ts +12 -0
  156. package/dist/src/ui/utils/getHttpHeaderValue.d.ts +0 -2
  157. package/src/react-native/network-inspector.ts +0 -247
  158. package/src/ui/utils/getHttpHeaderValue.ts +0 -14
  159. /package/dist/src/react-native/{network-requests-registry.d.ts → http/network-requests-registry.d.ts} +0 -0
  160. /package/src/react-native/{network-requests-registry.ts → http/network-requests-registry.ts} +0 -0
@@ -1,292 +1,75 @@
1
+ import React from 'react';
1
2
  import { ScrollArea } from '../components/ScrollArea';
2
- import { Badge } from '../components/Badge';
3
- import { NetworkEntry } from '../types';
4
- import { HttpHeaders } from '../../shared/client';
5
-
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
- };
3
+ import { Section } from '../components/Section';
4
+ import { CookieCard } from '../components/CookieCard';
5
+ import {
6
+ parseRequestCookiesFromHeaders,
7
+ parseResponseCookiesFromHeaders,
8
+ } from '../../utils/cookieParser';
9
+ import { HttpNetworkEntry, SSENetworkEntry } from '../state/model';
17
10
 
18
11
  export type CookiesTabProps = {
19
- selectedRequest: {
20
- id: string;
21
- };
22
- networkEntries: Map<string, NetworkEntry>;
12
+ selectedRequest: HttpNetworkEntry | SSENetworkEntry;
23
13
  };
24
14
 
25
- const parseCookieString = (cookieString: string): Cookie[] => {
26
- if (!cookieString) return [];
27
-
28
- return cookieString
29
- .split(';')
30
- .map((cookieStr) => {
31
- const [nameValue, ...attributes] = cookieStr.trim().split(';');
32
- const [name, value] = nameValue.split('=');
33
-
34
- const cookieObj: Cookie = {
35
- name: name?.trim() || '',
36
- value: value?.trim() || '',
37
- };
38
-
39
- // Parse attributes
40
- attributes.forEach((attr) => {
41
- const [attrName, attrValue] = attr.trim().split('=');
42
- const lowerAttrName = attrName.toLowerCase();
43
-
44
- switch (lowerAttrName) {
45
- case 'domain':
46
- cookieObj.domain = attrValue;
47
- break;
48
- case 'path':
49
- cookieObj.path = attrValue;
50
- break;
51
- case 'expires':
52
- cookieObj.expires = attrValue;
53
- break;
54
- case 'max-age':
55
- cookieObj.maxAge = attrValue;
56
- break;
57
- case 'secure':
58
- cookieObj.secure = true;
59
- break;
60
- case 'httponly':
61
- cookieObj.httpOnly = true;
62
- break;
63
- case 'samesite':
64
- cookieObj.sameSite = attrValue;
65
- break;
66
- }
67
- });
68
-
69
- return cookieObj;
70
- })
71
- .filter((cookieObj) => cookieObj.name); // Filter out empty cookies
72
- };
73
-
74
- const extractCookiesFromHeaders = (
75
- headers: HttpHeaders
76
- ): {
77
- requestCookies: Cookie[];
78
- responseCookies: Cookie[];
79
- } => {
80
- const requestCookies: Cookie[] = [];
81
- const responseCookies: Cookie[] = [];
82
-
83
- Object.entries(headers).forEach(([key, value]) => {
84
- const lowerKey = key.toLowerCase();
85
-
86
- if (lowerKey === 'cookie') {
87
- // Cookie header contains all cookies in one string
88
- requestCookies.push(...parseCookieString(value));
89
- } else if (lowerKey === 'set-cookie') {
90
- // Set-Cookie header contains one cookie with attributes
91
- const cookies = parseCookieString(value);
92
- responseCookies.push(...cookies);
93
- }
94
- });
95
-
96
- return { requestCookies, responseCookies };
97
- };
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
+ }
98
40
 
99
- export const CookiesTab = ({
100
- selectedRequest,
101
- networkEntries,
102
- }: CookiesTabProps) => {
103
41
  return (
104
42
  <ScrollArea className="h-full w-full">
105
43
  <div className="p-4">
106
- {(() => {
107
- const entry = networkEntries.get(selectedRequest.id);
108
- if (!entry) {
109
- return (
110
- <div className="text-sm text-gray-400">
111
- No request data available
112
- </div>
113
- );
114
- }
115
-
116
- // Extract cookies from request and response headers separately
117
- const requestHeaders = entry.request?.headers || {};
118
- const responseHeaders = entry.response?.headers || {};
119
-
120
- const { requestCookies } = extractCookiesFromHeaders(requestHeaders);
121
- const { responseCookies } = extractCookiesFromHeaders(responseHeaders);
122
-
123
- const hasRequestCookies = requestCookies.length > 0;
124
- const hasResponseCookies = responseCookies.length > 0;
125
-
126
- if (!hasRequestCookies && !hasResponseCookies) {
127
- return (
128
- <div className="text-sm text-gray-400">
129
- No cookies for this request
130
- </div>
131
- );
132
- }
133
-
134
- return (
135
- <div className="space-y-6">
136
- {/* Request Cookies */}
137
- {hasRequestCookies && (
138
- <div>
139
- <h4 className="text-sm font-medium text-gray-300 mb-3">
140
- Request Cookies ({requestCookies.length})
141
- </h4>
142
- <div className="space-y-2">
143
- {requestCookies.map((cookie, index) => (
144
- <div
145
- key={`request-${index}`}
146
- className="bg-gray-800 border border-gray-700 rounded p-3"
147
- >
148
- <div className="flex items-center justify-between mb-2">
149
- <span className="text-sm font-medium text-blue-400">
150
- {cookie.name}
151
- </span>
152
- <div className="flex items-center gap-2">
153
- {cookie.secure && (
154
- <Badge
155
- variant="outline"
156
- className="text-xs text-yellow-400 border-yellow-400"
157
- >
158
- Secure
159
- </Badge>
160
- )}
161
- {cookie.httpOnly && (
162
- <Badge
163
- variant="outline"
164
- className="text-xs text-purple-400 border-purple-400"
165
- >
166
- HttpOnly
167
- </Badge>
168
- )}
169
- </div>
170
- </div>
171
- <div className="text-sm text-gray-300 mb-2">
172
- {cookie.value}
173
- </div>
174
- <div className="grid grid-cols-2 gap-4 text-xs text-gray-400">
175
- {cookie.domain && (
176
- <div>
177
- <span className="font-medium">Domain:</span>{' '}
178
- {cookie.domain}
179
- </div>
180
- )}
181
- {cookie.path && (
182
- <div>
183
- <span className="font-medium">Path:</span>{' '}
184
- {cookie.path}
185
- </div>
186
- )}
187
- {cookie.expires && (
188
- <div>
189
- <span className="font-medium">Expires:</span>{' '}
190
- {cookie.expires}
191
- </div>
192
- )}
193
- {cookie.maxAge && (
194
- <div>
195
- <span className="font-medium">Max-Age:</span>{' '}
196
- {cookie.maxAge}
197
- </div>
198
- )}
199
- {cookie.sameSite && (
200
- <div>
201
- <span className="font-medium">SameSite:</span>{' '}
202
- {cookie.sameSite}
203
- </div>
204
- )}
205
- </div>
206
- </div>
207
- ))}
208
- </div>
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
+ ))}
209
55
  </div>
210
- )}
211
-
212
- {/* Response Cookies */}
213
- {hasResponseCookies && (
214
- <div>
215
- <h4 className="text-sm font-medium text-gray-300 mb-3">
216
- Response Cookies ({responseCookies.length})
217
- </h4>
218
- <div className="space-y-2">
219
- {responseCookies.map((cookie, index) => (
220
- <div
221
- key={`response-${index}`}
222
- className="bg-gray-800 border border-gray-700 rounded p-3"
223
- >
224
- <div className="flex items-center justify-between mb-2">
225
- <span className="text-sm font-medium text-green-400">
226
- {cookie.name}
227
- </span>
228
- <div className="flex items-center gap-2">
229
- {cookie.secure && (
230
- <Badge
231
- variant="outline"
232
- className="text-xs text-yellow-400 border-yellow-400"
233
- >
234
- Secure
235
- </Badge>
236
- )}
237
- {cookie.httpOnly && (
238
- <Badge
239
- variant="outline"
240
- className="text-xs text-purple-400 border-purple-400"
241
- >
242
- HttpOnly
243
- </Badge>
244
- )}
245
- </div>
246
- </div>
247
- <div className="text-sm text-gray-300 mb-2">
248
- {cookie.value}
249
- </div>
250
- <div className="grid grid-cols-2 gap-4 text-xs text-gray-400">
251
- {cookie.domain && (
252
- <div>
253
- <span className="font-medium">Domain:</span>{' '}
254
- {cookie.domain}
255
- </div>
256
- )}
257
- {cookie.path && (
258
- <div>
259
- <span className="font-medium">Path:</span>{' '}
260
- {cookie.path}
261
- </div>
262
- )}
263
- {cookie.expires && (
264
- <div>
265
- <span className="font-medium">Expires:</span>{' '}
266
- {cookie.expires}
267
- </div>
268
- )}
269
- {cookie.maxAge && (
270
- <div>
271
- <span className="font-medium">Max-Age:</span>{' '}
272
- {cookie.maxAge}
273
- </div>
274
- )}
275
- {cookie.sameSite && (
276
- <div>
277
- <span className="font-medium">SameSite:</span>{' '}
278
- {cookie.sameSite}
279
- </div>
280
- )}
281
- </div>
282
- </div>
283
- ))}
284
- </div>
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
+ ))}
285
69
  </div>
286
- )}
287
- </div>
288
- );
289
- })()}
70
+ </Section>
71
+ )}
72
+ </div>
290
73
  </div>
291
74
  </ScrollArea>
292
75
  );
@@ -1,116 +1,98 @@
1
+ import { useMemo } from 'react';
1
2
  import { ScrollArea } from '../components/ScrollArea';
2
- import { NetworkEntry } from '../types';
3
+ import { Section } from '../components/Section';
4
+ import { KeyValueGrid, KeyValueItem } from '../components/KeyValueGrid';
5
+ import { HttpNetworkEntry, SSENetworkEntry } from '../state/model';
6
+ import { getStatusColor } from '../utils/getStatusColor';
7
+ import { CopyRequestDropdown } from '../components/CopyRequestDropdown';
8
+ import { HttpHeaders } from '../../shared/client';
3
9
 
4
10
  export type HeadersTabProps = {
5
- selectedRequest: {
6
- id: string;
7
- domain: string;
8
- path: string;
9
- method: string;
10
- status: number;
11
- requestBody?: {
12
- type: string;
13
- data: string;
14
- };
15
- };
16
- networkEntries: Map<string, NetworkEntry>;
17
- getStatusColor: (status: number) => string;
11
+ selectedRequest: HttpNetworkEntry | SSENetworkEntry;
18
12
  };
19
13
 
20
- export const HeadersTab = ({
21
- selectedRequest,
22
- networkEntries,
23
- getStatusColor,
24
- }: HeadersTabProps) => {
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
+
30
+ export const HeadersTab = ({ selectedRequest }: HeadersTabProps) => {
31
+ const requestBody = selectedRequest.request.body;
32
+
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
+ );
71
+
25
72
  return (
26
73
  <ScrollArea className="h-full w-full">
27
74
  <div className="p-4 space-y-4">
28
- <div>
29
- <h4 className="text-sm font-medium text-gray-300 mb-2">General</h4>
30
- <div className="space-y-1 text-sm">
31
- <div className="flex">
32
- <span className="w-32 text-gray-400">Request URL:</span>
33
- <span className="text-blue-400">
34
- {selectedRequest.domain}
35
- {selectedRequest.path}
36
- </span>
37
- </div>
38
- <div className="flex">
39
- <span className="w-32 text-gray-400">Request Method:</span>
40
- <span>{selectedRequest.method}</span>
41
- </div>
42
- <div className="flex">
43
- <span className="w-32 text-gray-400">Status Code:</span>
44
- <span className={getStatusColor(selectedRequest.status)}>
45
- {selectedRequest.status}
46
- </span>
47
- </div>
48
- {selectedRequest.requestBody && (
49
- <div className="flex">
50
- <span className="w-32 text-gray-400">Content-Type:</span>
51
- <span className="text-blue-400">
52
- {selectedRequest.requestBody.type}
53
- </span>
54
- </div>
55
- )}
56
- </div>
57
- </div>
75
+ <CopyRequestDropdown selectedRequest={selectedRequest} />
76
+
77
+ <Section title="General">
78
+ <KeyValueGrid items={generalItems} />
79
+ </Section>
58
80
 
59
- <div>
60
- <h4 className="text-sm font-medium text-gray-300 mb-2">
61
- Response Headers
62
- </h4>
63
- <div className="space-y-1 text-sm font-mono">
64
- {(() => {
65
- const entry = networkEntries.get(selectedRequest.id);
66
- const responseHeaders = entry?.response?.headers;
67
- if (responseHeaders && Object.keys(responseHeaders).length > 0) {
68
- return Object.entries(responseHeaders).map(([key, value]) => (
69
- <div key={key} className="flex">
70
- <span className="w-32 text-gray-400">
71
- {key.toLowerCase()}:
72
- </span>
73
- <span className="flex-1 break-all">{value}</span>
74
- </div>
75
- ));
76
- } else {
77
- return (
78
- <div className="text-gray-500 italic">
79
- No response headers available
80
- </div>
81
- );
82
- }
83
- })()}
84
- </div>
85
- </div>
81
+ <Section title="Response Headers">
82
+ <KeyValueGrid
83
+ items={responseHeadersItems}
84
+ emptyMessage="No response headers available"
85
+ className="font-mono"
86
+ />
87
+ </Section>
86
88
 
87
- <div>
88
- <h4 className="text-sm font-medium text-gray-300 mb-2">
89
- Request Headers
90
- </h4>
91
- <div className="space-y-1 text-sm font-mono">
92
- {(() => {
93
- const entry = networkEntries.get(selectedRequest.id);
94
- const requestHeaders = entry?.request?.headers;
95
- if (requestHeaders && Object.keys(requestHeaders).length > 0) {
96
- return Object.entries(requestHeaders).map(([key, value]) => (
97
- <div key={key} className="flex">
98
- <span className="w-32 text-gray-400">
99
- {key.toLowerCase()}:
100
- </span>
101
- <span className="flex-1 break-all">{value}</span>
102
- </div>
103
- ));
104
- } else {
105
- return (
106
- <div className="text-gray-500 italic">
107
- No request headers available
108
- </div>
109
- );
110
- }
111
- })()}
112
- </div>
113
- </div>
89
+ <Section title="Request Headers">
90
+ <KeyValueGrid
91
+ items={requestHeadersItems}
92
+ emptyMessage="No request headers available"
93
+ className="font-mono"
94
+ />
95
+ </Section>
114
96
  </div>
115
97
  </ScrollArea>
116
98
  );