@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
@@ -0,0 +1,276 @@
1
+ import { useState, useMemo } from 'react';
2
+ import {
3
+ createColumnHelper,
4
+ flexRender,
5
+ getCoreRowModel,
6
+ useReactTable,
7
+ } from '@tanstack/react-table';
8
+ import { ScrollArea } from '../components/ScrollArea';
9
+ import { JsonTree } from '../components/JsonTree';
10
+ import { WebSocketMessageType } from '../../shared/websocket-events';
11
+ import { WebSocketNetworkEntry } from '../state/model';
12
+ import { useWebSocketMessages } from '../state/hooks';
13
+
14
+ export type MessagesTabProps = {
15
+ selectedRequest: WebSocketNetworkEntry;
16
+ };
17
+
18
+ interface WebSocketMessageRow {
19
+ id: string;
20
+ direction: 'sent' | 'received';
21
+ data: string;
22
+ messageType: 'text' | 'binary';
23
+ timestamp: number;
24
+ }
25
+
26
+ const columnHelper = createColumnHelper<WebSocketMessageRow>();
27
+
28
+ export const MessagesTab = ({ selectedRequest }: MessagesTabProps) => {
29
+ const websocketMessages = useWebSocketMessages(selectedRequest.id);
30
+ const [selectedMessageId, setSelectedMessageId] = useState<string | null>(
31
+ null
32
+ );
33
+
34
+ const selectedMessage = useMemo(() => {
35
+ if (!selectedMessageId) return null;
36
+ return (
37
+ websocketMessages.find((msg) => msg.id === selectedMessageId) || null
38
+ );
39
+ }, [selectedMessageId, websocketMessages]);
40
+
41
+ const formatTimestamp = (timestamp: number) => {
42
+ const date = new Date(timestamp);
43
+ const timeString = date.toLocaleTimeString('en-US', {
44
+ hour12: false,
45
+ hour: '2-digit',
46
+ minute: '2-digit',
47
+ second: '2-digit',
48
+ });
49
+ const milliseconds = date.getMilliseconds().toString().padStart(3, '0');
50
+ return `${timeString}.${milliseconds}`;
51
+ };
52
+
53
+ const formatData = (data: string, messageType: WebSocketMessageType) => {
54
+ if (messageType === 'binary') {
55
+ return 'Binary message';
56
+ }
57
+
58
+ if (typeof data === 'string') {
59
+ try {
60
+ const jsonData = JSON.parse(data);
61
+ return (
62
+ <div className="bg-gray-800 p-3 rounded border border-gray-700">
63
+ <JsonTree data={jsonData} />
64
+ </div>
65
+ );
66
+ } catch {
67
+ // Fallback to pre tag if JSON parsing fails
68
+ return (
69
+ <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">
70
+ {data}
71
+ </pre>
72
+ );
73
+ }
74
+ }
75
+
76
+ return 'Binary message';
77
+ };
78
+
79
+ const getMessageTypeColor = (type: 'sent' | 'received') => {
80
+ return type === 'sent' ? 'text-blue-400' : 'text-green-400';
81
+ };
82
+
83
+ const getMessageTypeIcon = (type: 'sent' | 'received') => {
84
+ return type === 'sent' ? '↑' : '↓';
85
+ };
86
+
87
+ const tableData = useMemo(() => {
88
+ return websocketMessages.map(
89
+ (message): WebSocketMessageRow => ({
90
+ id: message.id,
91
+ direction: message.direction,
92
+ data: message.data,
93
+ messageType: message.messageType,
94
+ timestamp: message.timestamp,
95
+ })
96
+ );
97
+ }, [websocketMessages]);
98
+
99
+ const formatPreviewData = (
100
+ data: string,
101
+ messageType: WebSocketMessageType
102
+ ) => {
103
+ if (messageType === 'binary') {
104
+ return <span className="text-gray-400">Binary message</span>;
105
+ }
106
+
107
+ return (
108
+ <span className="max-w-xs truncate text-gray-400">
109
+ {data.substring(0, 100) + (data.length > 100 ? '...' : '')}
110
+ </span>
111
+ );
112
+ };
113
+
114
+ const columns = [
115
+ columnHelper.accessor('direction', {
116
+ header: 'Type',
117
+ cell: ({ getValue }) => {
118
+ const direction = getValue();
119
+ return (
120
+ <span
121
+ className={`flex items-center gap-1 ${getMessageTypeColor(
122
+ direction
123
+ )}`}
124
+ >
125
+ <span className="text-xs">{getMessageTypeIcon(direction)}</span>
126
+ <span className="capitalize">{direction}</span>
127
+ </span>
128
+ );
129
+ },
130
+ size: 80,
131
+ }),
132
+ columnHelper.accessor('data', {
133
+ header: 'Data',
134
+ cell: ({ getValue, row }) => {
135
+ const data = getValue();
136
+ const messageType = row.original.messageType;
137
+ return formatPreviewData(data, messageType);
138
+ },
139
+ size: 300,
140
+ }),
141
+ columnHelper.accessor('timestamp', {
142
+ header: 'Timestamp',
143
+ cell: ({ getValue }) => (
144
+ <div className="text-gray-400">{formatTimestamp(getValue())}</div>
145
+ ),
146
+ size: 120,
147
+ }),
148
+ ];
149
+
150
+ const table = useReactTable({
151
+ data: tableData,
152
+ columns,
153
+ getCoreRowModel: getCoreRowModel(),
154
+ });
155
+
156
+ if (websocketMessages.length === 0) {
157
+ return (
158
+ <ScrollArea className="h-full min-h-0 p-4">
159
+ <div className="text-sm text-gray-400">
160
+ No WebSocket messages available for this connection. Messages will
161
+ appear here when the WebSocket connection sends or receives data.
162
+ </div>
163
+ </ScrollArea>
164
+ );
165
+ }
166
+
167
+ return (
168
+ <div className="h-full flex flex-col">
169
+ {/* Messages Table */}
170
+ <div className="flex-1 border border-gray-700 rounded overflow-hidden">
171
+ <div className="overflow-y-auto h-full">
172
+ <table className="w-full text-sm">
173
+ <thead className="bg-gray-800 border-b border-gray-700 sticky top-0 z-10">
174
+ {table.getHeaderGroups().map((headerGroup) => (
175
+ <tr key={headerGroup.id}>
176
+ {headerGroup.headers.map((header) => (
177
+ <th
178
+ key={header.id}
179
+ className="text-left p-2 font-medium text-gray-300"
180
+ style={{ width: header.getSize() }}
181
+ >
182
+ <div className="flex items-center gap-1">
183
+ {header.isPlaceholder
184
+ ? null
185
+ : flexRender(
186
+ header.column.columnDef.header,
187
+ header.getContext()
188
+ )}
189
+ </div>
190
+ </th>
191
+ ))}
192
+ </tr>
193
+ ))}
194
+ </thead>
195
+ <tbody>
196
+ {table.getRowModel().rows.map((row) => (
197
+ <tr
198
+ key={row.id}
199
+ className={`border-b border-gray-700 hover:bg-gray-800 cursor-pointer ${
200
+ selectedMessageId === row.original.id ? 'bg-gray-800' : ''
201
+ }`}
202
+ onClick={() => setSelectedMessageId(row.original.id)}
203
+ >
204
+ {row.getVisibleCells().map((cell) => (
205
+ <td
206
+ key={cell.id}
207
+ className="p-2"
208
+ style={{ width: cell.column.getSize() }}
209
+ >
210
+ {flexRender(
211
+ cell.column.columnDef.cell,
212
+ cell.getContext()
213
+ )}
214
+ </td>
215
+ ))}
216
+ </tr>
217
+ ))}
218
+ </tbody>
219
+ </table>
220
+ </div>
221
+ </div>
222
+
223
+ {/* Message Details Panel */}
224
+ {selectedMessage && (
225
+ <div className="border-t border-gray-700 bg-gray-800">
226
+ <div className="p-4">
227
+ <div className="flex items-center justify-between mb-3">
228
+ <h4 className="text-sm font-medium text-gray-300">
229
+ Message Details
230
+ </h4>
231
+ <button
232
+ onClick={() => setSelectedMessageId(null)}
233
+ className="text-gray-400 hover:text-blue-400 text-sm"
234
+ >
235
+ Close
236
+ </button>
237
+ </div>
238
+ <div className="space-y-3">
239
+ <div className="grid grid-cols-2 gap-4 text-sm">
240
+ <div>
241
+ <span className="text-gray-400">Type: </span>
242
+ <span
243
+ className={getMessageTypeColor(selectedMessage.direction)}
244
+ >
245
+ {selectedMessage.direction}
246
+ </span>
247
+ </div>
248
+ <div>
249
+ <span className="text-gray-400">Message Type: </span>
250
+ <span className="text-blue-400 capitalize">
251
+ {selectedMessage.messageType}
252
+ </span>
253
+ </div>
254
+ <div>
255
+ <span className="text-gray-400">Timestamp: </span>
256
+ <span className="text-gray-300">
257
+ {formatTimestamp(selectedMessage.timestamp)}
258
+ </span>
259
+ </div>
260
+ </div>
261
+ <div>
262
+ <span className="text-gray-400 text-sm">Content:</span>
263
+ <div className="mt-2 max-h-96 overflow-y-auto">
264
+ {formatData(
265
+ selectedMessage.data,
266
+ selectedMessage.messageType
267
+ )}
268
+ </div>
269
+ </div>
270
+ </div>
271
+ </div>
272
+ </div>
273
+ )}
274
+ </div>
275
+ );
276
+ };
@@ -1,71 +1,78 @@
1
1
  import { ScrollArea } from '../components/ScrollArea';
