@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
package/package.json CHANGED
@@ -1,37 +1,51 @@
1
1
  {
2
2
  "name": "@rozenite/network-activity-plugin",
3
- "version": "1.0.0-alpha.8",
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
- "@rozenite/plugin-bridge": "1.0.0-alpha.8"
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
- "@floating-ui/react": "^0.26.0",
31
- "@tanstack/react-virtual": "^3.0.0",
32
- "lucide-react": "^0.263.1",
33
- "@rozenite/vite-plugin": "1.0.0-alpha.8",
34
- "rozenite": "1.0.0-alpha.8"
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
- if (process.env.NODE_ENV !== 'production') {
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: string,
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: string) {
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
  };
@@ -9,6 +9,7 @@ declare type BlobData = {
9
9
 
10
10
  declare global {
11
11
  interface XMLHttpRequest {
12
+ _rozeniteRequestId: string;
12
13
  _requestId?: number;
13
14
  _subscriptions: Array<EventSubscription>;
14
15
  _aborted: boolean;
@@ -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
+ };