@rozenite/network-activity-plugin 1.1.0 → 1.2.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 +9 -0
- package/dist/App.html +1 -1
- package/dist/assets/{App-Kyi7zHUX.js → App-o_iVtD-5.js} +10 -4
- package/dist/boot-recording.cjs +1092 -0
- package/dist/boot-recording.js +1091 -0
- package/dist/react-native.cjs +3 -0
- package/dist/react-native.d.ts +3 -0
- package/dist/react-native.js +5 -1
- package/dist/rozenite.json +1 -1
- package/dist/src/react-native/boot-recording.d.ts +41 -0
- package/dist/src/react-native/config.d.ts +7 -4
- package/dist/src/react-native/events-listener.d.ts +44 -0
- package/dist/src/react-native/http/http-inspector.d.ts +10 -0
- package/dist/src/react-native/http/http-utils.d.ts +15 -0
- package/dist/src/react-native/inspector.d.ts +7 -0
- package/dist/src/react-native/network-inspector.d.ts +16 -0
- package/dist/src/react-native/sse/sse-inspector.d.ts +4 -7
- package/dist/src/react-native/useHttpInspector.d.ts +3 -0
- package/dist/src/react-native/useSSEInspector.d.ts +3 -0
- package/dist/src/react-native/useWebSocketInspector.d.ts +3 -0
- package/dist/src/react-native/websocket/websocket-inspector.d.ts +4 -7
- package/dist/src/shared/client.d.ts +3 -105
- package/dist/src/shared/http-events.d.ts +106 -0
- package/dist/src/shared/sse-events.d.ts +1 -1
- package/dist/src/ui/state/hooks.d.ts +3 -3
- package/dist/useNetworkActivityDevTools.cjs +112 -1011
- package/dist/useNetworkActivityDevTools.js +110 -1007
- package/package.json +4 -4
- package/react-native.ts +8 -0
- package/src/react-native/boot-recording.ts +90 -0
- package/src/react-native/config.ts +9 -4
- package/src/react-native/events-listener.ts +102 -0
- package/src/react-native/http/http-inspector.ts +174 -0
- package/src/react-native/http/http-utils.ts +217 -0
- package/src/react-native/inspector.ts +10 -0
- package/src/react-native/network-inspector.ts +78 -0
- package/src/react-native/sse/sse-inspector.ts +12 -10
- package/src/react-native/useHttpInspector.ts +59 -0
- package/src/react-native/useNetworkActivityDevTools.ts +60 -115
- package/src/react-native/useSSEInspector.ts +35 -0
- package/src/react-native/useWebSocketInspector.ts +35 -0
- package/src/react-native/websocket/websocket-inspector.ts +18 -10
- package/src/shared/client.ts +4 -140
- package/src/shared/http-events.ts +140 -0
- package/src/shared/sse-events.ts +1 -1
- package/src/ui/components/RequestList.tsx +9 -5
- package/src/ui/state/derived.ts +7 -3
- package/src/ui/state/store.ts +4 -3
- package/dist/src/react-native/http/network-inspector.d.ts +0 -8
- package/src/react-native/http/network-inspector.ts +0 -408
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
export type HttpHeaders = Record<string, string | string[]>;
|
|
2
|
+
export type XHRHeaders = NonNullable<XMLHttpRequest['responseHeaders']>;
|
|
3
|
+
|
|
4
|
+
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD';
|
|
5
|
+
|
|
6
|
+
export type RequestId = string;
|
|
7
|
+
export type Timestamp = number;
|
|
8
|
+
|
|
9
|
+
export type XHRPostData =
|
|
10
|
+
| string
|
|
11
|
+
| Blob
|
|
12
|
+
| FormData
|
|
13
|
+
| ArrayBuffer
|
|
14
|
+
| ArrayBufferView
|
|
15
|
+
| unknown
|
|
16
|
+
| null
|
|
17
|
+
| undefined;
|
|
18
|
+
|
|
19
|
+
export type RequestTextPostData = {
|
|
20
|
+
type: 'text';
|
|
21
|
+
value: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type RequestBinaryPostData = {
|
|
25
|
+
type: 'binary';
|
|
26
|
+
value: {
|
|
27
|
+
size: number;
|
|
28
|
+
type?: string;
|
|
29
|
+
name?: string;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type RequestFormDataPostData = {
|
|
34
|
+
type: 'form-data';
|
|
35
|
+
value: Record<string, RequestTextPostData | RequestBinaryPostData>;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type RequestPostData =
|
|
39
|
+
| RequestTextPostData
|
|
40
|
+
| RequestFormDataPostData
|
|
41
|
+
| RequestBinaryPostData
|
|
42
|
+
| null
|
|
43
|
+
| undefined;
|
|
44
|
+
|
|
45
|
+
export type Cookie = {
|
|
46
|
+
name: string;
|
|
47
|
+
value: string;
|
|
48
|
+
domain?: string;
|
|
49
|
+
path?: string;
|
|
50
|
+
expires?: string;
|
|
51
|
+
maxAge?: string;
|
|
52
|
+
secure?: boolean;
|
|
53
|
+
httpOnly?: boolean;
|
|
54
|
+
sameSite?: string;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export type Request = {
|
|
58
|
+
url: string;
|
|
59
|
+
method: HttpMethod;
|
|
60
|
+
headers: HttpHeaders;
|
|
61
|
+
postData?: RequestPostData;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export type Response = {
|
|
65
|
+
url: string;
|
|
66
|
+
status: number;
|
|
67
|
+
statusText: string;
|
|
68
|
+
headers: HttpHeaders;
|
|
69
|
+
contentType: string;
|
|
70
|
+
size: number | null;
|
|
71
|
+
responseTime: Timestamp;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export type Initiator = {
|
|
75
|
+
type: string;
|
|
76
|
+
url?: string;
|
|
77
|
+
lineNumber?: number;
|
|
78
|
+
columnNumber?: number;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export type ResourceType = 'XHR' | 'Fetch' | 'Other';
|
|
82
|
+
|
|
83
|
+
export type RequestOverride = {
|
|
84
|
+
status?: number;
|
|
85
|
+
body?: string;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export type HttpEventMap = {
|
|
89
|
+
'request-sent': {
|
|
90
|
+
requestId: RequestId;
|
|
91
|
+
request: Request;
|
|
92
|
+
timestamp: Timestamp;
|
|
93
|
+
initiator: Initiator;
|
|
94
|
+
type: ResourceType;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
'response-received': {
|
|
98
|
+
requestId: RequestId;
|
|
99
|
+
timestamp: Timestamp;
|
|
100
|
+
type: ResourceType;
|
|
101
|
+
response: Response;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
'request-completed': {
|
|
105
|
+
requestId: RequestId;
|
|
106
|
+
timestamp: Timestamp;
|
|
107
|
+
duration: number;
|
|
108
|
+
size: number | null;
|
|
109
|
+
ttfb: number;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
'request-failed': {
|
|
113
|
+
requestId: RequestId;
|
|
114
|
+
timestamp: Timestamp;
|
|
115
|
+
type: ResourceType;
|
|
116
|
+
error: string;
|
|
117
|
+
canceled: boolean;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
'request-progress': {
|
|
121
|
+
requestId: RequestId;
|
|
122
|
+
timestamp: Timestamp;
|
|
123
|
+
loaded: number;
|
|
124
|
+
total: number;
|
|
125
|
+
lengthComputable: boolean;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
'get-response-body': {
|
|
129
|
+
requestId: RequestId;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
'response-body': {
|
|
133
|
+
requestId: RequestId;
|
|
134
|
+
body: string | null;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
'set-overrides': {
|
|
138
|
+
overrides: [string, RequestOverride][];
|
|
139
|
+
};
|
|
140
|
+
};
|
package/src/shared/sse-events.ts
CHANGED
|
@@ -61,7 +61,7 @@ const formatStartTime = (startTime: number): string => {
|
|
|
61
61
|
};
|
|
62
62
|
|
|
63
63
|
const extractDomainAndPath = (
|
|
64
|
-
url: string
|
|
64
|
+
url: string,
|
|
65
65
|
): { domain: string; path: string } => {
|
|
66
66
|
try {
|
|
67
67
|
const { hostname, pathname, search, hash, port } = new URL(url);
|
|
@@ -80,7 +80,7 @@ const generateName = (url: string, showEntirePathName = false): string => {
|
|
|
80
80
|
const urlObj = new URL(url);
|
|
81
81
|
const pathname = urlObj.pathname;
|
|
82
82
|
const filename = showEntirePathName ? undefined : pathname.split('/').pop();
|
|
83
|
-
|
|
83
|
+
|
|
84
84
|
return filename || pathname || urlObj.hostname;
|
|
85
85
|
} catch {
|
|
86
86
|
return url;
|
|
@@ -128,7 +128,7 @@ const sortTime: SortingFn<NetworkRequest> = (rowA, rowB, columnId) => {
|
|
|
128
128
|
const processNetworkRequests = (
|
|
129
129
|
processedRequests: ProcessedRequest[],
|
|
130
130
|
overrides: Map<string, RequestOverride>,
|
|
131
|
-
showEntirePathAsName = false
|
|
131
|
+
showEntirePathAsName = false,
|
|
132
132
|
): NetworkRequest[] => {
|
|
133
133
|
return processedRequests.map((request): NetworkRequest => {
|
|
134
134
|
const { domain, path } = extractDomainAndPath(request.name);
|
|
@@ -262,7 +262,11 @@ export const RequestList = ({ filter }: RequestListProps) => {
|
|
|
262
262
|
}, [processedRequests, filter]);
|
|
263
263
|
|
|
264
264
|
const requests = useMemo(() => {
|
|
265
|
-
return processNetworkRequests(
|
|
265
|
+
return processNetworkRequests(
|
|
266
|
+
filteredRequests,
|
|
267
|
+
overrides,
|
|
268
|
+
clientUISettings?.showUrlAsName,
|
|
269
|
+
);
|
|
266
270
|
}, [filteredRequests, overrides, clientUISettings?.showUrlAsName]);
|
|
267
271
|
|
|
268
272
|
const table = useReactTable({
|
|
@@ -302,7 +306,7 @@ export const RequestList = ({ filter }: RequestListProps) => {
|
|
|
302
306
|
? null
|
|
303
307
|
: flexRender(
|
|
304
308
|
header.column.columnDef.header,
|
|
305
|
-
header.getContext()
|
|
309
|
+
header.getContext(),
|
|
306
310
|
)}
|
|
307
311
|
{header.column.getCanSort() && (
|
|
308
312
|
<span className="text-gray-500">
|
package/src/ui/state/derived.ts
CHANGED
|
@@ -21,7 +21,7 @@ export const getProcessedRequests = memoize((state: NetworkActivityState) => {
|
|
|
21
21
|
status: httpEntry.status,
|
|
22
22
|
timestamp: httpEntry.timestamp,
|
|
23
23
|
duration: httpEntry.duration,
|
|
24
|
-
size: httpEntry.size,
|
|
24
|
+
size: httpEntry.size ?? null,
|
|
25
25
|
method: httpEntry.request.method,
|
|
26
26
|
httpStatus: httpEntry.response?.status,
|
|
27
27
|
progress: httpEntry.progress,
|
|
@@ -35,6 +35,7 @@ export const getProcessedRequests = memoize((state: NetworkActivityState) => {
|
|
|
35
35
|
status: wsEntry.status,
|
|
36
36
|
timestamp: wsEntry.timestamp,
|
|
37
37
|
duration: wsEntry.duration,
|
|
38
|
+
size: null,
|
|
38
39
|
method: 'WS',
|
|
39
40
|
httpStatus: 0,
|
|
40
41
|
});
|
|
@@ -47,6 +48,7 @@ export const getProcessedRequests = memoize((state: NetworkActivityState) => {
|
|
|
47
48
|
status: sseEntry.status,
|
|
48
49
|
timestamp: sseEntry.timestamp,
|
|
49
50
|
duration: sseEntry.duration,
|
|
51
|
+
size: null,
|
|
50
52
|
method: 'SSE',
|
|
51
53
|
httpStatus: 0,
|
|
52
54
|
});
|
|
@@ -63,7 +65,7 @@ export const getSelectedRequest = memoize((state: NetworkActivityState) => {
|
|
|
63
65
|
});
|
|
64
66
|
|
|
65
67
|
export const getRequestSummary = (
|
|
66
|
-
requestId: string
|
|
68
|
+
requestId: string,
|
|
67
69
|
): ((state: NetworkActivityState) => ProcessedRequest | null) =>
|
|
68
70
|
memoize((state: NetworkActivityState) => {
|
|
69
71
|
const { networkEntries } = state;
|
|
@@ -79,7 +81,7 @@ export const getRequestSummary = (
|
|
|
79
81
|
status: httpEntry.status,
|
|
80
82
|
timestamp: httpEntry.timestamp,
|
|
81
83
|
duration: httpEntry.duration,
|
|
82
|
-
size: httpEntry.size,
|
|
84
|
+
size: httpEntry.size ?? null,
|
|
83
85
|
method: httpEntry.request.method,
|
|
84
86
|
httpStatus: httpEntry.response?.status || 0,
|
|
85
87
|
progress: httpEntry.progress,
|
|
@@ -93,6 +95,7 @@ export const getRequestSummary = (
|
|
|
93
95
|
status: wsEntry.status,
|
|
94
96
|
timestamp: wsEntry.timestamp,
|
|
95
97
|
duration: wsEntry.duration,
|
|
98
|
+
size: null,
|
|
96
99
|
method: 'WS',
|
|
97
100
|
httpStatus: 0,
|
|
98
101
|
};
|
|
@@ -105,6 +108,7 @@ export const getRequestSummary = (
|
|
|
105
108
|
status: sseEntry.status,
|
|
106
109
|
timestamp: sseEntry.timestamp,
|
|
107
110
|
duration: sseEntry.duration,
|
|
111
|
+
size: null,
|
|
108
112
|
method: 'SSE',
|
|
109
113
|
httpStatus: 0,
|
|
110
114
|
};
|
package/src/ui/state/store.ts
CHANGED
|
@@ -65,7 +65,7 @@ export const createNetworkActivityStore = () =>
|
|
|
65
65
|
persist(
|
|
66
66
|
(set, get) => ({
|
|
67
67
|
// Initial state
|
|
68
|
-
isRecording:
|
|
68
|
+
isRecording: true,
|
|
69
69
|
selectedRequestId: null,
|
|
70
70
|
networkEntries: new Map(),
|
|
71
71
|
websocketMessages: new Map(),
|
|
@@ -684,9 +684,10 @@ export const createNetworkActivityStore = () =>
|
|
|
684
684
|
typeof value === 'object' &&
|
|
685
685
|
value !== null &&
|
|
686
686
|
'_type' in value &&
|
|
687
|
-
value._type === 'map'
|
|
687
|
+
value._type === 'map' &&
|
|
688
|
+
'value' in value
|
|
688
689
|
) {
|
|
689
|
-
return new Map(value.value);
|
|
690
|
+
return new Map(value.value as [string, RequestOverride][]);
|
|
690
691
|
}
|
|
691
692
|
return value;
|
|
692
693
|
},
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { NetworkActivityDevToolsClient } from '../../shared/client';
|
|
2
|
-
export type NetworkInspector = {
|
|
3
|
-
enable: () => void;
|
|
4
|
-
disable: () => void;
|
|
5
|
-
isEnabled: () => boolean;
|
|
6
|
-
dispose: () => void;
|
|
7
|
-
};
|
|
8
|
-
export declare const getNetworkInspector: (pluginClient: NetworkActivityDevToolsClient) => NetworkInspector;
|
|
@@ -1,408 +0,0 @@
|
|
|
1
|
-
import { safeStringify } from '../../utils/safeStringify';
|
|
2
|
-
import {
|
|
3
|
-
HttpMethod,
|
|
4
|
-
NetworkActivityDevToolsClient,
|
|
5
|
-
RequestPostData,
|
|
6
|
-
RequestTextPostData,
|
|
7
|
-
RequestBinaryPostData,
|
|
8
|
-
RequestFormDataPostData,
|
|
9
|
-
XHRPostData,
|
|
10
|
-
} from '../../shared/client';
|
|
11
|
-
import { getContentType } from '../utils';
|
|
12
|
-
import { getNetworkRequestsRegistry } from './network-requests-registry';
|
|
13
|
-
import { getBlobName } from '../utils/getBlobName';
|
|
14
|
-
import { getFormDataEntries } from '../utils/getFormDataEntries';
|
|
15
|
-
import { XHRInterceptor } from './xhr-interceptor';
|
|
16
|
-
import { getStringSizeInBytes } from '../../utils/getStringSizeInBytes';
|
|
17
|
-
import { applyReactNativeResponseHeadersLogic } from '../../utils/applyReactNativeResponseHeadersLogic';
|
|
18
|
-
import {
|
|
19
|
-
isBlob,
|
|
20
|
-
isArrayBuffer,
|
|
21
|
-
isFormData,
|
|
22
|
-
isNullOrUndefined,
|
|
23
|
-
} from '../../utils/typeChecks';
|
|
24
|
-
import { getOverridesRegistry } from './overrides-registry';
|
|
25
|
-
|
|
26
|
-
const networkRequestsRegistry = getNetworkRequestsRegistry();
|
|
27
|
-
const overridesRegistry = getOverridesRegistry();
|
|
28
|
-
|
|
29
|
-
const getBinaryPostData = (body: Blob): RequestBinaryPostData => ({
|
|
30
|
-
type: 'binary',
|
|
31
|
-
value: {
|
|
32
|
-
size: body.size,
|
|
33
|
-
type: body.type,
|
|
34
|
-
name: getBlobName(body),
|
|
35
|
-
},
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
const getArrayBufferPostData = (
|
|
39
|
-
body: ArrayBuffer | ArrayBufferView
|
|
40
|
-
): RequestBinaryPostData => ({
|
|
41
|
-
type: 'binary',
|
|
42
|
-
value: {
|
|
43
|
-
size: body.byteLength,
|
|
44
|
-
},
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
const getTextPostData = (body: unknown): RequestTextPostData => ({
|
|
48
|
-
type: 'text',
|
|
49
|
-
value: safeStringify(body),
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
const getFormDataPostData = (body: FormData): RequestFormDataPostData => ({
|
|
53
|
-
type: 'form-data',
|
|
54
|
-
value: getFormDataEntries(body).reduce<RequestFormDataPostData['value']>(
|
|
55
|
-
(acc, [key, value]) => {
|
|
56
|
-
if (isBlob(value)) {
|
|
57
|
-
acc[key] = getBinaryPostData(value);
|
|
58
|
-
} else if (isArrayBuffer(value)) {
|
|
59
|
-
acc[key] = getArrayBufferPostData(value);
|
|
60
|
-
} else {
|
|
61
|
-
acc[key] = getTextPostData(value);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return acc;
|
|
65
|
-
},
|
|
66
|
-
{}
|
|
67
|
-
),
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
const getRequestBody = (body: XHRPostData): RequestPostData => {
|
|
71
|
-
if (isNullOrUndefined(body)) {
|
|
72
|
-
return body;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (isBlob(body)) {
|
|
76
|
-
return getBinaryPostData(body);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (isArrayBuffer(body)) {
|
|
80
|
-
return getArrayBufferPostData(body);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (isFormData(body)) {
|
|
84
|
-
return getFormDataPostData(body);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return getTextPostData(body);
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
const getResponseSize = (request: XMLHttpRequest): number | null => {
|
|
91
|
-
try {
|
|
92
|
-
const { responseType, response } = request;
|
|
93
|
-
|
|
94
|
-
// Handle a case of 204 where no-content was sent.
|
|
95
|
-
if (response === null) {
|
|
96
|
-
return 0;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (responseType === '' || responseType === 'text') {
|
|
100
|
-
return getStringSizeInBytes(request.responseText);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (responseType === 'json') {
|
|
104
|
-
return getStringSizeInBytes(safeStringify(response));
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (responseType === 'blob') {
|
|
108
|
-
return response.size;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (responseType === 'arraybuffer') {
|
|
112
|
-
return response.byteLength;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return 0;
|
|
116
|
-
} catch {
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
const getResponseBody = async (
|
|
122
|
-
request: XMLHttpRequest
|
|
123
|
-
): Promise<string | null> => {
|
|
124
|
-
const responseType = request.responseType;
|
|
125
|
-
|
|
126
|
-
// Response type is empty in certain cases, like when using axios.
|
|
127
|
-
if (responseType === '' || responseType === 'text') {
|
|
128
|
-
return request.responseText as string;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (responseType === 'blob') {
|
|
132
|
-
// This may be a text blob.
|
|
133
|
-
const contentType = request.getResponseHeader('Content-Type') || '';
|
|
134
|
-
|
|
135
|
-
if (
|
|
136
|
-
contentType.startsWith('text/') ||
|
|
137
|
-
contentType.startsWith('application/json')
|
|
138
|
-
) {
|
|
139
|
-
// It looks like a text blob, let's read it and forward it to the client.
|
|
140
|
-
return new Promise((resolve) => {
|
|
141
|
-
const reader = new FileReader();
|
|
142
|
-
reader.onload = () => {
|
|
143
|
-
resolve(reader.result as string);
|
|
144
|
-
};
|
|
145
|
-
reader.readAsText(request.response);
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (responseType === 'json') {
|
|
151
|
-
return safeStringify(request.response);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return null;
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
const getInitiatorFromStack = (): {
|
|
158
|
-
type: string;
|
|
159
|
-
url?: string;
|
|
160
|
-
lineNumber?: number;
|
|
161
|
-
columnNumber?: number;
|
|
162
|
-
} => {
|
|
163
|
-
try {
|
|
164
|
-
const stack = new Error().stack;
|
|
165
|
-
if (!stack) {
|
|
166
|
-
return { type: 'other' };
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const line = stack.split('\n')[9];
|
|
170
|
-
const match = line.match(/at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/);
|
|
171
|
-
if (match) {
|
|
172
|
-
return {
|
|
173
|
-
type: 'script',
|
|
174
|
-
url: match[2],
|
|
175
|
-
lineNumber: parseInt(match[3]),
|
|
176
|
-
columnNumber: parseInt(match[4]),
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
} catch {
|
|
180
|
-
// Ignore stack parsing errors
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return { type: 'other' };
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
export type NetworkInspector = {
|
|
187
|
-
enable: () => void;
|
|
188
|
-
disable: () => void;
|
|
189
|
-
isEnabled: () => boolean;
|
|
190
|
-
dispose: () => void;
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
const READY_STATE_HEADERS_RECEIVED = 2;
|
|
194
|
-
|
|
195
|
-
export const getNetworkInspector = (
|
|
196
|
-
pluginClient: NetworkActivityDevToolsClient
|
|
197
|
-
): NetworkInspector => {
|
|
198
|
-
const generateRequestId = (): string => {
|
|
199
|
-
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
const handleRequestSend = (
|
|
203
|
-
data: XHRPostData,
|
|
204
|
-
request: XMLHttpRequest
|
|
205
|
-
): void => {
|
|
206
|
-
const sendTime = Date.now();
|
|
207
|
-
|
|
208
|
-
const requestId = generateRequestId();
|
|
209
|
-
request._rozeniteRequestId = requestId;
|
|
210
|
-
|
|
211
|
-
const initiator = getInitiatorFromStack();
|
|
212
|
-
|
|
213
|
-
networkRequestsRegistry.addEntry(requestId, request);
|
|
214
|
-
|
|
215
|
-
let ttfb = 0;
|
|
216
|
-
|
|
217
|
-
pluginClient.send('request-sent', {
|
|
218
|
-
requestId: requestId,
|
|
219
|
-
timestamp: sendTime,
|
|
220
|
-
request: {
|
|
221
|
-
url: request._url as string,
|
|
222
|
-
method: request._method as HttpMethod,
|
|
223
|
-
headers: request._headers,
|
|
224
|
-
postData: getRequestBody(data),
|
|
225
|
-
},
|
|
226
|
-
type: 'XHR',
|
|
227
|
-
initiator,
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
request.addEventListener('progress', (event) => {
|
|
231
|
-
pluginClient.send('request-progress', {
|
|
232
|
-
requestId: requestId,
|
|
233
|
-
timestamp: Date.now(),
|
|
234
|
-
loaded: event.loaded,
|
|
235
|
-
total: event.total,
|
|
236
|
-
lengthComputable: event.lengthComputable,
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
request.addEventListener('readystatechange', () => {
|
|
241
|
-
if (request.readyState === READY_STATE_HEADERS_RECEIVED) {
|
|
242
|
-
ttfb = Date.now() - sendTime;
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
request.addEventListener('load', () => {
|
|
247
|
-
pluginClient.send('response-received', {
|
|
248
|
-
requestId: requestId,
|
|
249
|
-
timestamp: Date.now(),
|
|
250
|
-
type: 'XHR',
|
|
251
|
-
response: {
|
|
252
|
-
url: request._url as string,
|
|
253
|
-
status: request.status,
|
|
254
|
-
statusText: request.statusText,
|
|
255
|
-
headers: applyReactNativeResponseHeadersLogic(
|
|
256
|
-
request.responseHeaders || {}
|
|
257
|
-
),
|
|
258
|
-
contentType: getContentType(request),
|
|
259
|
-
size: getResponseSize(request),
|
|
260
|
-
responseTime: Date.now(),
|
|
261
|
-
},
|
|
262
|
-
});
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
request.addEventListener('loadend', () => {
|
|
266
|
-
pluginClient.send('request-completed', {
|
|
267
|
-
requestId: requestId,
|
|
268
|
-
timestamp: Date.now(),
|
|
269
|
-
duration: Date.now() - sendTime,
|
|
270
|
-
size: getResponseSize(request),
|
|
271
|
-
ttfb,
|
|
272
|
-
});
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
request.addEventListener('error', () => {
|
|
276
|
-
pluginClient.send('request-failed', {
|
|
277
|
-
requestId: requestId,
|
|
278
|
-
timestamp: Date.now(),
|
|
279
|
-
type: 'XHR',
|
|
280
|
-
error: 'Failed',
|
|
281
|
-
canceled: false,
|
|
282
|
-
});
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
request.addEventListener('abort', () => {
|
|
286
|
-
pluginClient.send('request-failed', {
|
|
287
|
-
requestId: requestId,
|
|
288
|
-
timestamp: Date.now(),
|
|
289
|
-
type: 'XHR',
|
|
290
|
-
error: 'Aborted',
|
|
291
|
-
canceled: true,
|
|
292
|
-
});
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
request.addEventListener('timeout', () => {
|
|
296
|
-
pluginClient.send('request-failed', {
|
|
297
|
-
requestId: requestId,
|
|
298
|
-
timestamp: Date.now(),
|
|
299
|
-
type: 'XHR',
|
|
300
|
-
error: 'Timeout',
|
|
301
|
-
canceled: false,
|
|
302
|
-
});
|
|
303
|
-
});
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
const handleRequestOverride = (request: XMLHttpRequest): void => {
|
|
307
|
-
const override = overridesRegistry.getOverrideForUrl(
|
|
308
|
-
request._url as string
|
|
309
|
-
);
|
|
310
|
-
|
|
311
|
-
if (!override) {
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
request.addEventListener('readystatechange', () => {
|
|
316
|
-
if (override.body !== undefined) {
|
|
317
|
-
Object.defineProperty(request, 'responseType', {
|
|
318
|
-
writable: true,
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
Object.defineProperty(request, 'response', {
|
|
322
|
-
writable: true,
|
|
323
|
-
});
|
|
324
|
-
Object.defineProperty(request, 'responseText', {
|
|
325
|
-
writable: true,
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
const contentType = getContentType(request);
|
|
329
|
-
|
|
330
|
-
if (contentType === 'application/json') {
|
|
331
|
-
request.responseType = 'json';
|
|
332
|
-
} else if (contentType === 'text/plain') {
|
|
333
|
-
request.responseType = 'text';
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// @ts-expect-error - Mocking response
|
|
337
|
-
request.response = override.body;
|
|
338
|
-
// @ts-expect-error - Mocking responseText
|
|
339
|
-
request.responseText = override.body;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
if (override.status !== undefined) {
|
|
343
|
-
Object.defineProperty(request, 'status', {
|
|
344
|
-
writable: true,
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
// @ts-expect-error - Mocking status
|
|
348
|
-
request.status = override.status;
|
|
349
|
-
}
|
|
350
|
-
});
|
|
351
|
-
};
|
|
352
|
-
|
|
353
|
-
const enable = () => {
|
|
354
|
-
XHRInterceptor.disableInterception();
|
|
355
|
-
XHRInterceptor.setSendCallback(handleRequestSend);
|
|
356
|
-
XHRInterceptor.setOverrideCallback(handleRequestOverride);
|
|
357
|
-
XHRInterceptor.enableInterception();
|
|
358
|
-
};
|
|
359
|
-
|
|
360
|
-
const disable = () => {
|
|
361
|
-
XHRInterceptor.disableInterception();
|
|
362
|
-
networkRequestsRegistry.clear();
|
|
363
|
-
};
|
|
364
|
-
|
|
365
|
-
const isEnabled = () => {
|
|
366
|
-
return XHRInterceptor.isInterceptorEnabled();
|
|
367
|
-
};
|
|
368
|
-
|
|
369
|
-
const enableSubscription = pluginClient.onMessage('network-enable', () => {
|
|
370
|
-
enable();
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
const disableSubscription = pluginClient.onMessage('network-disable', () => {
|
|
374
|
-
disable();
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
const handleBodySubscription = pluginClient.onMessage(
|
|
378
|
-
'get-response-body',
|
|
379
|
-
async ({ requestId }) => {
|
|
380
|
-
const request = networkRequestsRegistry.getEntry(requestId);
|
|
381
|
-
|
|
382
|
-
if (!request) {
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
const body = await getResponseBody(request);
|
|
387
|
-
|
|
388
|
-
pluginClient.send('response-body', {
|
|
389
|
-
requestId,
|
|
390
|
-
body,
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
);
|
|
394
|
-
|
|
395
|
-
const dispose = () => {
|
|
396
|
-
disable();
|
|
397
|
-
enableSubscription.remove();
|
|
398
|
-
disableSubscription.remove();
|
|
399
|
-
handleBodySubscription.remove();
|
|
400
|
-
};
|
|
401
|
-
|
|
402
|
-
return {
|
|
403
|
-
enable,
|
|
404
|
-
disable,
|
|
405
|
-
isEnabled,
|
|
406
|
-
dispose,
|
|
407
|
-
};
|
|
408
|
-
};
|