@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.
- package/README.md +2 -0
- package/dist/App.html +2 -2
- package/dist/assets/{App-R2ZMH9wJ.css → App-BrSkOkws.css} +269 -2
- package/dist/assets/{App-lNMijPJ4.js → App-C6wCDVkW.js} +17485 -10814
- package/dist/event-source.cjs +22 -0
- package/dist/event-source.js +23 -0
- package/dist/react-native.cjs +4 -1
- package/dist/react-native.js +4 -1
- package/dist/rozenite.json +1 -1
- package/dist/src/react-native/config.d.ts +20 -0
- package/dist/src/react-native/{network-inspector.d.ts → http/network-inspector.d.ts} +1 -1
- package/dist/src/react-native/http/overrides-registry.d.ts +6 -0
- package/dist/src/react-native/{xhr-interceptor.d.ts → http/xhr-interceptor.d.ts} +7 -1
- 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 -1
- package/dist/src/react-native/utils/getBlobName.d.ts +35 -0
- package/dist/src/react-native/utils/getFormDataEntries.d.ts +18 -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 +53 -6
- package/dist/src/shared/sse-events.d.ts +38 -0
- package/dist/src/shared/websocket-events.d.ts +60 -0
- package/dist/src/ui/components/Badge.d.ts +1 -1
- package/dist/src/ui/components/Button.d.ts +2 -2
- package/dist/src/ui/components/CodeBlock.d.ts +3 -0
- package/dist/src/ui/components/CodeEditor.d.ts +5 -0
- package/dist/src/ui/components/CookieCard.d.ts +7 -0
- package/dist/src/ui/components/CopyRequestDropdown.d.ts +7 -0
- package/dist/src/ui/components/DropdownMenu.d.ts +27 -0
- package/dist/src/ui/components/FilterBar.d.ts +10 -0
- package/dist/src/ui/components/JsonTreeCopyableItem.d.ts +7 -0
- package/dist/src/ui/components/KeyValueGrid.d.ts +13 -0
- package/dist/src/ui/components/OverrideResponse.d.ts +8 -0
- package/dist/src/ui/components/RequestBody.d.ts +6 -0
- package/dist/src/ui/components/RequestList.d.ts +13 -28
- package/dist/src/ui/components/ScrollArea.d.ts +3 -2
- package/dist/src/ui/components/Section.d.ts +8 -0
- package/dist/src/ui/components/Separator.d.ts +2 -1
- package/dist/src/ui/components/SidePanel.d.ts +1 -0
- package/dist/src/ui/components/Tabs.d.ts +7 -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 +21 -0
- package/dist/src/ui/state/model.d.ts +103 -0
- package/dist/src/ui/state/store.d.ts +48 -0
- package/dist/src/ui/tabs/CookiesTab.d.ts +3 -6
- package/dist/src/ui/tabs/HeadersTab.d.ts +3 -15
- package/dist/src/ui/tabs/MessagesTab.d.ts +5 -0
- package/dist/src/ui/tabs/RequestTab.d.ts +2 -7
- package/dist/src/ui/tabs/ResponseTab.d.ts +2 -8
- package/dist/src/ui/tabs/SSEMessagesTab.d.ts +5 -0
- package/dist/src/ui/tabs/TimingTab.d.ts +3 -5
- package/dist/src/ui/types.d.ts +4 -1
- package/dist/src/ui/utils/assert.d.ts +1 -0
- package/dist/src/ui/utils/checkRequestBodyBinary.d.ts +2 -0
- package/dist/src/ui/utils/copyToClipboard.d.ts +1 -0
- package/dist/src/ui/utils/escapeShellArg.d.ts +1 -0
- package/dist/src/ui/utils/generateCurlCommand.d.ts +2 -0
- package/dist/src/ui/utils/generateFetchCall.d.ts +2 -0
- package/dist/src/ui/utils/generateMultipartBody.d.ts +4 -0
- package/dist/src/ui/utils/getId.d.ts +1 -0
- package/dist/src/ui/utils/getStatusColor.d.ts +1 -0
- package/dist/src/utils/applyReactNativeRequestHeadersLogic.d.ts +7 -0
- package/dist/src/utils/applyReactNativeResponseHeadersLogic.d.ts +9 -0
- package/dist/src/utils/cookieParser.d.ts +6 -0
- package/dist/src/utils/getContentTypeMimeType.d.ts +2 -0
- package/dist/src/utils/getHttpHeader.d.ts +5 -0
- package/dist/src/utils/getHttpHeaderValueAsString.d.ts +11 -0
- package/dist/src/utils/getStringSizeInBytes.d.ts +1 -0
- package/dist/src/utils/inferContentTypeFromPostData.d.ts +2 -0
- package/dist/src/utils/safeStringify.d.ts +1 -0
- package/dist/src/utils/typeChecks.d.ts +9 -0
- package/dist/useNetworkActivityDevTools.cjs +724 -40
- package/dist/useNetworkActivityDevTools.js +723 -41
- package/package.json +22 -8
- package/react-native.ts +6 -1
- package/src/react-native/config.ts +43 -0
- package/src/react-native/http/network-inspector.ts +388 -0
- package/src/react-native/http/overrides-registry.ts +32 -0
- package/src/react-native/{xhr-interceptor.ts → http/xhr-interceptor.ts} +19 -2
- package/src/react-native/{xml-request.d.ts → http/xml-request.d.ts} +1 -0
- package/src/react-native/sse/event-source.ts +25 -0
- package/src/react-native/sse/sse-inspector.ts +139 -0
- package/src/react-native/sse/sse-interceptor.ts +180 -0
- package/src/react-native/sse/types.ts +9 -0
- package/src/react-native/useNetworkActivityDevTools.ts +156 -4
- package/src/react-native/utils/getBlobName.ts +45 -0
- package/src/react-native/utils/getFormDataEntries.ts +32 -0
- 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 +79 -6
- package/src/shared/sse-events.ts +47 -0
- package/src/shared/websocket-events.ts +79 -0
- package/src/ui/components/Button.tsx +1 -0
- package/src/ui/components/CodeBlock.tsx +19 -0
- package/src/ui/components/CodeEditor.tsx +26 -0
- package/src/ui/components/CookieCard.tsx +64 -0
- package/src/ui/components/CopyRequestDropdown.tsx +95 -0
- package/src/ui/components/DropdownMenu.tsx +206 -0
- package/src/ui/components/FilterBar.tsx +117 -0
- package/src/ui/components/Input.tsx +1 -1
- package/src/ui/components/JsonTree.tsx +20 -0
- package/src/ui/components/JsonTreeCopyableItem.tsx +37 -0
- package/src/ui/components/KeyValueGrid.tsx +51 -0
- package/src/ui/components/OverrideResponse.tsx +132 -0
- package/src/ui/components/RequestBody.tsx +86 -0
- package/src/ui/components/RequestList.tsx +101 -131
- package/src/ui/components/ScrollArea.tsx +1 -0
- package/src/ui/components/Section.tsx +46 -0
- package/src/ui/components/SidePanel.tsx +333 -0
- package/src/ui/components/Tabs.tsx +1 -1
- package/src/ui/components/Toolbar.tsx +45 -0
- package/src/ui/globals.css +4 -0
- package/src/ui/hooks/useCopyToClipboard.ts +28 -0
- package/src/ui/state/derived.ts +112 -0
- package/src/ui/state/hooks.ts +52 -0
- package/src/ui/state/model.ts +140 -0
- package/src/ui/state/store.ts +669 -0
- package/src/ui/tabs/CookiesTab.tsx +61 -278
- package/src/ui/tabs/HeadersTab.tsx +85 -103
- package/src/ui/tabs/MessagesTab.tsx +276 -0
- package/src/ui/tabs/RequestTab.tsx +58 -51
- package/src/ui/tabs/ResponseTab.tsx +101 -74
- package/src/ui/tabs/SSEMessagesTab.tsx +224 -0
- package/src/ui/tabs/TimingTab.tsx +30 -43
- package/src/ui/types.ts +4 -1
- package/src/ui/utils/assert.ts +5 -0
- package/src/ui/utils/checkRequestBodyBinary.ts +7 -0
- package/src/ui/utils/copyToClipboard.ts +3 -0
- package/src/ui/utils/escapeShellArg.ts +12 -0
- package/src/ui/utils/generateCurlCommand.ts +83 -0
- package/src/ui/utils/generateFetchCall.ts +64 -0
- package/src/ui/utils/generateMultipartBody.ts +19 -0
- package/src/ui/utils/getId.ts +10 -0
- package/src/ui/utils/getStatusColor.ts +15 -0
- package/src/ui/views/InspectorView.tsx +35 -319
- package/src/utils/applyReactNativeRequestHeadersLogic.ts +30 -0
- package/src/utils/applyReactNativeResponseHeadersLogic.ts +28 -0
- package/src/utils/cookieParser.ts +126 -0
- package/src/utils/getContentTypeMimeType.ts +17 -0
- package/src/utils/getHttpHeader.ts +17 -0
- package/src/utils/getHttpHeaderValueAsString.ts +13 -0
- package/src/utils/getStringSizeInBytes.ts +3 -0
- package/src/utils/inferContentTypeFromPostData.ts +9 -0
- package/src/utils/safeStringify.ts +7 -0
- package/src/utils/typeChecks.ts +27 -0
- package/tailwind.config.ts +3 -0
- package/vite.config.ts +12 -0
- package/dist/src/ui/utils/getHttpHeaderValue.d.ts +0 -2
- package/src/react-native/network-inspector.ts +0 -247
- package/src/ui/utils/getHttpHeaderValue.ts +0 -14
- /package/dist/src/react-native/{network-requests-registry.d.ts → http/network-requests-registry.d.ts} +0 -0
- /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
|
-
|
|
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
|
+
};
|