@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
@@ -0,0 +1,117 @@
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 = (eventSource: EventSourceWithInternals): string => {
26
+ const requestId = eventSource._xhr?._rozeniteRequestId;
27
+
28
+ if (!requestId) {
29
+ throw new Error(
30
+ 'No request ID found for EventSource. This should never happen!'
31
+ );
32
+ }
33
+
34
+ return requestId;
35
+ };
36
+
37
+ return {
38
+ enable: () => {
39
+ SSEInterceptor.setOpenEventCallback((_, eventSource) => {
40
+ const sseEventSource = eventSource as EventSourceWithInternals;
41
+ const requestId = getRequestId(sseEventSource);
42
+ const sseXhr = sseEventSource._xhr as XMLHttpRequest;
43
+
44
+ const event: SSEEvent = {
45
+ type: 'sse-open',
46
+ requestId,
47
+ timestamp: Date.now(),
48
+ response: {
49
+ url: sseXhr._url as string,
50
+ status: sseXhr.status,
51
+ statusText: sseXhr.statusText,
52
+ headers: sseXhr.responseHeaders || {},
53
+ contentType: getContentType(sseXhr),
54
+ size: 0,
55
+ responseTime: Date.now(),
56
+ },
57
+ };
58
+ eventEmitter.emit('sse-open', event);
59
+ });
60
+
61
+ SSEInterceptor.setMessageCallback((messageEvent, eventSource) => {
62
+ const sseEventSource = eventSource as EventSourceWithInternals;
63
+ const requestId = getRequestId(sseEventSource);
64
+
65
+ const event: SSEEvent = {
66
+ type: 'sse-message',
67
+ requestId,
68
+ timestamp: Date.now(),
69
+ data: messageEvent.data || '',
70
+ };
71
+ eventEmitter.emit('sse-message', event);
72
+ });
73
+
74
+ SSEInterceptor.setErrorCallback((errorEvent, eventSource) => {
75
+ const sseEventSource = eventSource as EventSourceWithInternals;
76
+ const requestId = getRequestId(sseEventSource);
77
+
78
+ const event: SSEEvent = {
79
+ type: 'sse-error',
80
+ requestId,
81
+ timestamp: Date.now(),
82
+ error: {
83
+ type: errorEvent.type,
84
+ message:
85
+ errorEvent.type === 'timeout' ? 'Timeout' : errorEvent.message,
86
+ },
87
+ };
88
+ eventEmitter.emit('sse-error', event);
89
+ });
90
+
91
+ SSEInterceptor.setCloseCallback((_, eventSource) => {
92
+ const sseEventSource = eventSource as EventSourceWithInternals;
93
+ const requestId = getRequestId(sseEventSource);
94
+
95
+ const event: SSEEvent = {
96
+ type: 'sse-close',
97
+ requestId,
98
+ timestamp: Date.now(),
99
+ };
100
+ eventEmitter.emit('sse-close', event);
101
+ });
102
+
103
+ SSEInterceptor.enableInterception();
104
+ },
105
+ disable: () => {
106
+ SSEInterceptor.disableInterception();
107
+ },
108
+ isEnabled: () => SSEInterceptor.isInterceptorEnabled(),
109
+ dispose: () => {
110
+ eventEmitter.events = {};
111
+ },
112
+ on: <TEventType extends keyof SSEEventMap>(
113
+ event: TEventType,
114
+ callback: (data: SSEEventMap[TEventType]) => void
115
+ ) => eventEmitter.on(event, callback as NanoEventsMap[TEventType]),
116
+ };
117
+ };
@@ -0,0 +1,162 @@
1
+ import type EventSource from 'react-native-sse';
2
+ import type {
3
+ MessageEvent,
4
+ ErrorEvent,
5
+ OpenEvent,
6
+ CloseEvent,
7
+ TimeoutEvent,
8
+ ExceptionEvent,
9
+ } from 'react-native-sse';
10
+ import { EventSourceWithInternals } from './types';
11
+ import { getEventSource } from './event-source';
12
+
13
+ export type SSEInterceptorConnectCallback = (
14
+ url: string,
15
+ request: EventSource
16
+ ) => void;
17
+
18
+ export type SSEInterceptorMessageCallback = (
19
+ event: MessageEvent,
20
+ request: EventSource
21
+ ) => void;
22
+
23
+ export type SSEInterceptorErrorCallback = (
24
+ error: ErrorEvent | TimeoutEvent | ExceptionEvent,
25
+ request: EventSource
26
+ ) => void;
27
+
28
+ export type SSEInterceptorOpenEventCallback = (
29
+ event: OpenEvent,
30
+ request: EventSource
31
+ ) => void;
32
+
33
+ export type SSEInterceptorCloseCallback = (
34
+ event: CloseEvent,
35
+ request: EventSource
36
+ ) => void;
37
+
38
+ let connectCallback: SSEInterceptorConnectCallback | null;
39
+ let messageCallback: SSEInterceptorMessageCallback | null;
40
+ let errorCallback: SSEInterceptorErrorCallback | null;
41
+ let openEventCallback: SSEInterceptorOpenEventCallback | null;
42
+ let closeCallback: SSEInterceptorCloseCallback | null;
43
+
44
+ let isInterceptorEnabled = false;
45
+
46
+ const eventSourceClass = getEventSource();
47
+
48
+ // Store original EventSource open method
49
+ const originalOpen = eventSourceClass.prototype.open;
50
+
51
+ /**
52
+ * A network interceptor which monkey-patches EventSource open method
53
+ * to gather all SSE connections and events, in order to show their
54
+ * information in the Network Activity panel.
55
+ */
56
+ export const SSEInterceptor = {
57
+ /**
58
+ * Invoked when EventSource.open() is called (connection attempt starting).
59
+ */
60
+ setConnectCallback(callback: SSEInterceptorConnectCallback) {
61
+ connectCallback = callback;
62
+ },
63
+
64
+ /**
65
+ * Invoked when a message event is received.
66
+ */
67
+ setMessageCallback(callback: SSEInterceptorMessageCallback) {
68
+ messageCallback = callback;
69
+ },
70
+
71
+ /**
72
+ * Invoked when an error event occurs.
73
+ */
74
+ setErrorCallback(callback: SSEInterceptorErrorCallback) {
75
+ errorCallback = callback;
76
+ },
77
+
78
+ /**
79
+ * Invoked when the connection is successfully opened (open event fired).
80
+ */
81
+ setOpenEventCallback(callback: SSEInterceptorOpenEventCallback) {
82
+ openEventCallback = callback;
83
+ },
84
+
85
+ /**
86
+ * Invoked when the connection is closed.
87
+ */
88
+ setCloseCallback(callback: SSEInterceptorCloseCallback) {
89
+ closeCallback = callback;
90
+ },
91
+
92
+ isInterceptorEnabled(): boolean {
93
+ return isInterceptorEnabled;
94
+ },
95
+
96
+ enableInterception() {
97
+ if (isInterceptorEnabled) {
98
+ return;
99
+ }
100
+
101
+ // Override EventSource open method to intercept SSE connections
102
+ eventSourceClass.prototype.open = function (
103
+ this: EventSourceWithInternals
104
+ ) {
105
+ // Invoke connect callback
106
+ if (connectCallback) {
107
+ connectCallback(this.url, this);
108
+ }
109
+
110
+ // Add event listeners to intercept all events
111
+ this.addEventListener('open', (event: OpenEvent) => {
112
+ if (openEventCallback) {
113
+ openEventCallback(event, this);
114
+ }
115
+ });
116
+
117
+ this.addEventListener('message', (event: MessageEvent) => {
118
+ if (messageCallback) {
119
+ messageCallback(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
+ isInterceptorEnabled = true;
143
+ },
144
+
145
+ // Unpatch EventSource open method and remove the callbacks.
146
+ disableInterception() {
147
+ if (!isInterceptorEnabled) {
148
+ return;
149
+ }
150
+ isInterceptorEnabled = false;
151
+
152
+ // Restore original open method
153
+ eventSourceClass.prototype.open = originalOpen;
154
+
155
+ // Clear callbacks
156
+ connectCallback = null;
157
+ messageCallback = null;
158
+ errorCallback = null;
159
+ openEventCallback = null;
160
+ closeCallback = null;
161
+ },
162
+ };
@@ -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,61 +1,15 @@
1
1
  import { useEffect } from 'react';
2
2
  import { useRozeniteDevToolsClient } from '@rozenite/plugin-bridge';
3
- import { NetworkEventMap } from '../types/network';
4
-
5
- let requestCounter = 0;
6
-
7
- function generateRequestId() {
8
- return (++requestCounter).toString();
9
- }
10
-
11
- // Helper function to convert Headers object to plain object
12
- function headersToObject(headers: HeadersInit | Record<string, string>): Record<string, string> {
13
- if (headers instanceof Headers) {
14
- const obj: Record<string, string> = {};
15
- headers.forEach((value: string, key: string) => {
16
- obj[key] = value;
17
- });
18
- return obj;
19
- } else if (Array.isArray(headers)) {
20
- const obj: Record<string, string> = {};
21
- headers.forEach(([key, value]) => {
22
- obj[key] = value;
23
- });
24
- return obj;
25
- }
26
- return headers as Record<string, string>;
27
- }
28
-
29
- // Helper function to get request info from fetch input
30
- function getRequestInfo(input: RequestInfo | URL, init?: RequestInit) {
31
- let url: string;
32
- let method: string;
33
- let headers: Record<string, string>;
34
- let body: unknown;
35
-
36
- if (typeof input === 'string') {
37
- url = input;
38
- method = init?.method || 'GET';
39
- headers = headersToObject(init?.headers || {});
40
- body = init?.body || undefined;
41
- } else if (input instanceof Request) {
42
- url = input.url;
43
- method = input.method;
44
- headers = headersToObject(input.headers);
45
- body = input.body || undefined;
46
- } else {
47
- // URL object
48
- url = input.toString();
49
- method = init?.method || 'GET';
50
- headers = headersToObject(init?.headers || {});
51
- body = init?.body || undefined;
52
- }
53
-
54
- return { url, method, headers, body };
55
- }
3
+ import { getNetworkInspector } from './http/network-inspector';
4
+ import { NetworkActivityEventMap } from '../shared/client';
5
+ import { getWebSocketInspector } from './websocket/websocket-inspector';
6
+ import { WebSocketEventMap } from '../shared/websocket-events';
7
+ import { UnionToTuple } from './utils';
8
+ import { getSSEInspector } from './sse/sse-inspector';
9
+ import { SSEEventMap } from '../shared/sse-events';
56
10
 
57
11
  export const useNetworkActivityDevTools = () => {
58
- const client = useRozeniteDevToolsClient<NetworkEventMap>({
12
+ const client = useRozeniteDevToolsClient<NetworkActivityEventMap>({
59
13
  pluginId: '@rozenite/network-activity-plugin',
60
14
  });
61
15
 
@@ -64,172 +18,81 @@ export const useNetworkActivityDevTools = () => {
64
18
  return;
65
19
  }
66
20
 
67
- const originalFetch = window.fetch.bind(window);
21
+ const networkInspector = getNetworkInspector(client);
68
22
 
69
- const sendCdpEvent = (method: keyof NetworkEventMap, params: NetworkEventMap[keyof NetworkEventMap]) => {
70
- client.send(method, params);
23
+ return () => {
24
+ networkInspector.dispose();
71
25
  };
26
+ }, [client]);
72
27
 
73
- window.fetch = async function(input: RequestInfo | URL, init?: RequestInit) {
74
- const requestId = generateRequestId();
75
- const requestInfo = getRequestInfo(input, init);
76
- const timestamp = Date.now() / 1000;
77
- const wallTime = timestamp;
78
-
79
- sendCdpEvent('Network.requestWillBeSent', {
80
- requestId,
81
- loaderId: requestId,
82
- documentURL: '',
83
- request: {
84
- url: requestInfo.url,
85
- method: requestInfo.method,
86
- headers: requestInfo.headers,
87
- postData: requestInfo.body,
88
- hasPostData: !!requestInfo.body
89
- },
90
- timestamp,
91
- wallTime,
92
- initiator: {
93
- type: 'script',
94
- stack: {
95
- callFrames: [
96
- {
97
- functionName: 'fetch',
98
- scriptId: '1',
99
- url: '',
100
- lineNumber: 1,
101
- columnNumber: 1
102
- }
103
- ]
104
- }
105
- },
106
- redirectHasExtraInfo: false,
107
- redirectResponse: null,
108
- referrerPolicy: 'no-referrer',
109
- type: 'Fetch',
110
- frameId: '1',
111
- hasUserGesture: false
112
- });
28
+ useEffect(() => {
29
+ if (!client) {
30
+ return;
31
+ }
113
32
 
114
- sendCdpEvent('Network.requestWillBeSentExtraInfo', {
115
- requestId,
116
- blockedCookies: [],
117
- headers: requestInfo.headers,
118
- connectTiming: {
119
- requestTime: timestamp
120
- },
121
- clientSecurityState: {
122
- initiatorIsSecureContext: false,
123
- initiatorIPAddressSpace: 'Public',
124
- privateNetworkRequestPolicy: 'Allow'
125
- },
126
- siteHasCookieInOtherPartition: false
33
+ const eventsToForward: UnionToTuple<keyof WebSocketEventMap> = [
34
+ 'websocket-connect',
35
+ 'websocket-open',
36
+ 'websocket-close',
37
+ 'websocket-message-sent',
38
+ 'websocket-message-received',
39
+ 'websocket-error',
40
+ 'websocket-connection-status-changed',
41
+ ];
42
+ const websocketInspector = getWebSocketInspector();
43
+
44
+ eventsToForward.forEach((event) => {
45
+ websocketInspector.on(event, (event) => {
46
+ client.send(event.type, event);
127
47
  });
48
+ });
49
+
50
+ client.onMessage('network-enable', () => {
51
+ websocketInspector.enable();
52
+ });
128
53
 
129
- let response;
130
- try {
131
- response = await originalFetch(input, init);
132
-
133
- // Get response headers
134
- const responseHeaders: Record<string, string> = {};
135
- response.headers.forEach((value: string, key: string) => {
136
- responseHeaders[key] = value;
137
- });
138
-
139
- const contentType = response.headers.get('content-type') || '';
140
- const contentLength = response.headers.get('content-length');
141
- const encodedDataLength = contentLength ? parseInt(contentLength, 10) : 0;
142
-
143
- // Get decoded body size (actual response size)
144
- let decodedBodySize = encodedDataLength;
145
- try {
146
- const responseClone = response.clone();
147
- const arrayBuffer = await responseClone.arrayBuffer();
148
- decodedBodySize = arrayBuffer.byteLength;
149
- } catch {
150
- // Fallback to content-length if we can't read the response
151
- decodedBodySize = encodedDataLength;
152
- }
153
-
154
- sendCdpEvent('Network.responseReceived', {
155
- requestId,
156
- loaderId: requestId,
157
- timestamp: Date.now() / 1000,
158
- type: 'Fetch',
159
- response: {
160
- url: requestInfo.url,
161
- status: response.status,
162
- statusText: response.statusText,
163
- headers: responseHeaders,
164
- mimeType: contentType,
165
- requestHeaders: requestInfo.headers,
166
- requestHeadersText: undefined,
167
- connectionReused: false,
168
- connectionId: 0,
169
- remoteIPAddress: undefined,
170
- remotePort: undefined,
171
- protocol: undefined,
172
- securityState: 'unknown',
173
- encodedDataLength,
174
- timing: {
175
- requestTime: timestamp,
176
- proxyStart: -1,
177
- proxyEnd: -1,
178
- dnsStart: -1,
179
- dnsEnd: -1,
180
- connectStart: -1,
181
- connectEnd: -1,
182
- sslStart: -1,
183
- sslEnd: -1,
184
- workerStart: -1,
185
- workerReadyStart: -1,
186
- workerReadyEnd: -1,
187
- sendStart: timestamp,
188
- sendEnd: timestamp,
189
- pushStart: -1,
190
- pushEnd: -1,
191
- receiveHeadersEnd: Date.now() / 1000
192
- },
193
- responseTime: Date.now() / 1000,
194
- fromDiskCache: false,
195
- fromServiceWorker: false,
196
- fromPrefetchCache: false,
197
- encodedBodySize: encodedDataLength,
198
- decodedBodySize: decodedBodySize,
199
- headersText: undefined,
200
- serviceWorkerResponseSource: undefined,
201
- responseSource: 'network',
202
- statusCode: response.status
203
- },
204
- hasExtraInfo: false
205
- });
206
-
207
- sendCdpEvent('Network.loadingFinished', {
208
- requestId,
209
- timestamp: Date.now() / 1000,
210
- encodedDataLength,
211
- shouldReportCorbBlocking: false
212
- });
213
-
214
- return response;
215
- } catch (err) {
216
- sendCdpEvent('Network.loadingFailed', {
217
- requestId,
218
- timestamp: Date.now() / 1000,
219
- type: 'Fetch',
220
- errorText: err instanceof Error ? err.message : 'Unknown error',
221
- canceled: false,
222
- blockedReason: undefined
223
- });
224
-
225
- throw err;
226
- }
54
+ client.onMessage('network-disable', () => {
55
+ websocketInspector.disable();
56
+ });
57
+
58
+ return () => {
59
+ // Subscriptions will be disposed by the inspector
60
+ websocketInspector.dispose();
227
61
  };
62
+ }, [client]);
63
+
64
+ useEffect(() => {
65
+ if (!client) {
66
+ return;
67
+ }
68
+
69
+ const eventsToForward: UnionToTuple<keyof SSEEventMap> = [
70
+ 'sse-open',
71
+ 'sse-message',
72
+ 'sse-error',
73
+ 'sse-close',
74
+ ];
75
+ const sseInspector = getSSEInspector();
76
+
77
+ eventsToForward.forEach((event) => {
78
+ sseInspector.on(event, (event) => {
79
+ client.send(event.type, event);
80
+ });
81
+ });
82
+
83
+ client.onMessage('network-enable', () => {
84
+ sseInspector.enable();
85
+ });
86
+
87
+ client.onMessage('network-disable', () => {
88
+ sseInspector.disable();
89
+ });
228
90
 
229
91
  return () => {
230
- window.fetch = originalFetch;
92
+ // Subscriptions will be disposed by the inspector
93
+ sseInspector.dispose();
231
94
  };
232
95
  }, [client]);
233
96
 
234
97
  return client;
235
- };
98
+ };
@@ -0,0 +1,43 @@
1
+ import { getHttpHeaderValue } from '../ui/utils/getHttpHeaderValue';
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 = getHttpHeaderValue(responseHeaders || {}, 'content-type');
26
+
27
+ if (contentType) {
28
+ return contentType.split(';')[0].trim();
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
+ };