@rozenite/network-activity-plugin 1.0.0-alpha.8 → 1.0.0-alpha.9
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/dist/App.html +2 -2
- package/dist/assets/{App-lNMijPJ4.js → App-CA1Fbh0I.js} +11995 -10804
- package/dist/assets/{App-R2ZMH9wJ.css → App-DoHQsY5s.css} +46 -0
- package/dist/event-source.cjs +22 -0
- package/dist/event-source.js +23 -0
- package/dist/rozenite.json +1 -1
- package/dist/src/react-native/{network-inspector.d.ts → http/network-inspector.d.ts} +1 -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/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 +5 -2
- package/dist/src/shared/sse-events.d.ts +35 -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 +1 -1
- package/dist/src/ui/components/JsonTreeCopyableItem.d.ts +7 -0
- package/dist/src/ui/components/RequestList.d.ts +6 -26
- 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 +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/copyToClipboard.d.ts +1 -0
- package/dist/src/ui/utils/getId.d.ts +1 -0
- package/dist/src/ui/utils/getStatusColor.d.ts +1 -0
- package/dist/useNetworkActivityDevTools.cjs +423 -34
- package/dist/useNetworkActivityDevTools.js +421 -34
- package/package.json +19 -8
- package/src/react-native/{network-inspector.ts → http/network-inspector.ts} +13 -34
- 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 +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 +75 -1
- 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 +6 -2
- package/src/shared/sse-events.ts +44 -0
- package/src/shared/websocket-events.ts +79 -0
- package/src/ui/components/JsonTree.tsx +13 -0
- package/src/ui/components/JsonTreeCopyableItem.tsx +33 -0
- package/src/ui/components/RequestList.tsx +42 -124
- package/src/ui/components/SidePanel.tsx +323 -0
- package/src/ui/components/Tabs.tsx +1 -1
- package/src/ui/components/Toolbar.tsx +45 -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 +162 -176
- package/src/ui/tabs/HeadersTab.tsx +23 -30
- package/src/ui/tabs/MessagesTab.tsx +276 -0
- package/src/ui/tabs/RequestTab.tsx +8 -13
- package/src/ui/tabs/ResponseTab.tsx +6 -10
- package/src/ui/tabs/SSEMessagesTab.tsx +213 -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/copyToClipboard.ts +3 -0
- package/src/ui/utils/getId.ts +10 -0
- package/src/ui/utils/getStatusColor.ts +15 -0
- package/src/ui/views/InspectorView.tsx +24 -320
- package/tailwind.config.ts +3 -0
- package/vite.config.ts +12 -0
- /package/dist/src/react-native/{network-requests-registry.d.ts → http/network-requests-registry.d.ts} +0 -0
- /package/dist/src/react-native/{xhr-interceptor.d.ts → http/xhr-interceptor.d.ts} +0 -0
- /package/src/react-native/{network-requests-registry.ts → http/network-requests-registry.ts} +0 -0
- /package/src/react-native/{xhr-interceptor.ts → http/xhr-interceptor.ts} +0 -0
|
@@ -1,34 +1,10 @@
|
|
|
1
|
-
import { NetworkActivityDevToolsClient } from '
|
|
2
|
-
import {
|
|
1
|
+
import { HttpMethod, NetworkActivityDevToolsClient } from '../../shared/client';
|
|
2
|
+
import { getContentType } from '../utils';
|
|
3
3
|
import { getNetworkRequestsRegistry } from './network-requests-registry';
|
|
4
4
|
import { XHRInterceptor } from './xhr-interceptor';
|
|
5
5
|
|
|
6
6
|
const networkRequestsRegistry = getNetworkRequestsRegistry();
|
|
7
7
|
|
|
8
|
-
const getContentType = (request: XMLHttpRequest): string => {
|
|
9
|
-
const responseHeaders = request.responseHeaders;
|
|
10
|
-
const responseType = request.responseType;
|
|
11
|
-
|
|
12
|
-
const contentType = getHttpHeaderValue(responseHeaders || {}, 'content-type');
|
|
13
|
-
|
|
14
|
-
if (contentType) {
|
|
15
|
-
return contentType.split(';')[0].trim();
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
switch (responseType) {
|
|
19
|
-
case 'arraybuffer':
|
|
20
|
-
case 'blob':
|
|
21
|
-
return 'application/octet-stream';
|
|
22
|
-
case 'text':
|
|
23
|
-
case '':
|
|
24
|
-
return 'text/plain';
|
|
25
|
-
case 'json':
|
|
26
|
-
return 'application/json';
|
|
27
|
-
case 'document':
|
|
28
|
-
return 'text/html';
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
|
|
32
8
|
const getResponseSize = (request: XMLHttpRequest): number => {
|
|
33
9
|
if (typeof request.response === 'object') {
|
|
34
10
|
return request.response.size;
|
|
@@ -42,7 +18,8 @@ const getResponseBody = async (
|
|
|
42
18
|
): Promise<string | null> => {
|
|
43
19
|
const responseType = request.responseType;
|
|
44
20
|
|
|
45
|
-
|
|
21
|
+
// Response type is empty in certain cases, like when using axios.
|
|
22
|
+
if (responseType === '' || responseType === 'text') {
|
|
46
23
|
return request.responseText as string;
|
|
47
24
|
}
|
|
48
25
|
|
|
@@ -117,6 +94,8 @@ export const getNetworkInspector = (
|
|
|
117
94
|
const sendTime = Date.now();
|
|
118
95
|
|
|
119
96
|
const requestId = generateRequestId();
|
|
97
|
+
request._rozeniteRequestId = requestId;
|
|
98
|
+
|
|
120
99
|
const initiator = getInitiatorFromStack();
|
|
121
100
|
|
|
122
101
|
networkRequestsRegistry.addEntry(requestId, request);
|
|
@@ -125,10 +104,10 @@ export const getNetworkInspector = (
|
|
|
125
104
|
|
|
126
105
|
pluginClient.send('request-sent', {
|
|
127
106
|
requestId: requestId,
|
|
128
|
-
timestamp: sendTime
|
|
107
|
+
timestamp: sendTime,
|
|
129
108
|
request: {
|
|
130
109
|
url: request._url as string,
|
|
131
|
-
method: request._method as
|
|
110
|
+
method: request._method as HttpMethod,
|
|
132
111
|
headers: request._headers,
|
|
133
112
|
postData: data,
|
|
134
113
|
},
|
|
@@ -145,7 +124,7 @@ export const getNetworkInspector = (
|
|
|
145
124
|
request.addEventListener('load', () => {
|
|
146
125
|
pluginClient.send('response-received', {
|
|
147
126
|
requestId: requestId,
|
|
148
|
-
timestamp: Date.now()
|
|
127
|
+
timestamp: Date.now(),
|
|
149
128
|
type: 'XHR',
|
|
150
129
|
response: {
|
|
151
130
|
url: request._url as string,
|
|
@@ -154,7 +133,7 @@ export const getNetworkInspector = (
|
|
|
154
133
|
headers: request.responseHeaders || {},
|
|
155
134
|
contentType: getContentType(request),
|
|
156
135
|
size: getResponseSize(request),
|
|
157
|
-
responseTime: Date.now()
|
|
136
|
+
responseTime: Date.now(),
|
|
158
137
|
},
|
|
159
138
|
});
|
|
160
139
|
});
|
|
@@ -162,7 +141,7 @@ export const getNetworkInspector = (
|
|
|
162
141
|
request.addEventListener('loadend', () => {
|
|
163
142
|
pluginClient.send('request-completed', {
|
|
164
143
|
requestId: requestId,
|
|
165
|
-
timestamp: Date.now()
|
|
144
|
+
timestamp: Date.now(),
|
|
166
145
|
duration: Date.now() - sendTime,
|
|
167
146
|
size: getResponseSize(request),
|
|
168
147
|
ttfb,
|
|
@@ -172,7 +151,7 @@ export const getNetworkInspector = (
|
|
|
172
151
|
request.addEventListener('error', () => {
|
|
173
152
|
pluginClient.send('request-failed', {
|
|
174
153
|
requestId: requestId,
|
|
175
|
-
timestamp: Date.now()
|
|
154
|
+
timestamp: Date.now(),
|
|
176
155
|
type: 'XHR',
|
|
177
156
|
error: 'Failed',
|
|
178
157
|
canceled: false,
|
|
@@ -182,7 +161,7 @@ export const getNetworkInspector = (
|
|
|
182
161
|
request.addEventListener('abort', () => {
|
|
183
162
|
pluginClient.send('request-failed', {
|
|
184
163
|
requestId: requestId,
|
|
185
|
-
timestamp: Date.now()
|
|
164
|
+
timestamp: Date.now(),
|
|
186
165
|
type: 'XHR',
|
|
187
166
|
error: 'Aborted',
|
|
188
167
|
canceled: true,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type EventSource from 'react-native-sse';
|
|
2
|
+
|
|
3
|
+
const NOOP = () => {
|
|
4
|
+
// noop
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
const MOCK_EVENT_SOURCE = class {
|
|
8
|
+
open = NOOP;
|
|
9
|
+
close = NOOP;
|
|
10
|
+
addEventListener = NOOP;
|
|
11
|
+
removeEventListener = NOOP;
|
|
12
|
+
dispatch = NOOP;
|
|
13
|
+
removeAllEventListeners = NOOP;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const getEventSource = (): typeof EventSource => {
|
|
17
|
+
try {
|
|
18
|
+
const { default: EventSource } = require('react-native-sse');
|
|
19
|
+
return EventSource;
|
|
20
|
+
} catch {
|
|
21
|
+
// This is a workaround for the fact that Vite doesn't support require() calls for in-project dependencies.
|
|
22
|
+
// We are going to return a mock object, so the code will work fine, but it will not be able to intercept SSE requests.
|
|
23
|
+
return MOCK_EVENT_SOURCE;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
@@ -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,7 +1,12 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
2
|
import { useRozeniteDevToolsClient } from '@rozenite/plugin-bridge';
|
|
3
|
-
import { getNetworkInspector } from './network-inspector';
|
|
3
|
+
import { getNetworkInspector } from './http/network-inspector';
|
|
4
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';
|
|
5
10
|
|
|
6
11
|
export const useNetworkActivityDevTools = () => {
|
|
7
12
|
const client = useRozeniteDevToolsClient<NetworkActivityEventMap>({
|
|
@@ -20,5 +25,74 @@ export const useNetworkActivityDevTools = () => {
|
|
|
20
25
|
};
|
|
21
26
|
}, [client]);
|
|
22
27
|
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (!client) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
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);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
client.onMessage('network-enable', () => {
|
|
51
|
+
websocketInspector.enable();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
client.onMessage('network-disable', () => {
|
|
55
|
+
websocketInspector.disable();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return () => {
|
|
59
|
+
// Subscriptions will be disposed by the inspector
|
|
60
|
+
websocketInspector.dispose();
|
|
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
|
+
});
|
|
90
|
+
|
|
91
|
+
return () => {
|
|
92
|
+
// Subscriptions will be disposed by the inspector
|
|
93
|
+
sseInspector.dispose();
|
|
94
|
+
};
|
|
95
|
+
}, [client]);
|
|
96
|
+
|
|
23
97
|
return client;
|
|
24
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
|
+
};
|