@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
@@ -0,0 +1,139 @@
1
+ import { createNanoEvents } from 'nanoevents';
2
+ import { SSEInterceptor } from './sse-interceptor';
3
+ import { EventSourceWithInternals } from './types';
4
+ import { SSEEvent, SSEEventMap } from '../../shared/sse-events';
5
+ import { getContentType } from '../utils';
6
+
7
+ type NanoEventsMap = {
8
+ [K in keyof SSEEventMap]: (data: SSEEventMap[K]) => void;
9
+ };
10
+
11
+ export type SSEInspector = {
12
+ enable: () => void;
13
+ disable: () => void;
14
+ isEnabled: () => boolean;
15
+ dispose: () => void;
16
+ on: <TEventType extends keyof SSEEventMap>(
17
+ event: TEventType,
18
+ callback: (data: SSEEventMap[TEventType]) => void
19
+ ) => () => void;
20
+ };
21
+
22
+ export const getSSEInspector = (): SSEInspector => {
23
+ const eventEmitter = createNanoEvents<NanoEventsMap>();
24
+
25
+ const getRequestId = (
26
+ eventSource: EventSourceWithInternals
27
+ ): string | null => {
28
+ const requestId = eventSource._xhr?._rozeniteRequestId;
29
+
30
+ if (!requestId) {
31
+ // It means that the EventSource was created before the inspector was enabled.
32
+ return null;
33
+ }
34
+
35
+ return requestId;
36
+ };
37
+
38
+ return {
39
+ enable: () => {
40
+ SSEInterceptor.setOpenEventCallback((_, eventSource) => {
41
+ const sseEventSource = eventSource as EventSourceWithInternals;
42
+ const requestId = getRequestId(sseEventSource);
43
+
44
+ if (!requestId) {
45
+ return;
46
+ }
47
+
48
+ const sseXhr = sseEventSource._xhr as XMLHttpRequest;
49
+
50
+ const event: SSEEvent = {
51
+ type: 'sse-open',
52
+ requestId,
53
+ timestamp: Date.now(),
54
+ response: {
55
+ url: sseXhr._url as string,
56
+ status: sseXhr.status,
57
+ statusText: sseXhr.statusText,
58
+ headers: sseXhr.responseHeaders || {},
59
+ contentType: getContentType(sseXhr),
60
+ size: 0,
61
+ responseTime: Date.now(),
62
+ },
63
+ };
64
+ eventEmitter.emit('sse-open', event);
65
+ });
66
+
67
+ SSEInterceptor.setMessageCallback((messageEvent, eventSource) => {
68
+ const sseEventSource = eventSource as EventSourceWithInternals;
69
+ const requestId = getRequestId(sseEventSource);
70
+
71
+ if (!requestId) {
72
+ return;
73
+ }
74
+
75
+ const event: SSEEvent = {
76
+ type: 'sse-message',
77
+ requestId,
78
+ timestamp: Date.now(),
79
+ payload: {
80
+ type: messageEvent.type,
81
+ data: messageEvent.data || '',
82
+ },
83
+ };
84
+ eventEmitter.emit('sse-message', event);
85
+ });
86
+
87
+ SSEInterceptor.setErrorCallback((errorEvent, eventSource) => {
88
+ const sseEventSource = eventSource as EventSourceWithInternals;
89
+ const requestId = getRequestId(sseEventSource);
90
+
91
+ if (!requestId) {
92
+ return;
93
+ }
94
+
95
+ const event: SSEEvent = {
96
+ type: 'sse-error',
97
+ requestId,
98
+ timestamp: Date.now(),
99
+ error: {
100
+ type: errorEvent.type,
101
+ message:
102
+ errorEvent.type === 'timeout' ? 'Timeout' : errorEvent.message,
103
+ },
104
+ };
105
+ eventEmitter.emit('sse-error', event);
106
+ });
107
+
108
+ SSEInterceptor.setCloseCallback((_, eventSource) => {
109
+ const sseEventSource = eventSource as EventSourceWithInternals;
110
+ const requestId = getRequestId(sseEventSource);
111
+
112
+ if (!requestId) {
113
+ return;
114
+ }
115
+
116
+ const event: SSEEvent = {
117
+ type: 'sse-close',
118
+ requestId,
119
+ timestamp: Date.now(),
120
+ };
121
+ eventEmitter.emit('sse-close', event);
122
+ });
123
+
124
+ SSEInterceptor.enableInterception();
125
+ },
126
+ disable: () => {
127
+ SSEInterceptor.disableInterception();
128
+ },
129
+ isEnabled: () => SSEInterceptor.isInterceptorEnabled(),
130
+ dispose: () => {
131
+ SSEInterceptor.disableInterception();
132
+ eventEmitter.events = {};
133
+ },
134
+ on: <TEventType extends keyof SSEEventMap>(
135
+ event: TEventType,
136
+ callback: (data: SSEEventMap[TEventType]) => void
137
+ ) => eventEmitter.on(event, callback as NanoEventsMap[TEventType]),
138
+ };
139
+ };
@@ -0,0 +1,180 @@
1
+ import type EventSource from 'react-native-sse';
2
+ import type {
3
+ MessageEvent,
4
+ ErrorEvent,
5
+ OpenEvent,
6
+ CloseEvent,
7
+ TimeoutEvent,
8
+ ExceptionEvent,
9
+ EventSourceEvent,
10
+ CustomEvent,
11
+ } from 'react-native-sse';
12
+ import { EventSourceWithInternals } from './types';
13
+ import { getEventSource } from './event-source';
14
+
15
+ export type SSEInterceptorConnectCallback = (
16
+ url: string,
17
+ request: EventSource
18
+ ) => void;
19
+
20
+ export type SSEInterceptorMessageCallback = (
21
+ event: MessageEvent | CustomEvent<string>,
22
+ request: EventSource
23
+ ) => void;
24
+
25
+ export type SSEInterceptorErrorCallback = (
26
+ error: ErrorEvent | TimeoutEvent | ExceptionEvent,
27
+ request: EventSource
28
+ ) => void;
29
+
30
+ export type SSEInterceptorOpenEventCallback = (
31
+ event: OpenEvent,
32
+ request: EventSource
33
+ ) => void;
34
+
35
+ export type SSEInterceptorCloseCallback = (
36
+ event: CloseEvent,
37
+ request: EventSource
38
+ ) => void;
39
+
40
+ let connectCallback: SSEInterceptorConnectCallback | null;
41
+ let messageCallback: SSEInterceptorMessageCallback | null;
42
+ let errorCallback: SSEInterceptorErrorCallback | null;
43
+ let openEventCallback: SSEInterceptorOpenEventCallback | null;
44
+ let closeCallback: SSEInterceptorCloseCallback | null;
45
+
46
+ let isInterceptorEnabled = false;
47
+
48
+ const eventSourceClass = getEventSource();
49
+
50
+ // Store original EventSource methods
51
+ const originalOpen = eventSourceClass.prototype.open;
52
+ const originalDispatch = eventSourceClass.prototype.dispatch;
53
+
54
+ // Built-in SSE event types that we don't want to capture as messages
55
+ const BUILT_IN_EVENT_TYPES = new Set(['open', 'error', 'close', 'done']);
56
+
57
+ /**
58
+ * A network interceptor which monkey-patches EventSource open method
59
+ * to gather all SSE connections and events, in order to show their
60
+ * information in the Network Activity panel.
61
+ */
62
+ export const SSEInterceptor = {
63
+ /**
64
+ * Invoked when EventSource.open() is called (connection attempt starting).
65
+ */
66
+ setConnectCallback(callback: SSEInterceptorConnectCallback) {
67
+ connectCallback = callback;
68
+ },
69
+
70
+ /**
71
+ * Invoked when a message event is received.
72
+ */
73
+ setMessageCallback(callback: SSEInterceptorMessageCallback) {
74
+ messageCallback = callback;
75
+ },
76
+
77
+ /**
78
+ * Invoked when an error event occurs.
79
+ */
80
+ setErrorCallback(callback: SSEInterceptorErrorCallback) {
81
+ errorCallback = callback;
82
+ },
83
+
84
+ /**
85
+ * Invoked when the connection is successfully opened (open event fired).
86
+ */
87
+ setOpenEventCallback(callback: SSEInterceptorOpenEventCallback) {
88
+ openEventCallback = callback;
89
+ },
90
+
91
+ /**
92
+ * Invoked when the connection is closed.
93
+ */
94
+ setCloseCallback(callback: SSEInterceptorCloseCallback) {
95
+ closeCallback = callback;
96
+ },
97
+
98
+ isInterceptorEnabled(): boolean {
99
+ return isInterceptorEnabled;
100
+ },
101
+
102
+ enableInterception() {
103
+ if (isInterceptorEnabled) {
104
+ return;
105
+ }
106
+
107
+ // Override EventSource open method to intercept SSE connections
108
+ eventSourceClass.prototype.open = function (
109
+ this: EventSourceWithInternals
110
+ ) {
111
+ // Invoke connect callback
112
+ if (connectCallback) {
113
+ connectCallback(this.url, this);
114
+ }
115
+
116
+ // Add event listeners to intercept all events
117
+ this.addEventListener('open', (event: OpenEvent) => {
118
+ if (openEventCallback) {
119
+ openEventCallback(event, this);
120
+ }
121
+ });
122
+
123
+ this.addEventListener(
124
+ 'error',
125
+ (event: ErrorEvent | TimeoutEvent | ExceptionEvent) => {
126
+ if (errorCallback) {
127
+ errorCallback(event, this);
128
+ }
129
+ }
130
+ );
131
+
132
+ this.addEventListener('close', (event: CloseEvent) => {
133
+ if (closeCallback) {
134
+ closeCallback(event, this);
135
+ }
136
+ });
137
+
138
+ // Call original open method
139
+ return originalOpen.call(this);
140
+ };
141
+
142
+ eventSourceClass.prototype.dispatch = function (
143
+ this: EventSourceWithInternals,
144
+ eventType: string,
145
+ data: EventSourceEvent<string>
146
+ ) {
147
+ if (!BUILT_IN_EVENT_TYPES.has(eventType)) {
148
+ if (messageCallback) {
149
+ messageCallback(data, this);
150
+ }
151
+ }
152
+
153
+ // Call original open method
154
+ return originalDispatch.call(this, eventType, data);
155
+ };
156
+
157
+ isInterceptorEnabled = true;
158
+ },
159
+
160
+ // Unpatch EventSource open method and remove the callbacks.
161
+ disableInterception() {
162
+ if (!isInterceptorEnabled) {
163
+ return;
164
+ }
165
+ isInterceptorEnabled = false;
166
+
167
+ // Restore original open method
168
+ eventSourceClass.prototype.open = originalOpen;
169
+
170
+ // Restore original dispatch method
171
+ eventSourceClass.prototype.dispatch = originalDispatch;
172
+
173
+ // Clear callbacks
174
+ connectCallback = null;
175
+ messageCallback = null;
176
+ errorCallback = null;
177
+ openEventCallback = null;
178
+ closeCallback = null;
179
+ },
180
+ };
@@ -0,0 +1,9 @@
1
+ import type EventSource from 'react-native-sse';
2
+
3
+ export interface EventSourceWithInternals<E extends string = never>
4
+ extends EventSource<E> {
5
+ url: string;
6
+
7
+ /** Used internally to mark the underlying XHR to skip it in XHR interceptor. */
8
+ _xhr?: XMLHttpRequest;
9
+ }
@@ -1,24 +1,176 @@
1
- import { useEffect } from 'react';
1
+ import { useEffect, useRef } from 'react';
2
2
  import { useRozeniteDevToolsClient } from '@rozenite/plugin-bridge';
