@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.
- package/README.md +2 -0
- package/dist/App.html +2 -2
- package/dist/assets/{App-R2ZMH9wJ.css → App-BrSkOkws.css} +269 -2
- package/dist/assets/{App-lNMijPJ4.js → App-C6wCDVkW.js} +17485 -10814
- package/dist/event-source.cjs +22 -0
- package/dist/event-source.js +23 -0
- package/dist/react-native.cjs +4 -1
- package/dist/react-native.js +4 -1
- package/dist/rozenite.json +1 -1
- package/dist/src/react-native/config.d.ts +20 -0
- package/dist/src/react-native/{network-inspector.d.ts → http/network-inspector.d.ts} +1 -1
- package/dist/src/react-native/http/overrides-registry.d.ts +6 -0
- package/dist/src/react-native/{xhr-interceptor.d.ts → http/xhr-interceptor.d.ts} +7 -1
- package/dist/src/react-native/sse/event-source.d.ts +2 -0
- package/dist/src/react-native/sse/sse-inspector.d.ts +9 -0
- package/dist/src/react-native/sse/sse-interceptor.d.ts +36 -0
- package/dist/src/react-native/sse/types.d.ts +6 -0
- package/dist/src/react-native/useNetworkActivityDevTools.d.ts +2 -1
- package/dist/src/react-native/utils/getBlobName.d.ts +35 -0
- package/dist/src/react-native/utils/getFormDataEntries.d.ts +18 -0
- package/dist/src/react-native/utils.d.ts +6 -0
- package/dist/src/react-native/websocket/websocket-inspector.d.ts +9 -0
- package/dist/src/react-native/websocket/websocket-interceptor.d.ts +74 -0
- package/dist/src/shared/client.d.ts +53 -6
- package/dist/src/shared/sse-events.d.ts +38 -0
- package/dist/src/shared/websocket-events.d.ts +60 -0
- package/dist/src/ui/components/Badge.d.ts +1 -1
- package/dist/src/ui/components/Button.d.ts +2 -2
- package/dist/src/ui/components/CodeBlock.d.ts +3 -0
- package/dist/src/ui/components/CodeEditor.d.ts +5 -0
- package/dist/src/ui/components/CookieCard.d.ts +7 -0
- package/dist/src/ui/components/CopyRequestDropdown.d.ts +7 -0
- package/dist/src/ui/components/DropdownMenu.d.ts +27 -0
- package/dist/src/ui/components/FilterBar.d.ts +10 -0
- package/dist/src/ui/components/JsonTreeCopyableItem.d.ts +7 -0
- package/dist/src/ui/components/KeyValueGrid.d.ts +13 -0
- package/dist/src/ui/components/OverrideResponse.d.ts +8 -0
- package/dist/src/ui/components/RequestBody.d.ts +6 -0
- package/dist/src/ui/components/RequestList.d.ts +13 -28
- package/dist/src/ui/components/ScrollArea.d.ts +3 -2
- package/dist/src/ui/components/Section.d.ts +8 -0
- package/dist/src/ui/components/Separator.d.ts +2 -1
- package/dist/src/ui/components/SidePanel.d.ts +1 -0
- package/dist/src/ui/components/Tabs.d.ts +7 -0
- package/dist/src/ui/components/Toolbar.d.ts +1 -0
- package/dist/src/ui/hooks/useCopyToClipboard.d.ts +4 -0
- package/dist/src/ui/state/derived.d.ts +5 -0
- package/dist/src/ui/state/hooks.d.ts +21 -0
- package/dist/src/ui/state/model.d.ts +103 -0
- package/dist/src/ui/state/store.d.ts +48 -0
- package/dist/src/ui/tabs/CookiesTab.d.ts +3 -6
- package/dist/src/ui/tabs/HeadersTab.d.ts +3 -15
- package/dist/src/ui/tabs/MessagesTab.d.ts +5 -0
- package/dist/src/ui/tabs/RequestTab.d.ts +2 -7
- package/dist/src/ui/tabs/ResponseTab.d.ts +2 -8
- package/dist/src/ui/tabs/SSEMessagesTab.d.ts +5 -0
- package/dist/src/ui/tabs/TimingTab.d.ts +3 -5
- package/dist/src/ui/types.d.ts +4 -1
- package/dist/src/ui/utils/assert.d.ts +1 -0
- package/dist/src/ui/utils/checkRequestBodyBinary.d.ts +2 -0
- package/dist/src/ui/utils/copyToClipboard.d.ts +1 -0
- package/dist/src/ui/utils/escapeShellArg.d.ts +1 -0
- package/dist/src/ui/utils/generateCurlCommand.d.ts +2 -0
- package/dist/src/ui/utils/generateFetchCall.d.ts +2 -0
- package/dist/src/ui/utils/generateMultipartBody.d.ts +4 -0
- package/dist/src/ui/utils/getId.d.ts +1 -0
- package/dist/src/ui/utils/getStatusColor.d.ts +1 -0
- package/dist/src/utils/applyReactNativeRequestHeadersLogic.d.ts +7 -0
- package/dist/src/utils/applyReactNativeResponseHeadersLogic.d.ts +9 -0
- package/dist/src/utils/cookieParser.d.ts +6 -0
- package/dist/src/utils/getContentTypeMimeType.d.ts +2 -0
- package/dist/src/utils/getHttpHeader.d.ts +5 -0
- package/dist/src/utils/getHttpHeaderValueAsString.d.ts +11 -0
- package/dist/src/utils/getStringSizeInBytes.d.ts +1 -0
- package/dist/src/utils/inferContentTypeFromPostData.d.ts +2 -0
- package/dist/src/utils/safeStringify.d.ts +1 -0
- package/dist/src/utils/typeChecks.d.ts +9 -0
- package/dist/useNetworkActivityDevTools.cjs +724 -40
- package/dist/useNetworkActivityDevTools.js +723 -41
- package/package.json +22 -8
- package/react-native.ts +6 -1
- package/src/react-native/config.ts +43 -0
- package/src/react-native/http/network-inspector.ts +388 -0
- package/src/react-native/http/overrides-registry.ts +32 -0
- package/src/react-native/{xhr-interceptor.ts → http/xhr-interceptor.ts} +19 -2
- package/src/react-native/{xml-request.d.ts → http/xml-request.d.ts} +1 -0
- package/src/react-native/sse/event-source.ts +25 -0
- package/src/react-native/sse/sse-inspector.ts +139 -0
- package/src/react-native/sse/sse-interceptor.ts +180 -0
- package/src/react-native/sse/types.ts +9 -0
- package/src/react-native/useNetworkActivityDevTools.ts +156 -4
- package/src/react-native/utils/getBlobName.ts +45 -0
- package/src/react-native/utils/getFormDataEntries.ts +32 -0
- package/src/react-native/utils.ts +43 -0
- package/src/react-native/websocket/websocket-inspector.ts +180 -0
- package/src/react-native/websocket/websocket-interceptor.d.ts +4 -0
- package/src/react-native/websocket/websocket-interceptor.ts +166 -0
- package/src/shared/client.ts +79 -6
- package/src/shared/sse-events.ts +47 -0
- package/src/shared/websocket-events.ts +79 -0
- package/src/ui/components/Button.tsx +1 -0
- package/src/ui/components/CodeBlock.tsx +19 -0
- package/src/ui/components/CodeEditor.tsx +26 -0
- package/src/ui/components/CookieCard.tsx +64 -0
- package/src/ui/components/CopyRequestDropdown.tsx +95 -0
- package/src/ui/components/DropdownMenu.tsx +206 -0
- package/src/ui/components/FilterBar.tsx +117 -0
- package/src/ui/components/Input.tsx +1 -1
- package/src/ui/components/JsonTree.tsx +20 -0
- package/src/ui/components/JsonTreeCopyableItem.tsx +37 -0
- package/src/ui/components/KeyValueGrid.tsx +51 -0
- package/src/ui/components/OverrideResponse.tsx +132 -0
- package/src/ui/components/RequestBody.tsx +86 -0
- package/src/ui/components/RequestList.tsx +101 -131
- package/src/ui/components/ScrollArea.tsx +1 -0
- package/src/ui/components/Section.tsx +46 -0
- package/src/ui/components/SidePanel.tsx +333 -0
- package/src/ui/components/Tabs.tsx +1 -1
- package/src/ui/components/Toolbar.tsx +45 -0
- package/src/ui/globals.css +4 -0
- package/src/ui/hooks/useCopyToClipboard.ts +28 -0
- package/src/ui/state/derived.ts +112 -0
- package/src/ui/state/hooks.ts +52 -0
- package/src/ui/state/model.ts +140 -0
- package/src/ui/state/store.ts +669 -0
- package/src/ui/tabs/CookiesTab.tsx +61 -278
- package/src/ui/tabs/HeadersTab.tsx +85 -103
- package/src/ui/tabs/MessagesTab.tsx +276 -0
- package/src/ui/tabs/RequestTab.tsx +58 -51
- package/src/ui/tabs/ResponseTab.tsx +101 -74
- package/src/ui/tabs/SSEMessagesTab.tsx +224 -0
- package/src/ui/tabs/TimingTab.tsx +30 -43
- package/src/ui/types.ts +4 -1
- package/src/ui/utils/assert.ts +5 -0
- package/src/ui/utils/checkRequestBodyBinary.ts +7 -0
- package/src/ui/utils/copyToClipboard.ts +3 -0
- package/src/ui/utils/escapeShellArg.ts +12 -0
- package/src/ui/utils/generateCurlCommand.ts +83 -0
- package/src/ui/utils/generateFetchCall.ts +64 -0
- package/src/ui/utils/generateMultipartBody.ts +19 -0
- package/src/ui/utils/getId.ts +10 -0
- package/src/ui/utils/getStatusColor.ts +15 -0
- package/src/ui/views/InspectorView.tsx +35 -319
- package/src/utils/applyReactNativeRequestHeadersLogic.ts +30 -0
- package/src/utils/applyReactNativeResponseHeadersLogic.ts +28 -0
- package/src/utils/cookieParser.ts +126 -0
- package/src/utils/getContentTypeMimeType.ts +17 -0
- package/src/utils/getHttpHeader.ts +17 -0
- package/src/utils/getHttpHeaderValueAsString.ts +13 -0
- package/src/utils/getStringSizeInBytes.ts +3 -0
- package/src/utils/inferContentTypeFromPostData.ts +9 -0
- package/src/utils/safeStringify.ts +7 -0
- package/src/utils/typeChecks.ts +27 -0
- package/tailwind.config.ts +3 -0
- package/vite.config.ts +12 -0
- package/dist/src/ui/utils/getHttpHeaderValue.d.ts +0 -2
- package/src/react-native/network-inspector.ts +0 -247
- package/src/ui/utils/getHttpHeaderValue.ts +0 -14
- /package/dist/src/react-native/{network-requests-registry.d.ts → http/network-requests-registry.d.ts} +0 -0
- /package/src/react-native/{network-requests-registry.ts → http/network-requests-registry.ts} +0 -0
|
@@ -1,348 +1,64 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { ResponseTab } from '../tabs/ResponseTab';
|
|
8
|
-
import { CookiesTab } from '../tabs/CookiesTab';
|
|
9
|
-
import { TimingTab } from '../tabs/TimingTab';
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { Toolbar } from '../components/Toolbar';
|
|
3
|
+
import { RequestList } from '../components/RequestList';
|
|
4
|
+
import { SidePanel } from '../components/SidePanel';
|
|
5
|
+
import { FilterBar, FilterState } from '../components/FilterBar';
|
|
6
|
+
import { NetworkActivityDevToolsClient } from '../../shared/client';
|
|
10
7
|
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} from '../
|
|
16
|
-
import { Circle, Square, Trash2, X } from 'lucide-react';
|
|
17
|
-
import {
|
|
18
|
-
NetworkActivityDevToolsClient,
|
|
19
|
-
NetworkActivityEventMap,
|
|
20
|
-
RequestId,
|
|
21
|
-
} from '../../shared/client';
|
|
22
|
-
import { NetworkEntry } from '../types';
|
|
8
|
+
useNetworkActivityClientManagement,
|
|
9
|
+
useHasSelectedRequest,
|
|
10
|
+
useNetworkActivityActions,
|
|
11
|
+
useOverrides,
|
|
12
|
+
} from '../state/hooks';
|
|
23
13
|
|
|
24
14
|
export type InspectorViewProps = {
|
|
25
15
|
client: NetworkActivityDevToolsClient;
|
|
26
16
|
};
|
|
27
17
|
|
|
28
18
|
export const InspectorView = ({ client }: InspectorViewProps) => {
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
);
|
|
33
|
-
const [
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const selectedRequest = useMemo(() => {
|
|
38
|
-
if (!selectedRequestId) return null;
|
|
39
|
-
const processedRequests = processNetworkEntries(networkEntries);
|
|
40
|
-
return (
|
|
41
|
-
processedRequests.find((request) => request.id === selectedRequestId) ||
|
|
42
|
-
null
|
|
43
|
-
);
|
|
44
|
-
}, [selectedRequestId, networkEntries]);
|
|
19
|
+
const actions = useNetworkActivityActions();
|
|
20
|
+
const clientManagement = useNetworkActivityClientManagement();
|
|
21
|
+
const hasSelectedRequest = useHasSelectedRequest();
|
|
22
|
+
const overrides = useOverrides();
|
|
23
|
+
const [filter, setFilter] = useState<FilterState>({
|
|
24
|
+
text: '',
|
|
25
|
+
types: new Set(['http', 'websocket', 'sse']),
|
|
26
|
+
});
|
|
45
27
|
|
|
46
28
|
useEffect(() => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const entry: NetworkEntry = {
|
|
51
|
-
requestId: data.requestId,
|
|
52
|
-
url: data.request.url,
|
|
53
|
-
method: data.request.method,
|
|
54
|
-
headers: data.request.headers,
|
|
55
|
-
postData: data.request.postData,
|
|
56
|
-
status: 'pending',
|
|
57
|
-
startTime: data.timestamp,
|
|
58
|
-
type: data.type,
|
|
59
|
-
initiator: data.initiator,
|
|
60
|
-
request: data.request,
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
setNetworkEntries((prev) => new Map(prev).set(data.requestId, entry));
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const handleResponseReceived = (
|
|
67
|
-
data: NetworkActivityEventMap['response-received']
|
|
68
|
-
) => {
|
|
69
|
-
setNetworkEntries((prev) => {
|
|
70
|
-
const entry = prev.get(data.requestId);
|
|
71
|
-
if (!entry) return prev;
|
|
72
|
-
|
|
73
|
-
const updatedEntry: NetworkEntry = {
|
|
74
|
-
...entry,
|
|
75
|
-
status: 'loading',
|
|
76
|
-
response: data.response,
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
return new Map(prev).set(data.requestId, updatedEntry);
|
|
80
|
-
});
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const handleRequestCompleted = (
|
|
84
|
-
data: NetworkActivityEventMap['request-completed']
|
|
85
|
-
) => {
|
|
86
|
-
setNetworkEntries((prev) => {
|
|
87
|
-
const entry = prev.get(data.requestId);
|
|
88
|
-
if (!entry) return prev;
|
|
89
|
-
|
|
90
|
-
const updatedEntry: NetworkEntry = {
|
|
91
|
-
...entry,
|
|
92
|
-
status: 'finished',
|
|
93
|
-
endTime: data.timestamp,
|
|
94
|
-
duration: data.duration,
|
|
95
|
-
ttfb: data.ttfb,
|
|
96
|
-
size: data.size,
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
return new Map(prev).set(data.requestId, updatedEntry);
|
|
100
|
-
});
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const handleRequestFailed = (
|
|
104
|
-
data: NetworkActivityEventMap['request-failed']
|
|
105
|
-
) => {
|
|
106
|
-
setNetworkEntries((prev) => {
|
|
107
|
-
const entry = prev.get(data.requestId);
|
|
108
|
-
if (!entry) return prev;
|
|
109
|
-
|
|
110
|
-
const updatedEntry: NetworkEntry = {
|
|
111
|
-
...entry,
|
|
112
|
-
status: 'failed',
|
|
113
|
-
error: data.error,
|
|
114
|
-
canceled: data.canceled,
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
return new Map(prev).set(data.requestId, updatedEntry);
|
|
118
|
-
});
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
// Subscribe to network events
|
|
122
|
-
const unsubscribeRequestSent = client.onMessage(
|
|
123
|
-
'request-sent',
|
|
124
|
-
handleRequestSent
|
|
125
|
-
);
|
|
126
|
-
const unsubscribeResponseReceived = client.onMessage(
|
|
127
|
-
'response-received',
|
|
128
|
-
handleResponseReceived
|
|
129
|
-
);
|
|
130
|
-
const unsubscribeRequestCompleted = client.onMessage(
|
|
131
|
-
'request-completed',
|
|
132
|
-
handleRequestCompleted
|
|
133
|
-
);
|
|
134
|
-
const unsubscribeRequestFailed = client.onMessage(
|
|
135
|
-
'request-failed',
|
|
136
|
-
handleRequestFailed
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
const handleResponseBody = (
|
|
140
|
-
data: NetworkActivityEventMap['response-body']
|
|
141
|
-
) => {
|
|
142
|
-
setNetworkEntries((prev) => {
|
|
143
|
-
const entry = prev.get(data.requestId);
|
|
144
|
-
if (!entry) return prev;
|
|
145
|
-
|
|
146
|
-
const updatedEntry: NetworkEntry = {
|
|
147
|
-
...entry,
|
|
148
|
-
responseBody: {
|
|
149
|
-
...entry.responseBody,
|
|
150
|
-
body: data.body,
|
|
151
|
-
},
|
|
152
|
-
};
|
|
29
|
+
if (!client) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
153
32
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
});
|
|
157
|
-
};
|
|
33
|
+
clientManagement.setupClient(client);
|
|
34
|
+
actions.setRecording(true);
|
|
158
35
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
);
|
|
36
|
+
client.send('set-overrides', {
|
|
37
|
+
overrides: Array.from(overrides.entries()),
|
|
38
|
+
});
|
|
163
39
|
|
|
164
40
|
return () => {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
unsubscribeRequestCompleted.remove();
|
|
168
|
-
unsubscribeRequestFailed.remove();
|
|
169
|
-
unsubscribeResponseBody.remove();
|
|
41
|
+
actions.setRecording(false);
|
|
42
|
+
clientManagement.cleanupClient();
|
|
170
43
|
};
|
|
171
|
-
}, [client]);
|
|
172
|
-
|
|
173
|
-
useEffect(() => {
|
|
174
|
-
if (isRecording) {
|
|
175
|
-
client.send('network-enable', {});
|
|
176
|
-
|
|
177
|
-
return () => {
|
|
178
|
-
client.send('network-disable', {});
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
}, [isRecording, client]);
|
|
182
|
-
|
|
183
|
-
const clearRequests = () => {
|
|
184
|
-
setNetworkEntries(new Map());
|
|
185
|
-
setSelectedRequestId(null);
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
const closeSidePanel = () => {
|
|
189
|
-
setSelectedRequestId(null);
|
|
190
|
-
};
|
|
44
|
+
}, [client, clientManagement, actions, overrides]);
|
|
191
45
|
|
|
192
46
|
return (
|
|
193
47
|
<div className="h-screen bg-gray-900 text-gray-100 flex flex-col">
|
|
194
|
-
|
|
195
|
-
<
|
|
196
|
-
<Button
|
|
197
|
-
variant="ghost"
|
|
198
|
-
size="sm"
|
|
199
|
-
onClick={() => setIsRecording(!isRecording)}
|
|
200
|
-
className={`h-8 w-8 p-0 ${
|
|
201
|
-
isRecording
|
|
202
|
-
? 'text-red-400 hover:text-red-300'
|
|
203
|
-
: 'text-gray-400 hover:text-blue-400'
|
|
204
|
-
}`}
|
|
205
|
-
>
|
|
206
|
-
{isRecording ? (
|
|
207
|
-
<Circle className="h-4 w-4 fill-current" />
|
|
208
|
-
) : (
|
|
209
|
-
<Square className="h-4 w-4" />
|
|
210
|
-
)}
|
|
211
|
-
</Button>
|
|
212
|
-
<Button
|
|
213
|
-
variant="ghost"
|
|
214
|
-
size="sm"
|
|
215
|
-
onClick={clearRequests}
|
|
216
|
-
className="h-8 w-8 p-0 text-gray-400 hover:text-blue-400"
|
|
217
|
-
>
|
|
218
|
-
<Trash2 className="h-4 w-4" />
|
|
219
|
-
</Button>
|
|
220
|
-
</div>
|
|
48
|
+
<Toolbar />
|
|
49
|
+
<FilterBar filter={filter} onFilterChange={setFilter} />
|
|
221
50
|
|
|
222
51
|
<div className="flex flex-1 overflow-hidden">
|
|
223
52
|
{/* Request List */}
|
|
224
53
|
<div
|
|
225
54
|
className={`flex flex-col ${
|
|
226
|
-
|
|
55
|
+
hasSelectedRequest ? 'w-1/2' : 'w-full'
|
|
227
56
|
} border-r border-gray-700 overflow-hidden`}
|
|
228
57
|
>
|
|
229
|
-
<RequestList
|
|
230
|
-
networkEntries={networkEntries}
|
|
231
|
-
selectedRequestId={selectedRequestId}
|
|
232
|
-
onRequestSelect={setSelectedRequestId}
|
|
233
|
-
/>
|
|
58
|
+
<RequestList filter={filter} />
|
|
234
59
|
</div>
|
|
235
60
|
|
|
236
|
-
{
|
|
237
|
-
{selectedRequest && (
|
|
238
|
-
<div className="w-1/2 flex flex-col bg-gray-900">
|
|
239
|
-
{/* Side Panel Header */}
|
|
240
|
-
<div className="flex items-center justify-between p-3 border-b border-gray-700 bg-gray-800">
|
|
241
|
-
<div className="flex items-center gap-2">
|
|
242
|
-
<div
|
|
243
|
-
className={`w-3 h-3 rounded-full ${getTypeColor(
|
|
244
|
-
selectedRequest.type
|
|
245
|
-
)}`}
|
|
246
|
-
></div>
|
|
247
|
-
<span className="font-medium">{selectedRequest.name}</span>
|
|
248
|
-
<Badge
|
|
249
|
-
variant="outline"
|
|
250
|
-
className={`${getStatusColor(
|
|
251
|
-
selectedRequest.status
|
|
252
|
-
)} border-current`}
|
|
253
|
-
>
|
|
254
|
-
{selectedRequest.status}
|
|
255
|
-
</Badge>
|
|
256
|
-
</div>
|
|
257
|
-
<Button
|
|
258
|
-
variant="ghost"
|
|
259
|
-
size="sm"
|
|
260
|
-
onClick={closeSidePanel}
|
|
261
|
-
className="h-6 w-6 p-0 text-gray-400 hover:text-blue-400"
|
|
262
|
-
>
|
|
263
|
-
<X className="h-4 w-4" />
|
|
264
|
-
</Button>
|
|
265
|
-
</div>
|
|
266
|
-
|
|
267
|
-
{/* Side Panel Content */}
|
|
268
|
-
<div className="flex-1 overflow-hidden">
|
|
269
|
-
<Tabs defaultValue="headers" className="h-full flex flex-col">
|
|
270
|
-
<TabsList className="grid w-full grid-cols-5 bg-gray-800 rounded-none border-b border-gray-700">
|
|
271
|
-
<TabsTrigger
|
|
272
|
-
value="headers"
|
|
273
|
-
className="data-[state=active]:bg-gray-700"
|
|
274
|
-
>
|
|
275
|
-
Headers
|
|
276
|
-
</TabsTrigger>
|
|
277
|
-
<TabsTrigger
|
|
278
|
-
value="request"
|
|
279
|
-
className="data-[state=active]:bg-gray-700"
|
|
280
|
-
>
|
|
281
|
-
Request
|
|
282
|
-
</TabsTrigger>
|
|
283
|
-
<TabsTrigger
|
|
284
|
-
value="response"
|
|
285
|
-
className="data-[state=active]:bg-gray-700"
|
|
286
|
-
>
|
|
287
|
-
Response
|
|
288
|
-
</TabsTrigger>
|
|
289
|
-
<TabsTrigger
|
|
290
|
-
value="cookies"
|
|
291
|
-
className="data-[state=active]:bg-gray-700"
|
|
292
|
-
>
|
|
293
|
-
Cookies
|
|
294
|
-
</TabsTrigger>
|
|
295
|
-
<TabsTrigger
|
|
296
|
-
value="timing"
|
|
297
|
-
className="data-[state=active]:bg-gray-700"
|
|
298
|
-
>
|
|
299
|
-
Timing
|
|
300
|
-
</TabsTrigger>
|
|
301
|
-
</TabsList>
|
|
302
|
-
|
|
303
|
-
<TabsContent
|
|
304
|
-
value="headers"
|
|
305
|
-
className="flex-1 m-0 overflow-hidden"
|
|
306
|
-
>
|
|
307
|
-
<HeadersTab
|
|
308
|
-
selectedRequest={selectedRequest}
|
|
309
|
-
networkEntries={networkEntries}
|
|
310
|
-
getStatusColor={getStatusColor}
|
|
311
|
-
/>
|
|
312
|
-
</TabsContent>
|
|
313
|
-
|
|
314
|
-
<TabsContent value="request" className="flex-1 m-0 overflow-hidden">
|
|
315
|
-
<RequestTab selectedRequest={selectedRequest} />
|
|
316
|
-
</TabsContent>
|
|
317
|
-
|
|
318
|
-
<TabsContent value="response" className="flex-1 m-0 overflow-hidden">
|
|
319
|
-
<ResponseTab
|
|
320
|
-
selectedRequest={selectedRequest}
|
|
321
|
-
onRequestResponseBody={(requestId) => {
|
|
322
|
-
client.send('get-response-body', {
|
|
323
|
-
requestId,
|
|
324
|
-
});
|
|
325
|
-
}}
|
|
326
|
-
/>
|
|
327
|
-
</TabsContent>
|
|
328
|
-
|
|
329
|
-
<TabsContent value="cookies" className="flex-1 m-0 overflow-hidden">
|
|
330
|
-
<CookiesTab
|
|
331
|
-
selectedRequest={selectedRequest}
|
|
332
|
-
networkEntries={networkEntries}
|
|
333
|
-
/>
|
|
334
|
-
</TabsContent>
|
|
335
|
-
|
|
336
|
-
<TabsContent value="timing" className="flex-1 m-0 overflow-hidden">
|
|
337
|
-
<TimingTab
|
|
338
|
-
selectedRequest={selectedRequest}
|
|
339
|
-
networkEntries={networkEntries}
|
|
340
|
-
/>
|
|
341
|
-
</TabsContent>
|
|
342
|
-
</Tabs>
|
|
343
|
-
</div>
|
|
344
|
-
</div>
|
|
345
|
-
)}
|
|
61
|
+
{hasSelectedRequest && <SidePanel />}
|
|
346
62
|
</div>
|
|
347
63
|
</div>
|
|
348
64
|
);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { HttpHeaders, RequestPostData } from '../shared/client';
|
|
2
|
+
import { getHttpHeader } from './getHttpHeader';
|
|
3
|
+
import { inferContentTypeFromPostData } from './inferContentTypeFromPostData';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Partially emulates React Native's behavior for setting HTTP headers.
|
|
7
|
+
*
|
|
8
|
+
* @see https://github.com/facebook/react-native/blob/de5093c88771977b58f7bec3f3ffa64a9595334e/packages/react-native/Libraries/Network/RCTNetworking.mm#L345-L349
|
|
9
|
+
*/
|
|
10
|
+
export function applyReactNativeRequestHeadersLogic(
|
|
11
|
+
headers: HttpHeaders,
|
|
12
|
+
postData?: RequestPostData
|
|
13
|
+
): HttpHeaders {
|
|
14
|
+
const existingContentType = getHttpHeader(headers, 'content-type');
|
|
15
|
+
|
|
16
|
+
if (existingContentType) {
|
|
17
|
+
return headers;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const inferredContentType = inferContentTypeFromPostData(postData);
|
|
21
|
+
|
|
22
|
+
if (!inferredContentType) {
|
|
23
|
+
return headers;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
...headers,
|
|
28
|
+
'Content-Type': inferredContentType,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { HttpHeaders, XHRHeaders } from '../shared/client';
|
|
2
|
+
import { splitSetCookieHeaderByComma } from './cookieParser';
|
|
3
|
+
import { getHttpHeader } from './getHttpHeader';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Applies React Native specific logic to response headers.
|
|
7
|
+
* React Native concatenates multiple header values into single strings,
|
|
8
|
+
* this function parses them back into arrays where appropriate.
|
|
9
|
+
*
|
|
10
|
+
* @see https://github.com/facebook/react-native/blob/588f0c5ce6c283f116228456da2170d2adc3cbf4/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java#L637
|
|
11
|
+
*/
|
|
12
|
+
export const applyReactNativeResponseHeadersLogic = (
|
|
13
|
+
headers: XHRHeaders
|
|
14
|
+
): HttpHeaders => {
|
|
15
|
+
const parsedHeaders: HttpHeaders = { ...headers };
|
|
16
|
+
|
|
17
|
+
const setCookieHeader = getHttpHeader(headers, 'set-cookie');
|
|
18
|
+
|
|
19
|
+
if (setCookieHeader) {
|
|
20
|
+
const { value, originalKey } = setCookieHeader;
|
|
21
|
+
|
|
22
|
+
const cookies = splitSetCookieHeaderByComma(value);
|
|
23
|
+
|
|
24
|
+
parsedHeaders[originalKey] = cookies.length > 0 ? cookies : value;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return parsedHeaders;
|
|
28
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { Cookie, HttpHeaders } from '../shared/client';
|
|
2
|
+
import { getHttpHeader } from './getHttpHeader';
|
|
3
|
+
|
|
4
|
+
export const parseSetCookieHeader = (setCookieStr: string): Cookie => {
|
|
5
|
+
const parts = setCookieStr.split(';').map((p) => p.trim());
|
|
6
|
+
const [nameValue, ...attributes] = parts;
|
|
7
|
+
const [name, ...valueParts] = nameValue.split('=');
|
|
8
|
+
|
|
9
|
+
const value = valueParts.join('=');
|
|
10
|
+
|
|
11
|
+
const cookieObj: Cookie = {
|
|
12
|
+
name: name?.trim() || '',
|
|
13
|
+
value: value?.trim() || '',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
attributes.forEach((attr) => {
|
|
17
|
+
const [attrName, ...attrValueParts] = attr.split('=');
|
|
18
|
+
const lowerAttrName = attrName.trim().toLowerCase();
|
|
19
|
+
const attrValue = attrValueParts.join('=');
|
|
20
|
+
|
|
21
|
+
switch (lowerAttrName) {
|
|
22
|
+
case 'domain':
|
|
23
|
+
cookieObj.domain = attrValue;
|
|
24
|
+
|
|
25
|
+
break;
|
|
26
|
+
|
|
27
|
+
case 'path':
|
|
28
|
+
cookieObj.path = attrValue;
|
|
29
|
+
|
|
30
|
+
break;
|
|
31
|
+
|
|
32
|
+
case 'expires':
|
|
33
|
+
cookieObj.expires = attrValue;
|
|
34
|
+
|
|
35
|
+
break;
|
|
36
|
+
|
|
37
|
+
case 'max-age':
|
|
38
|
+
cookieObj.maxAge = attrValue;
|
|
39
|
+
|
|
40
|
+
break;
|
|
41
|
+
|
|
42
|
+
case 'secure':
|
|
43
|
+
cookieObj.secure = true;
|
|
44
|
+
|
|
45
|
+
break;
|
|
46
|
+
|
|
47
|
+
case 'httponly':
|
|
48
|
+
cookieObj.httpOnly = true;
|
|
49
|
+
|
|
50
|
+
break;
|
|
51
|
+
|
|
52
|
+
case 'samesite':
|
|
53
|
+
cookieObj.sameSite = attrValue;
|
|
54
|
+
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return cookieObj;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const splitSetCookieHeaderByComma = (header: string): string[] => {
|
|
63
|
+
const regex = /(?:^|,\s)([^=;,]+=[^;]+(?:;[^,]*)*)/g;
|
|
64
|
+
const matches: string[] = [];
|
|
65
|
+
|
|
66
|
+
let match;
|
|
67
|
+
|
|
68
|
+
while ((match = regex.exec(header)) !== null) {
|
|
69
|
+
matches.push(match[1].trim());
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return matches;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const parseCookieHeader = (cookieString: string): Cookie[] => {
|
|
76
|
+
if (!cookieString) return [];
|
|
77
|
+
|
|
78
|
+
return cookieString
|
|
79
|
+
.split(';')
|
|
80
|
+
.map((cookieStr) => {
|
|
81
|
+
const [name, ...valueParts] = cookieStr.trim().split('=');
|
|
82
|
+
const value = valueParts.join('=');
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
name: name?.trim() || '',
|
|
86
|
+
value: value?.trim() || '',
|
|
87
|
+
};
|
|
88
|
+
})
|
|
89
|
+
.filter((cookieObj) => cookieObj.name);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const parseRequestCookiesFromHeaders = (
|
|
93
|
+
headers: HttpHeaders
|
|
94
|
+
): Cookie[] => {
|
|
95
|
+
const cookieHeader = getHttpHeader(headers, 'cookie');
|
|
96
|
+
|
|
97
|
+
if (!cookieHeader) {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const { value } = cookieHeader;
|
|
102
|
+
|
|
103
|
+
if (Array.isArray(value)) {
|
|
104
|
+
return value.flatMap(parseCookieHeader);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return parseCookieHeader(value);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export const parseResponseCookiesFromHeaders = (
|
|
111
|
+
headers: HttpHeaders
|
|
112
|
+
): Cookie[] => {
|
|
113
|
+
const setCookieHeader = getHttpHeader(headers, 'set-cookie');
|
|
114
|
+
|
|
115
|
+
if (!setCookieHeader) {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const { value } = setCookieHeader;
|
|
120
|
+
|
|
121
|
+
if (Array.isArray(value)) {
|
|
122
|
+
return value.flatMap(parseSetCookieHeader);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return [parseSetCookieHeader(value)];
|
|
126
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { HttpHeaders } from '../shared/client';
|
|
2
|
+
import { getHttpHeader } from './getHttpHeader';
|
|
3
|
+
|
|
4
|
+
export function getContentTypeMime(headers: HttpHeaders) {
|
|
5
|
+
const contentType = getHttpHeader(headers, 'content-type');
|
|
6
|
+
|
|
7
|
+
if (!contentType) {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { value } = contentType;
|
|
12
|
+
|
|
13
|
+
// Content-Type can't be an array, but if it does we simply get the first element.
|
|
14
|
+
const actualValue = Array.isArray(value) ? value[0] : value;
|
|
15
|
+
|
|
16
|
+
return actualValue.split(';')[0].trim();
|
|
17
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { HttpHeaders, XHRHeaders } from '../shared/client';
|
|
2
|
+
|
|
3
|
+
// Utility to get header value and actual key case-insensitively
|
|
4
|
+
export function getHttpHeader<T extends HttpHeaders | XHRHeaders>(
|
|
5
|
+
headers: T,
|
|
6
|
+
name: string
|
|
7
|
+
) {
|
|
8
|
+
const lowerName = name.toLowerCase();
|
|
9
|
+
|
|
10
|
+
for (const key in headers) {
|
|
11
|
+
if (key.toLowerCase() === lowerName) {
|
|
12
|
+
return { value: headers[key], originalKey: key };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Combines multiple HTTP header values according to RFC 7230 Section 3.2.2
|
|
3
|
+
*
|
|
4
|
+
* Per RFC 7230 Section 3.2.2: "A recipient MAY combine multiple header fields
|
|
5
|
+
* with the same field name into one 'field-name: field-value' pair, without
|
|
6
|
+
* changing the semantics of the message, by appending each subsequent field
|
|
7
|
+
* value to the combined field value in order, separated by a comma."
|
|
8
|
+
*
|
|
9
|
+
* @see https://tools.ietf.org/html/rfc7230#section-3.2.2
|
|
10
|
+
*/
|
|
11
|
+
export function getHttpHeaderValueAsString(value: string | string[]): string {
|
|
12
|
+
return Array.isArray(value) ? value.join(', ') : value;
|
|
13
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const isBlob = (value: unknown): value is Blob => value instanceof Blob;
|
|
2
|
+
|
|
3
|
+
export const isArrayBuffer = (
|
|
4
|
+
value: unknown
|
|
5
|
+
): value is ArrayBuffer | ArrayBufferView =>
|
|
6
|
+
value instanceof ArrayBuffer || ArrayBuffer.isView(value);
|
|
7
|
+
|
|
8
|
+
export const isFormData = (value: unknown): value is FormData =>
|
|
9
|
+
value instanceof FormData;
|
|
10
|
+
|
|
11
|
+
export const isNullOrUndefined = (value: unknown): value is null | undefined =>
|
|
12
|
+
value === null || value === undefined;
|
|
13
|
+
|
|
14
|
+
export const isString = (value: unknown): value is string =>
|
|
15
|
+
typeof value === 'string';
|
|
16
|
+
|
|
17
|
+
export const isNumber = (value: unknown): value is number =>
|
|
18
|
+
typeof value === 'number' && !isNaN(value);
|
|
19
|
+
|
|
20
|
+
export const isBoolean = (value: unknown): value is boolean =>
|
|
21
|
+
typeof value === 'boolean';
|
|
22
|
+
|
|
23
|
+
export const isObject = (value: unknown): value is object =>
|
|
24
|
+
typeof value === 'object' && value !== null;
|
|
25
|
+
|
|
26
|
+
export const isArray = (value: unknown): value is unknown[] =>
|
|
27
|
+
Array.isArray(value);
|
package/tailwind.config.ts
CHANGED