2
- import { JsonTree } from '../components/JsonTree';
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';
3
11
 
4
12
  export type RequestTabProps = {
5
- selectedRequest: {
6
- method: string;
7
- requestBody?: {
8
- type: string;
9
- data: string;
10
- };
11
- };
13
+ selectedRequest: HttpNetworkEntry | SSENetworkEntry;
14
+ };
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
+ }
12
29
  };
13
30
 
14
31
  export const RequestTab = ({ selectedRequest }: RequestTabProps) => {
15
- const renderRequestBody = () => {
16
- if (!selectedRequest?.requestBody) return null;
32
+ const queryParams = useMemo(() => {
33
+ const { searchParams } = new URL(selectedRequest.request.url);
17
34
 
18
- const { type, data } = selectedRequest.requestBody;
35
+ return Array.from(searchParams.entries()).map(([key, value]) => ({
36
+ key,
37
+ value,
38
+ }));
39
+ }, [selectedRequest.request.url]);
19
40
 
20
- if (type === 'application/json') {
21
- try {
22
- const jsonData = JSON.parse(data);
23
- return (
24
- <div className="bg-gray-800 p-3 rounded border border-gray-700">
25
- <JsonTree data={jsonData} />
26
- </div>
27
- );
28
- } catch {
29
- // Fallback to pre tag if JSON parsing fails
30
- return (
31
- <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">
32
- {data}
33
- </pre>
34
- );
35
- }
41
+ const requestBody = selectedRequest.request.body;
42
+ const hasQueryParams = queryParams.length > 0;
43
+
44
+ const renderQueryParams = () => {
45
+ if (!hasQueryParams) {
46
+ return null;
36
47
  }
37
48
 
38
- // For non-JSON content types, use the existing pre tag
39
49
  return (
40
- <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">
41
- {data}
42
- </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>
43
65
  );
44
66
  };
45
67
 
46
68
  return (
47
69
  <ScrollArea className="h-full w-full">
48
- <div className="p-4">
49
- {selectedRequest?.requestBody ? (
50
- <div className="space-y-4">
51
- <div>
52
- <h4 className="text-sm font-medium text-gray-300 mb-2">
53
- Request Body
54
- </h4>
55
- <div className="text-sm mb-2">
56
- <span className="text-gray-400">Content-Type: </span>
57
- <span className="text-blue-400">
58
- {selectedRequest.requestBody.type}
59
- </span>
60
- </div>
61
- </div>
62
- <div>{renderRequestBody()}</div>
63
- </div>
64
- ) : (
70
+ <div className="p-4 space-y-4">
71
+ {renderQueryParams()}
72
+ {renderRequestBody()}
73
+ {!hasQueryParams && !requestBody && (
65
74
  <div className="text-sm text-gray-400">
66
- {selectedRequest?.method === 'GET'
67
- ? "GET requests don't have a request body"
68
- : 'No request body for this request'}
75
+ No request body or query params for this request
69
76
  </div>
70
77
  )}
71
78
  </div>
@@ -1,24 +1,49 @@
1
- import { useEffect, useRef } from 'react';
1
+ import { useEffect, useRef, useState } from 'react';
2
2
  import { ScrollArea } from '../components/ScrollArea';
3
3
  import { JsonTree } from '../components/JsonTree';
4
+ import { HttpNetworkEntry } from '../state/model';
5
+ import { Section } from '../components/Section';
6
+ import { KeyValueGrid } from '../components/KeyValueGrid';
7
+ import { CodeBlock } from '../components/CodeBlock';
8
+ import { useOverrides } from '../state/hooks';
9
+ import { RequestOverride } from '../../shared/client';
10
+ import { OverrideResponse } from '../components/OverrideResponse';
11
+ import { Button } from '../components/Button';
12
+ import { Pencil } from 'lucide-react';
4
13
 
5
14
  export type ResponseTabProps = {
6
- selectedRequest: {
7
- id: string;
8
- type: string;
9
- responseBody?: {
10
- type: string;
11
- data: string | null;
12
- };
13
- };
15
+ selectedRequest: HttpNetworkEntry;
14
16
  onRequestResponseBody: (requestId: string) => void;
15
17
  };
16
18
 
19
+ type ResponseBodySectionProps = {
20
+ action?: React.ReactNode;
21
+ children: React.ReactNode;
22
+ };
23
+
24
+ const RenderResponseBodySection = ({
25
+ children,
26
+ action,
27
+ }: ResponseBodySectionProps) => {
28
+ return (
29
+ <Section title="Response Body" collapsible={false} action={action}>
30
+ <div className="space-y-4">{children}</div>
31
+ </Section>
32
+ );
33
+ };
34
+
17
35
  export const ResponseTab = ({
18
36
  selectedRequest,
19
37
  onRequestResponseBody,
20
38
  }: ResponseTabProps) => {
21
39
  const onRequestResponseBodyRef = useRef(onRequestResponseBody);
40
+ const overrides = useOverrides();
41
+ const [initialOverride, setInitialOverride] = useState<
42
+ RequestOverride | undefined
43
+ >(() => {
44
+ const override = overrides.get(selectedRequest.request.url);
45
+ return override;
46
+ });
22
47
 
23
48
  useEffect(() => {
24
49
  onRequestResponseBodyRef.current = onRequestResponseBody;
@@ -30,8 +55,10 @@ export const ResponseTab = ({
30
55
  }
31
56
  }, [selectedRequest.id]);
32
57
 
58
+ const responseBody = selectedRequest.response?.body;
59
+
33
60
  const renderResponseBody = () => {
34
- if (!selectedRequest?.responseBody) {
61
+ if (!responseBody || responseBody.data === null) {
35
62
  return (
36
63
  <div className="text-sm text-gray-400">
37
64
  No response body available for this request
@@ -39,104 +66,104 @@ export const ResponseTab = ({
39
66
  );
40
67
  }
41
68
 
42
- const { type, data } = selectedRequest.responseBody;
69
+ const { type, data } = responseBody;
70
+ const statusCode = selectedRequest.response?.status;
71
+
72
+ const contentTypeGrid = (
73
+ <KeyValueGrid
74
+ items={[
75
+ {
76
+ key: 'Content-Type',
77
+ value: type,
78
+ valueClassName: 'text-blue-400',
79
+ },
80
+ ]}
81
+ />
82
+ );
83
+
84
+ const overrideAction = (
85
+ <Button
86
+ variant="ghost"
87
+ size="xs"
88
+ className="text-violet-300 hover:text-violet-300"
89
+ onClick={() =>
90
+ setInitialOverride({
91
+ body: data,
92
+ status: statusCode,
93
+ })
94
+ }
95
+ >
96
+ <Pencil className="h-2 w-2" />
97
+ Override
98
+ </Button>
99
+ );
43
100
 
44
- // Handle null data
45
- if (data === null) {
101
+ if (initialOverride !== undefined) {
46
102
  return (
47
- <div className="text-sm text-gray-400">
48
- No response body available for this request
49
- </div>
103
+ <OverrideResponse
104
+ selectedRequest={selectedRequest}
105
+ initialOverride={initialOverride}
106
+ onClear={() => setInitialOverride(undefined)}
107
+ />
50
108
  );
51
109
  }
52
110
 
53
- // Handle JSON content
54
- if (type === 'application/json') {
111
+ if (type.startsWith('application/json')) {
112
+ let bodyContent;
113
+
55
114
  try {
56
115
  const jsonData = JSON.parse(data);
57
- return (
58
- <div className="space-y-4">
59
- <div className="text-sm mb-2">
60
- <span className="text-gray-400">Content-Type: </span>
61
- <span className="text-blue-400">{type}</span>
62
- </div>
63
- <div className="bg-gray-800 p-3 rounded border border-gray-700">
64
- <JsonTree data={jsonData} />
65
- </div>
66
- </div>
116
+
117
+ bodyContent = (
118
+ <CodeBlock>
119
+ <JsonTree data={jsonData} />
120
+ </CodeBlock>
67
121
  );
68
122
  } catch {
69
- // Fallback to pre tag if JSON parsing fails
70
- return (
71
- <div className="space-y-4">
72
- <div className="text-sm mb-2">
73
- <span className="text-gray-400">Content-Type: </span>
74
- <span className="text-blue-400">{type}</span>
75
- </div>
76
- <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">
77
- {data}
78
- </pre>
79
- <div className="text-xs text-gray-500">
123
+ bodyContent = (
124
+ <>
125
+ <CodeBlock>{data}</CodeBlock>
126
+ <div className="text-xs text-gray-500 mt-1">
80
127
  ⚠️ Failed to parse as JSON, showing as raw text
81
128
  </div>
82
- </div>
129
+ </>
83
130
  );
84
131
  }
85
- }
86
132
 
87
- // Handle HTML content
88
- if (type === 'text/html') {
89
133
  return (
90
- <div className="space-y-4">
91
- <div className="text-sm mb-2">
92
- <span className="text-gray-400">Content-Type: </span>
93
- <span className="text-blue-400">{type}</span>
94
- </div>
95
- <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">
96
- {data}
97
- </pre>
98
- </div>
134
+ <RenderResponseBodySection action={overrideAction}>
135
+ {contentTypeGrid}
136
+ {bodyContent}
137
+ </RenderResponseBodySection>
99
138
  );
100
139
  }
101
140
 
102
- // Handle other text content types
103
141
  if (
104
142
  type.startsWith('text/') ||
105
- type === 'application/xml' ||
106
- type === 'application/javascript'
143
+ type.startsWith('application/xml') ||
144
+ type.startsWith('application/javascript')
107
145
  ) {
108
146
  return (
109
- <div className="space-y-4">
110
- <div className="text-sm mb-2">
111
- <span className="text-gray-400">Content-Type: </span>
112
- <span className="text-blue-400">{type}</span>
113
- </div>
114
- <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">
115
- {data}
116
- </pre>
117
- </div>
147
+ <RenderResponseBodySection action={overrideAction}>
148
+ {contentTypeGrid}
149
+ <CodeBlock>{data}</CodeBlock>
150
+ </RenderResponseBodySection>
118
151
  );
119
152
  }
120
153
 
121
- // Handle other content types
122
154
  return (
123
- <div className="space-y-4">
124
- <div className="text-sm mb-2">
125
- <span className="text-gray-400">Content-Type: </span>
126
- <span className="text-blue-400">{type}</span>
127
- </div>
155
+ <RenderResponseBodySection>
156
+ {contentTypeGrid}
128
157
  <div className="text-sm text-gray-400">
129
158
  Binary content not shown - {data.length} bytes
130
159
  </div>
131
- </div>
160
+ </RenderResponseBodySection>
132
161
  );
133
162
  };
134
163
 
135
164
  return (
136
165
  <ScrollArea className="h-full w-full">
137
- <div className="p-4">
138
- {renderResponseBody()}
139
- </div>
166
+ <div className="p-4">{renderResponseBody()}</div>
140
167
  </ScrollArea>
141
168
  );
142
169
  };