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