@rozenite/network-activity-plugin 1.0.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-C6wCDVkW.js → App-o_iVtD-5.js} +50 -7
- 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 -98
- 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/src/ui/state/model.d.ts +10 -0
- package/dist/useNetworkActivityDevTools.cjs +112 -993
- package/dist/useNetworkActivityDevTools.js +110 -989
- 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 -132
- package/src/shared/http-events.ts +140 -0
- package/src/shared/sse-events.ts +1 -1
- package/src/ui/components/RequestList.tsx +18 -6
- package/src/ui/components/Toolbar.tsx +3 -2
- package/src/ui/state/derived.ts +9 -3
- package/src/ui/state/model.ts +10 -0
- package/src/ui/state/store.ts +34 -3
- package/dist/src/react-native/http/network-inspector.d.ts +0 -8
- package/src/react-native/http/network-inspector.ts +0 -388
|
@@ -1,388 +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('readystatechange', () => {
|
|
231
|
-
if (request.readyState === READY_STATE_HEADERS_RECEIVED) {
|
|
232
|
-
ttfb = Date.now() - sendTime;
|
|
233
|
-
}
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
request.addEventListener('load', () => {
|
|
237
|
-
pluginClient.send('response-received', {
|
|
238
|
-
requestId: requestId,
|
|
239
|
-
timestamp: Date.now(),
|
|
240
|
-
type: 'XHR',
|
|
241
|
-
response: {
|
|
242
|
-
url: request._url as string,
|
|
243
|
-
status: request.status,
|
|
244
|
-
statusText: request.statusText,
|
|
245
|
-
headers: applyReactNativeResponseHeadersLogic(
|
|
246
|
-
request.responseHeaders || {}
|
|
247
|
-
),
|
|
248
|
-
contentType: getContentType(request),
|
|
249
|
-
size: getResponseSize(request),
|
|
250
|
-
responseTime: Date.now(),
|
|
251
|
-
},
|
|
252
|
-
});
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
request.addEventListener('loadend', () => {
|
|
256
|
-
pluginClient.send('request-completed', {
|
|
257
|
-
requestId: requestId,
|
|
258
|
-
timestamp: Date.now(),
|
|
259
|
-
duration: Date.now() - sendTime,
|
|
260
|
-
size: getResponseSize(request),
|
|
261
|
-
ttfb,
|
|
262
|
-
});
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
request.addEventListener('error', () => {
|
|
266
|
-
pluginClient.send('request-failed', {
|
|
267
|
-
requestId: requestId,
|
|
268
|
-
timestamp: Date.now(),
|
|
269
|
-
type: 'XHR',
|
|
270
|
-
error: 'Failed',
|
|
271
|
-
canceled: false,
|
|
272
|
-
});
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
request.addEventListener('abort', () => {
|
|
276
|
-
pluginClient.send('request-failed', {
|
|
277
|
-
requestId: requestId,
|
|
278
|
-
timestamp: Date.now(),
|
|
279
|
-
type: 'XHR',
|
|
280
|
-
error: 'Aborted',
|
|
281
|
-
canceled: true,
|
|
282
|
-
});
|
|
283
|
-
});
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
const handleRequestOverride = (request: XMLHttpRequest): void => {
|
|
287
|
-
const override = overridesRegistry.getOverrideForUrl(
|
|
288
|
-
request._url as string
|
|
289
|
-
);
|
|
290
|
-
|
|
291
|
-
if (!override) {
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
request.addEventListener('readystatechange', () => {
|
|
296
|
-
if (override.body !== undefined) {
|
|
297
|
-
Object.defineProperty(request, 'responseType', {
|
|
298
|
-
writable: true,
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
Object.defineProperty(request, 'response', {
|
|
302
|
-
writable: true,
|
|
303
|
-
});
|
|
304
|
-
Object.defineProperty(request, 'responseText', {
|
|
305
|
-
writable: true,
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
const contentType = getContentType(request);
|
|
309
|
-
|
|
310
|
-
if (contentType === 'application/json') {
|
|
311
|
-
request.responseType = 'json';
|
|
312
|
-
} else if (contentType === 'text/plain') {
|
|
313
|
-
request.responseType = 'text';
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// @ts-expect-error - Mocking response
|
|
317
|
-
request.response = override.body;
|
|
318
|
-
// @ts-expect-error - Mocking responseText
|
|
319
|
-
request.responseText = override.body;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
if (override.status !== undefined) {
|
|
323
|
-
Object.defineProperty(request, 'status', {
|
|
324
|
-
writable: true,
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
// @ts-expect-error - Mocking status
|
|
328
|
-
request.status = override.status;
|
|
329
|
-
}
|
|
330
|
-
});
|
|
331
|
-
};
|
|
332
|
-
|
|
333
|
-
const enable = () => {
|
|
334
|
-
XHRInterceptor.disableInterception();
|
|
335
|
-
XHRInterceptor.setSendCallback(handleRequestSend);
|
|
336
|
-
XHRInterceptor.setOverrideCallback(handleRequestOverride);
|
|
337
|
-
XHRInterceptor.enableInterception();
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
const disable = () => {
|
|
341
|
-
XHRInterceptor.disableInterception();
|
|
342
|
-
networkRequestsRegistry.clear();
|
|
343
|
-
};
|
|
344
|
-
|
|
345
|
-
const isEnabled = () => {
|
|
346
|
-
return XHRInterceptor.isInterceptorEnabled();
|
|
347
|
-
};
|
|
348
|
-
|
|
349
|
-
const enableSubscription = pluginClient.onMessage('network-enable', () => {
|
|
350
|
-
enable();
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
const disableSubscription = pluginClient.onMessage('network-disable', () => {
|
|
354
|
-
disable();
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
const handleBodySubscription = pluginClient.onMessage(
|
|
358
|
-
'get-response-body',
|
|
359
|
-
async ({ requestId }) => {
|
|
360
|
-
const request = networkRequestsRegistry.getEntry(requestId);
|
|
361
|
-
|
|
362
|
-
if (!request) {
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
const body = await getResponseBody(request);
|
|
367
|
-
|
|
368
|
-
pluginClient.send('response-body', {
|
|
369
|
-
requestId,
|
|
370
|
-
body,
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
);
|
|
374
|
-
|
|
375
|
-
const dispose = () => {
|
|
376
|
-
disable();
|
|
377
|
-
enableSubscription.remove();
|
|
378
|
-
disableSubscription.remove();
|
|
379
|
-
handleBodySubscription.remove();
|
|
380
|
-
};
|
|
381
|
-
|
|
382
|
-
return {
|
|
383
|
-
enable,
|
|
384
|
-
disable,
|
|
385
|
-
isEnabled,
|
|
386
|
-
dispose,
|
|
387
|
-
};
|
|
388
|
-
};
|