@rozenite/network-activity-plugin 1.0.0-alpha.1 → 1.0.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -5
- package/dist/{panel.html → App.html} +3 -3
- package/dist/assets/App-CA1Fbh0I.js +25364 -0
- package/dist/assets/App-DoHQsY5s.css +1276 -0
- package/dist/event-source.cjs +22 -0
- package/dist/event-source.js +23 -0
- package/dist/react-native.cjs +8 -1
- package/dist/react-native.d.ts +1 -5
- package/dist/react-native.js +6 -171
- package/dist/rozenite.config.d.ts +7 -0
- package/dist/rozenite.json +1 -1
- package/dist/src/react-native/http/network-inspector.d.ts +8 -0
- package/dist/src/react-native/http/network-requests-registry.d.ts +6 -0
- package/dist/src/react-native/http/xhr-interceptor.d.ts +38 -0
- package/dist/src/react-native/sse/event-source.d.ts +2 -0
- package/dist/src/react-native/sse/sse-inspector.d.ts +9 -0
- package/dist/src/react-native/sse/sse-interceptor.d.ts +36 -0
- package/dist/src/react-native/sse/types.d.ts +6 -0
- package/dist/src/react-native/useNetworkActivityDevTools.d.ts +2 -0
- package/dist/src/react-native/utils.d.ts +6 -0
- package/dist/src/react-native/websocket/websocket-inspector.d.ts +9 -0
- package/dist/src/react-native/websocket/websocket-interceptor.d.ts +74 -0
- package/dist/src/shared/client.d.ts +68 -0
- package/dist/src/shared/sse-events.d.ts +35 -0
- package/dist/src/shared/websocket-events.d.ts +60 -0
- package/dist/src/ui/App.d.ts +1 -0
- package/dist/src/ui/components/Badge.d.ts +9 -0
- package/dist/src/ui/components/Button.d.ts +11 -0
- package/dist/src/ui/components/Input.d.ts +3 -0
- package/dist/src/ui/components/JsonTree.d.ts +5 -0
- package/dist/src/ui/components/JsonTreeCopyableItem.d.ts +7 -0
- package/dist/src/ui/components/RequestList.d.ts +25 -0
- package/dist/src/ui/components/ScrollArea.d.ts +4 -0
- package/dist/src/ui/components/Separator.d.ts +3 -0
- package/dist/src/ui/components/SidePanel.d.ts +1 -0
- package/dist/src/ui/components/Toolbar.d.ts +1 -0
- package/dist/src/ui/hooks/useCopyToClipboard.d.ts +4 -0
- package/dist/src/ui/state/derived.d.ts +5 -0
- package/dist/src/ui/state/hooks.d.ts +17 -0
- package/dist/src/ui/state/model.d.ts +98 -0
- package/dist/src/ui/state/store.d.ts +24 -0
- package/dist/src/ui/tabs/CookiesTab.d.ts +5 -0
- package/dist/src/ui/tabs/HeadersTab.d.ts +5 -0
- package/dist/src/ui/tabs/MessagesTab.d.ts +5 -0
- package/dist/src/ui/tabs/RequestTab.d.ts +5 -0
- package/dist/src/ui/tabs/ResponseTab.d.ts +6 -0
- package/dist/src/ui/tabs/SSEMessagesTab.d.ts +5 -0
- package/dist/src/ui/tabs/TimingTab.d.ts +5 -0
- package/dist/src/ui/types.d.ts +26 -0
- package/dist/src/ui/utils/assert.d.ts +1 -0
- package/dist/src/ui/utils/cn.d.ts +2 -0
- package/dist/src/ui/utils/copyToClipboard.d.ts +1 -0
- package/dist/src/ui/utils/getHttpHeaderValue.d.ts +2 -0
- package/dist/src/ui/utils/getId.d.ts +1 -0
- package/dist/src/ui/utils/getStatusColor.d.ts +1 -0
- package/dist/src/ui/views/InspectorView.d.ts +5 -0
- package/dist/src/ui/views/LoadingView.d.ts +1 -0
- package/dist/useNetworkActivityDevTools.cjs +759 -0
- package/dist/useNetworkActivityDevTools.js +757 -0
- package/package.json +31 -10
- package/postcss.config.js +6 -0
- package/project.json +12 -0
- package/react-native.ts +2 -1
- package/rozenite.config.ts +2 -2
- package/src/css-modules.d.ts +1 -1
- package/src/react-native/http/network-inspector.ts +226 -0
- package/src/react-native/http/network-requests-registry.ts +52 -0
- package/src/react-native/http/xhr-interceptor.ts +211 -0
- package/src/react-native/http/xml-request.d.ts +34 -0
- package/src/react-native/sse/event-source.ts +25 -0
- package/src/react-native/sse/sse-inspector.ts +117 -0
- package/src/react-native/sse/sse-interceptor.ts +162 -0
- package/src/react-native/sse/types.ts +9 -0
- package/src/react-native/useNetworkActivityDevTools.ts +73 -210
- package/src/react-native/utils.ts +43 -0
- package/src/react-native/websocket/websocket-inspector.ts +180 -0
- package/src/react-native/websocket/websocket-interceptor.d.ts +4 -0
- package/src/react-native/websocket/websocket-interceptor.ts +166 -0
- package/src/shared/client.ts +86 -0
- package/src/shared/sse-events.ts +44 -0
- package/src/shared/websocket-events.ts +79 -0
- package/src/ui/App.tsx +19 -0
- package/src/ui/components/Badge.tsx +36 -0
- package/src/ui/components/Button.tsx +56 -0
- package/src/ui/components/Input.tsx +22 -0
- package/src/ui/components/JsonTree.tsx +50 -0
- package/src/ui/components/JsonTreeCopyableItem.tsx +33 -0
- package/src/ui/components/RequestList.tsx +295 -0
- package/src/ui/components/ScrollArea.tsx +48 -0
- package/src/ui/components/Separator.tsx +31 -0
- package/src/ui/components/SidePanel.tsx +323 -0
- package/src/ui/components/Tabs.tsx +55 -0
- package/src/ui/components/Toolbar.tsx +45 -0
- package/src/ui/globals.css +90 -0
- package/src/ui/hooks/useCopyToClipboard.ts +28 -0
- package/src/ui/state/derived.ts +112 -0
- package/src/ui/state/hooks.ts +44 -0
- package/src/ui/state/model.ts +129 -0
- package/src/ui/state/store.ts +559 -0
- package/src/ui/tabs/CookiesTab.tsx +279 -0
- package/src/ui/tabs/HeadersTab.tsx +110 -0
- package/src/ui/tabs/MessagesTab.tsx +276 -0
- package/src/ui/tabs/RequestTab.tsx +69 -0
- package/src/ui/tabs/ResponseTab.tsx +138 -0
- package/src/ui/tabs/SSEMessagesTab.tsx +213 -0
- package/src/ui/tabs/TimingTab.tsx +60 -0
- package/src/ui/types.ts +34 -0
- package/src/ui/utils/assert.ts +5 -0
- package/src/ui/utils/cn.ts +6 -0
- package/src/ui/utils/copyToClipboard.ts +3 -0
- package/src/ui/utils/getHttpHeaderValue.ts +14 -0
- package/src/ui/utils/getId.ts +10 -0
- package/src/ui/utils/getStatusColor.ts +15 -0
- package/src/ui/views/InspectorView.tsx +53 -0
- package/src/ui/views/LoadingView.tsx +19 -0
- package/tailwind.config.ts +96 -0
- package/tsconfig.json +13 -6
- package/tsconfig.tsbuildinfo +1 -0
- package/vite.config.ts +13 -1
- package/dist/assets/panel-C5YgUUj5.js +0 -54
- package/dist/assets/panel-NCVczPb1.css +0 -1
- package/src/types/network.ts +0 -153
- package/src/ui/components.module.css +0 -158
- package/src/ui/components.tsx +0 -219
- package/src/ui/network-details.module.css +0 -57
- package/src/ui/network-details.tsx +0 -134
- package/src/ui/network-list.module.css +0 -122
- package/src/ui/network-list.tsx +0 -145
- package/src/ui/network-toolbar.module.css +0 -9
- package/src/ui/network-toolbar.tsx +0 -40
- package/src/ui/panel.module.css +0 -61
- package/src/ui/panel.tsx +0 -201
- package/src/ui/tanstack-query.tsx +0 -197
- package/src/ui/utils.ts +0 -89
|
@@ -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 {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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<
|
|
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
|
|
21
|
+
const networkInspector = getNetworkInspector(client);
|
|
68
22
|
|
|
69
|
-
|
|
70
|
-
|
|
23
|
+
return () => {
|
|
24
|
+
networkInspector.dispose();
|
|
71
25
|
};
|
|
26
|
+
}, [client]);
|
|
72
27
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
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
|
+
};
|