3
- import { getNetworkInspector } from './network-inspector';
3
+ import { getNetworkInspector } from './http/network-inspector';
4
+ import { getOverridesRegistry } from './http/overrides-registry';
4
5
  import { NetworkActivityEventMap } from '../shared/client';
6
+ import { getWebSocketInspector } from './websocket/websocket-inspector';
7
+ import { WebSocketEventMap } from '../shared/websocket-events';
8
+ import { UnionToTuple } from './utils';
9
+ import { getSSEInspector } from './sse/sse-inspector';
10
+ import { SSEEventMap } from '../shared/sse-events';
11
+ import {
12
+ DEFAULT_CONFIG,
13
+ NetworkActivityDevToolsConfig,
14
+ validateConfig,
15
+ } from './config';
5
16
 
6
- export const useNetworkActivityDevTools = () => {
17
+ const overridesRegistry = getOverridesRegistry();
18
+
19
+ export const useNetworkActivityDevTools = (
20
+ config: NetworkActivityDevToolsConfig = DEFAULT_CONFIG
21
+ ) => {
22
+ const isRecordingEnabledRef = useRef(false);
7
23
  const client = useRozeniteDevToolsClient<NetworkActivityEventMap>({
8
24
  pluginId: '@rozenite/network-activity-plugin',
9
25
  });
10
26
 
27
+ const isHttpInspectorEnabled = config.inspectors?.http ?? true;
28
+ const isWebSocketInspectorEnabled = config.inspectors?.websocket ?? true;
29
+ const isSSEInspectorEnabled = config.inspectors?.sse ?? true;
30
+ const showUrlAsName = config.clientUISettings?.showUrlAsName;
31
+
32
+ useEffect(() => {
33
+ if (!client) {
34
+ return;
35
+ }
36
+
37
+ validateConfig(config);
38
+ }, [config]);
39
+
40
+ /** Persist the recording state across hot reloads */
11
41
  useEffect(() => {
12
42
  if (!client) {
13
43
  return;
14
44
  }
15
45
 
46
+
47
+ const sendClientUISettings = () => {
48
+ client.send('client-ui-settings', {
49
+ settings: {
50
+ showUrlAsName: showUrlAsName ?? DEFAULT_CONFIG.clientUISettings?.showUrlAsName,
51
+ },
52
+ });
53
+ }
54
+
55
+ const subscriptions = [
56
+ client.onMessage('network-enable', () => {
57
+ isRecordingEnabledRef.current = true;
58
+ }),
59
+ client.onMessage('network-disable', () => {
60
+ isRecordingEnabledRef.current = false;
61
+ }),
62
+ client.onMessage('set-overrides', (data) => {
63
+ overridesRegistry.setOverrides(data.overrides);
64
+ }),
65
+
66
+ client.onMessage('get-client-ui-settings', () => {
67
+ sendClientUISettings();
68
+ }),
69
+ ];
70
+
71
+ // Send initial or changed values live
72
+ sendClientUISettings();
73
+
74
+ return () => {
75
+ subscriptions.forEach((subscription) => subscription.remove());
76
+ };
77
+ }, [client, showUrlAsName]);
78
+
79
+ useEffect(() => {
80
+ if (!client || !isHttpInspectorEnabled) {
81
+ return;
82
+ }
83
+
16
84
  const networkInspector = getNetworkInspector(client);
17
85
 
86
+ // If recording was previously enabled, enable the inspector (hot reload)
87
+ if (isRecordingEnabledRef.current) {
88
+ networkInspector.enable();
89
+ }
90
+
18
91
  return () => {
19
92
  networkInspector.dispose();
20
93
  };
21
- }, [client]);
94
+ }, [client, isHttpInspectorEnabled]);
95
+
96
+ useEffect(() => {
97
+ if (!client || !isWebSocketInspectorEnabled) {
98
+ return;
99
+ }
100
+
101
+ const eventsToForward: UnionToTuple<keyof WebSocketEventMap> = [
102
+ 'websocket-connect',
103
+ 'websocket-open',
104
+ 'websocket-close',
105
+ 'websocket-message-sent',
106
+ 'websocket-message-received',
107
+ 'websocket-error',
108
+ 'websocket-connection-status-changed',
109
+ ];
110
+ const websocketInspector = getWebSocketInspector();
111
+
112
+ eventsToForward.forEach((event) => {
113
+ websocketInspector.on(event, (event) => {
114
+ client.send(event.type, event);
115
+ });
116
+ });
117
+
118
+ client.onMessage('network-enable', () => {
119
+ websocketInspector.enable();
120
+ });
121
+
122
+ client.onMessage('network-disable', () => {
123
+ websocketInspector.disable();
124
+ });
125
+
126
+ // If recording was previously enabled, enable the inspector (hot reload)
127
+ if (isRecordingEnabledRef.current) {
128
+ websocketInspector.enable();
129
+ }
130
+
131
+ return () => {
132
+ // Subscriptions will be disposed by the inspector
133
+ websocketInspector.dispose();
134
+ };
135
+ }, [client, isWebSocketInspectorEnabled]);
136
+
137
+ useEffect(() => {
138
+ if (!client || !isSSEInspectorEnabled) {
139
+ return;
140
+ }
141
+
142
+ const eventsToForward: UnionToTuple<keyof SSEEventMap> = [
143
+ 'sse-open',
144
+ 'sse-message',
145
+ 'sse-error',
146
+ 'sse-close',
147
+ ];
148
+ const sseInspector = getSSEInspector();
149
+
150
+ eventsToForward.forEach((event) => {
151
+ sseInspector.on(event, (event) => {
152
+ client.send(event.type, event);
153
+ });
154
+ });
155
+
156
+ client.onMessage('network-enable', () => {
157
+ sseInspector.enable();
158
+ });
159
+
160
+ client.onMessage('network-disable', () => {
161
+ sseInspector.disable();
162
+ });
163
+
164
+ // If recording was previously enabled, enable the inspector (hot reload)
165
+ if (isRecordingEnabledRef.current) {
166
+ sseInspector.enable();
167
+ }
168
+
169
+ return () => {
170
+ // Subscriptions will be disposed by the inspector
171
+ sseInspector.dispose();
172
+ };
173
+ }, [client, isSSEInspectorEnabled]);
22
174
 
