@rozenite/network-activity-plugin 1.0.0-alpha.1 → 1.0.0-alpha.10
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 +3 -5
- package/dist/{panel.html → App.html} +3 -3
- package/dist/assets/App-CA1Fbh0I.js +25364 -0
- package/dist/assets/App-DoHQsY5s.css +1276 -0
- package/dist/event-source.cjs +22 -0
- package/dist/event-source.js +23 -0
- package/dist/react-native.cjs +8 -1
- package/dist/react-native.d.ts +1 -5
- package/dist/react-native.js +6 -171
- package/dist/rozenite.config.d.ts +7 -0
- package/dist/rozenite.json +1 -1
- package/dist/src/react-native/http/network-inspector.d.ts +8 -0
- package/dist/src/react-native/http/network-requests-registry.d.ts +6 -0
- package/dist/src/react-native/http/xhr-interceptor.d.ts +38 -0
- 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 -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 +68 -0
- package/dist/src/shared/sse-events.d.ts +35 -0
- package/dist/src/shared/websocket-events.d.ts +60 -0
- package/dist/src/ui/App.d.ts +1 -0
- package/dist/src/ui/components/Badge.d.ts +9 -0
- package/dist/src/ui/components/Button.d.ts +11 -0
- package/dist/src/ui/components/Input.d.ts +3 -0
- package/dist/src/ui/components/JsonTree.d.ts +5 -0
- package/dist/src/ui/components/JsonTreeCopyableItem.d.ts +7 -0
- package/dist/src/ui/components/RequestList.d.ts +25 -0
- package/dist/src/ui/components/ScrollArea.d.ts +4 -0
- package/dist/src/ui/components/Separator.d.ts +3 -0
- package/dist/src/ui/components/SidePanel.d.ts +1 -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 +17 -0
- package/dist/src/ui/state/model.d.ts +98 -0
- package/dist/src/ui/state/store.d.ts +24 -0
- package/dist/src/ui/tabs/CookiesTab.d.ts +5 -0
- package/dist/src/ui/tabs/HeadersTab.d.ts +5 -0
- package/dist/src/ui/tabs/MessagesTab.d.ts +5 -0
- package/dist/src/ui/tabs/RequestTab.d.ts +5 -0
- package/dist/src/ui/tabs/ResponseTab.d.ts +6 -0
- package/dist/src/ui/tabs/SSEMessagesTab.d.ts +5 -0
- package/dist/src/ui/tabs/TimingTab.d.ts +5 -0
- package/dist/src/ui/types.d.ts +26 -0
- package/dist/src/ui/utils/assert.d.ts +1 -0
- package/dist/src/ui/utils/cn.d.ts +2 -0
- package/dist/src/ui/utils/copyToClipboard.d.ts +1 -0
- package/dist/src/ui/utils/getHttpHeaderValue.d.ts +2 -0
- package/dist/src/ui/utils/getId.d.ts +1 -0
- package/dist/src/ui/utils/getStatusColor.d.ts +1 -0
- package/dist/src/ui/views/InspectorView.d.ts +5 -0
- package/dist/src/ui/views/LoadingView.d.ts +1 -0
- package/dist/useNetworkActivityDevTools.cjs +759 -0
- package/dist/useNetworkActivityDevTools.js +757 -0
- package/package.json +31 -10
- package/postcss.config.js +6 -0
- package/project.json +12 -0
- package/react-native.ts +2 -1
- package/rozenite.config.ts +2 -2
- package/src/css-modules.d.ts +1 -1
- package/src/react-native/http/network-inspector.ts +226 -0
- package/src/react-native/http/network-requests-registry.ts +52 -0
- package/src/react-native/http/xhr-interceptor.ts +211 -0
- package/src/react-native/http/xml-request.d.ts +34 -0
- package/src/react-native/sse/event-source.ts +25 -0
- package/src/react-native/sse/sse-inspector.ts +117 -0
- package/src/react-native/sse/sse-interceptor.ts +162 -0
- package/src/react-native/sse/types.ts +9 -0
- package/src/react-native/useNetworkActivityDevTools.ts +73 -210
- 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 +86 -0
- package/src/shared/sse-events.ts +44 -0
- package/src/shared/websocket-events.ts +79 -0
- package/src/ui/App.tsx +19 -0
- package/src/ui/components/Badge.tsx +36 -0
- package/src/ui/components/Button.tsx +56 -0
- package/src/ui/components/Input.tsx +22 -0
- package/src/ui/components/JsonTree.tsx +50 -0
- package/src/ui/components/JsonTreeCopyableItem.tsx +33 -0
- package/src/ui/components/RequestList.tsx +295 -0
- package/src/ui/components/ScrollArea.tsx +48 -0
- package/src/ui/components/Separator.tsx +31 -0
- package/src/ui/components/SidePanel.tsx +323 -0
- package/src/ui/components/Tabs.tsx +55 -0
- package/src/ui/components/Toolbar.tsx +45 -0
- package/src/ui/globals.css +90 -0
- package/src/ui/hooks/useCopyToClipboard.ts +28 -0
- package/src/ui/state/derived.ts +112 -0
- package/src/ui/state/hooks.ts +44 -0
- package/src/ui/state/model.ts +129 -0
- package/src/ui/state/store.ts +559 -0
- package/src/ui/tabs/CookiesTab.tsx +279 -0
- package/src/ui/tabs/HeadersTab.tsx +110 -0
- package/src/ui/tabs/MessagesTab.tsx +276 -0
- package/src/ui/tabs/RequestTab.tsx +69 -0
- package/src/ui/tabs/ResponseTab.tsx +138 -0
- package/src/ui/tabs/SSEMessagesTab.tsx +213 -0
- package/src/ui/tabs/TimingTab.tsx +60 -0
- package/src/ui/types.ts +34 -0
- package/src/ui/utils/assert.ts +5 -0
- package/src/ui/utils/cn.ts +6 -0
- package/src/ui/utils/copyToClipboard.ts +3 -0
- package/src/ui/utils/getHttpHeaderValue.ts +14 -0
- package/src/ui/utils/getId.ts +10 -0
- package/src/ui/utils/getStatusColor.ts +15 -0
- package/src/ui/views/InspectorView.tsx +53 -0
- package/src/ui/views/LoadingView.tsx +19 -0
- package/tailwind.config.ts +96 -0
- package/tsconfig.json +13 -6
- package/tsconfig.tsbuildinfo +1 -0
- package/vite.config.ts +13 -1
- package/dist/assets/panel-C5YgUUj5.js +0 -54
- package/dist/assets/panel-NCVczPb1.css +0 -1
- package/src/types/network.ts +0 -153
- package/src/ui/components.module.css +0 -158
- package/src/ui/components.tsx +0 -219
- package/src/ui/network-details.module.css +0 -57
- package/src/ui/network-details.tsx +0 -134
- package/src/ui/network-list.module.css +0 -122
- package/src/ui/network-list.tsx +0 -145
- package/src/ui/network-toolbar.module.css +0 -9
- package/src/ui/network-toolbar.tsx +0 -40
- package/src/ui/panel.module.css +0 -61
- package/src/ui/panel.tsx +0 -201
- package/src/ui/tanstack-query.tsx +0 -197
- package/src/ui/utils.ts +0 -89
package/package.json
CHANGED
|
@@ -1,27 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rozenite/network-activity-plugin",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.10",
|
|
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
|
-
"
|
|
11
|
-
"@
|
|
12
|
-
"@rozenite/plugin-bridge": "1.0.0-alpha.0"
|
|
10
|
+
"nanoevents": "^9.1.0",
|
|
11
|
+
"@rozenite/plugin-bridge": "1.0.0-alpha.10"
|
|
13
12
|
},
|
|
14
13
|
"devDependencies": {
|
|
14
|
+
"@floating-ui/react": "^0.26.0",
|
|
15
|
+
"@radix-ui/react-scroll-area": "^1.2.9",
|
|
16
|
+
"@radix-ui/react-separator": "^1.1.7",
|
|
17
|
+
"@radix-ui/react-slot": "^1.2.3",
|
|
18
|
+
"@radix-ui/react-tabs": "^1.1.12",
|
|
19
|
+
"@tanstack/react-table": "^8.21.3",
|
|
20
|
+
"@tanstack/react-virtual": "^3.0.0",
|
|
21
|
+
"autoprefixer": "^10.4.21",
|
|
22
|
+
"class-variance-authority": "^0.7.1",
|
|
23
|
+
"clsx": "^2.1.1",
|
|
24
|
+
"lucide-react": "^0.263.1",
|
|
25
|
+
"postcss": "^8.5.6",
|
|
26
|
+
"proxy-memoize": "^3.0.1",
|
|
27
|
+
"react": "*",
|
|
28
|
+
"react-json-tree": "^0.20.0",
|
|
29
|
+
"react-native-sse": "^1.2.1",
|
|
30
|
+
"tailwind-merge": "^3.3.1",
|
|
31
|
+
"tailwindcss": "^3.4.17",
|
|
32
|
+
"tailwindcss-animate": "^1.0.7",
|
|
15
33
|
"typescript": "^5.7.3",
|
|
16
34
|
"vite": "^6.0.0",
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"rozenite": "1.0.0-alpha.
|
|
20
|
-
"@rozenite/vite-plugin": "1.0.0-alpha.1"
|
|
35
|
+
"zustand": "^5.0.6",
|
|
36
|
+
"@rozenite/vite-plugin": "1.0.0-alpha.10",
|
|
37
|
+
"rozenite": "1.0.0-alpha.10"
|
|
21
38
|
},
|
|
22
39
|
"peerDependencies": {
|
|
23
|
-
"react": "
|
|
24
|
-
|
|
40
|
+
"react-native-sse": "*"
|
|
41
|
+
},
|
|
42
|
+
"peerDependenciesMeta": {
|
|
43
|
+
"react-native-sse": {
|
|
44
|
+
"optional": true
|
|
45
|
+
}
|
|
25
46
|
},
|
|
26
47
|
"license": "MIT",
|
|
27
48
|
"scripts": {
|
package/project.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
3
|
+
"name": "@rozenite/network-activity-plugin",
|
|
4
|
+
"targets": {
|
|
5
|
+
"build": {
|
|
6
|
+
"cache": true,
|
|
7
|
+
"dependsOn": ["^build"],
|
|
8
|
+
"inputs": ["{projectRoot}/src/**/*"],
|
|
9
|
+
"outputs": ["{projectRoot}/dist"]
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
package/react-native.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export let useNetworkActivityDevTools: typeof import('./src/react-native/useNetworkActivityDevTools').useNetworkActivityDevTools;
|
|
2
2
|
|
|
3
3
|
if (process.env.NODE_ENV !== 'production') {
|
|
4
|
-
useNetworkActivityDevTools =
|
|
4
|
+
useNetworkActivityDevTools =
|
|
5
|
+
require('./src/react-native/useNetworkActivityDevTools').useNetworkActivityDevTools;
|
|
5
6
|
} else {
|
|
6
7
|
useNetworkActivityDevTools = () => null;
|
|
7
8
|
}
|
package/rozenite.config.ts
CHANGED
package/src/css-modules.d.ts
CHANGED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { HttpMethod, NetworkActivityDevToolsClient } from '../../shared/client';
|
|
2
|
+
import { getContentType } from '../utils';
|
|
3
|
+
import { getNetworkRequestsRegistry } from './network-requests-registry';
|
|
4
|
+
import { XHRInterceptor } from './xhr-interceptor';
|
|
5
|
+
|
|
6
|
+
const networkRequestsRegistry = getNetworkRequestsRegistry();
|
|
7
|
+
|
|
8
|
+
const getResponseSize = (request: XMLHttpRequest): number => {
|
|
9
|
+
if (typeof request.response === 'object') {
|
|
10
|
+
return request.response.size;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return request.response.length || 0;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const getResponseBody = async (
|
|
17
|
+
request: XMLHttpRequest
|
|
18
|
+
): Promise<string | null> => {
|
|
19
|
+
const responseType = request.responseType;
|
|
20
|
+
|
|
21
|
+
// Response type is empty in certain cases, like when using axios.
|
|
22
|
+
if (responseType === '' || responseType === 'text') {
|
|
23
|
+
return request.responseText as string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (responseType === 'blob') {
|
|
27
|
+
// This may be a text blob.
|
|
28
|
+
const contentType = request.getResponseHeader('Content-Type') || '';
|
|
29
|
+
|
|
30
|
+
if (
|
|
31
|
+
contentType.startsWith('text/') ||
|
|
32
|
+
contentType.startsWith('application/json')
|
|
33
|
+
) {
|
|
34
|
+
// It looks like a text blob, let's read it and forward it to the client.
|
|
35
|
+
return new Promise((resolve) => {
|
|
36
|
+
const reader = new FileReader();
|
|
37
|
+
reader.onload = () => {
|
|
38
|
+
resolve(reader.result as string);
|
|
39
|
+
};
|
|
40
|
+
reader.readAsText(request.response);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return null;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const getInitiatorFromStack = (): {
|
|
49
|
+
type: string;
|
|
50
|
+
url?: string;
|
|
51
|
+
lineNumber?: number;
|
|
52
|
+
columnNumber?: number;
|
|
53
|
+
} => {
|
|
54
|
+
try {
|
|
55
|
+
const stack = new Error().stack;
|
|
56
|
+
if (!stack) {
|
|
57
|
+
return { type: 'other' };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const line = stack.split('\n')[9];
|
|
61
|
+
const match = line.match(/at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/);
|
|
62
|
+
if (match) {
|
|
63
|
+
return {
|
|
64
|
+
type: 'script',
|
|
65
|
+
url: match[2],
|
|
66
|
+
lineNumber: parseInt(match[3]),
|
|
67
|
+
columnNumber: parseInt(match[4]),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
// Ignore stack parsing errors
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return { type: 'other' };
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export type NetworkInspector = {
|
|
78
|
+
enable: () => void;
|
|
79
|
+
disable: () => void;
|
|
80
|
+
isEnabled: () => boolean;
|
|
81
|
+
dispose: () => void;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const READY_STATE_HEADERS_RECEIVED = 2;
|
|
85
|
+
|
|
86
|
+
export const getNetworkInspector = (
|
|
87
|
+
pluginClient: NetworkActivityDevToolsClient
|
|
88
|
+
): NetworkInspector => {
|
|
89
|
+
const generateRequestId = (): string => {
|
|
90
|
+
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const handleRequestSend = (data: string, request: XMLHttpRequest): void => {
|
|
94
|
+
const sendTime = Date.now();
|
|
95
|
+
|
|
96
|
+
const requestId = generateRequestId();
|
|
97
|
+
request._rozeniteRequestId = requestId;
|
|
98
|
+
|
|
99
|
+
const initiator = getInitiatorFromStack();
|
|
100
|
+
|
|
101
|
+
networkRequestsRegistry.addEntry(requestId, request);
|
|
102
|
+
|
|
103
|
+
let ttfb = 0;
|
|
104
|
+
|
|
105
|
+
pluginClient.send('request-sent', {
|
|
106
|
+
requestId: requestId,
|
|
107
|
+
timestamp: sendTime,
|
|
108
|
+
request: {
|
|
109
|
+
url: request._url as string,
|
|
110
|
+
method: request._method as HttpMethod,
|
|
111
|
+
headers: request._headers,
|
|
112
|
+
postData: data,
|
|
113
|
+
},
|
|
114
|
+
type: 'XHR',
|
|
115
|
+
initiator,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
request.addEventListener('readystatechange', () => {
|
|
119
|
+
if (request.readyState === READY_STATE_HEADERS_RECEIVED) {
|
|
120
|
+
ttfb = Date.now() - sendTime;
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
request.addEventListener('load', () => {
|
|
125
|
+
pluginClient.send('response-received', {
|
|
126
|
+
requestId: requestId,
|
|
127
|
+
timestamp: Date.now(),
|
|
128
|
+
type: 'XHR',
|
|
129
|
+
response: {
|
|
130
|
+
url: request._url as string,
|
|
131
|
+
status: request.status,
|
|
132
|
+
statusText: request.statusText,
|
|
133
|
+
headers: request.responseHeaders || {},
|
|
134
|
+
contentType: getContentType(request),
|
|
135
|
+
size: getResponseSize(request),
|
|
136
|
+
responseTime: Date.now(),
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
request.addEventListener('loadend', () => {
|
|
142
|
+
pluginClient.send('request-completed', {
|
|
143
|
+
requestId: requestId,
|
|
144
|
+
timestamp: Date.now(),
|
|
145
|
+
duration: Date.now() - sendTime,
|
|
146
|
+
size: getResponseSize(request),
|
|
147
|
+
ttfb,
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
request.addEventListener('error', () => {
|
|
152
|
+
pluginClient.send('request-failed', {
|
|
153
|
+
requestId: requestId,
|
|
154
|
+
timestamp: Date.now(),
|
|
155
|
+
type: 'XHR',
|
|
156
|
+
error: 'Failed',
|
|
157
|
+
canceled: false,
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
request.addEventListener('abort', () => {
|
|
162
|
+
pluginClient.send('request-failed', {
|
|
163
|
+
requestId: requestId,
|
|
164
|
+
timestamp: Date.now(),
|
|
165
|
+
type: 'XHR',
|
|
166
|
+
error: 'Aborted',
|
|
167
|
+
canceled: true,
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const enable = () => {
|
|
173
|
+
XHRInterceptor.disableInterception();
|
|
174
|
+
XHRInterceptor.setSendCallback(handleRequestSend);
|
|
175
|
+
XHRInterceptor.enableInterception();
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const disable = () => {
|
|
179
|
+
XHRInterceptor.disableInterception();
|
|
180
|
+
networkRequestsRegistry.clear();
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const isEnabled = () => {
|
|
184
|
+
return XHRInterceptor.isInterceptorEnabled();
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const enableSubscription = pluginClient.onMessage('network-enable', () => {
|
|
188
|
+
enable();
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const disableSubscription = pluginClient.onMessage('network-disable', () => {
|
|
192
|
+
disable();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const handleBodySubscription = pluginClient.onMessage(
|
|
196
|
+
'get-response-body',
|
|
197
|
+
async ({ requestId }) => {
|
|
198
|
+
const request = networkRequestsRegistry.getEntry(requestId);
|
|
199
|
+
|
|
200
|
+
if (!request) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const body = await getResponseBody(request);
|
|
205
|
+
|
|
206
|
+
pluginClient.send('response-body', {
|
|
207
|
+
requestId,
|
|
208
|
+
body,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
const dispose = () => {
|
|
214
|
+
disable();
|
|
215
|
+
enableSubscription.remove();
|
|
216
|
+
disableSubscription.remove();
|
|
217
|
+
handleBodySubscription.remove();
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
enable,
|
|
222
|
+
disable,
|
|
223
|
+
isEnabled,
|
|
224
|
+
dispose,
|
|
225
|
+
};
|
|
226
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
type NetworkRegistryEntry = {
|
|
2
|
+
id: string;
|
|
3
|
+
sentAt: number;
|
|
4
|
+
request: XMLHttpRequest;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type NetworkRequestRegistry = {
|
|
8
|
+
addEntry: (id: string, request: XMLHttpRequest) => void;
|
|
9
|
+
getEntry: (id: string) => XMLHttpRequest | null;
|
|
10
|
+
clear: () => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const REQUEST_TTL = 1000 * 60 * 5; // 5 minutes
|
|
14
|
+
|
|
15
|
+
export const getNetworkRequestsRegistry = (): NetworkRequestRegistry => {
|
|
16
|
+
const registry: Map<string, NetworkRegistryEntry> = new Map();
|
|
17
|
+
|
|
18
|
+
const trimRegistry = (): void => {
|
|
19
|
+
const now = Date.now();
|
|
20
|
+
|
|
21
|
+
registry.forEach((entry) => {
|
|
22
|
+
if (now - entry.sentAt < REQUEST_TTL) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
registry.delete(entry.id);
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const addEntry = (id: string, request: XMLHttpRequest): void => {
|
|
31
|
+
trimRegistry();
|
|
32
|
+
registry.set(id, {
|
|
33
|
+
id,
|
|
34
|
+
request,
|
|
35
|
+
sentAt: Date.now(),
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const getEntry = (id: string): XMLHttpRequest | null => {
|
|
40
|
+
return registry.get(id)?.request ?? null;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const clear = () => {
|
|
44
|
+
registry.clear();
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
addEntry,
|
|
49
|
+
getEntry,
|
|
50
|
+
clear,
|
|
51
|
+
};
|
|
52
|
+
};
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/* eslint-disable prefer-rest-params */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
5
|
+
*
|
|
6
|
+
* This source code is licensed under the MIT license found in the
|
|
7
|
+
* LICENSE file in the root directory of this source tree.
|
|
8
|
+
*
|
|
9
|
+
* Source: https://github.com/facebook/react-native/blob/2c683c5787dd03ac15d2aad45dcc53650529ee7f/packages/react-native/src/private/devsupport/devmenu/elementinspector/XHRInterceptor.js
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const originalXHROpen = XMLHttpRequest.prototype.open;
|
|
13
|
+
const originalXHRSend = XMLHttpRequest.prototype.send;
|
|
14
|
+
const originalXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
|
|
15
|
+
|
|
16
|
+
type XHRInterceptorOpenCallback = (
|
|
17
|
+
method: string,
|
|
18
|
+
url: string,
|
|
19
|
+
request: XMLHttpRequest
|
|
20
|
+
) => void;
|
|
21
|
+
|
|
22
|
+
type XHRInterceptorSendCallback = (
|
|
23
|
+
data: string,
|
|
24
|
+
request: XMLHttpRequest
|
|
25
|
+
) => void;
|
|
26
|
+
|
|
27
|
+
type XHRInterceptorRequestHeaderCallback = (
|
|
28
|
+
header: string,
|
|
29
|
+
value: string,
|
|
30
|
+
request: XMLHttpRequest
|
|
31
|
+
) => void;
|
|
32
|
+
|
|
33
|
+
type XHRInterceptorHeaderReceivedCallback = (
|
|
34
|
+
responseContentType: string | undefined,
|
|
35
|
+
responseSize: number | undefined,
|
|
36
|
+
allHeaders: string,
|
|
37
|
+
request: XMLHttpRequest
|
|
38
|
+
) => void;
|
|
39
|
+
|
|
40
|
+
type XHRInterceptorResponseCallback = (
|
|
41
|
+
status: number,
|
|
42
|
+
timeout: number,
|
|
43
|
+
response: string,
|
|
44
|
+
responseURL: string,
|
|
45
|
+
responseType: string,
|
|
46
|
+
request: XMLHttpRequest
|
|
47
|
+
) => void;
|
|
48
|
+
|
|
49
|
+
let openCallback: XHRInterceptorOpenCallback | null;
|
|
50
|
+
let sendCallback: XHRInterceptorSendCallback | null;
|
|
51
|
+
let requestHeaderCallback: XHRInterceptorRequestHeaderCallback | null;
|
|
52
|
+
let headerReceivedCallback: XHRInterceptorHeaderReceivedCallback | null;
|
|
53
|
+
let responseCallback: XHRInterceptorResponseCallback | null;
|
|
54
|
+
|
|
55
|
+
let isInterceptorEnabled = false;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A network interceptor which monkey-patches XMLHttpRequest methods
|
|
59
|
+
* to gather all network requests/responses, in order to show their
|
|
60
|
+
* information in the React Native inspector development tool.
|
|
61
|
+
* This supports interception with XMLHttpRequest API, including Fetch API
|
|
62
|
+
* and any other third party libraries that depend on XMLHttpRequest.
|
|
63
|
+
*/
|
|
64
|
+
export const XHRInterceptor = {
|
|
65
|
+
/**
|
|
66
|
+
* Invoked before XMLHttpRequest.open(...) is called.
|
|
67
|
+
*/
|
|
68
|
+
setOpenCallback(callback: XHRInterceptorOpenCallback) {
|
|
69
|
+
openCallback = callback;
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Invoked before XMLHttpRequest.send(...) is called.
|
|
74
|
+
*/
|
|
75
|
+
setSendCallback(callback: XHRInterceptorSendCallback) {
|
|
76
|
+
sendCallback = callback;
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Invoked after xhr's readyState becomes xhr.HEADERS_RECEIVED.
|
|
81
|
+
*/
|
|
82
|
+
setHeaderReceivedCallback(callback: XHRInterceptorHeaderReceivedCallback) {
|
|
83
|
+
headerReceivedCallback = callback;
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Invoked after xhr's readyState becomes xhr.DONE.
|
|
88
|
+
*/
|
|
89
|
+
setResponseCallback(callback: XHRInterceptorResponseCallback) {
|
|
90
|
+
responseCallback = callback;
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Invoked before XMLHttpRequest.setRequestHeader(...) is called.
|
|
95
|
+
*/
|
|
96
|
+
setRequestHeaderCallback(callback: XHRInterceptorRequestHeaderCallback) {
|
|
97
|
+
requestHeaderCallback = callback;
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
isInterceptorEnabled(): boolean {
|
|
101
|
+
return isInterceptorEnabled;
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
enableInterception() {
|
|
105
|
+
if (isInterceptorEnabled) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Override `open` method for all XHR requests to intercept the request
|
|
109
|
+
// method and url, then pass them through the `openCallback`.
|
|
110
|
+
// $FlowFixMe[cannot-write]
|
|
111
|
+
// $FlowFixMe[missing-this-annot]
|
|
112
|
+
XMLHttpRequest.prototype.open = function (method: string, url: string) {
|
|
113
|
+
if (openCallback) {
|
|
114
|
+
openCallback(method, url, this);
|
|
115
|
+
}
|
|
116
|
+
// @ts-expect-error - Needed for the originalXHROpen to work
|
|
117
|
+
originalXHROpen.apply(this, arguments);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Override `setRequestHeader` method for all XHR requests to intercept
|
|
121
|
+
// the request headers, then pass them through the `requestHeaderCallback`.
|
|
122
|
+
// $FlowFixMe[cannot-write]
|
|
123
|
+
// $FlowFixMe[missing-this-annot]
|
|
124
|
+
XMLHttpRequest.prototype.setRequestHeader = function (
|
|
125
|
+
header: string,
|
|
126
|
+
value: string
|
|
127
|
+
) {
|
|
128
|
+
if (requestHeaderCallback) {
|
|
129
|
+
requestHeaderCallback(header, value, this);
|
|
130
|
+
}
|
|
131
|
+
// @ts-expect-error - Needed for the originalXHRSetRequestHeader to work
|
|
132
|
+
originalXHRSetRequestHeader.apply(this, arguments);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Override `send` method of all XHR requests to intercept the data sent,
|
|
136
|
+
// register listeners to intercept the response, and invoke the callbacks.
|
|
137
|
+
// $FlowFixMe[cannot-write]
|
|
138
|
+
// $FlowFixMe[missing-this-annot]
|
|
139
|
+
XMLHttpRequest.prototype.send = function (data: string) {
|
|
140
|
+
if (sendCallback) {
|
|
141
|
+
sendCallback(data, this);
|
|
142
|
+
}
|
|
143
|
+
if (this.addEventListener) {
|
|
144
|
+
this.addEventListener(
|
|
145
|
+
'readystatechange',
|
|
146
|
+
() => {
|
|
147
|
+
if (!isInterceptorEnabled) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (this.readyState === this.HEADERS_RECEIVED) {
|
|
151
|
+
const contentTypeString = this.getResponseHeader('Content-Type');
|
|
152
|
+
const contentLengthString =
|
|
153
|
+
this.getResponseHeader('Content-Length');
|
|
154
|
+
let responseContentType, responseSize;
|
|
155
|
+
if (contentTypeString) {
|
|
156
|
+
responseContentType = contentTypeString.split(';')[0];
|
|
157
|
+
}
|
|
158
|
+
if (contentLengthString) {
|
|
159
|
+
responseSize = parseInt(contentLengthString, 10);
|
|
160
|
+
}
|
|
161
|
+
if (headerReceivedCallback) {
|
|
162
|
+
headerReceivedCallback(
|
|
163
|
+
responseContentType,
|
|
164
|
+
responseSize,
|
|
165
|
+
this.getAllResponseHeaders(),
|
|
166
|
+
this
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (this.readyState === this.DONE) {
|
|
171
|
+
if (responseCallback) {
|
|
172
|
+
responseCallback(
|
|
173
|
+
this.status,
|
|
174
|
+
this.timeout,
|
|
175
|
+
this.response,
|
|
176
|
+
this.responseURL,
|
|
177
|
+
this.responseType,
|
|
178
|
+
this
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
false
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// @ts-expect-error - Needed for the originalXHRSend to work
|
|
188
|
+
originalXHRSend.apply(this, arguments);
|
|
189
|
+
};
|
|
190
|
+
isInterceptorEnabled = true;
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
// Unpatch XMLHttpRequest methods and remove the callbacks.
|
|
194
|
+
disableInterception() {
|
|
195
|
+
if (!isInterceptorEnabled) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
isInterceptorEnabled = false;
|
|
199
|
+
// $FlowFixMe[cannot-write]
|
|
200
|
+
XMLHttpRequest.prototype.send = originalXHRSend;
|
|
201
|
+
// $FlowFixMe[cannot-write]
|
|
202
|
+
XMLHttpRequest.prototype.open = originalXHROpen;
|
|
203
|
+
// $FlowFixMe[cannot-write]
|
|
204
|
+
XMLHttpRequest.prototype.setRequestHeader = originalXHRSetRequestHeader;
|
|
205
|
+
responseCallback = null;
|
|
206
|
+
openCallback = null;
|
|
207
|
+
sendCallback = null;
|
|
208
|
+
headerReceivedCallback = null;
|
|
209
|
+
requestHeaderCallback = null;
|
|
210
|
+
},
|
|
211
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
declare type BlobData = {
|
|
2
|
+
blobId: string;
|
|
3
|
+
lastModified?: number;
|
|
4
|
+
name?: string;
|
|
5
|
+
offset: number;
|
|
6
|
+
size: number;
|
|
7
|
+
type?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
declare global {
|
|
11
|
+
interface XMLHttpRequest {
|
|
12
|
+
_rozeniteRequestId: string;
|
|
13
|
+
_requestId?: number;
|
|
14
|
+
_subscriptions: Array<EventSubscription>;
|
|
15
|
+
_aborted: boolean;
|
|
16
|
+
_cachedResponse: Response;
|
|
17
|
+
_hasError: boolean;
|
|
18
|
+
_headers: { [key: string]: string };
|
|
19
|
+
_lowerCaseResponseHeaders: { [key: string]: string };
|
|
20
|
+
_method?: string | null;
|
|
21
|
+
_perfKey?: string | null;
|
|
22
|
+
_responseType: ResponseType;
|
|
23
|
+
_response: string | BlobData;
|
|
24
|
+
_sent: boolean;
|
|
25
|
+
_url?: string | null;
|
|
26
|
+
_timedOut: boolean;
|
|
27
|
+
_trackingName?: string;
|
|
28
|
+
_incrementalEvents: boolean;
|
|
29
|
+
_startTime?: number | null;
|
|
30
|
+
responseHeaders?: { [key: string]: string };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export {};
|
|
@@ -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
|
+
};
|