@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
package/package.json
CHANGED
|
@@ -1,37 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rozenite/network-activity-plugin",
|
|
3
|
-
"version": "1.0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Network Activity for Rozenite.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/react-native.cjs",
|
|
7
7
|
"module": "./dist/react-native.js",
|
|
8
8
|
"types": "./dist/react-native.d.ts",
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"
|
|
10
|
+
"nanoevents": "^9.1.0",
|
|
11
|
+
"@rozenite/plugin-bridge": "1.0.0"
|
|
11
12
|
},
|
|
12
13
|
"devDependencies": {
|
|
14
|
+
"@floating-ui/react": "^0.26.0",
|
|
13
15
|
"@radix-ui/react-scroll-area": "^1.2.9",
|
|
14
16
|
"@radix-ui/react-separator": "^1.1.7",
|
|
15
17
|
"@radix-ui/react-slot": "^1.2.3",
|
|
16
18
|
"@radix-ui/react-tabs": "^1.1.12",
|
|
19
|
+
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
|
17
20
|
"@tanstack/react-table": "^8.21.3",
|
|
21
|
+
"@tanstack/react-virtual": "^3.0.0",
|
|
18
22
|
"autoprefixer": "^10.4.21",
|
|
19
23
|
"class-variance-authority": "^0.7.1",
|
|
20
24
|
"clsx": "^2.1.1",
|
|
25
|
+
"lucide-react": "^0.263.1",
|
|
21
26
|
"postcss": "^8.5.6",
|
|
27
|
+
"proxy-memoize": "^3.0.1",
|
|
22
28
|
"react": "*",
|
|
23
29
|
"react-json-tree": "^0.20.0",
|
|
24
|
-
"react-native": "
|
|
30
|
+
"react-native-sse": "^1.2.1",
|
|
25
31
|
"tailwind-merge": "^3.3.1",
|
|
26
32
|
"tailwindcss": "^3.4.17",
|
|
27
33
|
"tailwindcss-animate": "^1.0.7",
|
|
28
34
|
"typescript": "^5.7.3",
|
|
29
35
|
"vite": "^6.0.0",
|
|
30
|
-
"
|
|
31
|
-
"@
|
|
32
|
-
"
|
|
33
|
-
"@rozenite/vite-plugin": "1.0.0
|
|
34
|
-
"rozenite": "1.0.0
|
|
36
|
+
"zustand": "^5.0.6",
|
|
37
|
+
"@types/react": "~18.3.23",
|
|
38
|
+
"@types/react-dom": "~18.3.1",
|
|
39
|
+
"@rozenite/vite-plugin": "1.0.0",
|
|
40
|
+
"rozenite": "1.0.0"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"react-native-sse": "*"
|
|
44
|
+
},
|
|
45
|
+
"peerDependenciesMeta": {
|
|
46
|
+
"react-native-sse": {
|
|
47
|
+
"optional": true
|
|
48
|
+
}
|
|
35
49
|
},
|
|
36
50
|
"license": "MIT",
|
|
37
51
|
"scripts": {
|
package/react-native.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
export let useNetworkActivityDevTools: typeof import('./src/react-native/useNetworkActivityDevTools').useNetworkActivityDevTools;
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
const isWeb =
|
|
4
|
+
typeof window !== 'undefined' && window.navigator.product !== 'ReactNative';
|
|
5
|
+
const isDev = process.env.NODE_ENV !== 'production';
|
|
6
|
+
const isServer = typeof window === 'undefined';
|
|
7
|
+
|
|
8
|
+
if (isDev && !isWeb && !isServer) {
|
|
4
9
|
useNetworkActivityDevTools =
|
|
5
10
|
require('./src/react-native/useNetworkActivityDevTools').useNetworkActivityDevTools;
|
|
6
11
|
} else {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export type InspectorType = 'http' | 'websocket' | 'sse';
|
|
2
|
+
|
|
3
|
+
export type NetworkActivityDevToolsConfig = {
|
|
4
|
+
/**
|
|
5
|
+
* Specifies which network inspectors are enabled.
|
|
6
|
+
* Set to `false` to disable monitoring for a specific type of network traffic.
|
|
7
|
+
* @default { http: true, websocket: true, sse: true }
|
|
8
|
+
*/
|
|
9
|
+
inspectors?: {
|
|
10
|
+
[key in InspectorType]?: boolean;
|
|
11
|
+
};
|
|
12
|
+
clientUISettings?: {
|
|
13
|
+
/**
|
|
14
|
+
* If true, display the entire relative URL as the request name in the UI instead of only the last path segment.
|
|
15
|
+
* @default false
|
|
16
|
+
*/
|
|
17
|
+
showUrlAsName?: boolean;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const DEFAULT_CONFIG: NetworkActivityDevToolsConfig = {
|
|
22
|
+
inspectors: {
|
|
23
|
+
http: true,
|
|
24
|
+
websocket: true,
|
|
25
|
+
sse: true,
|
|
26
|
+
},
|
|
27
|
+
clientUISettings: {
|
|
28
|
+
showUrlAsName: false,
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const validateConfig = (config: NetworkActivityDevToolsConfig): void => {
|
|
33
|
+
const inspectors = config.inspectors;
|
|
34
|
+
|
|
35
|
+
if (!inspectors) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// For SSE, HTTP must be enabled
|
|
40
|
+
if (inspectors.sse && !inspectors.http) {
|
|
41
|
+
throw new Error('SSE inspector requires HTTP inspector to be enabled.');
|
|
42
|
+
}
|
|
43
|
+
};
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { safeStringify } from '../../utils/safeStringify';
|
|
2
|
+
import {
|
|
3
|
+
HttpMethod,
|
|
4
|
+
NetworkActivityDevToolsClient,
|
|
5
|
+
RequestPostData,
|
|
6
|
+
RequestTextPostData,
|
|
7
|
+
RequestBinaryPostData,
|
|
8
|
+
RequestFormDataPostData,
|
|
9
|
+
XHRPostData,
|
|
10
|
+
} from '../../shared/client';
|
|
11
|
+
import { getContentType } from '../utils';
|
|
12
|
+
import { getNetworkRequestsRegistry } from './network-requests-registry';
|
|
13
|
+
import { getBlobName } from '../utils/getBlobName';
|
|
14
|
+
import { getFormDataEntries } from '../utils/getFormDataEntries';
|
|
15
|
+
import { XHRInterceptor } from './xhr-interceptor';
|
|
16
|
+
import { getStringSizeInBytes } from '../../utils/getStringSizeInBytes';
|
|
17
|
+
import { applyReactNativeResponseHeadersLogic } from '../../utils/applyReactNativeResponseHeadersLogic';
|
|
18
|
+
import {
|
|
19
|
+
isBlob,
|
|
20
|
+
isArrayBuffer,
|
|
21
|
+
isFormData,
|
|
22
|
+
isNullOrUndefined,
|
|
23
|
+
} from '../../utils/typeChecks';
|
|
24
|
+
import { getOverridesRegistry } from './overrides-registry';
|
|
25
|
+
|
|
26
|
+
const networkRequestsRegistry = getNetworkRequestsRegistry();
|
|
27
|
+
const overridesRegistry = getOverridesRegistry();
|
|
28
|
+
|
|
29
|
+
const getBinaryPostData = (body: Blob): RequestBinaryPostData => ({
|
|
30
|
+
type: 'binary',
|
|
31
|
+
value: {
|
|
32
|
+
size: body.size,
|
|
33
|
+
type: body.type,
|
|
34
|
+
name: getBlobName(body),
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const getArrayBufferPostData = (
|
|
39
|
+
body: ArrayBuffer | ArrayBufferView
|
|
40
|
+
): RequestBinaryPostData => ({
|
|
41
|
+
type: 'binary',
|
|
42
|
+
value: {
|
|
43
|
+
size: body.byteLength,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const getTextPostData = (body: unknown): RequestTextPostData => ({
|
|
48
|
+
type: 'text',
|
|
49
|
+
value: safeStringify(body),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const getFormDataPostData = (body: FormData): RequestFormDataPostData => ({
|
|
53
|
+
type: 'form-data',
|
|
54
|
+
value: getFormDataEntries(body).reduce<RequestFormDataPostData['value']>(
|
|
55
|
+
(acc, [key, value]) => {
|
|
56
|
+
if (isBlob(value)) {
|
|
57
|
+
acc[key] = getBinaryPostData(value);
|
|
58
|
+
} else if (isArrayBuffer(value)) {
|
|
59
|
+
acc[key] = getArrayBufferPostData(value);
|
|
60
|
+
} else {
|
|
61
|
+
acc[key] = getTextPostData(value);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return acc;
|
|
65
|
+
},
|
|
66
|
+
{}
|
|
67
|
+
),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const getRequestBody = (body: XHRPostData): RequestPostData => {
|
|
71
|
+
if (isNullOrUndefined(body)) {
|
|
72
|
+
return body;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (isBlob(body)) {
|
|
76
|
+
return getBinaryPostData(body);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (isArrayBuffer(body)) {
|
|
80
|
+
return getArrayBufferPostData(body);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (isFormData(body)) {
|
|
84
|
+
return getFormDataPostData(body);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return getTextPostData(body);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const getResponseSize = (request: XMLHttpRequest): number | null => {
|
|
91
|
+
try {
|
|
92
|
+
const { responseType, response } = request;
|
|
93
|
+
|
|
94
|
+
// Handle a case of 204 where no-content was sent.
|
|
95
|
+
if (response === null) {
|
|
96
|
+
return 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (responseType === '' || responseType === 'text') {
|
|
100
|
+
return getStringSizeInBytes(request.responseText);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (responseType === 'json') {
|
|
104
|
+
return getStringSizeInBytes(safeStringify(response));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (responseType === 'blob') {
|
|
108
|
+
return response.size;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (responseType === 'arraybuffer') {
|
|
112
|
+
return response.byteLength;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return 0;
|
|
116
|
+
} catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const getResponseBody = async (
|
|
122
|
+
request: XMLHttpRequest
|
|
123
|
+
): Promise<string | null> => {
|
|
124
|
+
const responseType = request.responseType;
|
|
125
|
+
|
|
126
|
+
// Response type is empty in certain cases, like when using axios.
|
|
127
|
+
if (responseType === '' || responseType === 'text') {
|
|
128
|
+
return request.responseText as string;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (responseType === 'blob') {
|
|
132
|
+
// This may be a text blob.
|
|
133
|
+
const contentType = request.getResponseHeader('Content-Type') || '';
|
|
134
|
+
|
|
135
|
+
if (
|
|
136
|
+
contentType.startsWith('text/') ||
|
|
137
|
+
contentType.startsWith('application/json')
|
|
138
|
+
) {
|
|
139
|
+
// It looks like a text blob, let's read it and forward it to the client.
|
|
140
|
+
return new Promise((resolve) => {
|
|
141
|
+
const reader = new FileReader();
|
|
142
|
+
reader.onload = () => {
|
|
143
|
+
resolve(reader.result as string);
|
|
144
|
+
};
|
|
145
|
+
reader.readAsText(request.response);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (responseType === 'json') {
|
|
151
|
+
return safeStringify(request.response);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return null;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const getInitiatorFromStack = (): {
|
|
158
|
+
type: string;
|
|
159
|
+
url?: string;
|
|
160
|
+
lineNumber?: number;
|
|
161
|
+
columnNumber?: number;
|
|
162
|
+
} => {
|
|
163
|
+
try {
|
|
164
|
+
const stack = new Error().stack;
|
|
165
|
+
if (!stack) {
|
|
166
|
+
return { type: 'other' };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const line = stack.split('\n')[9];
|
|
170
|
+
const match = line.match(/at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/);
|
|
171
|
+
if (match) {
|
|
172
|
+
return {
|
|
173
|
+
type: 'script',
|
|
174
|
+
url: match[2],
|
|
175
|
+
lineNumber: parseInt(match[3]),
|
|
176
|
+
columnNumber: parseInt(match[4]),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
} catch {
|
|
180
|
+
// Ignore stack parsing errors
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return { type: 'other' };
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export type NetworkInspector = {
|
|
187
|
+
enable: () => void;
|
|
188
|
+
disable: () => void;
|
|
189
|
+
isEnabled: () => boolean;
|
|
190
|
+
dispose: () => void;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const READY_STATE_HEADERS_RECEIVED = 2;
|
|
194
|
+
|
|
195
|
+
export const getNetworkInspector = (
|
|
196
|
+
pluginClient: NetworkActivityDevToolsClient
|
|
197
|
+
): NetworkInspector => {
|
|
198
|
+
const generateRequestId = (): string => {
|
|
199
|
+
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const handleRequestSend = (
|
|
203
|
+
data: XHRPostData,
|
|
204
|
+
request: XMLHttpRequest
|
|
205
|
+
): void => {
|
|
206
|
+
const sendTime = Date.now();
|
|
207
|
+
|
|
208
|
+
const requestId = generateRequestId();
|
|
209
|
+
request._rozeniteRequestId = requestId;
|
|
210
|
+
|
|
211
|
+
const initiator = getInitiatorFromStack();
|
|
212
|
+
|
|
213
|
+
networkRequestsRegistry.addEntry(requestId, request);
|
|
214
|
+
|
|
215
|
+
let ttfb = 0;
|
|
216
|
+
|
|
217
|
+
pluginClient.send('request-sent', {
|
|
218
|
+
requestId: requestId,
|
|
219
|
+
timestamp: sendTime,
|
|
220
|
+
request: {
|
|
221
|
+
url: request._url as string,
|
|
222
|
+
method: request._method as HttpMethod,
|
|
223
|
+
headers: request._headers,
|
|
224
|
+
postData: getRequestBody(data),
|
|
225
|
+
},
|
|
226
|
+
type: 'XHR',
|
|
227
|
+
initiator,
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
request.addEventListener('readystatechange', () => {
|
|
231
|
+
if (request.readyState === READY_STATE_HEADERS_RECEIVED) {
|
|
232
|
+
ttfb = Date.now() - sendTime;
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
request.addEventListener('load', () => {
|
|
237
|
+
pluginClient.send('response-received', {
|
|
238
|
+
requestId: requestId,
|
|
239
|
+
timestamp: Date.now(),
|
|
240
|
+
type: 'XHR',
|
|
241
|
+
response: {
|
|
242
|
+
url: request._url as string,
|
|
243
|
+
status: request.status,
|
|
244
|
+
statusText: request.statusText,
|
|
245
|
+
headers: applyReactNativeResponseHeadersLogic(
|
|
246
|
+
request.responseHeaders || {}
|
|
247
|
+
),
|
|
248
|
+
contentType: getContentType(request),
|
|
249
|
+
size: getResponseSize(request),
|
|
250
|
+
responseTime: Date.now(),
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
request.addEventListener('loadend', () => {
|
|
256
|
+
pluginClient.send('request-completed', {
|
|
257
|
+
requestId: requestId,
|
|
258
|
+
timestamp: Date.now(),
|
|
259
|
+
duration: Date.now() - sendTime,
|
|
260
|
+
size: getResponseSize(request),
|
|
261
|
+
ttfb,
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
request.addEventListener('error', () => {
|
|
266
|
+
pluginClient.send('request-failed', {
|
|
267
|
+
requestId: requestId,
|
|
268
|
+
timestamp: Date.now(),
|
|
269
|
+
type: 'XHR',
|
|
270
|
+
error: 'Failed',
|
|
271
|
+
canceled: false,
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
request.addEventListener('abort', () => {
|
|
276
|
+
pluginClient.send('request-failed', {
|
|
277
|
+
requestId: requestId,
|
|
278
|
+
timestamp: Date.now(),
|
|
279
|
+
type: 'XHR',
|
|
280
|
+
error: 'Aborted',
|
|
281
|
+
canceled: true,
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const handleRequestOverride = (request: XMLHttpRequest): void => {
|
|
287
|
+
const override = overridesRegistry.getOverrideForUrl(
|
|
288
|
+
request._url as string
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
if (!override) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
request.addEventListener('readystatechange', () => {
|
|
296
|
+
if (override.body !== undefined) {
|
|
297
|
+
Object.defineProperty(request, 'responseType', {
|
|
298
|
+
writable: true,
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
Object.defineProperty(request, 'response', {
|
|
302
|
+
writable: true,
|
|
303
|
+
});
|
|
304
|
+
Object.defineProperty(request, 'responseText', {
|
|
305
|
+
writable: true,
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
const contentType = getContentType(request);
|
|
309
|
+
|
|
310
|
+
if (contentType === 'application/json') {
|
|
311
|
+
request.responseType = 'json';
|
|
312
|
+
} else if (contentType === 'text/plain') {
|
|
313
|
+
request.responseType = 'text';
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// @ts-expect-error - Mocking response
|
|
317
|
+
request.response = override.body;
|
|
318
|
+
// @ts-expect-error - Mocking responseText
|
|
319
|
+
request.responseText = override.body;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (override.status !== undefined) {
|
|
323
|
+
Object.defineProperty(request, 'status', {
|
|
324
|
+
writable: true,
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// @ts-expect-error - Mocking status
|
|
328
|
+
request.status = override.status;
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const enable = () => {
|
|
334
|
+
XHRInterceptor.disableInterception();
|
|
335
|
+
XHRInterceptor.setSendCallback(handleRequestSend);
|
|
336
|
+
XHRInterceptor.setOverrideCallback(handleRequestOverride);
|
|
337
|
+
XHRInterceptor.enableInterception();
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const disable = () => {
|
|
341
|
+
XHRInterceptor.disableInterception();
|
|
342
|
+
networkRequestsRegistry.clear();
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const isEnabled = () => {
|
|
346
|
+
return XHRInterceptor.isInterceptorEnabled();
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const enableSubscription = pluginClient.onMessage('network-enable', () => {
|
|
350
|
+
enable();
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
const disableSubscription = pluginClient.onMessage('network-disable', () => {
|
|
354
|
+
disable();
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
const handleBodySubscription = pluginClient.onMessage(
|
|
358
|
+
'get-response-body',
|
|
359
|
+
async ({ requestId }) => {
|
|
360
|
+
const request = networkRequestsRegistry.getEntry(requestId);
|
|
361
|
+
|
|
362
|
+
if (!request) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const body = await getResponseBody(request);
|
|
367
|
+
|
|
368
|
+
pluginClient.send('response-body', {
|
|
369
|
+
requestId,
|
|
370
|
+
body,
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
const dispose = () => {
|
|
376
|
+
disable();
|
|
377
|
+
enableSubscription.remove();
|
|
378
|
+
disableSubscription.remove();
|
|
379
|
+
handleBodySubscription.remove();
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
return {
|
|
383
|
+
enable,
|
|
384
|
+
disable,
|
|
385
|
+
isEnabled,
|
|
386
|
+
dispose,
|
|
387
|
+
};
|
|
388
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { RequestOverride } from '../../shared/client';
|
|
2
|
+
|
|
3
|
+
export type OverridesRegistry = {
|
|
4
|
+
setOverrides: (newOverrides: [string, RequestOverride][]) => void;
|
|
5
|
+
getOverrideForUrl: (url: string) => RequestOverride | undefined;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const createOverridesRegistry = (): OverridesRegistry => {
|
|
9
|
+
let overrides = new Map<string, RequestOverride>();
|
|
10
|
+
|
|
11
|
+
const setOverrides = (newOverrides: [string, RequestOverride][]) => {
|
|
12
|
+
overrides = new Map(newOverrides);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const getOverrideForUrl = (url: string) => {
|
|
16
|
+
return overrides.get(url);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
setOverrides,
|
|
21
|
+
getOverrideForUrl,
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
let registryInstance: OverridesRegistry | null = null;
|
|
26
|
+
|
|
27
|
+
export const getOverridesRegistry = (): OverridesRegistry => {
|
|
28
|
+
if (!registryInstance) {
|
|
29
|
+
registryInstance = createOverridesRegistry();
|
|
30
|
+
}
|
|
31
|
+
return registryInstance;
|
|
32
|
+
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/* eslint-disable prefer-rest-params */
|
|
2
2
|
|
|
3
|
+
import { XHRPostData } from '../../shared/client';
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
5
7
|
*
|
|
@@ -9,6 +11,7 @@
|
|
|
9
11
|
* Source: https://github.com/facebook/react-native/blob/2c683c5787dd03ac15d2aad45dcc53650529ee7f/packages/react-native/src/private/devsupport/devmenu/elementinspector/XHRInterceptor.js
|
|
10
12
|
*/
|
|
11
13
|
|
|
14
|
+
const XMLHttpRequest = global.XMLHttpRequest || window.XMLHttpRequest;
|
|
12
15
|
const originalXHROpen = XMLHttpRequest.prototype.open;
|
|
13
16
|
const originalXHRSend = XMLHttpRequest.prototype.send;
|
|
14
17
|
const originalXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
|
|
@@ -20,7 +23,7 @@ type XHRInterceptorOpenCallback = (
|
|
|
20
23
|
) => void;
|
|
21
24
|
|
|
22
25
|
type XHRInterceptorSendCallback = (
|
|
23
|
-
data:
|
|
26
|
+
data: XHRPostData,
|
|
24
27
|
request: XMLHttpRequest
|
|
25
28
|
) => void;
|
|
26
29
|
|
|
@@ -46,11 +49,14 @@ type XHRInterceptorResponseCallback = (
|
|
|
46
49
|
request: XMLHttpRequest
|
|
47
50
|
) => void;
|
|
48
51
|
|
|
52
|
+
type XHRInterceptorOverrideCallback = (request: XMLHttpRequest) => void;
|
|
53
|
+
|
|
49
54
|
let openCallback: XHRInterceptorOpenCallback | null;
|
|
50
55
|
let sendCallback: XHRInterceptorSendCallback | null;
|
|
51
56
|
let requestHeaderCallback: XHRInterceptorRequestHeaderCallback | null;
|
|
52
57
|
let headerReceivedCallback: XHRInterceptorHeaderReceivedCallback | null;
|
|
53
58
|
let responseCallback: XHRInterceptorResponseCallback | null;
|
|
59
|
+
let overrideCallback: XHRInterceptorOverrideCallback | null;
|
|
54
60
|
|
|
55
61
|
let isInterceptorEnabled = false;
|
|
56
62
|
|
|
@@ -97,6 +103,13 @@ export const XHRInterceptor = {
|
|
|
97
103
|
requestHeaderCallback = callback;
|
|
98
104
|
},
|
|
99
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Invoked before XMLHttpRequest.send(...) is called.
|
|
108
|
+
*/
|
|
109
|
+
setOverrideCallback(callback: XHRInterceptorOverrideCallback) {
|
|
110
|
+
overrideCallback = callback;
|
|
111
|
+
},
|
|
112
|
+
|
|
100
113
|
isInterceptorEnabled(): boolean {
|
|
101
114
|
return isInterceptorEnabled;
|
|
102
115
|
},
|
|
@@ -136,10 +149,13 @@ export const XHRInterceptor = {
|
|
|
136
149
|
// register listeners to intercept the response, and invoke the callbacks.
|
|
137
150
|
// $FlowFixMe[cannot-write]
|
|
138
151
|
// $FlowFixMe[missing-this-annot]
|
|
139
|
-
XMLHttpRequest.prototype.send = function (data:
|
|
152
|
+
XMLHttpRequest.prototype.send = function (data: XHRPostData) {
|
|
140
153
|
if (sendCallback) {
|
|
141
154
|
sendCallback(data, this);
|
|
142
155
|
}
|
|
156
|
+
if (overrideCallback) {
|
|
157
|
+
overrideCallback(this);
|
|
158
|
+
}
|
|
143
159
|
if (this.addEventListener) {
|
|
144
160
|
this.addEventListener(
|
|
145
161
|
'readystatechange',
|
|
@@ -207,5 +223,6 @@ export const XHRInterceptor = {
|
|
|
207
223
|
sendCallback = null;
|
|
208
224
|
headerReceivedCallback = null;
|
|
209
225
|
requestHeaderCallback = null;
|
|
226
|
+
overrideCallback = null;
|
|
210
227
|
},
|
|
211
228
|
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type EventSource from 'react-native-sse';
|
|
2
|
+
|
|
3
|
+
const NOOP = () => {
|
|
4
|
+
// noop
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
const MOCK_EVENT_SOURCE = class {
|
|
8
|
+
open = NOOP;
|
|
9
|
+
close = NOOP;
|
|
10
|
+
addEventListener = NOOP;
|
|
11
|
+
removeEventListener = NOOP;
|
|
12
|
+
dispatch = NOOP;
|
|
13
|
+
removeAllEventListeners = NOOP;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const getEventSource = (): typeof EventSource => {
|
|
17
|
+
try {
|
|
18
|
+
const { default: EventSource } = require('react-native-sse');
|
|
19
|
+
return EventSource;
|
|
20
|
+
} catch {
|
|
21
|
+
// This is a workaround for the fact that Vite doesn't support require() calls for in-project dependencies.
|
|
22
|
+
// We are going to return a mock object, so the code will work fine, but it will not be able to intercept SSE requests.
|
|
23
|
+
return MOCK_EVENT_SOURCE;
|
|
24
|
+
}
|
|
25
|
+
};
|