23
175
  return client;
24
176
  };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Utility function to get the name of a blob. Handles both the direct name property and the data object.
3
+ *
4
+ * ```
5
+ * // node_modules/react-native/Libraries/Blob/Blob.js
6
+ *
7
+ * export type BlobData = {
8
+ * blobId: string,
9
+ * offset: number,
10
+ * size: number,
11
+ * name?: string,
12
+ * type?: string,
13
+ * lastModified?: number,
14
+ * __collector?: ?BlobCollector,
15
+ * ...
16
+ * };
17
+ *
18
+ * get data(): BlobData {
19
+ * if (!this._data) {
20
+ * throw new Error('Blob has been closed and is no longer available');
21
+ * }
22
+ *
23
+ * return this._data;
24
+ * }
25
+ *
26
+ * get size(): number {
27
+ * return this.data.size;
28
+ * }
29
+ *
30
+ * get type(): string {
31
+ * return this.data.type || '';
32
+ * }
33
+ * ```
34
+ */
35
+ export function getBlobName(blob: any): string | undefined {
36
+ if (typeof blob?.name === 'string') {
37
+ return blob.name;
38
+ }
39
+
40
+ if (blob?.data && typeof blob.data.name === 'string') {
41
+ return blob.data.name;
42
+ }
43
+
44
+ return undefined;
45
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Extracts form data parts from a FormData object.
3
+ * Handles both the standard FormData API and the React Native FormData format.
4
+ *
5
+ * ```
6
+ * // node_modules/react-native/Libraries/Network/FormData.js
7
+ *
8
+ * class FormData {
9
+ * _parts: Array<FormDataNameValuePair>;
10
+ *
11
+ * constructor() {
12
+ * this._parts = [];
13
+ * }
14
+ *
15
+ * ...
16
+ * ```
17
+ */
18
+ export function getFormDataEntries(formData: any): [string, unknown][] {
19
+ if (!formData || typeof formData !== 'object') {
20
+ return [];
21
+ }
22
+
23
+ if (typeof formData.entries === 'function') {
24
+ return formData.entries();
25
+ }
26
+
27
+ if (Array.isArray(formData._parts)) {
28
+ return formData._parts;
29
+ }
30
+
31
+ return [];
32
+ }
@@ -0,0 +1,43 @@
1
+ import { getContentTypeMime } from '../utils/getContentTypeMimeType';
2
+
3
+ type UnionToIntersection<U> = (
4
+ U extends unknown ? (k: U) => void : never
5
+ ) extends (k: infer I) => void
6
+ ? I
7
+ : never;
8
+
9
+ type LastOf<T> = UnionToIntersection<
10
+ T extends unknown ? () => T : never
11
+ > extends () => infer R
12
+ ? R
13
+ : never;
14
+
15
+ type Push<T extends unknown[], V> = [...T, V];
16
+
17
+ export type UnionToTuple<T, L = LastOf<T>> = [T] extends [never]
18
+ ? []
19
+ : Push<UnionToTuple<Exclude<T, L>>, L>;
20
+
21
+ export const getContentType = (request: XMLHttpRequest): string => {
22
+ const responseHeaders = request.responseHeaders;
23
+ const responseType = request.responseType;
24
+
25
+ const contentType = getContentTypeMime(responseHeaders || {});
26
+
27
+ if (contentType) {
28
+ return contentType;
29
+ }
30
+
31
+ switch (responseType) {
32
+ case 'arraybuffer':
33
+ case 'blob':
34
+ return 'application/octet-stream';
35
+ case 'text':
36
+ case '':
37
+ return 'text/plain';
38
+ case 'json':
39
+ return 'application/json';
40
+ case 'document':
41
+ return 'text/html';
42
+ }
43
+ };