@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.
Files changed (53) hide show
  1. package/README.md +9 -0
  2. package/dist/App.html +1 -1
  3. package/dist/assets/{App-C6wCDVkW.js → App-o_iVtD-5.js} +50 -7
  4. package/dist/boot-recording.cjs +1092 -0
  5. package/dist/boot-recording.js +1091 -0
  6. package/dist/react-native.cjs +3 -0
  7. package/dist/react-native.d.ts +3 -0
  8. package/dist/react-native.js +5 -1
  9. package/dist/rozenite.json +1 -1
  10. package/dist/src/react-native/boot-recording.d.ts +41 -0
  11. package/dist/src/react-native/config.d.ts +7 -4
  12. package/dist/src/react-native/events-listener.d.ts +44 -0
  13. package/dist/src/react-native/http/http-inspector.d.ts +10 -0
  14. package/dist/src/react-native/http/http-utils.d.ts +15 -0
  15. package/dist/src/react-native/inspector.d.ts +7 -0
  16. package/dist/src/react-native/network-inspector.d.ts +16 -0
  17. package/dist/src/react-native/sse/sse-inspector.d.ts +4 -7
  18. package/dist/src/react-native/useHttpInspector.d.ts +3 -0
  19. package/dist/src/react-native/useSSEInspector.d.ts +3 -0
  20. package/dist/src/react-native/useWebSocketInspector.d.ts +3 -0
  21. package/dist/src/react-native/websocket/websocket-inspector.d.ts +4 -7
  22. package/dist/src/shared/client.d.ts +3 -98
  23. package/dist/src/shared/http-events.d.ts +106 -0
  24. package/dist/src/shared/sse-events.d.ts +1 -1
  25. package/dist/src/ui/state/hooks.d.ts +3 -3
  26. package/dist/src/ui/state/model.d.ts +10 -0
  27. package/dist/useNetworkActivityDevTools.cjs +112 -993
  28. package/dist/useNetworkActivityDevTools.js +110 -989
  29. package/package.json +4 -4
  30. package/react-native.ts +8 -0
  31. package/src/react-native/boot-recording.ts +90 -0
  32. package/src/react-native/config.ts +9 -4
  33. package/src/react-native/events-listener.ts +102 -0
  34. package/src/react-native/http/http-inspector.ts +174 -0
  35. package/src/react-native/http/http-utils.ts +217 -0
  36. package/src/react-native/inspector.ts +10 -0
  37. package/src/react-native/network-inspector.ts +78 -0
  38. package/src/react-native/sse/sse-inspector.ts +12 -10
  39. package/src/react-native/useHttpInspector.ts +59 -0
  40. package/src/react-native/useNetworkActivityDevTools.ts +60 -115
  41. package/src/react-native/useSSEInspector.ts +35 -0
  42. package/src/react-native/useWebSocketInspector.ts +35 -0
  43. package/src/react-native/websocket/websocket-inspector.ts +18 -10
  44. package/src/shared/client.ts +4 -132
  45. package/src/shared/http-events.ts +140 -0
  46. package/src/shared/sse-events.ts +1 -1
  47. package/src/ui/components/RequestList.tsx +18 -6
  48. package/src/ui/components/Toolbar.tsx +3 -2
  49. package/src/ui/state/derived.ts +9 -3
  50. package/src/ui/state/model.ts +10 -0
  51. package/src/ui/state/store.ts +34 -3
  52. package/dist/src/react-native/http/network-inspector.d.ts +0 -8
  53. 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
- };