@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.
Files changed (134) hide show
  1. package/README.md +3 -5
  2. package/dist/{panel.html → App.html} +3 -3
  3. package/dist/assets/App-CA1Fbh0I.js +25364 -0
  4. package/dist/assets/App-DoHQsY5s.css +1276 -0
  5. package/dist/event-source.cjs +22 -0
  6. package/dist/event-source.js +23 -0
  7. package/dist/react-native.cjs +8 -1
  8. package/dist/react-native.d.ts +1 -5
  9. package/dist/react-native.js +6 -171
  10. package/dist/rozenite.config.d.ts +7 -0
  11. package/dist/rozenite.json +1 -1
  12. package/dist/src/react-native/http/network-inspector.d.ts +8 -0
  13. package/dist/src/react-native/http/network-requests-registry.d.ts +6 -0
  14. package/dist/src/react-native/http/xhr-interceptor.d.ts +38 -0
  15. package/dist/src/react-native/sse/event-source.d.ts +2 -0
  16. package/dist/src/react-native/sse/sse-inspector.d.ts +9 -0
  17. package/dist/src/react-native/sse/sse-interceptor.d.ts +36 -0
  18. package/dist/src/react-native/sse/types.d.ts +6 -0
  19. package/dist/src/react-native/useNetworkActivityDevTools.d.ts +2 -0
  20. package/dist/src/react-native/utils.d.ts +6 -0
  21. package/dist/src/react-native/websocket/websocket-inspector.d.ts +9 -0
  22. package/dist/src/react-native/websocket/websocket-interceptor.d.ts +74 -0
  23. package/dist/src/shared/client.d.ts +68 -0
  24. package/dist/src/shared/sse-events.d.ts +35 -0
  25. package/dist/src/shared/websocket-events.d.ts +60 -0
  26. package/dist/src/ui/App.d.ts +1 -0
  27. package/dist/src/ui/components/Badge.d.ts +9 -0
  28. package/dist/src/ui/components/Button.d.ts +11 -0
  29. package/dist/src/ui/components/Input.d.ts +3 -0
  30. package/dist/src/ui/components/JsonTree.d.ts +5 -0
  31. package/dist/src/ui/components/JsonTreeCopyableItem.d.ts +7 -0
  32. package/dist/src/ui/components/RequestList.d.ts +25 -0
  33. package/dist/src/ui/components/ScrollArea.d.ts +4 -0
  34. package/dist/src/ui/components/Separator.d.ts +3 -0
  35. package/dist/src/ui/components/SidePanel.d.ts +1 -0
  36. package/dist/src/ui/components/Toolbar.d.ts +1 -0
  37. package/dist/src/ui/hooks/useCopyToClipboard.d.ts +4 -0
  38. package/dist/src/ui/state/derived.d.ts +5 -0
  39. package/dist/src/ui/state/hooks.d.ts +17 -0
  40. package/dist/src/ui/state/model.d.ts +98 -0
  41. package/dist/src/ui/state/store.d.ts +24 -0
  42. package/dist/src/ui/tabs/CookiesTab.d.ts +5 -0
  43. package/dist/src/ui/tabs/HeadersTab.d.ts +5 -0
  44. package/dist/src/ui/tabs/MessagesTab.d.ts +5 -0
  45. package/dist/src/ui/tabs/RequestTab.d.ts +5 -0
  46. package/dist/src/ui/tabs/ResponseTab.d.ts +6 -0
  47. package/dist/src/ui/tabs/SSEMessagesTab.d.ts +5 -0
  48. package/dist/src/ui/tabs/TimingTab.d.ts +5 -0
  49. package/dist/src/ui/types.d.ts +26 -0
  50. package/dist/src/ui/utils/assert.d.ts +1 -0
  51. package/dist/src/ui/utils/cn.d.ts +2 -0
  52. package/dist/src/ui/utils/copyToClipboard.d.ts +1 -0
  53. package/dist/src/ui/utils/getHttpHeaderValue.d.ts +2 -0
  54. package/dist/src/ui/utils/getId.d.ts +1 -0
  55. package/dist/src/ui/utils/getStatusColor.d.ts +1 -0
  56. package/dist/src/ui/views/InspectorView.d.ts +5 -0
  57. package/dist/src/ui/views/LoadingView.d.ts +1 -0
  58. package/dist/useNetworkActivityDevTools.cjs +759 -0
  59. package/dist/useNetworkActivityDevTools.js +757 -0
  60. package/package.json +31 -10
  61. package/postcss.config.js +6 -0
  62. package/project.json +12 -0
  63. package/react-native.ts +2 -1
  64. package/rozenite.config.ts +2 -2
  65. package/src/css-modules.d.ts +1 -1
  66. package/src/react-native/http/network-inspector.ts +226 -0
  67. package/src/react-native/http/network-requests-registry.ts +52 -0
  68. package/src/react-native/http/xhr-interceptor.ts +211 -0
  69. package/src/react-native/http/xml-request.d.ts +34 -0
  70. package/src/react-native/sse/event-source.ts +25 -0
  71. package/src/react-native/sse/sse-inspector.ts +117 -0
  72. package/src/react-native/sse/sse-interceptor.ts +162 -0
  73. package/src/react-native/sse/types.ts +9 -0
  74. package/src/react-native/useNetworkActivityDevTools.ts +73 -210
  75. package/src/react-native/utils.ts +43 -0
  76. package/src/react-native/websocket/websocket-inspector.ts +180 -0
  77. package/src/react-native/websocket/websocket-interceptor.d.ts +4 -0
  78. package/src/react-native/websocket/websocket-interceptor.ts +166 -0
  79. package/src/shared/client.ts +86 -0
  80. package/src/shared/sse-events.ts +44 -0
  81. package/src/shared/websocket-events.ts +79 -0
  82. package/src/ui/App.tsx +19 -0
  83. package/src/ui/components/Badge.tsx +36 -0
  84. package/src/ui/components/Button.tsx +56 -0
  85. package/src/ui/components/Input.tsx +22 -0
  86. package/src/ui/components/JsonTree.tsx +50 -0
  87. package/src/ui/components/JsonTreeCopyableItem.tsx +33 -0
  88. package/src/ui/components/RequestList.tsx +295 -0
  89. package/src/ui/components/ScrollArea.tsx +48 -0
  90. package/src/ui/components/Separator.tsx +31 -0
  91. package/src/ui/components/SidePanel.tsx +323 -0
  92. package/src/ui/components/Tabs.tsx +55 -0
  93. package/src/ui/components/Toolbar.tsx +45 -0
  94. package/src/ui/globals.css +90 -0
  95. package/src/ui/hooks/useCopyToClipboard.ts +28 -0
  96. package/src/ui/state/derived.ts +112 -0
  97. package/src/ui/state/hooks.ts +44 -0
  98. package/src/ui/state/model.ts +129 -0
  99. package/src/ui/state/store.ts +559 -0
  100. package/src/ui/tabs/CookiesTab.tsx +279 -0
  101. package/src/ui/tabs/HeadersTab.tsx +110 -0
  102. package/src/ui/tabs/MessagesTab.tsx +276 -0
  103. package/src/ui/tabs/RequestTab.tsx +69 -0
  104. package/src/ui/tabs/ResponseTab.tsx +138 -0
  105. package/src/ui/tabs/SSEMessagesTab.tsx +213 -0
  106. package/src/ui/tabs/TimingTab.tsx +60 -0
  107. package/src/ui/types.ts +34 -0
  108. package/src/ui/utils/assert.ts +5 -0
  109. package/src/ui/utils/cn.ts +6 -0
  110. package/src/ui/utils/copyToClipboard.ts +3 -0
  111. package/src/ui/utils/getHttpHeaderValue.ts +14 -0
  112. package/src/ui/utils/getId.ts +10 -0
  113. package/src/ui/utils/getStatusColor.ts +15 -0
  114. package/src/ui/views/InspectorView.tsx +53 -0
  115. package/src/ui/views/LoadingView.tsx +19 -0
  116. package/tailwind.config.ts +96 -0
  117. package/tsconfig.json +13 -6
  118. package/tsconfig.tsbuildinfo +1 -0
  119. package/vite.config.ts +13 -1
  120. package/dist/assets/panel-C5YgUUj5.js +0 -54
  121. package/dist/assets/panel-NCVczPb1.css +0 -1
  122. package/src/types/network.ts +0 -153
  123. package/src/ui/components.module.css +0 -158
  124. package/src/ui/components.tsx +0 -219
  125. package/src/ui/network-details.module.css +0 -57
  126. package/src/ui/network-details.tsx +0 -134
  127. package/src/ui/network-list.module.css +0 -122
  128. package/src/ui/network-list.tsx +0 -145
  129. package/src/ui/network-toolbar.module.css +0 -9
  130. package/src/ui/network-toolbar.tsx +0 -40
  131. package/src/ui/panel.module.css +0 -61
  132. package/src/ui/panel.tsx +0 -201
  133. package/src/ui/tanstack-query.tsx +0 -197
  134. 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.1",
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
- "@tanstack/react-virtual": "^3.0.0",
11
- "@floating-ui/react": "^0.26.0",
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
- "react": "*",
18
- "react-native": "*",
19
- "rozenite": "1.0.0-alpha.2",
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": ">= 17.0.0",
24
- "react-native": ">= 0.74.0"
40
+ "react-native-sse": "*"
41
+ },
42
+ "peerDependenciesMeta": {
43
+ "react-native-sse": {
44
+ "optional": true
45
+ }
25
46
  },
26
47
  "license": "MIT",
27
48
  "scripts": {
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ };
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 = require('./src/react-native/useNetworkActivityDevTools').useNetworkActivityDevTools;
4
+ useNetworkActivityDevTools =
5
+ require('./src/react-native/useNetworkActivityDevTools').useNetworkActivityDevTools;
5
6
  } else {
6
7
  useNetworkActivityDevTools = () => null;
7
8
  }
@@ -2,7 +2,7 @@ export default {
2
2
  panels: [
3
3
  {
4
4
  name: 'Network Activity',
5
- source: './src/ui/panel.tsx',
6
- }
5
+ source: './src/ui/App.tsx',
6
+ },
7
7
  ],
8
8
  };
@@ -1,4 +1,4 @@
1
1
  declare module '*.module.css' {
2
2
  const classes: { [key: string]: string };
3
3
  export default classes;
4
- }
4
+ }
@@ -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
+ };